From 148db92918b2e47a6f7ab51175501aeb49ec7c83 Mon Sep 17 00:00:00 2001 From: epochryphon Date: Mon, 6 Apr 2026 23:21:43 -0500 Subject: [PATCH] initial commit --- base-config.yaml | 7 +++ maubot.yaml | 12 +++++ youtube.mbp | Bin 0 -> 15986 bytes youtube.py | 132 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 base-config.yaml create mode 100644 maubot.yaml create mode 100644 youtube.mbp create mode 100644 youtube.py diff --git a/base-config.yaml b/base-config.yaml new file mode 100644 index 0000000..8e42085 --- /dev/null +++ b/base-config.yaml @@ -0,0 +1,7 @@ +# Your youtube API key +# To obtain an API key: +# 1. Log into the Google Developer Console (https://console.developers.google.com) +# 2. Create a new project +# 3. Click "Enable APIs and Services", select the "YouTube Data API v3", and click "Enable" +# 4. Click "Create Credentials", select "Youtube Data API v3", and choose "Public data" and copy your key below +api_key: '' diff --git a/maubot.yaml b/maubot.yaml new file mode 100644 index 0000000..da29c60 --- /dev/null +++ b/maubot.yaml @@ -0,0 +1,12 @@ +maubot: 0.5.1 +id: io.laboon.maubot.youtube +version: 1.0.0 +license: AGPL-3.0-only +modules: + - youtube +main_class: YouTube +extra_files: + - base-config.yaml +config: true +webapp: false +database: false diff --git a/youtube.mbp b/youtube.mbp new file mode 100644 index 0000000000000000000000000000000000000000..42711cd32dfdf9c734cfaf4e84dd819bc47aa894 GIT binary patch literal 15986 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. +""" +import re +from typing import Type +from yarl import URL + +from maubot import Plugin, MessageEvent +from maubot.handlers import event +from mautrix.types import TextMessageEventContent, MessageType, Format, EventType +from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper + +class Config(BaseProxyConfig): + def do_update(self, helper: ConfigUpdateHelper) -> None: + helper.copy("api_key") + +class YouTube(Plugin): + async def start(self) -> None: + self.config.load_and_update() + + @classmethod + def get_config_class(cls) -> Type[BaseProxyConfig]: + return Config + + # Fetch an image URL, upload it to the matrix media store, and return the mcx:// address + async def upload_img(self, link: str) -> str: + image_req = await self.http.get(link) + if (image_req.status < 400): + image = await image_req.read() + mxc = await self.client.upload_media(image, filename=image_req.url.name) + return mxc + return "" + + # Listen to all messages passively instead of requiring a command + @event.on(EventType.ROOM_MESSAGE) + async def handle_message(self, evt: MessageEvent) -> None: + # Ignore messages from the bot itself to prevent infinite loops + if evt.sender == self.client.mxid or not evt.content.body: + return + + # Only process standard text messages or notices + if evt.content.msgtype not in (MessageType.TEXT, MessageType.NOTICE): + return + + # Find all youtube URLs in the message body using regex + # The regex avoids matching trailing markdown brackets/parentheses + urls = re.findall(r'(https?://(?:www\.)?(?:youtube\.com|youtu\.be)[^\s\]\)\>]+)', evt.content.body) + + if not urls: + return + + for video_url_str in urls: + video_url = URL(video_url_str) + video_params = video_url.query + video_id = None + offset = "" + + # Handle URLs + if video_url.host in ("youtube.com", "www.youtube.com"): + if "shorts" in video_url.parts: + video_id = video_url.name + else: + if "v" in video_params: + video_id = video_params["v"] + if "t" in video_params: + offset = f"&t={video_params['t']}" + elif video_url.host == "youtu.be": + video_id = video_url.name + if "t" in video_params: + offset = f"&t={video_params['t']}" + + # If we couldn't extract a video_id (e.g., they linked to a channel page), skip silently + if not video_id: + continue + + # Fetch video info + params = { + "key": self.config["api_key"], + "part": "snippet", + "id": video_id + } + async with self.http.get("https://www.googleapis.com/youtube/v3/videos", params=params) as response: + videos = await response.json() + if "error" in videos: + self.log.error(f"GET {str(response.url)}: {response.status} {videos['error']['message']}") + continue + elif response.status >= 400: + self.log.error(f"GET {str(response.url)}: {response.status}") + continue + + # If video is private, deleted, or wrong ID, skip silently + if len(videos.get("items", [])) < 1: + continue + + video = videos["items"][0] + + # Fix KeyError: 'maxres' by gracefully falling back to available resolutions + thumbnails = video["snippet"]["thumbnails"] + thumb_url = "" + for res in ["maxres", "high", "standard", "medium", "default"]: + if res in thumbnails: + thumb_url = thumbnails[res]["url"] + break + + # Upload thumbnail + img_html = "" + if thumb_url: + mxc = await self.upload_img(thumb_url) + if mxc: + img_html = f"" + + # Construct and send response + content = TextMessageEventContent( + msgtype=MessageType.NOTICE, + format=Format.HTML, + body=f"> YouTube\n> [{video['snippet']['channelTitle']}](https://www.youtube.com/channel/{video['snippet']['channelId']})\n> **[{video['snippet']['title']}](https://www.youtube.com/watch?v={video['id']}{offset})**", + formatted_body=f"
YouTube

{video['snippet']['channelTitle']}

{video['snippet']['title']}

{img_html}
" + ) + await evt.respond(content)