Initial commit

This commit is contained in:
2024-08-27 20:33:44 +02:00
commit 1f1832267d
14794 changed files with 1599592 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
from functools import lru_cache
from importlib import import_module
from django.conf import settings
from django.utils.module_loading import import_string
def import_finder_class(dotted_path):
"""
Imports a finder class from a dotted path. If the dotted path points to a
module, that module is imported and its "embed_finder_class" class returned.
If not, this will assume the dotted path points to directly a class and
will attempt to import that instead.
"""
try:
finder_module = import_module(dotted_path)
return finder_module.embed_finder_class
except ImportError as e:
try:
return import_string(dotted_path)
except ImportError:
raise ImportError from e
def _get_config_from_settings():
if hasattr(settings, "WAGTAILEMBEDS_FINDERS"):
return settings.WAGTAILEMBEDS_FINDERS
else:
# Default to the oembed backend
return [
{
"class": "wagtail.embeds.finders.oembed",
}
]
@lru_cache(maxsize=None)
def get_finders():
finders = []
for finder_config in _get_config_from_settings():
finder_config = finder_config.copy()
cls = import_finder_class(finder_config.pop("class"))
finders.append(cls(**finder_config))
return finders

View File

@@ -0,0 +1,6 @@
class EmbedFinder:
def accept(self, url):
return False
def find_embed(self, url, max_width=None, max_height=None):
raise NotImplementedError

View File

@@ -0,0 +1,76 @@
from django.utils.html import format_html
from wagtail.embeds.exceptions import EmbedException, EmbedNotFoundException
from .base import EmbedFinder
class EmbedlyException(EmbedException):
pass
class AccessDeniedEmbedlyException(EmbedlyException):
pass
class EmbedlyFinder(EmbedFinder):
key = None
def __init__(self, key=None):
if key:
self.key = key
def get_key(self):
return self.key
def accept(self, url):
# We don't really know what embedly supports so accept everything
return True
def find_embed(self, url, max_width=None, key=None):
from embedly import Embedly
# Get embedly key
if key is None:
key = self.get_key()
# Get embedly client
client = Embedly(key=key)
# Call embedly
if max_width is not None:
oembed = client.oembed(url, maxwidth=max_width, better=False)
else:
oembed = client.oembed(url, better=False)
# Check for error
if oembed.get("error"):
if oembed["error_code"] in [401, 403]:
raise AccessDeniedEmbedlyException
elif oembed["error_code"] == 404:
raise EmbedNotFoundException
else:
raise EmbedlyException
# Convert photos into HTML
if oembed["type"] == "photo":
html = format_html('<img src="{}" alt="">', oembed["url"])
else:
html = oembed.get("html")
# Return embed as a dict
return {
"title": oembed["title"] if "title" in oembed else "",
"author_name": oembed["author_name"] if "author_name" in oembed else "",
"provider_name": oembed["provider_name"]
if "provider_name" in oembed
else "",
"type": oembed["type"],
"thumbnail_url": oembed.get("thumbnail_url"),
"width": oembed.get("width"),
"height": oembed.get("height"),
"html": html,
}
embed_finder_class = EmbedlyFinder

View File

