Add URL embedding functionality with metadata extraction and responsive design
This commit is contained in:
		
							parent
							
								
									7818410d55
								
							
						
					
					
						commit
						4f8edef42b
					
				@ -544,3 +544,44 @@ dialog .dialog-button.secondary:hover {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.embed-url-link {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.embed-url-link img,
 | 
			
		||||
.embed-url-link video,
 | 
			
		||||
.embed-url-link iframe,
 | 
			
		||||
.embed-url-link div {
 | 
			
		||||
  width: auto;
 | 
			
		||||
  height: auto;
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
  max-height: 400px;
 | 
			
		||||
  object-fit: contain;
 | 
			
		||||
  border-radius: 12px 12px 0 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.embed-url-link .page-site {
 | 
			
		||||
  font-size: 0.9em;
 | 
			
		||||
  color: #aaa;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.embed-url-link .page-name {
 | 
			
		||||
  font-size: 1.2em;
 | 
			
		||||
  color: #f05a28;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.embed-url-link .page-description {
 | 
			
		||||
  font-size: 1em;
 | 
			
		||||
  color: #e6e6e6;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.embed-url-link .page-link {
 | 
			
		||||
  font-size: 0.9em;
 | 
			
		||||
  color: #f05a28;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,14 @@
 | 
			
		||||
import asyncio
 | 
			
		||||
import re
 | 
			
		||||
from functools import lru_cache
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
from urllib.parse import urlparse, parse_qs
 | 
			
		||||
from types import SimpleNamespace
 | 
			
		||||
 | 
			
		||||
import mimetypes
 | 
			
		||||
 | 
			
		||||
import aiohttp
 | 
			
		||||
import emoji
 | 
			
		||||
from bs4 import BeautifulSoup
 | 
			
		||||
from jinja2 import TemplateSyntaxError, nodes
 | 
			
		||||
@ -231,6 +237,153 @@ def linkify_https(text):
 | 
			
		||||
    return set_link_target_blank(str(soup))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@lru_cache(maxsize=128)
 | 
			
		||||
def get_url_content(url):
 | 
			
		||||
    try:
 | 
			
		||||
        response = requests.get(url, timeout=5)
 | 
			
		||||
        response.raise_for_status()
 | 
			
		||||
        return response.text
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"Error fetching {url}: {e}")
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def embed_url(text):
 | 
			
		||||
    soup = BeautifulSoup(text, "html.parser")
 | 
			
		||||
 | 
			
		||||
    attachments = {}
 | 
			
		||||
 | 
			
		||||
    for element in soup.find_all("a"):
 | 
			
		||||
        if "href" in element.attrs and element.attrs["href"].startswith("http"):
 | 
			
		||||
            page_url = urlparse(element.attrs["href"])
 | 
			
		||||
            page = get_url_content(element.attrs["href"])
 | 
			
		||||
            if page:
 | 
			
		||||
                parsed_page = BeautifulSoup(page, "html.parser")
 | 
			
		||||
                head_info = parsed_page.find("head")
 | 
			
		||||
                if head_info:
 | 
			
		||||
 | 
			
		||||
                    def get_element_options(
 | 
			
		||||
                        elem=None, meta=None, ograph=None, twitter=None
 | 
			
		||||
                    ):
 | 
			
		||||
                        if twitter:
 | 
			
		||||
                            tw_tag = head_info.find(
 | 
			
		||||
                                "meta", attrs={"name": "twitter:" + twitter}
 | 
			
		||||
                            ) or head_info.find(
 | 
			
		||||
                                "meta", attrs={"property": "twitter:" + twitter}
 | 
			
		||||
                            )
 | 
			
		||||
                            if tw_tag:
 | 
			
		||||
                                return tw_tag.get("content", tw_tag.get("value", None))
 | 
			
		||||
 | 
			
		||||
                        if ograph:
 | 
			
		||||
                            og_tag = head_info.find(
 | 
			
		||||
                                "meta", attrs={"property": "og:" + ograph}
 | 
			
		||||
                            ) or head_info.find("meta", attrs={"name": "og:" + ograph})
 | 
			
		||||
                            if og_tag:
 | 
			
		||||
                                return og_tag.get("content", og_tag.get("value", None))
 | 
			
		||||
 | 
			
		||||
                        if meta:
 | 
			
		||||
                            meta_tag = head_info.find(
 | 
			
		||||
                                "meta", attrs={"name": meta}
 | 
			
		||||
                            ) or head_info.find("meta", attrs={"property": meta})
 | 
			
		||||
                            if meta_tag:
 | 
			
		||||
                                return meta_tag.get(
 | 
			
		||||
                                    "content", meta_tag.get("value", None)
 | 
			
		||||
                                )
 | 
			
		||||
 | 
			
		||||
                        if elem:
 | 
			
		||||
                            elem_tag = head_info.find(elem)
 | 
			
		||||
                            if elem_tag:
 | 
			
		||||
                                return elem_tag.text
 | 
			
		||||
 | 
			
		||||
                        return None
 | 
			
		||||
 | 
			
		||||
                    original_link_name = element.attrs["href"]
 | 
			
		||||
 | 
			
		||||
                    if original_link_name in attachments:
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    page_name = (
 | 
			
		||||
                        get_element_options("title", "title", "title", "title")
 | 
			
		||||
                        or page_url.netloc
 | 
			
		||||
                    )
 | 
			
		||||
                    page_site = (
 | 
			
		||||
                        get_element_options(None, "site", "site", "site")
 | 
			
		||||
                        or page_url.netloc
 | 
			
		||||
                    )
 | 
			
		||||
                    page_description = get_element_options(
 | 
			
		||||
                        None, "description", "description", "description"
 | 
			
		||||
                    )
 | 
			
		||||
                    page_image = get_element_options(None, "image", "image", "image")
 | 
			
		||||
                    page_image_alt = get_element_options(
 | 
			
		||||
                        None, "image:alt", "image:alt", "image:alt"
 | 
			
		||||
                    )
 | 
			
		||||
                    page_video = get_element_options(None, "video", "video", "video")
 | 
			
		||||
                    page_audio = get_element_options(None, "audio", "audio", "audio")
 | 
			
		||||
 | 
			
		||||
                    preview_size = (
 | 
			
		||||
                        get_element_options(None, None, None, "card")
 | 
			
		||||
                        or "summary_large_image"
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    attachment_base = BeautifulSoup(str(element), "html.parser")
 | 
			
		||||
                    attachments[original_link_name] = attachment_base
 | 
			
		||||
 | 
			
		||||
                    attachment = next(attachment_base.children)
 | 
			
		||||
 | 
			
		||||
                    attachment.clear()
 | 
			
		||||
                    attachment.attrs["class"] = "embed-url-link"
 | 
			
		||||
 | 
			
		||||
                    render_element = attachment
 | 
			
		||||
 | 
			
		||||
                    if page_image:
 | 
			
		||||
                        image_template = f'<span><img src="{page_image}" alt="{page_image_alt or page_name}" title="{page_name}" width="420" height="240" /></span>'
 | 
			
		||||
                        render_element.append(
 | 
			
		||||
                            BeautifulSoup(image_template, "html.parser")
 | 
			
		||||
                        )
 | 
			
		||||
                    if page_video:
 | 
			
		||||
                        video_template = f'<video controls><source src="{page_video}">Your browser does not support the video tag.</video>'
 | 
			
		||||
                        render_element.append(
 | 
			
		||||
                            BeautifulSoup(video_template, "html.parser")
 | 
			
		||||
                        )
 | 
			
		||||
                    if page_audio:
 | 
			
		||||
                        audio_template = f'<audio controls><source src="{page_audio}">Your browser does not support the audio tag.</audio>'
 | 
			
		||||
                        render_element.append(
 | 
			
		||||
                            BeautifulSoup(audio_template, "html.parser")
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                    description_element_base = BeautifulSoup(
 | 
			
		||||
                        "<span class='description'></span>", "html.parser"
 | 
			
		||||
                    )
 | 
			
		||||
                    description_element = next(description_element_base.children)
 | 
			
		||||
                    description_element.append(
 | 
			
		||||
                        BeautifulSoup(
 | 
			
		||||
                            f'<p class="page-site">{page_site}</p>',
 | 
			
		||||
                            "html.parser",
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    description_element.append(
 | 
			
		||||
                        BeautifulSoup(f'<strong class="page-name">{page_name}</strong>', "html.parser")
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    description_element.append(
 | 
			
		||||
                        BeautifulSoup(f"<p class='page-description'>{page_description or "No description available."}</p>", "html.parser")
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    description_element.append(
 | 
			
		||||
                        BeautifulSoup(f"<p class='page-original-link'>{original_link_name}</p>", "html.parser")
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    render_element.append(description_element_base)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    for attachment in attachments.values():
 | 
			
		||||
        soup.append(attachment)
 | 
			
		||||
 | 
			
		||||
    return str(soup)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmojiExtension(Extension):
 | 
			
		||||
    tags = {"emoji"}
 | 
			
		||||
 | 
			
		||||
@ -276,6 +429,9 @@ class LinkifyExtension(Extension):
 | 
			
		||||
        result = embed_youtube(result)
 | 
			
		||||
 | 
			
		||||
        result = enrich_image_rendering(result)
 | 
			
		||||
 | 
			
		||||
        result = embed_url(result)
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user