commit 10aa70702cbf1edb9d309cece0a94f7fe546cd3c Author: Alexander Popov Date: Wed Nov 27 02:58:28 2024 +0300 second init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f3430c3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83c2c3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Pyrogram files +*.session +*.session-journal + +# App +.env +db.sqlite diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b2bcf61 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright 2024 Alexander Popov + +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. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ca02ea --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# TODO + +- [ ] Добавить уведомления, если что-то пошло не так diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..67fdb3d --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,27 @@ +# Информация о программе +__author__ = 'Alexander Popov' +__version__ = (0, 0, 1) + +# Импорт модулей стандартной библиотеки +from os import getenv + +# Импорт сторонних модулей +from loguru import logger +from dotenv import load_dotenv +from pyrogram import Client + +# Импорт модулей приложения +from .db import DataBase + +load_dotenv() # Выполняет чтение .env + +# Приложение +app = Client(getenv('ACCOUNT'), api_id=getenv('APP_ID'), api_hash=getenv('APP_HASH')) + +# База данных +db = DataBase(getenv('DB_PATH')) + +# Логгер +logger.add(getenv('LOG_PATH'), compression='zip') + +BOT_NAME = '@anicardplaybot' diff --git a/app/__main__.py b/app/__main__.py new file mode 100644 index 0000000..60fc16c --- /dev/null +++ b/app/__main__.py @@ -0,0 +1,32 @@ +# Импорт модулей стандартной библиотеки +import time + +# Импорт модулей приложения +from . import app, db, logger +from .actions import get_top_wins, get_top_donates + + +async def main(): + await app.start() + logger.info('Клиент Telegram запущен') + + # Основной цикл программы + while True: + await get_top_wins() # Получает топ клана по победам + await get_top_donates() # Получает топ клана по пожертвованиям + + time.sleep(12 * 60 * 60) # 12 часов + + await app.stop() + logger.info('Клиент Telegram остановлен') + + +if __name__ == '__main__': + logger.info('Выполняется запуск приложения') + + logger.info('Выполняется подключение к базе данных') + db.connect() + + app.run(main()) + db.close() + logger.info('Приложение завершило работу') diff --git a/app/actions.py b/app/actions.py new file mode 100644 index 0000000..cbdd4d0 --- /dev/null +++ b/app/actions.py @@ -0,0 +1,76 @@ +# Импорт модулей стандартной библиотеки +import time + +# Импорт модулей приложения +from . import app, logger, BOT_NAME +from .collect import parse_wins_top, parse_donates_top + + +async def get_top_wins() -> None: + logger.info('Выполняется получения списка побед клана') + + await app.send_message(BOT_NAME, '🛡 Мой клан') + time.sleep(1) + + message_id = 0 + async for message in app.get_chat_history(BOT_NAME, limit=1): + message_id = message.id + + await app.request_callback_answer( + chat_id=BOT_NAME, + message_id=message_id, + callback_data='my_clan:tops:91:0:0:0:0:1', + ) + time.sleep(1) + + async for message in app.get_chat_history(BOT_NAME, limit=1): + message_id = message.id + + await app.request_callback_answer( + chat_id=BOT_NAME, + message_id=message_id, + callback_data='my_clan:top-wins:91:0:0:0:0:1', + ) + time.sleep(1) + + async for message in app.get_chat_history(BOT_NAME, limit=1): + parse_wins_top(message) + + await app.read_chat_history(BOT_NAME) + + logger.info('Получение списка побед клана завершено') + + +async def get_top_donates() -> None: + logger.info('Выполняется получения списка пожертвований клана') + + await app.send_message(BOT_NAME, '🛡 Мой клан') + time.sleep(1) + + message_id = 0 + async for message in app.get_chat_history(BOT_NAME, limit=1): + message_id = message.id + + await app.request_callback_answer( + chat_id=BOT_NAME, + message_id=message_id, + callback_data='my_clan:tops:91:0:0:0:0:1', + ) + time.sleep(1) + + async for message in app.get_chat_history(BOT_NAME, limit=1): + message_id = message.id + + await app.request_callback_answer( + chat_id=BOT_NAME, + message_id=message_id, + callback_data='my_clan:top-donates:91:0:0:0:0:1', + ) + time.sleep(1) + + async for message in app.get_chat_history(BOT_NAME, limit=1): + parse_donates_top(message) + + await app.read_chat_history(BOT_NAME) + + logger.info('Получение списка пожертвований клана завершено') diff --git a/app/collect.py b/app/collect.py new file mode 100644 index 0000000..e677d3e --- /dev/null +++ b/app/collect.py @@ -0,0 +1,81 @@ +# Импорт модулей стандартной библиотеки +import re + +# Импорт сторонних модулей +from pyrogram.types import Message + +# Импорт модулей приложения +from . import db + + +def parse_wins_top(message: Message) -> None: + if message.text.startswith('🏆 Топ по победам'): + gamers = message.text.split('\n') + gamers.pop(0) + gamers.pop(0) + + WINS = list() + + for idx, gamer in enumerate(gamers): + gamer = re.sub(r'^\d+. ', '', gamer) # удаляет нумерацию + gamer, battle_count = gamer.split(' - ') # разделяет ник и количество побед в клановых сражениях + + # оставляет ники игроков, Python не может в эмодзи в RegExp, по этому пришлось делать через str.replace() + # gamer = re.sub(r'\s[⚡⚜]$', '', gamer) + gamer = gamer.replace(' ⚡', '') + gamer = gamer.replace(' ⚜', '') + + battle_count = battle_count.replace(' ⚔', '') # удаляем эмодзи мечей + battle_count = int(re.sub(r'[^\x00-\x7F]', '', battle_count)) # преобразовывает строку в число + + WINS.append( + { + 'telegram_id': message.entities[idx].url.strip('http://t.me/'), + 'username': gamer, + 'count': battle_count, + } + ) + + # print('{0} - {1} (@{2})'.format(gamer, battle_count, message.entities[idx].url.strip('http://t.me/'))) + + db.add_data(WINS, True) + else: + pass + + +def parse_donates_top(message: Message) -> None: + if message.text.startswith('🏆 Топ по пожертвованиям'): + players = message.text.split('\n') + + # Удаляет из массива строку с заголовком и последующую пустую строку + players.pop(0) + players.pop(0) + + DONATES = list() + + for idx, player in enumerate(players): + player = re.sub(r'^\d+. ', '', player) # удаляет нумерацию + player, donates_count = player.split(' - ') # разделяет ник и количество пожертвований в клановую сокровищницу + + # Оставляет только ники игроков. + # Python не может парсить эмодзи в RegExp, по этому пришлось делать через str.replace() + # player = re.sub(r'\s[⚡⚜]$', '', player) + player = player.replace(' ⚡', '') + player = player.replace(' ⚜', '') + + donates_count = donates_count.replace(' 💠', '') # удаляем эмодзи пожертвований + donates_count = int(re.sub(r'[^\x00-\x7F]', '', donates_count)) # преобразовывает строку в число + + DONATES.append( + { + 'telegram_id': message.entities[idx].url.strip('http://t.me/'), + 'username': player, + 'count': donates_count, + } + ) + + # print('{0} - {1} (@{2})'.format(player, donates_count, message.entities[idx].url.strip('http://t.me/'))) + + db.add_data(DONATES, False) + else: + pass diff --git a/app/db.py b/app/db.py new file mode 100644 index 0000000..72897dd --- /dev/null +++ b/app/db.py @@ -0,0 +1,36 @@ +# Импорт модулей стандартной библиотеки +import sqlite3 +import json + + +class DataBase(object): + """...""" + + def __init__(self, path): + super(DataBase, self).__init__() + self.path = path + + def connect(self) -> bool: + self.conn = sqlite3.connect(self.path) + return True + + def close(self) -> bool: + self.conn.close() + return True + + def commit(self) -> None: + self.conn.commit() + return True + + def add_data(self, data, wins: bool) -> bool: # wins изменит на Enum + if wins: + table = 'wins' + else: + table = 'donates' + + cur = self.conn.cursor() + cur.execute('INSERT INTO {table} (\'data\') VALUES (\'{data}\')'.format(table=table, data=json.dumps(data))) + self.commit() + cur.close() + + return True diff --git a/app/utils.py b/app/utils.py new file mode 100644 index 0000000..32b2264 --- /dev/null +++ b/app/utils.py @@ -0,0 +1,8 @@ +from . import app + + +async def get_telegram_id(username: str) -> int: + """Возвращает Telegram ID по имени пользователя""" + telegram_user = await app.get_users(username) + + return telegram_user.id diff --git a/env.example b/env.example new file mode 100644 index 0000000..38b1842 --- /dev/null +++ b/env.example @@ -0,0 +1,5 @@ +APP_ID="" +APP_HASH="" +ACCOUNT="" +LOG_PATH="/tmp/test.log" +DB_PATH="./db.sqlite" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..92afe8c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 123 +skip-string-normalization = 1 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..ad2b44c --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +black==24.10.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b40bbbf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Pyrogram==2.0.106 +TgCrypto==1.2.5 +python_dotenv==1.0.1 diff --git a/sql/README.md b/sql/README.md new file mode 100644 index 0000000..f761fd3 --- /dev/null +++ b/sql/README.md @@ -0,0 +1 @@ +`wins` заменить на `donates` для создания второй таблицы. diff --git a/sql/table.sql b/sql/table.sql new file mode 100644 index 0000000..0a28a15 --- /dev/null +++ b/sql/table.sql @@ -0,0 +1,12 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS 'wins' +( + 'id' Integer PRIMARY KEY AUTOINCREMENT, + 'timestamp' DateTime NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')), + 'data' Text NOT NULL +); + +CREATE INDEX IF NOT EXISTS 'index_timestamp' ON 'wins' ('timestamp'); + +COMMIT; diff --git a/systemd/user/anicardclanstats.service b/systemd/user/anicardclanstats.service new file mode 100644 index 0000000..7cf1f3b --- /dev/null +++ b/systemd/user/anicardclanstats.service @@ -0,0 +1,14 @@ +[Unit] +Description=AniCard Clan Stats +After=network.target network-online.target + +[Service] +Type=exec +Environment=APP_DIR=/home/bot/AniCardClan +Environment=VIRTUAL_ENV=${APP_DIR}/venv +Environment=PYTHONPATH=${APP_DIR} +Environment=PATH=${VIRTUAL_ENV}/bin:$PATH +ExecStart=/bin/bash -c '${VIRTUAL_ENV}/bin/python3 -m norify' + +[Install] +WantedBy=default.target diff --git a/tests/donates-top.json b/tests/donates-top.json new file mode 100644 index 0000000..73625f6 --- /dev/null +++ b/tests/donates-top.json @@ -0,0 +1,267 @@ +{ + "_": "Message", + "id": 72311, + "from_user": { + "_": "User", + "id": 6018105307, + "is_self": false, + "is_contact": false, + "is_mutual_contact": false, + "is_deleted": false, + "is_bot": true, + "is_verified": false, + "is_restricted": false, + "is_scam": false, + "is_fake": false, + "is_support": false, + "is_premium": false, + "first_name": "AniCard", + "username": "anicardplaybot", + "dc_id": 2, + "photo": { + "_": "ChatPhoto", + "small_file_id": "AQADAgADiN0xG9osoEgAEAIAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "small_photo_unique_id": "AgADiN0xG9osoEg", + "big_file_id": "AQADAgADiN0xG9osoEgAEAMAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "big_photo_unique_id": "AgADiN0xG9osoEg" + } + }, + "date": "2024-11-15 21:37:15", + "chat": { + "_": "Chat", + "id": 6018105307, + "type": "ChatType.BOT", + "is_verified": false, + "is_restricted": false, + "is_scam": false, + "is_fake": false, + "is_support": false, + "username": "anicardplaybot", + "first_name": "AniCard", + "photo": { + "_": "ChatPhoto", + "small_file_id": "AQADAgADiN0xG9osoEgAEAIAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "small_photo_unique_id": "AgADiN0xG9osoEg", + "big_file_id": "AQADAgADiN0xG9osoEgAEAMAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "big_photo_unique_id": "AgADiN0xG9osoEg" + }, + "dc_id": 2 + }, + "mentioned": false, + "scheduled": false, + "from_scheduled": false, + "edit_date": "2024-11-15 21:37:17", + "has_protected_content": false, + "text": "🏆 Топ по пожертвованиям 💠\n\n1. Seraph ⚜ - 2825 💠\n2. Timur ⚡ - 2220 💠\n3. Valerix 02 ⚡ - 1666 💠\n4. Thomas ⚡ - 1623 💠\n5. Nota ⚡ - 1548 💠\n6. Marpol ⚡ - 1231 💠\n7. Юсти ⚡ - 1199 💠\n8. K_L_E_Y--- ⚡ - 1144 💠\n9. Abror ⚡ - 1062 💠\n10. Миша ⚡ - 1048 💠\n11. Easy ⚜ - 1000 💠\n12. Ssoh ⚡ - 823 💠\n13. Glad_loz ⚡ - 795 💠\n14. You are my sunshine ⚡ - 775 💠\n15. Po ⚡ - 774 💠\n16. Vladimir ⚡ - 715 💠\n17. Влад ⚡ - 671 💠\n18. Name ⚡ - 658 💠\n19. Мизик ⚡ - 611 💠\n20. Ksw ⚡ - 599 💠\n21. Тимофей ⚡ - 562 💠\n22. W1zard ⚡ - 550 💠\n23. Андрей ⚡ - 498 💠\n24. Данила ⚡ - 364 💠\n25.  ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ Lqidatorᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ⚡ - 293 💠\n26. Александр ⚡ - 213 💠\n27. Ch0my_ ⚡ - 0 💠\n28. Musso ⚡ - 0 💠", + "entities": [ + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 32, + "length": 9, + "url": "http://t.me/owariserph" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 55, + "length": 8, + "url": "http://t.me/timurgatiatullin" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 77, + "length": 13, + "url": "http://t.me/valerix02" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 104, + "length": 9, + "url": "http://t.me/thomas_02" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 127, + "length": 7, + "url": "http://t.me/NoTa145" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 148, + "length": 9, + "url": "http://t.me/marpol_l" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 171, + "length": 7, + "url": "http://t.me/Yustes_GuardianOfTheWorlds" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 192, + "length": 13, + "url": "http://t.me/KLEY_MOMENTS" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 219, + "length": 8, + "url": "http://t.me/Abroryy" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 242, + "length": 7, + "url": "http://t.me/alien1life" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 264, + "length": 7, + "url": "http://t.me/db_o_qp" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 286, + "length": 7, + "url": "http://t.me/Gnu_Linuks" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 307, + "length": 11, + "url": "http://t.me/Glad_loz1" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 332, + "length": 22, + "url": "http://t.me/Tailer_Jirden" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 368, + "length": 5, + "url": "http://t.me/Poooooooooooopp" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 387, + "length": 11, + "url": "http://t.me/h1ssoka" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 412, + "length": 7, + "url": "http://t.me/vlad_n5q" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 433, + "length": 7, + "url": "http://t.me/MrGhoul279" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 454, + "length": 8, + "url": "http://t.me/mizlony" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 476, + "length": 6, + "url": "http://t.me/Aassbube" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 496, + "length": 10, + "url": "http://t.me/halzov10" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 520, + "length": 9, + "url": "http://t.me/W1zard00" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 543, + "length": 9, + "url": "http://t.me/Allastar123" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 566, + "length": 9, + "url": "http://t.me/Staldent" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 589, + "length": 59, + "url": "http://t.me/qaak01" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 662, + "length": 12, + "url": "http://t.me/yneznauchto" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 688, + "length": 9, + "url": "http://t.me/to4no_ch0my" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 709, + "length": 8, + "url": "http://t.me/mus_wf" + } + ], + "outgoing": false, + "reply_markup": { + "_": "InlineKeyboardMarkup", + "inline_keyboard": [ + [ + { + "_": "InlineKeyboardButton", + "text": "Назад 🔙", + "callback_data": "my_clan:tops:91:0:0:0:0:1" + } + ] + ] + } +} diff --git a/tests/win-top.json b/tests/win-top.json new file mode 100644 index 0000000..1029ebd --- /dev/null +++ b/tests/win-top.json @@ -0,0 +1,267 @@ +{ + "_": "Message", + "id": 71945, + "from_user": { + "_": "User", + "id": 6018105307, + "is_self": false, + "is_contact": false, + "is_mutual_contact": false, + "is_deleted": false, + "is_bot": true, + "is_verified": false, + "is_restricted": false, + "is_scam": false, + "is_fake": false, + "is_support": false, + "is_premium": false, + "first_name": "AniCard", + "username": "anicardplaybot", + "dc_id": 2, + "photo": { + "_": "ChatPhoto", + "small_file_id": "AQADAgADiN0xG9osoEgAEAIAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "small_photo_unique_id": "AgADiN0xG9osoEg", + "big_file_id": "AQADAgADiN0xG9osoEgAEAMAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "big_photo_unique_id": "AgADiN0xG9osoEg" + } + }, + "date": "2024-11-15 19:23:05", + "chat": { + "_": "Chat", + "id": 6018105307, + "type": "ChatType.BOT", + "is_verified": false, + "is_restricted": false, + "is_scam": false, + "is_fake": false, + "is_support": false, + "username": "anicardplaybot", + "first_name": "AniCard", + "photo": { + "_": "ChatPhoto", + "small_file_id": "AQADAgADiN0xG9osoEgAEAIAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "small_photo_unique_id": "AgADiN0xG9osoEg", + "big_file_id": "AQADAgADiN0xG9osoEgAEAMAA9v_tGYBAAOnb8uWDDJj8AAEHgQ", + "big_photo_unique_id": "AgADiN0xG9osoEg" + }, + "dc_id": 2 + }, + "mentioned": false, + "scheduled": false, + "from_scheduled": false, + "edit_date": "2024-11-15 19:23:07", + "has_protected_content": false, + "text": "🏆 Топ по победам ⚔\n\n1. Valerix 02 ⚡ - 110 ⚔\n2. Thomas ⚡ - 94 ⚔\n3. Nota ⚡ - 93 ⚔\n4. Timur ⚡ - 86 ⚔\n5. Marpol ⚡ - 65 ⚔\n6. Easy ⚜ - 60 ⚔\n7. Vladimir ⚡ - 58 ⚔\n8. Abror ⚡ - 57 ⚔\n9. Миша ⚡ - 52 ⚔\n10. Юсти ⚡ - 52 ⚔\n11. Ssoh ⚡ - 49 ⚔\n12. K_L_E_Y--- ⚡ - 41 ⚔\n13. Влад ⚡ - 40 ⚔\n14. Name ⚡ - 39 ⚔\n15. Seraph ⚜ - 39 ⚔\n16. Po ⚡ - 32 ⚔\n17. You are my sunshine ⚡ - 31 ⚔\n18. W1zard ⚡ - 30 ⚔\n19. Ksw ⚡ - 22 ⚔\n20. Тимофей ⚡ - 17 ⚔\n21. Мизик ⚡ - 16 ⚔\n22. Glad_loz ⚡ - 15 ⚔\n23. Андрей ⚡ - 15 ⚔\n24. Данила ⚡ - 9 ⚔\n25.  ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ Lqidatorᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ⚡ - 6 ⚔\n26. Ch0my_ ⚡ - 6 ⚔\n27. Musso ⚡ - 6 ⚔\n28. Александр ⚡ - 5 ⚔", + "entities": [ + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 25, + "length": 13, + "url": "http://t.me/valerix02" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 51, + "length": 9, + "url": "http://t.me/thomas_02" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 72, + "length": 7, + "url": "http://t.me/NoTa145" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 91, + "length": 8, + "url": "http://t.me/timurgatiatullin" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 111, + "length": 9, + "url": "http://t.me/marpol_l" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 132, + "length": 7, + "url": "http://t.me/db_o_qp" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 151, + "length": 11, + "url": "http://t.me/h1ssoka" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 174, + "length": 8, + "url": "http://t.me/Abroryy" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 194, + "length": 7, + "url": "http://t.me/alien1life" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 214, + "length": 7, + "url": "http://t.me/Yustes_GuardianOfTheWorlds" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 234, + "length": 7, + "url": "http://t.me/Gnu_Linuks" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 254, + "length": 13, + "url": "http://t.me/KLEY_MOMENTS" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 280, + "length": 7, + "url": "http://t.me/vlad_n5q" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 300, + "length": 7, + "url": "http://t.me/MrGhoul279" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 320, + "length": 9, + "url": "http://t.me/owariserph" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 342, + "length": 5, + "url": "http://t.me/Poooooooooooopp" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 360, + "length": 22, + "url": "http://t.me/Tailer_Jirden" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 395, + "length": 9, + "url": "http://t.me/W1zard00" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 417, + "length": 6, + "url": "http://t.me/Aassbube" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 436, + "length": 10, + "url": "http://t.me/halzov10" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 459, + "length": 8, + "url": "http://t.me/mizlony" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 480, + "length": 11, + "url": "http://t.me/Glad_loz1" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 504, + "length": 9, + "url": "http://t.me/Allastar123" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 526, + "length": 9, + "url": "http://t.me/Staldent" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 547, + "length": 59, + "url": "http://t.me/qaak01" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 618, + "length": 9, + "url": "http://t.me/to4no_ch0my" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 639, + "length": 8, + "url": "http://t.me/mus_wf" + }, + { + "_": "MessageEntity", + "type": "MessageEntityType.TEXT_LINK", + "offset": 659, + "length": 12, + "url": "http://t.me/yneznauchto" + } + ], + "outgoing": false, + "reply_markup": { + "_": "InlineKeyboardMarkup", + "inline_keyboard": [ + [ + { + "_": "InlineKeyboardButton", + "text": "Назад 🔙", + "callback_data": "my_clan:tops:91:0:0:0:0:1" + } + ] + ] + } +}