@@ -0,0 +1,103 @@
import json
from urllib import request as urllib_request
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request
from wagtail.embeds.exceptions import EmbedException, EmbedNotFoundException
from .oembed import OEmbedFinder
class AccessDeniedFacebookOEmbedException(EmbedException):
pass
FACEBOOK_PROVIDERS = [
# Videos
{
"endpoint": "https://graph.facebook.com/v11.0/oembed_video",
"urls": [
r"^https://(?:www\.)?facebook\.com/.+?/videos/.+$",
r"^https://(?:www\.)?facebook\.com/video\.php\?(?:v|id)=.+$",
r"^https://fb.watch/.+$",
],
},
# Posts
{
"endpoint": "https://graph.facebook.com/v11.0/oembed_post",
"urls": [
r"^https://(?:www\.)?facebook\.com/.+?/(?:posts|activity)/.+$",
r"^https://(?:www\.)?facebook\.com/photo\.php\?fbid=.+$",
r"^https://(?:www\.)?facebook\.com/(?:photos|questions)/.+$",
r"^https://(?:www\.)?facebook\.com/permalink\.php\?story_fbid=.+$",
r"^https://(?:www\.)?facebook\.com/media/set/?\?set=.+$",
r"^https://(?:www\.)?facebook\.com/notes/.+?/.+?/.+$",
# At the moment, not documented on https://developers.facebook.com/docs/plugins/oembed-endpoints
# Works for posts with a single photo
r"^https://(?:www\.)?facebook\.com/.+?/photos/.+$",
],
},
]
class FacebookOEmbedFinder(OEmbedFinder):
"""
An embed finder that supports the authenticated Facebook oEmbed Endpoint.
https://developers.facebook.com/docs/plugins/oembed
"""
def __init__(self, omitscript=False, app_id=None, app_secret=None):
# {settings.facebook_APP_ID}|{settings.facebook_APP_SECRET}
self.app_id = app_id
self.app_secret = app_secret
self.omitscript = omitscript
super().__init__(providers=FACEBOOK_PROVIDERS)
def find_embed(self, url, max_width=None, max_height=None):
# Find provider
endpoint = self._get_endpoint(url)
if endpoint is None:
raise EmbedNotFoundException
params = {"url": url, "format": "json"}
if max_width:
params["maxwidth"] = max_width
if max_height:
params["maxheight"] = max_height
if self.omitscript:
params["omitscript"] = "true"
# Configure request
request = Request(endpoint + "?" + urlencode(params))
request.add_header("Authorization", f"Bearer {self.app_id}|{self.app_secret}")
# Perform request
try:
r = urllib_request.urlopen(request)
except (HTTPError, URLError) as e:
if isinstance(e, HTTPError) and e.code == 404:
raise EmbedNotFoundException
elif isinstance(e, HTTPError) and e.code in [400, 401, 403]:
raise AccessDeniedFacebookOEmbedException
else:
raise EmbedNotFoundException
oembed = json.loads(r.read().decode("utf-8"))
# Return embed as a dict
return {
"title": oembed["title"] if "title" in oembed else "",
"author_name": oembed["author_name"] if "author_name" in oembed else "",
"provider_name": oembed["provider_name"]
if "provider_name" in oembed
else "Facebook",
"type": oembed["type"],
"thumbnail_url": oembed.get("thumbnail_url"),
"width": oembed.get("width"),
"height": oembed.get("height"),
"html": oembed.get("html"),
}
embed_finder_class = FacebookOEmbedFinder

View File

