Repository: MasterGroosha/my-id-bot Branch: master Commit: 882d7b9b15ab Files: 29 Total size: 37.7 KB Directory structure: gitextract_0g8cu8tb/ ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bot/ │ ├── __init__.py │ ├── __main__.py │ ├── config_reader.py │ ├── fluent_helper.py │ ├── handlers/ │ │ ├── __init__.py │ │ ├── add_or_migrate.py │ │ ├── commands.py │ │ ├── errors.py │ │ ├── inline_mode.py │ │ └── pm.py │ ├── locales/ │ │ ├── en/ │ │ │ └── strings.ftl │ │ ├── es/ │ │ │ └── strings.ftl │ │ ├── ro/ │ │ │ └── strings.ftl │ │ ├── ru/ │ │ │ └── strings.ftl │ │ └── uk/ │ │ └── strings.ftl │ ├── logs.py │ ├── middlewares/ │ │ ├── __init__.py │ │ ├── l10n.py │ │ └── log_unhandled.py │ ├── migration_cache.py │ └── ui_commands.py ├── docker-compose.example.yml ├── env_example ├── my-id-bot.example.service └── requirements.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ venv/ __pycache__/ *.log /.env my-id-bot.service docker-compose.yml ================================================ FILE: Dockerfile ================================================ # Separate "build" image FROM python:3.11-slim as builder WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY bot /app/bot # Final stage FROM gcr.io/distroless/python3-debian12:nonroot COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --from=builder /app /app WORKDIR /app ENV PYTHONPATH=/usr/local/lib/python3.11/site-packages CMD ["-m", "bot"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019-present Aleksandr K. (also known as MasterGroosha on GitHub) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Bot to get users/chats IDs in Telegram This is a simple bot written with [aiogram 3.x](https://github.com/aiogram/aiogram) framework to show some IDs, like: * Your user ID (when asked in inline mode or in private chat with any message); * Group/supergroup ID (when added to that group or with /id command); * Channel ID (when message forwarded from channel to one-to-one chat with bot); * Supergroup ID (when message forwarded from anonymous group admin); * Topic ID for [forum supergroups](https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups); * Sticker ID (they can be re-used with any bot); * Group to supergroup migrate information (both old and new ID). ## Requirements: * Python 3.11 and newer; * Linux (should work on Windows, but not tested); * Systemd init system (optional). * Docker (optional). ## Installation: ### Just to test (not recommended) 1. Clone this repo; 2. `cd` to cloned directory and initialize Python virtual environment (venv); 3. Activate the venv and install all dependencies from `requirements.txt` file; 4. Copy `env_example` to `.env` (with the leading dot), open `.env` and edit the variables; 5. In the activated venv: `python -m bot` ### Systemd 1. Perform steps 1-4 from "just to test" option above; 2. Copy `my-id-bot.example.service` to `my-id-bot.service` (or whatever your prefer), open it and edit `WorkingDirectory` and `ExecStart` directives; 3. Copy (or symlink) that service file to `/etc/systemd/system/` directory; 4. Enable your service `sudo systemctl enable my-id-bot --now`; 5. Check that service is running: `systemctcl status my-id-bot` (can be used without root privileges). ### Docker + Docker Compose 1. Get `docker-compose.example.yml` file and rename it as `docker-compose.yml`; 2. Get `env_example` file, rename it as `.env` (with the leading dot), open it and edit the variables; 3. Run the bot: `docker compose up -d`; 4. Check that container is up and running: `docker compose ps` ================================================ FILE: bot/__init__.py ================================================ ================================================ FILE: bot/__main__.py ================================================ import asyncio from pathlib import Path import structlog from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode from structlog.typing import FilteringBoundLogger from bot.config_reader import bot_config, log_config from bot.fluent_helper import FluentDispenser from bot.handlers import commands, pm, add_or_migrate, inline_mode, errors from bot.logs import get_structlog_config from bot.middlewares import L10nMiddleware, UnhandledUpdatesLoggerMiddleware from bot.ui_commands import set_bot_commands logger: FilteringBoundLogger = structlog.get_logger() async def main(): structlog.configure(**get_structlog_config(log_config)) bot = Bot( token=bot_config.bot_token.get_secret_value(), default=DefaultBotProperties( parse_mode=ParseMode.HTML, ) ) # Setup dispatcher dp = Dispatcher() dispenser = FluentDispenser( locales_dir=Path(__file__).parent.joinpath("locales"), default_language="en" ) dp.update.middleware(L10nMiddleware(dispenser)) if log_config.log_unhandled: dp.update.outer_middleware(UnhandledUpdatesLoggerMiddleware()) # Register handlers dp.include_routers( commands.router, pm.router, add_or_migrate.router, inline_mode.router, errors.router ) # Set bot commands in UI await set_bot_commands(bot, dispenser) # Run bot await logger.awarning( "Important! This version is the last one to use environment variables for configuration. " "The next version is going to use TOML file. Be careful when upgrading bot version in the future." ) await logger.ainfo("Starting bot") await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()) await logger.ainfo("Bot stopped") if __name__ == "__main__": asyncio.run(main()) ================================================ FILE: bot/config_reader.py ================================================ from enum import Enum from pydantic import SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict class LoggingRenderer(str, Enum): JSON = "json" CONSOLE = "console" class LoggingSettings(BaseSettings): level: str = "INFO" format: str = "%Y-%m-%d %H:%M:%S" is_utc: bool = False renderer: LoggingRenderer = LoggingRenderer.JSON log_unhandled: bool = False model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", env_prefix="LOGGING_", extra="allow", ) class BotSettings(BaseSettings): bot_token: SecretStr model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="allow", ) bot_config = BotSettings() log_config = LoggingSettings() ================================================ FILE: bot/fluent_helper.py ================================================ from pathlib import Path from fluent.runtime import FluentLocalization, FluentResourceLoader class FluentDispenser: def __init__(self, locales_dir: Path, default_language: str = "en"): self.__loader = FluentResourceLoader(str(locales_dir) + "/{locale}") self.__default_language = default_language self.languages = dict() dirs_names = set() default_language_dir = None for item in locales_dir.iterdir(): dirs_names.add(item.name) if item.name == self.__default_language: default_language_dir = item if not default_language_dir: raise ValueError("FluentDispenser: default language directory not found") ftl_files_list = [item.name for item in default_language_dir.iterdir() if item.suffix == ".ftl"] for name in dirs_names: if name == default_language: self.languages[name] = FluentLocalization([self.__default_language], ftl_files_list, self.__loader) else: self.languages[name] = FluentLocalization([name, self.__default_language], ftl_files_list, self.__loader) @property def default_locale(self) -> FluentLocalization: return self.languages[self.__default_language] @property def available_languages(self) -> list[str]: return list(self.languages.keys()) def get_language(self, language_code: str): return self.languages.get(language_code, self.default_locale) ================================================ FILE: bot/handlers/__init__.py ================================================ ================================================ FILE: bot/handlers/add_or_migrate.py ================================================ from asyncio import sleep from aiogram import Bot, html, Router, F from aiogram.enums import ChatType from aiogram.filters.chat_member_updated import \ ChatMemberUpdatedFilter, JOIN_TRANSITION from aiogram.types import ChatMemberUpdated, Message from fluent.runtime import FluentLocalization from bot.migration_cache import cache router = Router() @router.my_chat_member( ChatMemberUpdatedFilter( member_status_changed=JOIN_TRANSITION ), F.chat.type.in_({ChatType.GROUP, ChatType.SUPERGROUP}) ) async def bot_added_to_group(event: ChatMemberUpdated, bot: Bot, l10n: FluentLocalization): """ Bot was added to group. :param event: an event from Telegram of type "my_chat_member" :param bot: bot who message was addressed to :param l10n: Fluent localization object :return: """ await sleep(1.0) if event.chat.id not in cache.keys(): await bot.send_message( chat_id=event.chat.id, text=l10n.format_value( "any-chat", args={"type": event.chat.type, "id": html.code(event.chat.id)} ) ) @router.message(F.migrate_to_chat_id) async def group_to_supergroup_migration(message: Message, bot: Bot, l10n: FluentLocalization): await bot.send_message( message.migrate_to_chat_id, l10n.format_value( "group-to-supergroup", args={"old_id": html.code(message.chat.id), "new_id": html.code(message.migrate_to_chat_id)} ) ) cache[message.migrate_to_chat_id] = True ================================================ FILE: bot/handlers/commands.py ================================================ from aiogram import Router, html, F from aiogram.enums import ChatType from aiogram.filters import Command, CommandStart from aiogram.types import Message from aiogram.utils.keyboard import InlineKeyboardBuilder from fluent.runtime import FluentLocalization router = Router() router.message.filter(~F.forward_from & ~F.forward_from_chat) @router.message(F.chat.type == ChatType.PRIVATE, CommandStart()) async def cmd_start(message: Message, l10n: FluentLocalization): """ /start command handler for private chats :param message: Telegram message with "/start" command :param l10n: Fluent localization object """ builder = InlineKeyboardBuilder() builder.button(text=l10n.format_value("cmd-start-inline-try-here"), switch_inline_query_current_chat="") builder.button(text=l10n.format_value("cmd-start-inline-try-other"), switch_inline_query="") await message.answer( l10n.format_value("cmd-start", args={"id": html.code(message.chat.id)}), reply_markup=builder.adjust(1).as_markup() ) @router.message(F.chat.type == ChatType.PRIVATE, Command("id")) async def cmd_id_pm(message: Message, l10n: FluentLocalization): """ /id command handler for private messages :param message: Telegram message with "/id" command :param l10n: Fluent localization object """ await message.answer( l10n.format_value("cmd-id-pm", args={"id": html.code(message.from_user.id)}) ) @router.message( F.chat.type.in_({ChatType.GROUP, ChatType.SUPERGROUP}), Command("id") ) @router.message( F.chat.type.in_({ChatType.GROUP, ChatType.SUPERGROUP}), CommandStart(deep_link=True, magic=F.args == "id") ) async def cmd_id_groups(message: Message, l10n: FluentLocalization): """ /id command handler for (super)groups :param message: Telegram message with "/id" command :param l10n: Fluent localization object """ chat_type_str = l10n.format_value(message.chat.type) msg = [l10n.format_value("any-chat", args={"type": chat_type_str, "id": html.code(message.chat.id)})] if message.is_topic_message: msg.append( l10n.format_value( "cmd-id-group-topic-id", args={"type": message.chat.type, "id": html.code(message.message_thread_id)} ) ) if message.sender_chat is None: msg.append(l10n.format_value("cmd-id-pm", args={"id": html.code(message.from_user.id)})) else: msg.append(l10n.format_value("cmd-id-group-as-channel", args={"id": html.code(message.sender_chat.id)})) await message.reply("\n".join(msg)) @router.message(Command("help")) async def cmd_help(message: Message, l10n: FluentLocalization): """ /help command handler for all chats :param message: Telegram message with "/help" command :param l10n: Fluent localization object """ await message.answer(l10n.format_value("cmd-help"), disable_web_page_preview=True) ================================================ FILE: bot/handlers/errors.py ================================================ import structlog from aiogram import Router from aiogram.exceptions import TelegramAPIError from aiogram.types.error_event import ErrorEvent from structlog.typing import FilteringBoundLogger router = Router(name="errors-router") logger: FilteringBoundLogger = structlog.get_logger() @router.errors() async def handle_errors(event: ErrorEvent): if isinstance(event.exception, TelegramAPIError): error_message = event.exception.message error_source = "BotAPI" else: error_message = str(event.exception) error_source = "Python" await logger.aerror( "Outgoing bot message error", exception_type=event.exception.__class__.__name__, message=error_message, update=event.update.dict(), error_source=error_source ) ================================================ FILE: bot/handlers/inline_mode.py ================================================ from aiogram import html, Router from aiogram.enums import ChatType from aiogram.types import InlineQuery, InlineQueryResultArticle, InputTextMessageContent from fluent.runtime import FluentLocalization router = Router() @router.inline_query() async def inline_mode_handler(query: InlineQuery, l10n: FluentLocalization): result = InlineQueryResultArticle( id=".", title=l10n.format_value("inline-mode-title", args={"id": query.from_user.id}), description=l10n.format_value("inline-mode-description"), input_message_content=InputTextMessageContent( message_text=l10n.format_value("inline-mode-text", args={"id": html.code(query.from_user.id)}) ) ) # Do not forget about is_personal parameter! Otherwise, all people will see the same ID switch_pm_text = l10n.format_value("inline-mode-tryme") if query.chat_type != ChatType.SENDER else None await query.answer( results=[result], cache_time=3600, is_personal=True, switch_pm_parameter="1", switch_pm_text=switch_pm_text ) ================================================ FILE: bot/handlers/pm.py ================================================ from aiogram import Router, html, F from aiogram.enums import ChatType from aiogram.filters import MagicData from aiogram.types import Message from fluent.runtime import FluentLocalization router = Router() router.message.filter(F.chat.type == ChatType.PRIVATE) @router.message(F.forward_from_chat.type.as_("chat_type")) async def get_channel_or_supergroup_id(message: Message, chat_type: str, l10n: FluentLocalization): """ Handler for message forwarded from channel or from anonymous admin writing on behalf of a supergroup :param message: Telegram message with "forward_from_chat" field not empty :param chat_type: parsed chat_type ("channel" or "supergroup") :param l10n: Fluent localization object """ chat_type_str = l10n.format_value(chat_type) msg = l10n.format_value("any-chat", args={"type": chat_type_str, "id": html.code(message.forward_from_chat.id)}) if message.sticker: msg += "\n" + l10n.format_value("sticker-id", args={"id": html.code(message.sticker.file_id)}) await message.reply(msg) @router.message(F.forward_from) async def get_user_id_no_privacy(message: Message, l10n: FluentLocalization): """ Handler for message forwarded from other user who doesn't hide their ID :param message: Telegram message with "forward_from" field not empty :param l10n: Fluent localization object """ account_type = "bot" if message.forward_from.is_bot else "user" chat_type_str = l10n.format_value(account_type) msg = l10n.format_value("any-chat", args={"type": chat_type_str, "id": html.code(message.forward_from.id)}) if message.sticker: msg += "\n" + l10n.format_value("sticker-id", args={"id": html.code(message.sticker.file_id)}) await message.reply(msg) @router.message(F.forward_sender_name) async def get_user_id_with_privacy(message: Message, l10n: FluentLocalization): """ Handler for message forwarded from other user who hides their ID :param message: Telegram message with "forward_sender_name" field not empty :param l10n: Fluent localization object """ msg = l10n.format_value("user-id-hidden") if message.sticker: msg += "\n\n" + l10n.format_value("sticker-id", args={"id": html.code(message.sticker.file_id)}) await message.reply(msg) @router.message(F.sticker) async def sticker_in_pm(message: Message, l10n: FluentLocalization): """ /start command handler for private chats :param message: Telegram message with "/start" command :param l10n: Fluent localization object """ # await message.reply( l10n.format_value("sticker-id-extended", args={"id": html.code(message.sticker.file_id)}) ) @router.message(F.via_bot, MagicData(F.event.via_bot.id != F.bot.id)) # noqa async def other_inline_bot_in_pm(message: Message, l10n: FluentLocalization): """ Message via some other inline bot in PM :param message: Any Telegram message :param l10n: Fluent localization object """ bot_str = l10n.format_value("bot") await message.answer( l10n.format_value("any-chat", args={"type": bot_str, "id": html.code(message.via_bot.id)}) ) @router.message(~F.via_bot) async def other_in_pm(message: Message, l10n: FluentLocalization): """ Any other message in PM, not via inline bot :param message: Any Telegram message :param l10n: Fluent localization object """ await message.answer(l10n.format_value("cmd-id-pm", args={"id": html.code(message.from_user.id)})) ================================================ FILE: bot/locales/en/strings.ftl ================================================ # without @ ! bot-username = my_id_bot # do not translate! bot-group-deeplink = https://t.me/{bot-username}?startgroup=id source-code-link = https://github.com/MasterGroosha/my-id-bot cmd-start = Your Telegram ID is { $id } Help and source code: /help You can also use this bot in inline mode to share its ID! Try using one of the buttons below. Please note, that bot uses your language in PM and English in any other chats. cmd-start-inline-try-here = Try here cmd-start-inline-try-other = Try in other chat # Used in strings like "This channel id is xxx" supergroup = supergroup group = group channel = channel user = user bot = bot any-chat = This { $type } ID is { $id } cmd-id-pm = Your Telegram ID is { $id } cmd-id-group-topic-id = This forum topic ID is { $id } cmd-id-group-as-channel = And you've sent this message as channel with ID { $id } cmd-help = Use this bot to get ID for different entities across Telegram: • Forward message from channel to get channel ID; • Forward message from anonymous supergroup admin to get supergroup ID; • Forward message from user to get their ID (unless they restrict from doing so); • Forward message from some other bot or use it via inline mode to get bot ID; • Send a sticker to get its file_id (currently you can use the sticker's file_id with any bot); • Add bot to group to get its ID (it will even tell you when you migrate from group to supergroup); • Use inline mode to send your Telegram ID to any chat. Source code: { source-code-link } group-to-supergroup = Group upgraded to supergroup. Old ID: { $old_id } New ID: { $new_id } inline-mode-title = Your ID is { NUMBER($id, useGrouping: 0) } inline-mode-description = Tap to send your ID to current chat inline-mode-text = My Telegram ID is { $id } inline-mode-tryme = Or try me in PM >>> sticker-id = Also this sticker's ID is { $id } sticker-id-extended = This sticker ID is { $id } Sticker is currently the only media type which file_ids can be used by any bot. user-id-hidden = This user decided to hide their ID. Learn more about this feature here. # Commands in UI (when you press "/" or "Menu" button) cmd-hint-id-pm = Print your Telegram ID cmd-hint-id-group = Print Telegram ID of this group chat cmd-hint-help = Help and source code ================================================ FILE: bot/locales/es/strings.ftl ================================================ # without @ ! bot-username = my_id_bot # do not translate! bot-group-deeplink = https://t.me/{bot-username}?startgroup=id source-code-link = https://github.com/MasterGroosha/my-id-bot cmd-start = Tu ID de Telegram es { $id }. Ayuda y código fuente: /help También puedes usar este bot en modo inline para compartir la ID! Prueba usando lso botones de abajo. Por favor, ten en cuenta que el bot usa tu idioma en privado y en inglés en cualquier otro chat. cmd-start-inline-try-here = Intenta aquí cmd-start-inline-try-other = Intenta en otro chat # Used in strings like "This channel id is xxx" supergroup = supergrupo group = grupo channel = canal user = usuario bot = bot any-chat = Esta { $type } ID es { $id } cmd-id-pm = Tu ID de Telegram es { $id }. cmd-id-group-topic-id = La ID de este grupo es { $id }. cmd-id-group-as-channel = Y has envido este mensaje de un canal con ID { $id }. cmd-help = Usa este bot para obtener la ID de diferentes entidades en Telegram: • Reenvía un mensaje de un canal para obtener la ID del canal; • Reenvía un mensaje de un admin de supergrupo anónimo para obtener la ID del supergrupo; • Reenvía un mensaje de un usuario para obtener su ID (Solo si no tiene activada la protección); • Reenvía un mensaje de otro bot o usa el modo inline para obtener la ID de este; • Envía un sticker para obtener su file_id (Puedes usar el file_id de sticker con cualquier bot); • Añade el bot a un grupo para obtener su ID (incluso te dirá cuando pases de grupo a supergrupo); • Usa el modo inline para enviar tu ID de Telegram a otros chats. Source code: { source-code-link } group-to-supergroup = El grupo se ha convertido en supergroup. Antigua ID: { $old_id } Nueva ID: { $new_id } inline-mode-title = Tu ID es { NUMBER($id, useGrouping: 0) } inline-mode-description = has click para mandar tu ID al chat actual. inline-mode-text = Mi ID de Telegram es { $id }. inline-mode-tryme = O prueba el bot en PM >>> sticker-id = Ademas, la ID del sticker es { $id }. sticker-id-extended = La ID del sticker es { $id } El sticker es actualmente el único tipo de datos cuya file_ids pueden usar los bots. user-id-hidden = Este usuario ha decidido ocultar su ID. Puedes leer más sobre este cambio aquí. # Commands in UI (when you press "/" or "Menu" button) cmd-hint-id-pm = Muestra tu ID de Telegram. cmd-hint-id-group = Muestra la ID de Telegram de este chat de grupo. cmd-hint-help = Ayuda y código fuente. ================================================ FILE: bot/locales/ro/strings.ftl ================================================ # without @ ! bot-username = my_id_bot # do not translate! bot-group-deeplink = https://t.me/{bot-username}?startgroup=id source-code-link = https://github.com/MasterGroosha/my-id-bot cmd-start = ID-ul tău de Telegram este { $id } Ajutor si cod sursă: /help Poți folosi și acest bot în modul inline pentru a partaja ID-ul său! Încearcă să folosești unul dintre butoanele de mai jos. Te rugăm să reții că botul folosește limba ta în mesajele private și engleza în orice alte conversații. cmd-start-inline-try-here = Încearcă aici cmd-start-inline-try-other = Încearcă într-un alt chat # Used in strings like "This channel id is xxx" supegroup = supegrup group = grup channel = canal user = utilizator bot = bot any-chat = Acest { $type } de ID este { $id } cmd-id-pm = ID-ul tău de Telegram este { $id } cmd-id-group-topic-id = ID-ul acestui subiect de forum este { $id } cmd-id-groupd-as-channel = Și ai trimis acest mesaj ca canal cu ID-ul { $id } cmd-help = Folosește acest bot pentru a obține ID-ul pentru diferite entități din Telegram: • Trimite mai departe mesajul din canal pentru a obține ID-ul canalului; • Trimite mai departe mesajul de la un administrator anonim al supergrupului pentru a obține ID-ul supergrupului; • Trimite mai departe mesajul de la utilizator pentru a obține ID-ul lor (cu excepția cazului în care aceștia restricționează acest lucru); • Trimite mai departe mesajul de la un alt bot sau folosește-l prin modul inline pentru a obține ID-ul botului; • Trimite un sticker pentru a obține file_id-ul său (în prezent, poți folosi file_id-ul sticker-ului cu orice bot); • Adaugă botul în grup pentru a obține ID-ul său (îți va spune chiar și când migrezi de la grup la supergrup); • Folosește modul inline pentru a trimite ID-ul tău de Telegram în orice conversație. Cod sursă: { source-code-link } group-to-supegroup = Grupul a fost actualizat la supergrup. ID-ul vechi: { $old_id } ID-ul nou: { $new_id } inline-mode-title = ID-ul tău este { NUMBER($id, useGrouping: 0) } inline-mode-description = Apasă pentru a trimite ID-ul tău în conversația curentă inline-mode-text = ID-ul meu de Telegram este { $id } inline-mode-tryme = Sau acceseaza-mă în mesaj privat >>> sticker-id = De asemenea, ID-ul acestui sticker este { $id } sticker-id-extended = ID-ul acestui sticker este { $id } Stickerul este în prezent singurul tip de media al cărui file_id poate fi folosit de orice bot. user-id-hidden = Acest utilizator a decis să își ascundă ID-ul. Află mai multe despre această funcție aici. # Commands in UI (when you press "/" or "Menu" button) cmd-hint-id-pm = Printează ID-ul tău de Telegram cmd-hint-id-group = Printează Telegram ID-ul al acestui chat de grup cmd-hint-help = Suport și cod sursă ================================================ FILE: bot/locales/ru/strings.ftl ================================================ # without @ ! bot-username = my_id_bot # do not translate! bot-group-deeplink = https://t.me/{bot-username}?startgroup=id source-code-link = https://github.com/MasterGroosha/my-id-bot cmd-start = Ваш Telegram ID: { $id } Помощь и исходники: /help Вы также можете использовать этого бота в инлайн-режиме! Попробуйте одну из кнопок ниже. Учтите, что бот отвечает на вашем языке в личных сообщениях и на английском во всех других чатах. cmd-start-inline-try-here = Попробовать здесь cmd-start-inline-try-other = Попробовать в другом чате # Used in strings like "This channel id is xxx" supergroup = супергруппа group = группа channel = канал user = пользователь bot = бот any-chat = Это { $type } с ID { $id } cmd-id-pm = Ваш Telegram ID: { $id } cmd-id-group-topic-id = Это топик с ID { $id } cmd-id-group-as-channel = И вы отправили это сообщение от имени канала с ID { $id } cmd-help = Этот бот предназначен для получения ID разных сущностей в Telegram: • Перешлите сообщение из канала, чтобы узнать его ID; • Перешлите сообщение от анонимного администратора супергруппы, чтобы узнать ID этой супергруппы; • Перешлите сообщение от юзера, чтобы узнать его/её ID (если они не запретили это); • Перешлите сообщение от другого бота или используйте его в инлайн-режиме, чтобы узнать ID бота; • Отправьте стикер, чтобы узнать его file_id (их можно использовать с любыми ботами); • Добавьте бота в группу, чтобы узнать её ID (бот также сообщит о миграции группы в супергруппу); • Попробуйте бота в инлайн-режиме, чтобы отправить свой Telegram ID в любой чат. Исходники бота: { source-code-link } group-to-supergroup = Группа обновлена до супергруппы. Старый ID: { $old_id } Новый ID: { $new_id } inline-mode-title = Ваш ID { NUMBER($id, useGrouping: 0) } inline-mode-description = Нажмите, чтобы отправить его в текущий чат inline-mode-text = Мой Telegram ID { $id } inline-mode-tryme = Попробуйте меня в ЛС >>> sticker-id = ID этого стикера: { $id } sticker-id-extended = ID этого стикера { $id } В настоящий момент только айди стикеров можно использовать любыми ботами. user-id-hidden = Этот пользователь скрыл свой айди при пересылке. Подробнее об этой фиче. # Commands in UI (when you press "/" or "Menu" button) cmd-hint-id-pm = Узнать свой ID cmd-hint-id-group = Узнать ID этой группы cmd-hint-help = Справка и исходники ================================================ FILE: bot/locales/uk/strings.ftl ================================================ # without @ ! bot-username = my_id_bot # do not translate! bot-group-deeplink = https://t.me/{bot-username}?startgroup=id source-code-link = https://github.com/MasterGroosha/my-id-bot cmd-start = Ваш Telegram ID: { $id } Допомога та вихідний код: /help Ви також можете використовувати цього бота в інлайн-режимі! Спробуйте одну з кнопок нижче. Врахуйте, що бот відповідає на вашій мові лише в особистих повідомленнях і на англійській у всіх інших чатах. cmd-start-inline-try-here = Спробувати тут cmd-start-inline-try-other = Спробувати в іншому чаті # Used in strings like "This channel id is xxx" supergroup = супергрупа group = група channel = канал user = користувач bot = бот any-chat = Це { $type } з ID { $id } cmd-id-pm = Ваш Telegram ID: { $id } cmd-id-group-topic-id = Це топік з ID { $id } cmd-id-group-as-channel = І ви відправили це повідомлення від імені каналу з ID { $id } cmd-help = Цей бот призначений для отримання ID різних об'єктів у Telegram: • Перешліть повідомлення з каналу, щоб дізнатися його ID; • Перешліть повідомлення від анонімного адміністратора супергрупи, щоб дізнатися ID цієї супергрупи; • Перешліть повідомлення від юзера, щоб дізнатися його/її ID (якщо вони не заборонили це); • Перешліть повідомлення від іншого бота або використайте його в інлайн-режимі, щоб дізнатись ID бота; • Відправте стікер, щоб дізнатись його file_id (надалі, цей file_id можна використовувати у будь-яких ботах); • Додайте бота до групи, щоб дізнатись її ID (бот також повідомить про міграцію групи у супергрупу); • Спробуйте бота в інлайн-режимі, щоб надіслати свій Telegram ID у будь-який чат. Вихідний код бота: { source-code-link } group-to-supergroup = Група перетворена у супергрупу. Старий ID: { $old_id } Новий ID: { $new_id } inline-mode-title = Ваш ID { NUMBER($id, useGrouping: 0) } inline-mode-description = Натисніть, щоб надіслати його у поточний чат inline-mode-text = Мій Telegram ID { $id } inline-mode-tryme = Спробуйте мене в особистих повідомленнях >>> sticker-id = ID этого стикера: { $id } sticker-id-extended = ID цього стікера { $id } У даний момент тільки айді стікерів можна використовувати у будь-яких ботах. user-id-hidden = Цей користувач приховав свій айді при пересиланні. Детальніше про цю фічу. # Commands in UI (when you press "/" or "Menu" button) cmd-hint-id-pm = Дізнатись свій ID cmd-hint-id-group = Дізнатись ID цієї групи cmd-hint-help = Допомога ти вихідний код ================================================ FILE: bot/logs.py ================================================ import logging from json import dumps import structlog from structlog import WriteLoggerFactory from bot.config_reader import LoggingSettings, LoggingRenderer def get_structlog_config(config: LoggingSettings) -> dict: return { "processors": get_processors(config), "cache_logger_on_first_use": True, "wrapper_class": structlog.make_filtering_bound_logger(logging.getLevelName(config.level)), "logger_factory": WriteLoggerFactory() } def get_processors(config: LoggingSettings) -> list: def custom_json_serializer(data, *args, **kwargs): result = dict() # Set keys in specific order for key in ("timestamp", "level", "event"): if key in data: result[key] = data.pop(key) # Add all other fields result.update(**data) return dumps(result, default=str) processors = [ structlog.processors.TimeStamper(fmt=config.format, utc=config.is_utc), structlog.processors.add_log_level ] if config.renderer == LoggingRenderer.JSON: processors.append(structlog.processors.JSONRenderer(serializer=custom_json_serializer)) else: processors.append(structlog.dev.ConsoleRenderer()) return processors ================================================ FILE: bot/middlewares/__init__.py ================================================ from .l10n import L10nMiddleware from .log_unhandled import UnhandledUpdatesLoggerMiddleware __all__ = [ "L10nMiddleware", "UnhandledUpdatesLoggerMiddleware" ] ================================================ FILE: bot/middlewares/l10n.py ================================================ from typing import Any, Awaitable, Callable, Dict, Final from aiogram import BaseMiddleware from aiogram.enums import ChatType from aiogram.types import TelegramObject, User, Update from bot.fluent_helper import FluentDispenser def is_pm(event: Update) -> bool: return \ (event.message and event.message.chat.type == ChatType.PRIVATE) or \ (event.inline_query and event.inline_query.chat_type == ChatType.SENDER) class L10nMiddleware(BaseMiddleware): middleware_key: Final[str] = "l10n" def __init__(self, dispenser: FluentDispenser): self.dispenser = dispenser async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], event: TelegramObject, data: Dict[str, Any], ) -> Any: event: Update if is_pm(event): user: User = data["event_from_user"] data[self.middleware_key] = self.dispenser.get_language(user.language_code) else: data[self.middleware_key] = self.dispenser.default_locale return await handler(event, data) ================================================ FILE: bot/middlewares/log_unhandled.py ================================================ from typing import Any, Awaitable, Callable, Dict import structlog from aiogram import BaseMiddleware from aiogram.dispatcher.event.bases import UNHANDLED from aiogram.types import TelegramObject from structlog.typing import FilteringBoundLogger logger: FilteringBoundLogger = structlog.get_logger() class UnhandledUpdatesLoggerMiddleware(BaseMiddleware): async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], event: TelegramObject, data: Dict[str, Any], ) -> Any: result = await handler(event, data) if result is UNHANDLED: await logger.awarning( "Unhandled update", update=event.dict() ) ================================================ FILE: bot/migration_cache.py ================================================ from math import inf from cachetools import TTLCache """ This is a simple TTL Cache, so that my_chat_member doesn't trigger on group to supergroup migration event """ cache = TTLCache(maxsize=inf, ttl=10.0) ================================================ FILE: bot/ui_commands.py ================================================ from aiogram import Bot from aiogram.types import BotCommand, BotCommandScopeAllPrivateChats, BotCommandScopeAllGroupChats from fluent.runtime import FluentLocalization from bot.fluent_helper import FluentDispenser async def set_bot_commands(bot: Bot, dispenser: FluentDispenser) -> None: """ Set bot commands in UI (using Menu or "/" buttons) :param bot: Bot object :param dispenser: FluentDispenser object """ data = list() for lang_key in dispenser.available_languages: locale_object: FluentLocalization = dispenser.get_language(lang_key) data.extend( [ ( [ BotCommand(command="id", description=locale_object.format_value("cmd-hint-id-pm")), BotCommand(command="help", description=locale_object.format_value("cmd-hint-help")), ], BotCommandScopeAllPrivateChats(), lang_key ), ( [BotCommand(command="id", description=locale_object.format_value("cmd-hint-id-group"))], BotCommandScopeAllGroupChats(), lang_key ), ] ) for commands_list, commands_scope, language in data: await bot.set_my_commands(commands=commands_list, scope=commands_scope, language_code=language) ================================================ FILE: docker-compose.example.yml ================================================ version: "3.8" services: bot: image: groosha/my-id-bot:latest restart: unless-stopped env_file: .env # uncomment and set your paths if you want to use your own locales # volumes: # - "./locales:/app/bot/locales" ================================================ FILE: env_example ================================================ # rename this file to .env (with the following dot) # Bot token, get one from https://t.me/botfather BOT_TOKEN=12345:abcxyz ### Logging configuration ### # Render mode: pretty with "console", or more production-like with "json" # Warning: case-sensitive! LOGGING_RENDERER=console # Minimum logging level: DEBUG, INFO, WARNING, ERROR or CRITICAL # Warning: case-sensitive! LOGGING_LEVEL=INFO # Datetime format for logs LOGGING_FORMAT=%Y-%m-%d %H:%M:%S # true/yes/1 if you want to show time in UTC timezone instead of computer's local tz LOGGING_IS_UTC=no # true/yes/1 if you want to log unhandled updates (updates which your bot received but could not find handler to answer) LOGGING_LOG_UNHANDLED=no ================================================ FILE: my-id-bot.example.service ================================================ [Unit] Description=Telegram My Id Bot After=network.target [Service] Type=simple WorkingDirectory=/home/user/my-id-bot EnvironmentFile=/home/user/my-id-bot/.env ExecStart=/home/user/my-id-bot/venv/bin/python -m bot KillMode=process Restart=always RestartSec=10 [Install] WantedBy=multi-user.target ================================================ FILE: requirements.txt ================================================ # This file was autogenerated by uv via the following command: # uv pip compile -o requirements.txt requirements.in aiofiles==24.1.0 # via aiogram aiogram==3.18.0 # via -r requirements.in aiohappyeyeballs==2.4.6 # via aiohttp aiohttp==3.11.13 # via aiogram aiosignal==1.3.2 # via aiohttp annotated-types==0.7.0 # via pydantic attrs==25.1.0 # via # aiohttp # fluent-runtime babel==2.17.0 # via fluent-runtime cachetools==5.5.2 # via -r requirements.in certifi==2025.1.31 # via aiogram fluent-runtime==0.4.0 # via -r requirements.in fluent-syntax==0.19.0 # via fluent-runtime frozenlist==1.5.0 # via # aiohttp # aiosignal idna==3.10 # via yarl magic-filter==1.0.12 # via aiogram multidict==6.1.0 # via # aiohttp # yarl propcache==0.3.0 # via # aiohttp # yarl pydantic==2.10.6 # via # aiogram # pydantic-settings pydantic-core==2.27.2 # via pydantic pydantic-settings==2.4.0 # via -r requirements.in python-dotenv==1.0.1 # via pydantic-settings pytz==2025.1 # via fluent-runtime structlog==25.1.0 # via -r requirements.in typing-extensions==4.12.2 # via # aiogram # fluent-runtime # fluent-syntax # pydantic # pydantic-core yarl==1.18.3 # via aiohttp