Telegram User Bot that plays uno with mau_mau_bot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

553 lines
21 KiB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. from telethon.sync import TelegramClient
  4. from telethon import events
  5. import re
  6. import logging
  7. import time
  8. import sys
  9. from telethon.tl.functions.messages import GetInlineBotResultsRequest, SendInlineBotResultRequest, SendMessageRequest, SetTypingRequest
  10. from telethon.tl.types import SendMessageTypingAction, SendMessageCancelAction
  11. from telethon.tl.types import PeerUser, PeerChat, PeerChannel, User as _User, Chat as _Chat, Channel as _Channel
  12. from telethon.errors.rpcbaseerrors import RPCError
  13. import asyncio
  14. from game import Game
  15. from card import GREY_SET_ID
  16. game = Game()
  17. from config import api_id, api_hash, PHONE, session_name, unobot_username, default_delay, print_cards, disable_all_commands, game_autostart, unogroup_chatname, game_consts
  18. game.delay = default_delay
  19. SAFE_MODE = False
  20. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
  21. logger = logging.getLogger(__name__)
  22. client = TelegramClient(session_name, api_id, api_hash, sequential_updates=True, use_ipv6=False)
  23. while not client.start(phone=PHONE):
  24. logger.info("Start failed. Retrying...")
  25. time.sleep(5)
  26. logger.info("Started!")
  27. my_username = client.get_me().username
  28. my_firstname = client.get_me().first_name
  29. # parse game_consts
  30. for key in game_consts:
  31. game_consts[key] = game_consts[key].replace('%username%', my_username)
  32. game_consts[key] = game_consts[key].replace('%firstname%', my_firstname)
  33. def _print(*args, **kwargs):
  34. print(*args, **kwargs)
  35. sys.stdout.flush()
  36. logger.info("Getting dialogs")
  37. for dialog in client.iter_dialogs():
  38. pass
  39. #_print(dialog)
  40. logger.info("Done getting dialogs")
  41. unobot = client.get_input_entity(unobot_username)
  42. unochat = None
  43. if unogroup_chatname:
  44. unochat = client.get_input_entity(unogroup_chatname)
  45. async def startgame_task():
  46. logger.info("startgame_task run")
  47. if not game.is_playing:
  48. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  49. await asyncio.sleep(60)
  50. game.delay = 8
  51. #try starting the game
  52. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  53. logger.info("startgame_task game started")
  54. async def task_run():
  55. import schedule_async as schedule
  56. """ To start game automatically
  57. """
  58. at_times = ("9:00", "12:00", "16:00", "18:00", "19:00", "20:00", "21:30")
  59. logger.info("Coroutine task_run started")
  60. for at_time in at_times:
  61. schedule.every().day.at(at_time).do(startgame_task)
  62. # for debug only
  63. #schedule.every(20).seconds.do(testjob)
  64. while True:
  65. await schedule.run_pending()
  66. await asyncio.sleep(60)
  67. async def inline_query(fail=None):
  68. """ This part handles interaction with unobot.
  69. """
  70. # typing @unobot and a space :)
  71. async def set_typing(cancel=False):
  72. '''let others see you are typing'''
  73. if cancel:
  74. try:
  75. await client(SetTypingRequest(
  76. peer = unochat,
  77. action = SendMessageCancelAction()
  78. ))
  79. except Exception as err:
  80. print(err)
  81. else:
  82. try:
  83. await client(SetTypingRequest(
  84. peer = unochat,
  85. action = SendMessageTypingAction()
  86. ))
  87. except Exception as err:
  88. print(err)
  89. async def typing_sleep(seconds):
  90. ''' Telegram cancels your typing notification in several secs
  91. we also call set_typing in the beginning
  92. '''
  93. INTERVAL = 5
  94. seconds = int(seconds)
  95. if seconds <= 0:
  96. pass
  97. else:
  98. if seconds <= INTERVAL:
  99. await asyncio.sleep(INTERVAL)
  100. else:
  101. rounds = int(seconds/INTERVAL)
  102. for i in range(rounds):
  103. await asyncio.sleep(INTERVAL)
  104. await set_typing()
  105. assert (seconds - rounds * INTERVAL) > 0
  106. await asyncio.sleep(int(seconds - rounds * INTERVAL))
  107. await set_typing()
  108. for tries in range(10):
  109. try:
  110. bot_results = await client(GetInlineBotResultsRequest(
  111. unobot, unochat, '', ''
  112. ))
  113. except RPCError:
  114. await asyncio.sleep(1)
  115. else:
  116. break
  117. if bot_results.results:
  118. query_id = bot_results.query_id
  119. for result in bot_results.results:
  120. # is it a grey card? if so, get it.
  121. try:
  122. if hasattr(result.document.attributes[1], 'stickerset'):
  123. try:
  124. (result_id, anti_cheat) = result.id.split(':')
  125. except:
  126. pass
  127. else:
  128. if len(result_id) == 36:
  129. # uuid result for grey cards
  130. sset = result.document.attributes[1].stickerset
  131. if str(sset.id) == GREY_SET_ID:
  132. game.add_grey_card(result.document.id)
  133. continue
  134. except (AttributeError, IndexError):
  135. pass
  136. except Exception as err:
  137. logger.critical('Exception while getting grey cards, {}'.format(str(err)))
  138. # get ordinary cards
  139. try:
  140. (result_id, anti_cheat) = result.id.split(':')
  141. except ValueError:
  142. if fail is None:
  143. fail = 1
  144. await asyncio.sleep(1)
  145. await inline_query(fail=fail)
  146. return
  147. elif fail < 5:
  148. fail += 1
  149. await asyncio.sleep(1)
  150. await inline_query(fail=fail)
  151. return
  152. else:
  153. return
  154. game.add_card(result_id, anti_cheat)
  155. if game.delay:
  156. await typing_sleep(game.delay)
  157. if print_cards:
  158. _print(game.print_cards())
  159. callback_id = game.play_card()
  160. if not callback_id:
  161. callback_id = 'draw'
  162. await client(SendMessageRequest(unochat, 'Error: No card can be played. Leaving game'))
  163. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  164. return
  165. for tries in range(6):
  166. try:
  167. await client(SendInlineBotResultRequest(
  168. unochat,
  169. query_id,
  170. "{}:{}".format(callback_id, game.anti_cheat)
  171. ))
  172. except Exception as err:
  173. logger.critical('Exception: {}'.format(err))
  174. #await client(SendMessageRequest(unochat, 'Exception: {}'.format(err)))
  175. else:
  176. break
  177. await set_typing(cancel=True)
  178. game.rotate_deck()
  179. return True
  180. else:
  181. logger.critical('Bad inline result from bot')
  182. _print(bot_results)
  183. return None
  184. def safety_check(chat_id, force=False):
  185. if SAFE_MODE or force:
  186. safe_ids = [-1001000100100, ]
  187. if chat_id in safe_ids:
  188. return True
  189. else:
  190. return False
  191. else:
  192. return True
  193. def commandify(text, my_commands=True, wild_card=True):
  194. args = text.split()
  195. if not args:
  196. return [None]
  197. match = re.match(r'/([^@]+$)', args[0])
  198. if match:
  199. command = match.group(1)
  200. if not wild_card:
  201. return [None]
  202. username = my_username
  203. args = args[1:]
  204. return [command, username, args]
  205. else:
  206. match = re.match(r'/([^@]+)@([^@]+)$', args[0])
  207. if match:
  208. username = match.group(2)
  209. command = match.group(1)
  210. args = args[1:]
  211. if username != my_username and my_commands == True:
  212. return [None]
  213. else:
  214. return [command, username, args]
  215. else:
  216. return [None]
  217. def get_peer_id(peer, reverse=False):
  218. '''
  219. reverse = False:
  220. peer: telethon.tl.types.PeerUser, PeerChat, PeerChannel / User, Chat, Channel
  221. return int (-100xxxx)
  222. reverse = True:
  223. peer: int (-100xxxx)
  224. return int (xxxx)
  225. '''
  226. if reverse:
  227. str_peer = str(peer)
  228. if str_peer.startswith("-100"):
  229. orig_id = str_peer[4:]
  230. return orig_id
  231. else:
  232. return peer
  233. else:
  234. peerid = None
  235. if type(peer) is PeerChannel:
  236. peerid = getattr(peer, 'channel_id')
  237. peerid = int('-100{}'.format(peerid))
  238. elif type(peer) in [ _Channel, _Chat]:
  239. peerid = getattr(peer, 'id')
  240. peerid = int('-100{}'.format(peerid))
  241. elif type(peer) is PeerChat:
  242. peerid = getattr(peer, 'chat_id')
  243. peerid = int('-100{}'.format(peerid))
  244. elif type(peer) is PeerUser:
  245. peerid = getattr(peer, 'user_id')
  246. elif type(peer) is _User:
  247. peerid = getattr(peer, 'id')
  248. else:
  249. _print("Error: ", peer)
  250. if not peerid:
  251. _print('W: Peer_id is none')
  252. return peerid
  253. class EmptyChat:
  254. def __init__(self, title=None):
  255. self.title = title
  256. class EmptyUser:
  257. def __init__(self, first_name="None", last_name=None, username=None):
  258. self.first_name = first_name
  259. self.last_name = last_name
  260. self.username = username
  261. def display_username(user, atuser=False, shorten=False):
  262. if user.first_name and user.last_name:
  263. name = "{} {}".format(user.first_name, user.last_name)
  264. else:
  265. name = user.first_name
  266. if shorten:
  267. return name
  268. if user.username:
  269. if atuser:
  270. name += " (@{})".format(user.username)
  271. else:
  272. name += " ({})".format(user.username)
  273. return name
  274. max_items = 10000
  275. cached_ids = list()
  276. cached_entity = list()
  277. # It's a mess, but works
  278. # Welcome to pr
  279. async def mwt_get_entity(entity_type, client, peer, retry=0, from_group=None):
  280. global max_items, cached_ids, cached_entity
  281. def get(unique_id):
  282. global cached_ids, cached_entity
  283. try:
  284. my_index = cached_ids.index(unique_id)
  285. entity = cached_entity[my_index]
  286. return entity
  287. except ValueError:
  288. return None
  289. def store(unique_id, entity):
  290. global cached_ids, cached_entity
  291. cached_ids.append(unique_id)
  292. cached_entity.append(entity)
  293. while len(cached_ids) > max_items:
  294. cached_ids.pop(0)
  295. cached_entity.pop(0)
  296. try:
  297. if entity_type == 'group':
  298. unique_id = get_peer_id(peer)
  299. entity = get(unique_id)
  300. #_print("cache")
  301. if not entity:
  302. #_print("new")
  303. entity = await client.get_entity(peer)
  304. elif entity_type == 'user':
  305. unique_id = peer
  306. entity = get(unique_id)
  307. #_print("cache")
  308. if not entity:
  309. #_print("new")
  310. entity = await client.get_entity(PeerUser(user_id=peer))
  311. else:
  312. return None
  313. store(unique_id, entity)
  314. return entity
  315. except (ValueError, KeyError) as err:
  316. if retry < 1:
  317. retry += 1
  318. if entity_type == 'group':
  319. sys.stdout.write("[Fetching user from group] Error while getting chat: {}".format(err))
  320. sys.stdout.flush()
  321. elif entity_type == 'user':
  322. sys.stdout.write("[Fetching user from group] Error while getting user: {}".format(err))
  323. sys.stdout.flush()
  324. if from_group:
  325. for user in await client.get_participants(from_group):
  326. unique_id = user.id
  327. entity = user
  328. if unique_id not in cached_ids:
  329. store(unique_id, entity)
  330. entity = await mwt_get_entity(entity_type, client, peer, retry=retry)
  331. store(unique_id, entity)
  332. return entity
  333. else:
  334. if entity_type == 'group':
  335. sys.stdout.write("[Give up] Error while getting chat: {}".format(err))
  336. sys.stdout.flush()
  337. entity = EmptyChat(str(id))
  338. elif entity_type == 'user':
  339. sys.stdout.write("[Give up] Error while getting user: {}".format(err))
  340. sys.stdout.flush()
  341. entity = EmptyUser(first_name="PeerUser(user_id={})".format(peer))
  342. store(unique_id, entity)
  343. return entity
  344. async def get_full_info(event):
  345. '''
  346. # full_user = client(GetFullUserRequest(id=PeerUser(user_id=msg.from_id)))
  347. # full_chat = client(GetFullChatRequest(chat_id=chat_id))
  348. # full_channel = client(GetFullChannelRequest(channel=PeerChannel(channel_id=None)))
  349. # first_name = full_user.user.first_name
  350. # last_name = full_user.user.last_name
  351. # username = full_user.user.username
  352. # title = full_chat.chats[0].title
  353. # title = full_channel.chats[0].title
  354. '''
  355. orig_user_id = event.message.from_id
  356. user_id = get_peer_id(PeerUser(user_id=orig_user_id))
  357. if event.is_channel:
  358. #channel = client.get_entity(event.message.to_id)
  359. #user = client.get_entity(PeerUser(user_id=event.message.from_id))
  360. channel = await mwt_get_entity('group', client, event.message.to_id)
  361. user = await mwt_get_entity('user', client, event.message.from_id, from_group=event.message.to_id)
  362. channel_id = get_peer_id(channel)
  363. full_info = ['Channel', channel, user, channel_id, user_id]
  364. elif event.is_group:
  365. group = await mwt_get_entity('group', client, event.message.to_id)
  366. group_id = get_peer_id(group)
  367. user = await mwt_get_entity('user', client, event.message.from_id, from_group=event.message.to_id)
  368. full_info = ['Group', group, user, group_id, user_id]
  369. elif event.is_private:
  370. user = await mwt_get_entity('user', client, event.message.from_id)
  371. full_info = ['User', EmptyChat(), user, user_id, user_id]
  372. else:
  373. return None
  374. return full_info
  375. @client.on(events.NewMessage)
  376. async def new_msg_handler(event):
  377. global unochat
  378. #print(event)
  379. #sys.stdout.flush()
  380. full_info = await get_full_info(event)
  381. msg = event.message
  382. if msg.message and (not msg.media):
  383. # Text handler
  384. logger.info("{} - {} - {}: {}".format(full_info[0], full_info[1].title, display_username(full_info[2]), msg.message))
  385. if not safety_check(get_peer_id(msg.to_id)):
  386. return
  387. # react to commands
  388. if not disable_all_commands:
  389. c = commandify(event.raw_text, wild_card=False)
  390. if c[0]:
  391. if c[0] == 'hello':
  392. await event.reply('hi!')
  393. if c[0] in ['startgame', 'start', 'join'] and full_info[0] == "Channel":
  394. if game.is_playing:
  395. await event.reply("I'm playing right now.")
  396. else:
  397. unochat = msg.to_id
  398. game.join_game(get_peer_id(unochat))
  399. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  400. elif c[0] in ['stopgame', 'stop', 'leave'] and full_info[0] == "Channel":
  401. if game.is_playing:
  402. game.leave_game(get_peer_id(unochat))
  403. game.stop_game()
  404. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  405. elif game.joined and get_peer_id(unochat) in game.joined:
  406. game.leave_game(get_peer_id(unochat))
  407. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  408. else:
  409. await event.reply("I'm not playing right now.")
  410. elif c[0] in ['wait', 'delay'] and full_info[0] == "Channel":
  411. if game.delay or (not game.is_playing):
  412. await event.reply("Nothing to do.")
  413. else:
  414. game.delay = 8
  415. await event.reply("OK. {} seconds of delay has been set.".format(game.delay))
  416. elif c[0] in ['nowait', 'nodelay'] and full_info[0] == "Channel":
  417. if game.delay and game.is_playing:
  418. myreply = "OK. {} seconds of delay has been removed.".format(game.delay)
  419. game.delay = None
  420. await event.reply(myreply)
  421. else:
  422. await event.reply("Nothing to do.")
  423. return
  424. # react to unobot
  425. if full_info[2].username and full_info[2].username == unobot_username:
  426. if re.search(game_consts['myturn'], msg.message):
  427. if re.search(game_consts['start'], msg.message):
  428. # I'm the first player
  429. if not game.is_playing:
  430. unochat = msg.to_id
  431. if game.joined and get_peer_id(unochat) in game.joined:
  432. logger.info('Bot: Game started. I\'m the first player.')
  433. game.start_game()
  434. logger.info('Bot: It\'s my turn.')
  435. if game.joined and get_peer_id(unochat) in game.joined:
  436. if not game.is_playing:
  437. logger.info('Bot: Game started a long time ago. I joined midway.')
  438. game.start_game()
  439. await inline_query()
  440. else:
  441. logger.info('I\'m not playing in {} - {}'.format(full_info[1].title, get_peer_id(unochat)))
  442. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  443. elif re.search(game_consts['end'], msg.message):
  444. if game.is_playing:
  445. logger.info('Bot: Game ended.')
  446. game.clear_deck()
  447. game.leave_game(get_peer_id(unochat))
  448. game.stop_game()
  449. game.delay = default_delay
  450. elif re.search(game_consts['create'], msg.message):
  451. if not game.is_playing:
  452. logger.info('Bot: New game created.')
  453. if default_delay:
  454. await asyncio.sleep(default_delay)
  455. unochat = msg.to_id
  456. game.join_game(get_peer_id(unochat))
  457. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  458. elif re.search(game_consts['start'], msg.message):
  459. if not game.is_playing:
  460. unochat = msg.to_id
  461. if game.joined and get_peer_id(unochat) in game.joined:
  462. logger.info('Bot: Game started.')
  463. game.start_game()
  464. else:
  465. await client(SendMessageRequest(unochat, "/[email protected]{}".format(unobot_username)))
  466. elif re.search(game_consts['win'], msg.message):
  467. if game.is_playing:
  468. logger.info('Bot: I win.')
  469. game.clear_deck()
  470. game.leave_game(get_peer_id(unochat))
  471. game.stop_game()
  472. elif msg.media:
  473. # Has media, interesting.
  474. media = msg.media
  475. type = None
  476. if hasattr(media, 'photo'):
  477. type = "photo"
  478. logger.info("{} - {} - {}: [Photo]".format(full_info[0], full_info[1].title, display_username(full_info[2])))
  479. elif hasattr(media, 'document'):
  480. try:
  481. if hasattr(media.document.attributes[1], 'stickerset'):
  482. type = "Sticker"
  483. logger.info("{} - {} - {}: [Sticker]:{}".format(full_info[0], full_info[1].title, display_username(full_info[2]), media.document.attributes[1].alt))
  484. else:
  485. type = "Document (file)"
  486. logger.info("{} - {} - {}: [Document (file)]".format(full_info[0], full_info[1].title, display_username(full_info[2])))
  487. except (AttributeError, IndexError):
  488. type = "Document"
  489. logger.info("{} - {} - {}: [Document]".format(full_info[0], full_info[1].title, display_username(full_info[2])))
  490. else:
  491. type = "Unknown media"
  492. logger.info("{} - {} - {}: [Unknown media]".format(full_info[0], full_info[1].title, display_username(full_info[2])))
  493. logger.debug("Media Type: {}".format(type))
  494. # Handler complete
  495. # for debug only
  496. async def testjob():
  497. print('aaa')
  498. async def main():
  499. # requires python 3.7 +
  500. #if game_autostart:
  501. #asyncio.create_task(task_run())
  502. await client.run_until_disconnected()
  503. #await client.disconnected
  504. loop = asyncio.get_event_loop()
  505. # this can run on python 3.5
  506. if game_autostart:
  507. loop.create_task(task_run())
  508. try:
  509. loop.run_until_complete(main())
  510. except KeyboardInterrupt:
  511. client.disconnect()