@@ -0,0 +1,91 @@
import json
from urllib import request as urllib_request
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request
from wagtail.embeds.exceptions import EmbedException, EmbedNotFoundException
from .oembed import OEmbedFinder
class AccessDeniedInstagramOEmbedException(EmbedException):
pass
INSTAGRAM_PROVIDER = {
"endpoint": "https://graph.facebook.com/v11.0/instagram_oembed",
"urls": [
r"^https?://(?:www\.)?instagram\.com/p/.+$",
r"^https?://(?:www\.)?instagram\.com/tv/.+$",
r"^https?://(?:www\.)?instagram\.com/reel/.+$",
],
}
class InstagramOEmbedFinder(OEmbedFinder):
"""
An embed finder that supports the authenticated Instagram oEmbed Endpoint.
https://developers.facebook.com/docs/instagram/oembed
"""
def __init__(self, omitscript=False, app_id=None, app_secret=None):
# {settings.INSTAGRAM_APP_ID}|{settings.INSTAGRAM_APP_SECRET}
self.app_id = app_id
self.app_secret = app_secret
self.omitscript = omitscript
super().__init__(providers=[INSTAGRAM_PROVIDER])
def find_embed(self, url, max_width=None, max_height=None):
# Find provider
endpoint = self._get_endpoint(url)
if endpoint is None:
raise EmbedNotFoundException
params = {"url": url, "format": "json"}
if max_width:
params["maxwidth"] = max_width
if max_height:
params["maxheight"] = max_height
if self.omitscript:
params["omitscript"] = "true"
# Configure request
request = Request(endpoint + "?" + urlencode(params))
request.add_header("Authorization", f"Bearer {self.app_id}|{self.app_secret}")
# Perform request
try:
r = urllib_request.urlopen(request)
except (HTTPError, URLError) as e:
if isinstance(e, HTTPError) and e.code == 404:
raise EmbedNotFoundException
elif isinstance(e, HTTPError) and e.code in [400, 401, 403]:
raise AccessDeniedInstagramOEmbedException
else:
raise EmbedNotFoundException
oembed = json.loads(r.read().decode("utf-8"))
# Convert photos into HTML
if oembed["type"] == "photo":
html = '<img src="{}" alt="">'.format(oembed["url"])
else:
html = oembed.get("html")
# Return embed as a dict
return {
"title": oembed["title"] if "title" in oembed else "",
"author_name": oembed["author_name"] if "author_name" in oembed else "",
"provider_name": oembed["provider_name"]
if "provider_name" in oembed
else "Instagram",
"type": oembed["type"],
"thumbnail_url": oembed.get("thumbnail_url"),
"width": oembed.get("width"),
"height": oembed.get("height"),
"html": html,
}
embed_finder_class = InstagramOEmbedFinder

View File

@@ -0,0 +1,99 @@
import json
import re
from datetime import timedelta
from urllib import request as urllib_request
from urllib.error import URLError
from urllib.parse import urlencode
from urllib.request import Request
from django.utils import timezone
from wagtail.embeds.exceptions import EmbedNotFoundException
from wagtail.embeds.oembed_providers import all_providers
from .base import EmbedFinder
class OEmbedFinder(EmbedFinder):
options = {}
_endpoints = None
def __init__(self, providers=None, options=None):
self._endpoints = {}
for provider in providers or all_providers:
patterns = []
endpoint = provider["endpoint"].replace("{format}", "json")
for url in provider["urls"]:
patterns.append(re.compile(url))
self._endpoints[endpoint] = patterns
if options:
self.options = self.options.copy()
self.options.update(options)
def _get_endpoint(self, url):
for endpoint, patterns in self._endpoints.items():
for pattern in patterns:
if re.match(pattern, url):
return endpoint
def accept(self, url):
return self._get_endpoint(url) is not None
def find_embed(self, url, max_width=None, max_height=None):
# Find provider
endpoint = self._get_endpoint(url)
if endpoint is None:
raise EmbedNotFoundException
# Work out params
params = self.options.copy()
params["url"] = url
params["format"] = "json"
if max_width:
params["maxwidth"] = max_width
if max_height:
params["maxheight"] = max_height
# Perform request
request = Request(endpoint + "?" + urlencode(params))
request.add_header("User-agent", "Mozilla/5.0")
try:
r = urllib_request.urlopen(request)
oembed = json.loads(r.read().decode("utf-8"))
except (URLError, json.decoder.JSONDecodeError):
raise EmbedNotFoundException
# Convert photos into HTML
if oembed["type"] == "photo":
html = '<img src="{}" alt="">'.format(oembed["url"])
else:
html = oembed.get("html")
# Return embed as a dict
result = {
"title": oembed.get("title", ""),
"author_name": oembed.get("author_name", ""),
"provider_name": oembed.get("provider_name", ""),
"type": oembed["type"],
"thumbnail_url": oembed.get("thumbnail_url"),
"width": oembed.get("width"),
"height": oembed.get("height"),
"html": html,
}
try:
cache_age = int(oembed["cache_age"])
except (KeyError, TypeError, ValueError):
pass
else:
result["cache_until"] = timezone.now() + timedelta(seconds=cache_age)
return result
embed_finder_class = OEmbedFinder