From e554fc59378d8a4e5704d4aaafb5b40be53e89eb Mon Sep 17 00:00:00 2001 From: Dawid Rejowski Date: Sun, 11 Sep 2022 18:34:24 +0200 Subject: [PATCH] Initial commit --- .directory | 5 + .gitignore | 3 + .idea/.gitignore | 3 + .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/misc.xml | 4 + .idea/modules.xml | 8 ++ .idea/stickerbridge.iml | 10 ++ .idea/vcs.xml | 6 ++ avatar.png | Bin 0 -> 41124 bytes bot_commands.py | 80 ++++++++++++++++ callbacks.py | 49 ++++++++++ chat_functions.py | 75 +++++++++++++++ config.yaml.example | 13 +++ main.py | 52 +++++++++++ matrix_reuploader.py | 63 +++++++++++++ requirements.txt | 7 ++ sticker_types.py | 31 +++++++ telegram_exporter.py | 86 ++++++++++++++++++ 18 files changed, 501 insertions(+) create mode 100644 .directory create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/stickerbridge.iml create mode 100644 .idea/vcs.xml create mode 100644 avatar.png create mode 100644 bot_commands.py create mode 100644 callbacks.py create mode 100644 chat_functions.py create mode 100644 config.yaml.example create mode 100644 main.py create mode 100644 matrix_reuploader.py create mode 100644 requirements.txt create mode 100644 sticker_types.py create mode 100644 telegram_exporter.py diff --git a/.directory b/.directory new file mode 100644 index 0000000..c10fc58 --- /dev/null +++ b/.directory @@ -0,0 +1,5 @@ +[Dolphin] +SortOrder=1 +SortRole=creationtime +Timestamp=2022,9,6,22,9,56.222 +Version=4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6d7d26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.yaml +data/* +telegram_secrets.session \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d0fa063 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e4618e2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/stickerbridge.iml b/.idea/stickerbridge.iml new file mode 100644 index 0000000..00c1736 --- /dev/null +++ b/.idea/stickerbridge.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/avatar.png b/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..057cfbaaed6415bcf5d27e4708bf291c1dd868ba GIT binary patch literal 41124 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�qz5rIHFdU{+Q?QJ&-EU!Y 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 literal 0 HcmV?d00001 diff --git a/bot_commands.py b/bot_commands.py new file mode 100644 index 0000000..0d7bf62 --- /dev/null +++ b/bot_commands.py @@ -0,0 +1,80 @@ +from nio import AsyncClient, MatrixRoom + +from chat_functions import send_text_to_room, upload_image, upload_stickerpack, is_stickerpack_existing, has_permission +from matrix_reuploader import MatrixReuploader +from sticker_types import MatrixStickerset +from telegram_exporter import TelegramExporter + +import tempfile + + +class Command: + def __init__(self, client: AsyncClient, room: MatrixRoom, command: str, tg_exporter: TelegramExporter): + self.client = client + self.room = room + self.command = command.lower() + self.tg_exporter = tg_exporter + self.args = self.command.split()[1:] + + async def process(self): + if self.command.startswith("help"): + await self._show_help() + elif self.command.startswith("import"): + await self._import_stickerpack() + else: + await self._unknown_command() + + async def _show_help(self): + text = ( + "I am the bot that imports stickers from Telegram and upload them to Matrix rooms\n\n" + "List of commands:\n" + "help - Show this help message.\n" + "import - Use this to import Telegram stickers from given link" + ) + await send_text_to_room(self.client, self.room.room_id, text) + + async def _import_stickerpack(self): + + if not self.args: + text = ( + "You need to enter stickerpack name.\n" + "Type command 'help' for more information." + ) + await send_text_to_room(self.client, self.room.room_id, text) + return + + pack_name = self.args[0] + reuploader = MatrixReuploader(self.client, self.room, exporter=self.tg_exporter) + async for status in reuploader.import_stickerset_to_room(pack_name): + if status == MatrixReuploader.STATUS_DOWNLOADING: + text = f'Downloading stickerpack {pack_name}...' + if status == MatrixReuploader.STATUS_UPLOADING: + text = f'Uploading stickerpack {pack_name}...' + if status == MatrixReuploader.STATUS_UPDATING_ROOM_STATE: + text = f'Updating room state...️' + await send_text_to_room(self.client, self.room.room_id, text) + + if reuploader.result == MatrixReuploader.RESULT_OK: + text = 'Done 😄' + if reuploader.result == MatrixReuploader.RESULT_NO_PERMISSION: + text = ( + 'I do not have permissions to create any stickerpack in this room\n' + 'Please, give me mod 🙏' + ) + if reuploader.result == MatrixReuploader.RESULT_PACK_EXISTS: + text = ( + f"Stickerpack '{pack_name}' already exists.\n" + 'Please delete it first.' + ) + if reuploader.result == MatrixReuploader.RESULT_PACK_EMPTY: + text = ( + f'Warning: Telegram pack {pack_name} find out empty or not existing.' + ) + await send_text_to_room(self.client, self.room.room_id, text) + + async def _unknown_command(self): + await send_text_to_room( + self.client, + self.room.room_id, + f"Unknown command '{self.command}'. Try the 'help' command for more information.", + ) diff --git a/callbacks.py b/callbacks.py new file mode 100644 index 0000000..481f0cd --- /dev/null +++ b/callbacks.py @@ -0,0 +1,49 @@ +import logging +import traceback + +from nio import AsyncClient, MatrixRoom, RoomMessageText, InviteEvent, InviteMemberEvent + +from bot_commands import Command +from chat_functions import send_text_to_room +from telegram_exporter import TelegramExporter + + +class Callbacks: + def __init__(self, client: AsyncClient, command_prefix: str, config: dict, tg_exporter: TelegramExporter): + self.client = client + self.command_prefix = command_prefix + self.config = config + self.tg_exporter = tg_exporter + + async def sync(self, response): + with open('data/next_batch', 'w') as next_batch_token: + next_batch_token.write(response.next_batch) + + async def message(self, room: MatrixRoom, event: RoomMessageText) -> None: + + # Ignore messages from ourselves + if event.sender == self.client.user: + return + + if event.body.startswith(self.command_prefix) or room.member_count <= 2: + command_string = event.body.replace(self.command_prefix, '').strip() + command = Command(self.client, room, command_string, self.tg_exporter) + try: + await command.process() + except Exception as e: + logging.error(traceback.format_exc()) + await send_text_to_room(self.client, room.room_id, 'Sorry, there was an internal error:\n' + str(e)) + + async def autojoin_room(self, room: MatrixRoom, event: InviteMemberEvent): + + # Only react to invites for us + if not event.state_key == self.client.user_id: + return + + await self.client.join(room.room_id) + text = ( + f"Hi, I'm a {self.config['matrix_bot_name']}.\n" + "Type '!sb help' to display available commands.\n\n" + "Please do note this bot would not work in encrypted rooms." + ) + await send_text_to_room(self.client, room.room_id, text) diff --git a/chat_functions.py b/chat_functions.py new file mode 100644 index 0000000..e335e48 --- /dev/null +++ b/chat_functions.py @@ -0,0 +1,75 @@ +import os + +import aiofiles.os +import logging + +from nio import AsyncClient, UploadResponse, ErrorResponse, RoomGetStateEventError + +from sticker_types import MatrixStickerset + + +async def send_text_to_room(client: AsyncClient, room_id: str, message: str): + content = { + "msgtype": "m.text", + "body": message, + } + return await client.room_send( + room_id, + "m.room.message", + content, + ) + + +async def has_permission(client: AsyncClient, room_id: str, permission_type: str): + """Reimplementation of AsyncClient.has_permission because matrix-nio version always gives an error + https://github.com/poljar/matrix-nio/issues/324""" + user_id = client.user + power_levels = await client.room_get_state_event(room_id, "m.room.power_levels") + try: + user_power_level = power_levels.content['users'][user_id] + except KeyError: + try: + user_power_level = power_levels.content['users_default'] + except KeyError: + return ErrorResponse("Couldn't get user power levels") + + try: + permission_power_level = power_levels.content[permission_type] + except KeyError: + return ErrorResponse(f"permission_type {permission_type} unknown") + + return user_power_level >= permission_power_level + + +async def is_stickerpack_existing(client: AsyncClient, room_id: str, pack_name: str): + response = (await client.room_get_state_event(room_id, 'im.ponies.room_emotes', pack_name)) + if isinstance(response, RoomGetStateEventError) and response.status_code == 'M_NOT_FOUND': + return False + return not response.content == {} + + +async def upload_stickerpack(client: AsyncClient, room_id: str, stickerset: MatrixStickerset): + return await client.room_put_state(room_id, 'im.ponies.room_emotes', stickerset.json(), state_key=stickerset.name()) + + +async def upload_image(client: AsyncClient, image: str, mime_type: str): + file_stat = await aiofiles.os.stat(image) + async with aiofiles.open(image, "r+b") as f: + resp, maybe_keys = await client.upload( + f, + content_type=mime_type, + filename=os.path.basename(image), + filesize=file_stat.st_size, + ) + if isinstance(resp, UploadResponse): + logging.debug(f"Image {image} was uploaded successfully to server.") + return resp.content_uri + else: + logging.error(f"Failed to upload image ({image}). Failure response: {resp}") + return "" + + +async def upload_avatar(client: AsyncClient, image: str): + avatar_mxc = await upload_image(client, image) + if avatar_mxc: + await client.set_avatar(avatar_mxc) diff --git a/config.yaml.example b/config.yaml.example new file mode 100644 index 0000000..02f8fd0 --- /dev/null +++ b/config.yaml.example @@ -0,0 +1,13 @@ +command_prefix: "!sb" + +matrix_homeserver: "https://matrix.org" +matrix_username: "@username:example.com" +matrix_password: "password" + +matrix_bot_name: "Telegram stickers bot" + +telegram_api_id: 1234567 +telegram_api_hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +telegram_bot_token: "1234567890:aaaaaaaaaaaaaaaaaaaaaa--aaaaaaaaaaa" + +log_level: INFO diff --git a/main.py b/main.py new file mode 100644 index 0000000..492ce50 --- /dev/null +++ b/main.py @@ -0,0 +1,52 @@ +import asyncio +import os +import shutil +import tempfile + +import yaml +import logging + +from nio import AsyncClient, SyncResponse, RoomMessageText, InviteEvent, InviteMemberEvent + +from callbacks import Callbacks +from chat_functions import upload_avatar +from telegram_exporter import TelegramExporter + + +async def main(): + if not os.path.exists('config.yaml'): + shutil.copy('config.yaml.example', 'config.yaml') + logging.warning('Please fill in config.yaml file, then restart the bot') + return + with open("config.yaml", 'r') as config_file: + config = yaml.safe_load(config_file) + + logging.basicConfig(level=os.environ.get("LOGLEVEL", config['log_level'])) + + client = AsyncClient(config['matrix_homeserver'], config['matrix_username']) + client.device_id = config['matrix_bot_name'] + + tg_exporter = TelegramExporter(config['telegram_api_id'], config['telegram_api_hash'], config['telegram_bot_token'], + 'data/telegram_secrets') + await tg_exporter.connect() + + callbacks = Callbacks(client, config['command_prefix'], config, tg_exporter) + client.add_response_callback(callbacks.sync, SyncResponse) + client.add_event_callback(callbacks.message, RoomMessageText) + client.add_event_callback(callbacks.autojoin_room, InviteMemberEvent) + + login_response = await client.login(config['matrix_password']) + logging.info(login_response) + + if os.path.exists('data/next_batch'): + with open("data/next_batch", "r") as next_batch_token: + client.next_batch = next_batch_token.read() + else: + await upload_avatar(client, 'avatar.png') + await client.set_displayname(config['matrix_bot_name']) + + await client.sync_forever(30000) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/matrix_reuploader.py b/matrix_reuploader.py new file mode 100644 index 0000000..7c18acb --- /dev/null +++ b/matrix_reuploader.py @@ -0,0 +1,63 @@ +import tempfile + +from nio import MatrixRoom, AsyncClient + +from chat_functions import has_permission, is_stickerpack_existing, send_text_to_room, upload_image, upload_stickerpack +from sticker_types import Sticker, MatrixStickerset +from telegram_exporter import TelegramExporter + + +class MatrixReuploader: + + RESULT_OK = 0 + RESULT_NO_PERMISSION = 1 + RESULT_PACK_EXISTS = 2 + RESULT_PACK_EMPTY = 3 + + STATUS_DOWNLOADING = 1 + STATUS_UPLOADING = 2 + STATUS_UPDATING_ROOM_STATE = 3 + + def __init__(self, client: AsyncClient, room: MatrixRoom, exporter: TelegramExporter = None, + pack: list[Sticker] = None): + + if not exporter and not pack: + raise ValueError('Either exporter or the pack must be set') + + self.client = client + self.room = room + self.exporter = exporter + self.pack = pack + + self.result = -1 + + async def _has_permission_to_upload(self) -> bool: + return await has_permission(self.client, self.room.room_id, 'state_default') + + async def import_stickerset_to_room(self, pack_name: str): + if not await self._has_permission_to_upload(): + self.result = self.RESULT_NO_PERMISSION + return + + stickerset = MatrixStickerset(pack_name) + if await is_stickerpack_existing(self.client, self.room.room_id, stickerset.name()): + self.result = self.RESULT_PACK_EXISTS + return + + yield self.STATUS_DOWNLOADING + converted_stickerset = await self.exporter.get_stickerset(stickerset.name()) + yield self.STATUS_UPLOADING + for sticker in converted_stickerset: + with tempfile.NamedTemporaryFile('w+b') as file: + file.write(sticker.image_data) + sticker_mxc = await upload_image(self.client, file.name, sticker.mime_type) + stickerset.add_sticker(sticker_mxc, sticker.alt_text) + + if not stickerset.count(): + self.result = self.RESULT_PACK_EMPTY + return + + yield self.STATUS_UPDATING_ROOM_STATE + await upload_stickerpack(self.client, self.room.room_id, stickerset) + + self.result = self.RESULT_OK diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cad4a2f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +telethon +pillow +matrix-nio +pyyaml +aiofiles +lottie +cairosvg \ No newline at end of file diff --git a/sticker_types.py b/sticker_types.py new file mode 100644 index 0000000..b35e57a --- /dev/null +++ b/sticker_types.py @@ -0,0 +1,31 @@ +class Sticker: + """Custom type for easier transfering sticker data between functions and classes with simple lists and returns""" + def __init__(self, image_data, alt_text: str, mime_type: str): + self.image_data = image_data + self.alt_text = alt_text + self.mime_type = mime_type + + +class MatrixStickerset: + def __init__(self, pack_name: str): + self._content = { + "pack": { + "display_name": pack_name + }, + "images": {} + } + + def add_sticker(self, mxc_uri: str, alt_text: str): + self._content['images'][alt_text] = { + "url": mxc_uri, + "usage": ["sticker"] + } + + def count(self): + return len(self._content['images']) + + def name(self): + return self._content['pack']['display_name'] + + def json(self): + return self._content diff --git a/telegram_exporter.py b/telegram_exporter.py new file mode 100644 index 0000000..c61c6e9 --- /dev/null +++ b/telegram_exporter.py @@ -0,0 +1,86 @@ +from typing import List + +from lottie.importers import importers +from lottie.exporters import exporters +from telethon import TelegramClient +from telethon.errors import StickersetInvalidError +from telethon.tl.functions.messages import GetStickerSetRequest +from telethon.tl.types import InputStickerSetShortName + +from io import BytesIO +from PIL import Image + +from sticker_types import Sticker + + +def _convert_image(data: bytes) -> (bytes, int, int): + image: Image.Image = Image.open(BytesIO(data)).convert("RGBA") + new_file = BytesIO() + image.save(new_file, "webp") + w, h = image.size + if w > 256 or h > 256: + if w > h: + h = int(h / (w / 256)) + w = 256 + else: + w = int(w / (h / 256)) + h = 256 + return new_file.getvalue(), w, h + + +def _convert_animation(data: bytes, width=256, height=0): + importer = importers.get_from_extension('tgs') + exporter = exporters.get('webp') + an = importer.process(BytesIO(data)) + + an.frame_rate = 24 + + if width or height: + if not width: + width = an.width * height / an.height + if not height: + height = an.height * width / an.width + an.scale(width, height) + + out = BytesIO() + exporter.process(an, out) + return out.getvalue() + + +class TelegramExporter: + def __init__(self, api_id: int, api_hash: str, bot_token: str, secrets_filename: str): + """Exports Telegram stickers as images. + + :param api_id: Can be obtained at https://my.telegram.org/apps + :param api_hash: Can be obtained at https://my.telegram.org/apps + :param bot_token: Required to get stickers, can be obtained by talking to https://t.me/botfather + :param secrets_filename: Session name, it would be filename of stored creditials + """ + self.api_id = api_id + self.api_hash = api_hash + self.bot_token = bot_token + self.secrets_filename = secrets_filename + + self.client = TelegramClient(self.secrets_filename, self.api_id, self.api_hash) + + async def connect(self): + await self.client.start(bot_token=self.bot_token) + + async def get_stickerset(self, pack_name: str) -> list[Sticker]: + result: List[Sticker] = list() + + try: + sticker_set = await self.client(GetStickerSetRequest(InputStickerSetShortName(short_name=pack_name), hash=0)) + except StickersetInvalidError: + return result + for sticker_document in sticker_set.documents: + alt = sticker_document.attributes[1].alt + raw_data = await self.client.download_media(sticker_document, file=bytes) + if sticker_document.mime_type == 'image/webp': + data, width, height = _convert_image(raw_data) + result.append(Sticker(data, alt, 'image/png')) + if sticker_document.mime_type == 'application/x-tgsticker': + data = _convert_animation(raw_data) + result.append(Sticker(data, alt, 'image/webp')) + + return result