bot.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. import asyncio
  2. import json
  3. import sqlite3
  4. import traceback
  5. from sqlite3 import OperationalError
  6. from typing import NamedTuple, Iterable
  7. from aiogram import Bot
  8. from aiogram.dispatcher import Dispatcher, FSMContext
  9. from aiogram.dispatcher.filters import BoundFilter, AdminFilter, Command
  10. from aiogram.dispatcher.handler import SkipHandler, CancelHandler
  11. from aiogram.dispatcher.middlewares import BaseMiddleware
  12. from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Update, ChatPermissions, BotCommand, \
  13. Chat, BotCommandScopeChat, \
  14. BotCommandScopeAllChatAdministrators
  15. from aiogram.utils import executor
  16. from aiogram.contrib.fsm_storage.memory import MemoryStorage
  17. from aiogram import types
  18. from aiogram.utils.exceptions import ChatNotFound, MessageToDeleteNotFound, Unauthorized, MessageCantBeDeleted
  19. from aiogram.utils.markdown import hlink, hbold, hpre, hitalic, underline, hunderline
  20. from loguru import logger
  21. from sqliter import Database
  22. import config
  23. bot = Bot(token=config.bot_token, parse_mode='HTML')
  24. dp = Dispatcher(bot, storage=MemoryStorage())
  25. db = Database('database.db')
  26. logger.add('bot.log', format="{time:DD-MM-YYYY HH:mm:ss} | {level} | {message}",
  27. rotation='1MB', compression='zip', enqueue=True)
  28. filter_worlds = [word[1] for word in db.select_all_word()]
  29. chats = {}
  30. async def update_chat_list():
  31. '''
  32. :return: dict{chat_id: [require_channel_id]}
  33. '''
  34. new_chats = {}
  35. chats_db = db.select_all_chats()
  36. for i in chats_db:
  37. new_chats[i[0]] = []
  38. for i in chats_db:
  39. new_chats[i[0]].append(i[1])
  40. return new_chats
  41. async def check_subs(m: types.Message, user_id=None):
  42. if user_id is None:
  43. user_id = m.from_user.id
  44. try:
  45. req_channels = chats[m.chat.id]
  46. except KeyError:
  47. return 2
  48. subscribe_channels = []
  49. for channel in req_channels:
  50. res = await dp.bot.get_chat_member(channel, user_id)
  51. if res.status == 'left':
  52. return 1
  53. elif res.status == 'member':
  54. subscribe_channels.append(channel)
  55. if len(req_channels) == len(subscribe_channels):
  56. return 0
  57. async def subs_keys(chat_id, user_id) -> InlineKeyboardMarkup:
  58. global chats
  59. chats_entity = [await dp.bot.get_chat(c) for c in chats[chat_id]]
  60. chats_button = [(chat.title, chat.invite_link) for chat in chats_entity]
  61. keyboard = InlineKeyboardMarkup(row_width=1)
  62. keyboard.add(*[InlineKeyboardButton(text=n, url=url) for n, url in chats_button])
  63. keyboard.add(*[InlineKeyboardButton(text=n, callback_data=c) for n, c in
  64. {'✅ Проверить подписку': json.dumps({'subs': user_id})}.items()])
  65. return keyboard
  66. class JoinUsers(BaseMiddleware):
  67. async def on_pre_process_update(self, update: Update, data: dict):
  68. global chats
  69. try:
  70. if update.my_chat_member.new_chat_member.status == "administrator":
  71. if update.my_chat_member.chat.id not in chats:
  72. db.add_channel(0, update.my_chat_member.chat.id)
  73. except:
  74. pass
  75. try:
  76. if update.message.new_chat_members:
  77. await welcome_message(update.message)
  78. raise CancelHandler
  79. except Exception:
  80. return
  81. class IsAdmin(BoundFilter):
  82. async def check(self, m: types.Message) -> bool:
  83. if m.from_user.id in config.ADMIN_ID:
  84. return True
  85. async def welcome_message(m: types.Message):
  86. await dp.bot.delete_message(m.chat.id, m.message_id)
  87. user_id = m.from_user.id
  88. chat_id = m.chat.id
  89. if chat_id in chats:
  90. text = f'Привет, {hlink(m.from_user.full_name, f"tg://user?id={user_id}")}\n' \
  91. f'Чтобы начать общение в этом чате подпишись по ссылкам ниже и нажми кнопку чтобы проверить подписку\n'
  92. text += '\nНа подписку у тебя еcть 10 минут'
  93. keyboard = await subs_keys(chat_id, user_id)
  94. mess = await dp.bot.send_message(m.chat.id, text, reply_markup=keyboard)
  95. await asyncio.sleep(10 * 60)
  96. await mess.delete()
  97. logger.info(f'New User: {m.from_user.full_name}(id: {m.from_user.id})')
  98. @dp.callback_query_handler()
  99. async def check_subscribe_button(c: types.CallbackQuery):
  100. global chats
  101. m = c.message
  102. data = json.loads(c.data)
  103. if c.from_user.id not in [data['subs']]:
  104. await c.answer(f'Нажми на кнопку под сообщением, где упоминают именно тебя\n', show_alert=True)
  105. return
  106. result = await check_subs(m, data['subs'])
  107. if result == 0:
  108. await c.answer('Умничка, теперь твои сообщения не будут удаляться. Пиши на здоровье', show_alert=True)
  109. try:
  110. await m.delete()
  111. except MessageToDeleteNotFound:
  112. pass
  113. elif result == 1:
  114. await c.answer("Ты не на всё подписался", show_alert=True)
  115. @dp.message_handler(commands=['help'])
  116. async def help_message(m: types.Message):
  117. text = f'Чтобы добавить канал или группу в список обязательных для подписки нужно:\n' \
  118. f'1. Добавить бота как админа в этот канал или группу.\n' \
  119. f'2. Переслать сообщение из канала или группы в группу где этот канал или ' \
  120. f'группа должны быть обязательными к подписке\n' \
  121. f'{hitalic("При пересылке сообщения из группы есть ньюанс.")} Надо пересылать сообщение от имени группы.\n' \
  122. f'Такие сообщения может отправлять админ с включенной анонимностью.\n' \
  123. f'3. Ответить на пересланное сообщение командой <code>/channel_add</code> \n' \
  124. f'4. Готово! Канал/Группа теперь обязательна для подписки в этом чате.\n' \
  125. f'\tP.S. По такой е схеме группа или канал удаляется из списка обязательных, ' \
  126. f'а именно, ответом на пересланное сообщение из нужного канала или чата, только ' \
  127. f'с коммандой <code>/channel_del</code>\n' \
  128. f'\tP.P.S. Кликнув по коммандам выше, они скопируются.\n\n' \
  129. f'/channel_list - покажет все обязательные каналы для этого чата' \
  130. f'{hbold("Работа с стоп словами:")}\n' \
  131. f'В любом чате где есть бот или в личном диалоге с ним напишите <code>/stop_add </code>[нужное вам слово]\n' \
  132. f'Например так: /stop_add бинанс\n' \
  133. f'Теперь все сообщения со словом "бинанс" будут удаляться.\n' \
  134. f'Удаляются слова командой <code>/stop_del </code>\n' \
  135. f'/stop_list - Покажет список всех стоп слов\n'
  136. await m.answer(text)
  137. @dp.message_handler(AdminFilter(), commands=['channel_add'])
  138. async def channel_add(m: types.Message):
  139. global chats
  140. try:
  141. t = chats[m.chat.id]
  142. except KeyError:
  143. chats[m.chat.id] = []
  144. try:
  145. if m.reply_to_message.forward_from_chat.type in ['channel', 'supergroup']:
  146. if m.reply_to_message.forward_from_chat.id in chats[m.chat.id]:
  147. exist = await m.reply('Этот канал уже есть в списке обязательных\n'
  148. 'Через 10 секунд это сообщение удалится')
  149. await asyncio.sleep(10)
  150. await exist.delete()
  151. await m.delete()
  152. return 0
  153. try:
  154. cha = await dp.bot.get_chat(m.reply_to_message.forward_from_chat.id)
  155. db.add_channel(m.chat.id, m.reply_to_message.forward_from_chat.id)
  156. chats = await update_chat_list()
  157. logger.success(f'Добавил в список обязательных каналов {m.reply_to_message.forward_from_chat.title} '
  158. f'в чате{m.chat.title}')
  159. mess = await m.reply('Добавил в список обязательных каналов\n'
  160. 'Через 10 секунд это сообщение удалится')
  161. await asyncio.sleep(10)
  162. await m.delete()
  163. await mess.delete()
  164. except Unauthorized:
  165. logger.error(f"[{m.chat.title}] Бот не является админом в канале: "
  166. f"{m.reply_to_message.forward_from_chat.title} "
  167. f"id:{m.reply_to_message.forward_from_chat.id}")
  168. reply = await m.answer('Бот не является админом в этом канале!')
  169. await asyncio.sleep(10)
  170. await reply.delete()
  171. await m.delete()
  172. return 0
  173. except MessageToDeleteNotFound:
  174. pass
  175. except MessageCantBeDeleted as e:
  176. logger.error(f'[{m.chat.title}] {e}')
  177. except AttributeError:
  178. pass
  179. @dp.message_handler(AdminFilter(), commands=['channel_del'])
  180. async def channel_del(m: types.Message):
  181. global chats
  182. try:
  183. if m.reply_to_message.forward_from_chat.type in ['channel', 'supergroup']:
  184. db.delete_channel(m.chat.id, m.reply_to_message.forward_from_chat.id)
  185. chats = await update_chat_list()
  186. logger.success(
  187. f'Канал {m.reply_to_message.forward_from_chat.title} успешно удалён из обязательных в чате {m.chat.title}')
  188. text = 'Канал успешно удалён'
  189. text += '\n\nСообщение удалится через 20 секунд'
  190. mess = await m.reply(text)
  191. await asyncio.sleep(20)
  192. await m.delete()
  193. await mess.delete()
  194. except MessageCantBeDeleted as e:
  195. logger.error(f'{e}\n{json.loads(m)}')
  196. except AttributeError:
  197. pass
  198. except OperationalError as e:
  199. logger.error(f"[channel_del] {e}")
  200. await asyncio.sleep(10)
  201. await channel_del(m)
  202. @dp.message_handler(AdminFilter(), commands=['channel_list'])
  203. async def channel_list(m: types.Message):
  204. channels_id = [c[1] for c in db.select_channels(m.chat.id)]
  205. channels_entity = []
  206. for c in channels_id:
  207. try:
  208. entity = await dp.bot.get_chat(c)
  209. channels_entity.append(entity)
  210. except ChatNotFound:
  211. continue
  212. except Unauthorized:
  213. text = f"chat_id:{c}\n Похоже, что бот не является админом канала"
  214. channels_entity.append(Chat(id=c, title=text, invite_link='not_found'))
  215. chats_formated = "\n".join([f"🔸\t{hlink(chat.title, chat.invite_link)}" for chat in channels_entity])
  216. text = f'Список обязательных каналов для этого чата: \n' \
  217. f'{chats_formated}' if len(channels_entity) > 0 else 'Здесь нет обязательных каналов для подписки'
  218. text += '\n\nСообщение удалится через 20 секунд'
  219. mess = await m.reply(text)
  220. await asyncio.sleep(20)
  221. await m.delete()
  222. await mess.delete()
  223. @dp.message_handler(IsAdmin(), commands=['channel_add', 'channel_del', 'channel_list'])
  224. async def err_message(m: types.Message):
  225. logger.error(f'Этот {m.from_user.full_name}, опять мне личку пишет.....')
  226. await m.answer('Эту команду используй в групповом чате.')
  227. async def collect_words(m: types.Message):
  228. lines = m.text.split('\n')
  229. words = []
  230. # если одна строка и после команды что-то написанно
  231. if len(lines) == 1 and len(lines[0].split()) > 1:
  232. # склеиваем слова после команды и добавляем в общий список
  233. word = ' '.join([i.strip() for i in lines[0].split()[1::]])
  234. words.append(word)
  235. # Если строк больше
  236. elif len(lines) > 1:
  237. # В первой строчке не только команда
  238. if len(lines[0].split()) > 1:
  239. word = ' '.join([i.strip() for i in lines[0].split()[1::]])
  240. words.append(word)
  241. for line in lines[1::]:
  242. words.append(line.strip())
  243. return words
  244. @logger.catch
  245. @dp.message_handler(IsAdmin(), commands=['stop_add'])
  246. @dp.message_handler(AdminFilter(), commands=['stop_add'])
  247. async def add_world(m: types.Message):
  248. words = await collect_words(m)
  249. processed_words = []
  250. for w in words:
  251. try:
  252. db.add_word(w)
  253. processed_words.append(f"✅ {w} - добавленно в список стоп слов")
  254. logger.success(f'{w} - добавленно в список стоп слов')
  255. await asyncio.sleep(1)
  256. except sqlite3.IntegrityError as e:
  257. if 'UNIQUE constraint failed' in str(e):
  258. processed_words.append(f'❌ {w} - Уже есть в списке')
  259. continue
  260. words_success = "\n".join(processed_words)
  261. text = f'{hpre(words_success)}'
  262. await m.reply(text)
  263. global filter_worlds
  264. filter_worlds = [word[1] for word in db.select_all_word()]
  265. @logger.catch
  266. @dp.message_handler(IsAdmin(), commands=['stop_del'])
  267. @dp.message_handler(AdminFilter(), commands=['stop_del'])
  268. async def stop_delete(m: types.Message):
  269. words = await collect_words(m)
  270. processed_words = []
  271. for w in words:
  272. try:
  273. db.delete_word(w)
  274. processed_words.append(f"✅ {w} - удалено из списка стоп слов")
  275. logger.success(f'{w} - удалено из списка стоп слов')
  276. await asyncio.sleep(1)
  277. except Exception as e:
  278. print(e)
  279. processed_words.append(f'❌ {w} - Ошибка')
  280. # logger.error(f'❌ {w} - Ошибка')
  281. continue
  282. words_success = "\n".join(processed_words)
  283. text = f'{hpre(words_success)}'
  284. await m.reply(text)
  285. global filter_worlds
  286. filter_worlds = [word[1] for word in db.select_all_word()]
  287. @logger.catch
  288. @dp.message_handler(IsAdmin(), Command('stop_list'))
  289. @dp.message_handler(AdminFilter(), Command('stop_list'))
  290. async def list_stop_worlds(m: types.Message):
  291. messages = []
  292. temp_text = ''
  293. for i in filter_worlds:
  294. temp_text += f"{i}\n"
  295. if len(temp_text) > 4000:
  296. messages.append(temp_text)
  297. temp_text = ''
  298. if i is filter_worlds[-1]:
  299. messages.append(temp_text)
  300. for list_words in messages:
  301. no_words = 'Сюда пока что ничего не добавили((\nНапишите <code>/stop_add </code>' \
  302. '[слово] - чтобы добавить слово в фильтр\n'
  303. text = f'Список всех стоп слов: \n{hpre(list_words)}' if len(filter_worlds) > 0 else no_words
  304. if len(messages) > 1 and list_words not in messages[0]:
  305. text = hpre(list_words)
  306. await m.answer(text)
  307. @dp.message_handler(AdminFilter())
  308. async def any_admin_message(m: types.Message):
  309. # await m.answer('u are admin in chat')
  310. pass
  311. @dp.message_handler()
  312. # @logger.catch
  313. async def any_message(m: types.Message):
  314. global chats
  315. chat_id = m.chat.id
  316. user_id = m.from_user.id
  317. if chat_id in chats:
  318. result = await check_subs(m)
  319. if result == 1:
  320. keyboard = await subs_keys(chat_id, user_id)
  321. text = f'{hlink(m.from_user.full_name, f"tg://user?id={m.from_user.id}")},\n' \
  322. f'❗️ Вы не подписались на каналы, поэтому Ваше сообщение удаляется.\n' \
  323. f'✅ Подпишитесь на каналы, указанные ниже и сможете писать в чат.\n'
  324. warn = await m.answer(text, reply_markup=keyboard)
  325. try:
  326. await m.delete()
  327. except MessageToDeleteNotFound:
  328. pass
  329. await asyncio.sleep(60)
  330. await warn.delete()
  331. # try:
  332. # except MessageToDeleteNotFound:
  333. # logger.error(f'Cant delete {warn}\n{traceback.extract_tb()}')
  334. # return
  335. answers = f"Добрый день, {hlink(m.from_user.full_name, f'tg://user?id={m.from_user.id}')}\n\n" \
  336. f"⚠️ Если Ваше объявление удаляется в чате, то Вы можете разместить его " \
  337. f"только на {hunderline('ПЛАТНОЙ ОСНОВЕ.')}\n" \
  338. f"С тарифами на размещение можно ознакомиться в боте: @Vacansy_resume_bot\n\n" \
  339. f"Администрация чата не несёт ответственности за размещаемую пользователями информацию и не исправляет её."
  340. for w in filter_worlds:
  341. if w.lower() in m.text.lower():
  342. mess = await m.answer(answers)
  343. try:
  344. await m.delete()
  345. except MessageToDeleteNotFound:
  346. pass
  347. await asyncio.sleep(10)
  348. try:
  349. logger.info(f'[delete] {m.chat.title[:20]} | {m.from_user.full_name[:20]}: {w[:20]}')
  350. await mess.delete()
  351. except MessageToDeleteNotFound:
  352. pass
  353. break
  354. async def default_command(dp):
  355. for admin_id in config.ADMIN_ID:
  356. await dp.bot.set_my_commands([
  357. BotCommand('help', 'показать все комманды'),
  358. BotCommand('stop_add', 'Напиши после команды слово которое ловить и удалять'),
  359. BotCommand('stop_del', 'Напиши после команды слово которое хочешь удалить из базы'),
  360. BotCommand('stop_list', 'Показать все стоп-слова')
  361. ], BotCommandScopeChat(chat_id=admin_id))
  362. await dp.bot.set_my_commands([
  363. BotCommand('help', 'показать все комманды'),
  364. BotCommand('channel_add', 'Добавить канал в обязательный к подписке'),
  365. BotCommand('channel_del', 'Удалить канал из обязательных к подписке'),
  366. BotCommand('channel_list', 'Показать каналы этого чата'),
  367. BotCommand('stop_add', 'Напиши после команды слово которое ловить и удалять'),
  368. BotCommand('stop_del', 'Напиши после команды слово которое хочешь удалить из базы'),
  369. BotCommand('stop_list', 'Показать все стоп-слова'),
  370. ], BotCommandScopeAllChatAdministrators())
  371. async def startup(dp):
  372. global chats
  373. chats = await update_chat_list()
  374. me = await dp.bot.get_me()
  375. await default_command(dp)
  376. logger.debug(f"bot started | {me.first_name} @{me.username}")
  377. async def shut(dp):
  378. logger.debug("bot stopped")
  379. if __name__ == '__main__':
  380. dp.middleware.setup(JoinUsers())
  381. # dp.middleware.setup(CheckSubscribe())
  382. dp.filters_factory.bind(IsAdmin)
  383. executor.start_polling(dp, on_startup=startup, on_shutdown=shut)