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