|
1 | 1 | from vk_api.longpoll import VkLongPoll, VkEventType |
| 2 | +from vk_api.utils import get_random_id |
2 | 3 | import vk_api |
3 | 4 | import telebot |
4 | 5 | import os |
5 | 6 | import requests |
| 7 | +import json |
| 8 | +import time |
6 | 9 |
|
| 10 | +# Токены от ботов |
7 | 11 | VK_DM_BOT_TOKEN = os.environ["VK_DM_BOT_TOKEN"] |
8 | 12 | TG_DM_BOT_TOKEN = os.environ["TG_DM_BOT_TOKEN"] |
9 | 13 |
|
10 | | -VK_CHAT_IDS = [int(cid) for cid in os.environ["VK_CHAT_IDS"].split(":")] |
11 | | -VK_USER_IDS = [int(uid) for uid in os.environ["VK_USER_IDS"].split(":")] |
12 | | -TG_CHAT_IDS = [int(cid) for cid in os.environ["TG_CHAT_IDS"].split(":")] |
| 14 | +# Кортежи с айдишниками чатов и админов |
| 15 | +VK_CHAT_IDS = tuple(int(cid) for cid in os.environ["VK_CHAT_IDS"].split(":")) |
| 16 | +VK_ADMIN_IDS = tuple(int(uid) for uid in os.environ["VK_ADMIN_IDS"].split(":")) |
| 17 | +TG_CHAT_IDS = tuple(int(cid) for cid in os.environ["TG_CHAT_IDS"].split(":")) |
13 | 18 |
|
| 19 | +# Файл со списком отслеживаемых пользователей |
| 20 | +VK_USERS_FILE = "vk_users.json" |
| 21 | + |
| 22 | + |
| 23 | +# Вспомогательные функции для работы с файлом |
| 24 | +def load_users() -> set: |
| 25 | + if not os.path.exists(VK_USERS_FILE): |
| 26 | + return set() |
| 27 | + with open(VK_USERS_FILE, "r") as f: |
| 28 | + return set(json.load(f).get("users", [])) |
| 29 | + |
| 30 | +def save_users(users: set) -> None: |
| 31 | + with open(VK_USERS_FILE, "w") as f: |
| 32 | + json.dump({"users": list(users)}, f, indent=2) |
| 33 | + |
| 34 | + |
| 35 | +# Список айдишников отслеживаемых пользователей |
| 36 | +vk_user_ids = load_users() |
| 37 | + |
| 38 | + |
| 39 | +# Переменные с сессиями ботов |
14 | 40 | VK_BOT = vk_api.VkApi(token=VK_DM_BOT_TOKEN) |
| 41 | +VK_LONG_POLL = VkLongPoll(VK_BOT, preload_messages=True) |
| 42 | +VK_API = VK_BOT.get_api() |
15 | 43 | TG_BOT = telebot.TeleBot(TG_DM_BOT_TOKEN) |
16 | 44 |
|
| 45 | + |
| 46 | +# Вспомогательные функции для бота в ВК |
| 47 | +def get_entity_fullname(entity_info: dict) -> str: |
| 48 | + if entity_info.get("name", ""): |
| 49 | + return entity_info["name"] |
| 50 | + else: |
| 51 | + return f"{entity_info['first_name']} {entity_info['last_name']}" |
| 52 | + |
| 53 | +def if_empty2attachment(message_text: str) -> str: |
| 54 | + return message_text if message_text else "[<b>Вложения не поддерживаются</b>]" |
| 55 | + |
| 56 | +def is_negative(entity_id: str) -> bool: |
| 57 | + return entity_id.startswith("-") and entity_id[1:].isdigit() |
| 58 | + |
| 59 | +def resolve_entity(entity_id: int | str) -> dict: |
| 60 | + entity_id = str(entity_id) |
| 61 | + if is_negative(entity_id): |
| 62 | + resp = VK_API.groups.get_by_id(group_id=abs(int(entity_id))) |
| 63 | + else: |
| 64 | + resp = VK_API.users.get(user_ids=entity_id) |
| 65 | + if resp: |
| 66 | + return resp[0] |
| 67 | + else: |
| 68 | + return {} |
| 69 | + |
| 70 | +def get_numeric_entity_id(entity_id: str) -> int: |
| 71 | + entity_id = entity_id.strip().replace("@id", "").replace("@", "") |
| 72 | + if entity_id.isdigit() or is_negative(entity_id): |
| 73 | + return int(entity_id) |
| 74 | + entity_info = resolve_entity(entity_id) |
| 75 | + return entity_info["id"] if entity_info else 0 |
| 76 | + |
| 77 | + |
| 78 | +# Функции команд для бота в ВК |
| 79 | +def cmd_add(chat_id: int, argument: str) -> None: |
| 80 | + if argument: |
| 81 | + uid = get_numeric_entity_id(argument) |
| 82 | + if uid < 1: |
| 83 | + msg = "Такого пользователя не существует :(" |
| 84 | + elif uid in vk_user_ids: |
| 85 | + msg = "Пользователь уже есть в списке :(" |
| 86 | + else: |
| 87 | + vk_user_ids.add(uid) |
| 88 | + save_users(vk_user_ids) |
| 89 | + msg = f"Пользователь @id{uid} добавлен" |
| 90 | + else: |
| 91 | + msg = "Пользователь не указан :(" |
| 92 | + |
| 93 | + VK_API.messages.send(peer_id=chat_id, message=msg, random_id=get_random_id()) |
| 94 | + |
| 95 | +def cmd_del(chat_id: int, argument: str) -> None: |
| 96 | + if argument: |
| 97 | + uid = get_numeric_entity_id(argument) |
| 98 | + if uid < 1: |
| 99 | + msg = "Такого пользователя не существует :(" |
| 100 | + elif uid not in vk_user_ids: |
| 101 | + msg = "Такого пользователя нет в списке :(" |
| 102 | + else: |
| 103 | + vk_user_ids.remove(uid) |
| 104 | + save_users(vk_user_ids) |
| 105 | + msg = f"Пользователь @id{uid} удалён" |
| 106 | + else: |
| 107 | + msg = "Пользователь не указан :(" |
| 108 | + |
| 109 | + VK_API.messages.send(peer_id=chat_id, message=msg, random_id=get_random_id()) |
| 110 | + |
| 111 | +def cmd_list(chat_id: int, _) -> None: |
| 112 | + if not vk_user_ids: |
| 113 | + msg = "Список отслеживаемых пользователей пуст :(" |
| 114 | + else: |
| 115 | + msg = ( |
| 116 | + "Список отслеживаемых пользователей:\n" |
| 117 | + + "\n".join(f"@id{uid}" for uid in vk_user_ids) |
| 118 | + ) |
| 119 | + VK_API.messages.send(peer_id=chat_id, message=msg, random_id=get_random_id()) |
| 120 | + |
| 121 | + |
| 122 | +# Словарь команд |
| 123 | +COMMANDS = { |
| 124 | + "/del": cmd_del, |
| 125 | + "/add": cmd_add, |
| 126 | + "/list": cmd_list, |
| 127 | +} |
| 128 | + |
| 129 | +# Цикл бота в ВК |
17 | 130 | while 1: |
18 | 131 | try: |
19 | | - for event in VkLongPoll(VK_BOT, preload_messages=True).listen(): |
20 | | - if event.type == VkEventType.MESSAGE_NEW and event.from_chat: |
21 | | - peer_id = event.peer_id |
22 | | - user_id = event.user_id |
23 | | - if peer_id in VK_CHAT_IDS and user_id in VK_USER_IDS: |
24 | | - vk_msg = "Сообщение из ВК:\n<blockquote>" + event.text + "</blockquote>" |
25 | | - vk_fwd_msgs = event.message_data["fwd_messages"] |
26 | | - tg_msg = vk_msg |
27 | | - if vk_fwd_msgs: |
28 | | - tg_msg += "\n\nВложенные в него сообщения:\n<blockquote>" |
29 | | - for vk_fwd_msg in vk_fwd_msgs: |
30 | | - tg_msg += ("<tg-emoji emoji-id='5453969572354878595'>" |
31 | | - "⭐</tg-emoji> " + vk_fwd_msg["text"] + "\n\n") |
32 | | - tg_msg += "</blockquote>" |
33 | | - |
34 | | - for tg_chat_id in TG_CHAT_IDS: |
35 | | - TG_BOT.send_message(tg_chat_id, tg_msg, parse_mode="HTML") |
36 | | - |
37 | | - except requests.exceptions.ReadTimeout or requests.exceptions.ConnectionError: |
38 | | - print("Временно потеряно соедиение с сервером ВК") |
| 132 | + for event in VK_LONG_POLL.listen(): |
| 133 | + if event.type != VkEventType.MESSAGE_NEW: |
| 134 | + continue |
| 135 | + if not event.from_chat or hasattr(event, "source_act"): |
| 136 | + continue |
| 137 | + |
| 138 | + peer_id = event.peer_id |
| 139 | + user_id = event.user_id |
| 140 | + text = event.text |
| 141 | + |
| 142 | + # Команды для админов |
| 143 | + if user_id in VK_ADMIN_IDS and text.startswith("/"): |
| 144 | + parts = text.split(maxsplit=1) |
| 145 | + cmd = parts[0] |
| 146 | + arg = parts[1] if len(parts) > 1 else "" |
| 147 | + |
| 148 | + if cmd in COMMANDS: |
| 149 | + COMMANDS[cmd](peer_id, arg) |
| 150 | + |
| 151 | + # Отслеживание сообщений ВК и отправка в ТГ |
| 152 | + if peer_id in VK_CHAT_IDS and user_id in vk_user_ids: |
| 153 | + user = resolve_entity(user_id) |
| 154 | + fullname = get_entity_fullname(user) |
| 155 | + |
| 156 | + tg_msg = ( |
| 157 | + f"Сообщение от пользователя <b>{fullname}</b>:\n" |
| 158 | + f"<blockquote>{if_empty2attachment(text)}</blockquote>" |
| 159 | + ) |
| 160 | + |
| 161 | + if hasattr(event, "message_data"): |
| 162 | + message_data = event.message_data |
| 163 | + else: |
| 164 | + message_data = {} |
| 165 | + vk_fwd_msgs = message_data.get("fwd_messages", []) |
| 166 | + vk_rpl_msg = message_data.get("reply_message", {}) |
| 167 | + |
| 168 | + if vk_fwd_msgs: |
| 169 | + tg_msg += "\n\nВложенные сообщения:\n<blockquote>" |
| 170 | + for vk_fwd_msg in vk_fwd_msgs: |
| 171 | + vk_fwd_entity_id = vk_fwd_msg["from_id"] |
| 172 | + entity = resolve_entity(vk_fwd_entity_id) |
| 173 | + fullname = get_entity_fullname(entity) |
| 174 | + tg_msg += ( |
| 175 | + "<tg-emoji emoji-id='5453969572354878595'>⭐</tg-emoji> " |
| 176 | + f"<b>{fullname}</b>: {if_empty2attachment(vk_fwd_msg['text'])}\n\n" |
| 177 | + ) |
| 178 | + tg_msg += "</blockquote>" |
| 179 | + |
| 180 | + if vk_rpl_msg: |
| 181 | + vk_rpl_entity_id = vk_rpl_msg["from_id"] |
| 182 | + entity = resolve_entity(vk_rpl_entity_id) |
| 183 | + fullname = get_entity_fullname(entity) |
| 184 | + tg_msg += ( |
| 185 | + f"\n\nВ ответ на сообщение <b>{fullname}</b>:\n" |
| 186 | + f"<blockquote>" |
| 187 | + f"{if_empty2attachment(vk_rpl_msg['text'])}" |
| 188 | + "</blockquote>" |
| 189 | + ) |
| 190 | + |
| 191 | + for tg_chat_id in TG_CHAT_IDS: |
| 192 | + TG_BOT.send_message( |
| 193 | + tg_chat_id, |
| 194 | + tg_msg, |
| 195 | + parse_mode="HTML" |
| 196 | + ) |
| 197 | + |
| 198 | + except ( |
| 199 | + requests.exceptions.ReadTimeout, |
| 200 | + requests.exceptions.ConnectionError |
| 201 | + ): |
| 202 | + print("Временно потеряно соединение с сервером ВК") |
| 203 | + time.sleep(5) |
| 204 | + except vk_api.exceptions.ApiError: |
| 205 | + print("Произошла ошибка при обращении к VK API") |
| 206 | + time.sleep(1) |
| 207 | + except telebot.apihelper.ApiTelegramException: |
| 208 | + print("Произошла ошибка при обращении к Telegram Bot API") |
| 209 | + time.sleep(1) |
| 210 | + except Exception as e: |
| 211 | + print(f"Произошла непредвиденная ошибка: {repr(e)}") |
| 212 | + time.sleep(1) |
0 commit comments