diff --git a/cookbook/templates/url_import.html b/cookbook/templates/url_import.html
index f7e588482..7e20178b3 100644
--- a/cookbook/templates/url_import.html
+++ b/cookbook/templates/url_import.html
@@ -1,10 +1,177 @@
-
-
-
-
- $Title$
-
-
-$END$
-
-
\ No newline at end of file
+{% extends "base.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans 'URL Import' %}{% endblock %}
+
+{% block extra_head %}
+
+ {% include 'include/vue_base.html' %}
+
+
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+ [[recipe_data]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/cookbook/urls.py b/cookbook/urls.py
index e127befe3..3bb392e0a 100644
--- a/cookbook/urls.py
+++ b/cookbook/urls.py
@@ -53,12 +53,14 @@ urlpatterns = [
path('data/batch/import', data.batch_import, name='data_batch_import'),
path('data/sync/wait', data.sync_wait, name='data_sync_wait'),
path('data/statistics', data.statistics, name='data_stats'),
+ path('data/import/url', data.import_url, name='data_import_url'),
path('api/get_external_file_link//', api.get_external_file_link, name='api_get_external_file_link'),
path('api/get_recipe_file//', api.get_recipe_file, name='api_get_recipe_file'),
path('api/sync_all/', api.sync_all, name='api_sync'),
path('api/log_cooking//', api.log_cooking, name='api_log_cooking'),
path('api/plan-ical//', api.get_plan_ical, name='api_get_plan_ical'),
+ path('api/recipe-from-url//', api.recipe_from_url, name='api_recipe_from_url'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index b22cc6da7..c481a3ce5 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -2,12 +2,14 @@ import io
import json
import re
+import requests
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
+from bs4 import BeautifulSoup
from django.contrib import messages
from django.contrib.auth.models import User
from django.db.models import Q
-from django.http import HttpResponse, FileResponse
+from django.http import HttpResponse, FileResponse, JsonResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from icalendar import Calendar, Event
@@ -16,7 +18,7 @@ from rest_framework.exceptions import APIException
from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, ListModelMixin
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin
-from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook
+from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Keyword
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.nextcloud import Nextcloud
from cookbook.serializer import MealPlanSerializer, MealTypeSerializer, RecipeSerializer, ViewLogSerializer, UserNameSerializer, UserPreferenceSerializer, RecipeBookSerializer
@@ -242,3 +244,72 @@ def get_plan_ical(request, html_week):
response["Content-Disposition"] = f'attachment; filename=meal_plan_{html_week}.ics'
return response
+
+
+@group_required('user')
+def recipe_from_url(request, url):
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'}
+ response = requests.get(url, headers=headers)
+
+ if response.status_code == 403:
+ return JsonResponse({'error': _('The requested page refused to provide any information (Status Code 403).')})
+
+ soup = BeautifulSoup(response.text, "html.parser")
+ for ld in soup.find_all('script', type='application/ld+json'):
+ ld_json = json.loads(ld.string)
+
+ # recipes type might be wrapped in @graph type
+ if '@graph' in ld_json:
+ for x in ld_json['@graph']:
+ if '@type' in x and x['@type'] == 'Recipe':
+ ld_json = x
+
+ if '@type' in ld_json and ld_json['@type'] == 'Recipe':
+
+ if 'recipeIngredient' in ld_json:
+ ingredients = []
+
+ for x in ld_json['recipeIngredient']:
+ ingredient_split = x.split()
+ if len(ingredient_split) > 2:
+ ingredients.append({'amount': ingredient_split[0], 'unit': ingredient_split[1], 'ingredient': " ".join(ingredient_split[2:])})
+ if len(ingredient_split) == 2:
+ ingredients.append({'amount': ingredient_split[0], 'unit': '', 'ingredient': " ".join(ingredient_split[1:])})
+ if len(ingredient_split) == 1:
+ ingredients.append({'amount': 0, 'unit': '', 'ingredient': " ".join(ingredient_split)})
+
+ ld_json['recipeIngredient'] = ingredients
+
+ if 'keywords' in ld_json:
+ keywords = []
+ if type(ld_json['keywords']) == str:
+ ld_json['keywords'] = ld_json['keywords'].split(',')
+
+ for kw in ld_json['keywords']:
+ if k := Keyword.objects.filter(name=kw).first():
+ keywords.append({'id': str(k.id), 'text': str(k).strip()})
+ else:
+ keywords.append({'id': "null", 'text': kw.strip()})
+
+ ld_json['keywords'] = keywords
+
+ if 'recipeInstructions' in ld_json:
+ instructions = ''
+ if type(ld_json['recipeInstructions']) == list:
+ for i in ld_json['recipeInstructions']:
+ if type(i) == str:
+ instructions += i
+ else:
+ instructions += i['text'] + '\n\n'
+ ld_json['recipeInstructions'] = instructions
+
+ if 'image' in ld_json:
+ if (type(ld_json['image'])) == list:
+ if type(ld_json['image'][0]) == str:
+ ld_json['image'] = ld_json['image'][0]
+ elif 'url' in ld_json['image'][0]:
+ ld_json['image'] = ld_json['image'][0]['url']
+
+ return JsonResponse(ld_json)
+
+ return JsonResponse({'error': _('The requested site does not provide any recognized data format to import the recipe from.')})
diff --git a/cookbook/views/data.py b/cookbook/views/data.py
index d59fb26f4..0e2e7db4c 100644
--- a/cookbook/views/data.py
+++ b/cookbook/views/data.py
@@ -88,6 +88,11 @@ def batch_edit(request):
return render(request, 'batch/edit.html', {'form': form})
+@group_required('user')
+def import_url(request):
+ return render(request, 'url_import.html', {})
+
+
class Object(object):
pass
diff --git a/requirements.txt b/requirements.txt
index 10dc51027..68635e67e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,4 +22,5 @@ webdavclient3==3.14.4
whitenoise==5.1.0
icalendar==4.0.6
pyyaml==5.3.1
-uritemplate==3.0.1
\ No newline at end of file
+uritemplate==3.0.1
+beautifulsoup4==4.9.1
\ No newline at end of file