Created
February 15, 2024 03:02
-
-
Save jachin/508fe1b7b2717faa1da5a84ae793b027 to your computer and use it in GitHub Desktop.
OpenEats to Nextcloud Cookbook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
import os | |
import json | |
import getpass | |
import datetime | |
from shutil import copyfileobj, make_archive | |
from urllib.parse import urljoin | |
from pathlib import Path | |
# Configuration | |
openeats_base_url = input( | |
"Enter your OpenEats url (eg: https://openeats.example.com): " | |
) | |
login_endpoint = ( | |
"/api/v1/accounts/obtain-auth-token/" # Or the specific endpoint used for login | |
) | |
output_directory = "nextcloud_cookbook_recipes" # Directory to save JSON files | |
# Prompt for username and password | |
username = input("Enter your OpenEats username: ") | |
password = getpass.getpass("Enter your OpenEats password: ") | |
# Start a session | |
session = requests.Session() | |
# Login function to obtain a session cookie | |
def login(base_url, endpoint, user, pwd): | |
url = base_url + endpoint | |
login_data = {"username": user, "password": pwd} | |
response = session.post(url, data=login_data) | |
response.raise_for_status() # Raises an error if the login failed | |
# The session is now authenticated; the session cookies contain the auth token | |
# Fetch recipes using authenticated session | |
def get_openeats_recipes(): | |
api_url = f"{openeats_base_url}/api/v1/recipe/recipes/" | |
response = session.get(api_url) | |
response.raise_for_status() | |
return response.json()["results"] | |
# Helper function to format time | |
def format_duration(minutes): | |
if not minutes: | |
return "" | |
return f"PT{minutes}M" | |
# Function to download an image and return the saved path | |
def download_image(image_url, save_as): | |
if not image_url: | |
return None | |
response = requests.get(image_url, stream=True) | |
if response.status_code == 200: | |
with open(save_as, "wb") as f: | |
copyfileobj(response.raw, f) | |
return save_as | |
return None | |
# Function to convert an OpenEats recipe to the Nextcloud Cookbook format | |
def convert_to_nextcloud_cookbook_format(openeats_recipe): | |
# Assuming OpenEats uses minutes as integers for prep_time and cook_time | |
prep_time = format_duration(openeats_recipe.get("prep_time")) | |
cook_time = format_duration(openeats_recipe.get("cook_time")) | |
total_time = format_duration( | |
openeats_recipe.get("prep_time") + openeats_recipe.get("cook_time") | |
) | |
ingredients = [] | |
for group in openeats_recipe["ingredient_groups"]: | |
group_title = group.get("title", "").strip() | |
for ingredient in group["ingredients"]: | |
quantity = ( | |
f"{ingredient['numerator']}/{ingredient['denominator']}" | |
if ingredient["denominator"] > 1 | |
else f"{ingredient['numerator']}" | |
) | |
ingredient_str = ( | |
f"{quantity} {ingredient['measurement']} {ingredient['title']}" | |
) | |
if group_title: | |
ingredient_str = f"{group_title}: {ingredient_str}" | |
ingredients.append(ingredient_str) | |
instructions = [ | |
{"@type": "HowToStep", "text": step} | |
for step in openeats_recipe["directions"].split("\n") | |
if step.strip() | |
] | |
# Format the date as a full-date (YYYY-MM-DD) | |
publish_date = ( | |
datetime.datetime.strptime(openeats_recipe["pub_date"], "%Y-%m-%d") | |
.date() | |
.isoformat() | |
) | |
return { | |
"name": openeats_recipe["title"], | |
"description": openeats_recipe.get("info", ""), | |
"prepTime": prep_time, | |
"cookTime": cook_time, | |
"totalTime": total_time, | |
"recipeYield": openeats_recipe["servings"], | |
"image": openeats_recipe.get("photo", ""), | |
"recipeIngredient": ingredients, | |
"recipeInstructions": instructions, | |
"author": {"name": openeats_recipe["username"]}, | |
"datePublished": publish_date, | |
"keywords": [tag["title"] for tag in openeats_recipe["tags"]], | |
"recipeCategory": openeats_recipe["course"]["title"], | |
"recipeCuisine": openeats_recipe["cuisine"]["title"], | |
# Additional fields here if applicable | |
} | |
# Function to save recipe as a JSON file | |
def save_recipe_as_json(recipe, directory): | |
filename = f"{recipe['name'].replace(' ', '_')}.json" | |
filepath = os.path.join(directory, filename) | |
with open(filepath, "w", encoding="utf-8") as f: | |
json.dump(recipe, f, ensure_ascii=False, indent=4) | |
# Function to convert an OpenEats recipe to the Nextcloud Cookbook format | |
# and download images to the recipe folder | |
def process_recipe(openeats_recipe, output_directory): | |
# Create a recipe folder | |
safe_title = openeats_recipe["title"].replace(" ", "_").replace("/", "_") | |
recipe_dir = Path(output_directory) / safe_title | |
recipe_dir.mkdir(parents=True, exist_ok=True) | |
# Download main photo | |
if openeats_recipe.get("photo"): | |
full_image_path = recipe_dir / "full.jpg" | |
download_image( | |
urljoin(openeats_base_url, openeats_recipe["photo"]), full_image_path | |
) | |
# Download thumbnail photo (if different) | |
if ( | |
openeats_recipe.get("photo_thumbnail") | |
and openeats_recipe["photo_thumbnail"] != openeats_recipe["photo"] | |
): | |
thumb_image_path = recipe_dir / "thumb.jpg" | |
download_image( | |
urljoin(openeats_base_url, openeats_recipe["photo_thumbnail"]), | |
thumb_image_path, | |
) | |
# Construct Nextcloud recipe JSON | |
nextcloud_recipe = convert_to_nextcloud_cookbook_format(openeats_recipe) | |
# Save Nextcloud recipe JSON file | |
recipe_json_path = recipe_dir / "recipe.json" | |
with open(recipe_json_path, "w", encoding="utf-8") as f: | |
json.dump(nextcloud_recipe, f, ensure_ascii=False, indent=4) | |
return recipe_dir | |
# Function to zip the directory of recipes | |
def zip_recipes(directory, zip_name): | |
make_archive(zip_name, "zip", directory) | |
# Main script execution | |
if __name__ == "__main__": | |
# Create output directory if it does not exist | |
if not os.path.exists(output_directory): | |
os.makedirs(output_directory) | |
try: | |
# Perform login | |
login(openeats_base_url, login_endpoint, username, password) | |
# Fetch recipes from OpenEats | |
openeats_recipes = get_openeats_recipes() | |
# Convert and save each recipe and its associated images | |
for openeats_recipe in openeats_recipes: | |
recipe_dir = process_recipe(openeats_recipe, output_directory) | |
print(f"Recipe saved in: {recipe_dir}") | |
# Compress the recipes directory into a zip file | |
zip_recipes(output_directory, "nextcloud") | |
print(f"Recipes have been zipped into nextcloud.zip") | |
except requests.RequestException as error: | |
print(f"An error occurred: {error}") |
Thanks! This worked great for me. Mealie is so much nicer than OpenEats.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wrote this (with some help from ChatGTP) export all my recipes in OpenEats to a format that Mealie can import.
I suppose it would be easy to imagine you could also import your recipies into the Nextcloud Cookbook app, but I haven't actually tried that.
It's also not perfect, there's probably some cleanup you'll need to do afterwards. Mostly around cleaning up the directions. If anyone wants to help make it better, please do.