Queer European MD passionate about IT

useful_tools.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. """General purpose functions for Telegram bots."""
  2. # Standard library
  3. import datetime
  4. import json
  5. from collections import OrderedDict
  6. from typing import List, Union
  7. # Project modules
  8. from .api import TelegramError
  9. from .bot import Bot
  10. from .messages import default_useful_tools_messages
  11. from .utilities import (get_cleaned_text, get_user, make_button,
  12. make_inline_keyboard, recursive_dictionary_update, )
  13. def get_calc_buttons() -> OrderedDict:
  14. buttons = OrderedDict()
  15. buttons['pow'] = dict(
  16. value='**',
  17. symbol='**',
  18. order='A1',
  19. )
  20. buttons['floordiv'] = dict(
  21. value='//',
  22. symbol='//',
  23. order='A2',
  24. )
  25. buttons['mod'] = dict(
  26. value='%',
  27. symbol='mod',
  28. order='A3',
  29. )
  30. buttons['info'] = dict(
  31. value='info',
  32. symbol='ℹ️',
  33. order='A4',
  34. )
  35. buttons[0] = dict(
  36. value=0,
  37. symbol='0️⃣',
  38. order='E1',
  39. )
  40. buttons[1] = dict(
  41. value=1,
  42. symbol='1️⃣',
  43. order='D1',
  44. )
  45. buttons[2] = dict(
  46. value=2,
  47. symbol='2️⃣',
  48. order='D2',
  49. )
  50. buttons[3] = dict(
  51. value=3,
  52. symbol='3️⃣',
  53. order='D3',
  54. )
  55. buttons[4] = dict(
  56. value=4,
  57. symbol='4️⃣',
  58. order='C1',
  59. )
  60. buttons[5] = dict(
  61. value=5,
  62. symbol='5️⃣',
  63. order='C2',
  64. )
  65. buttons[6] = dict(
  66. value=6,
  67. symbol='6️⃣',
  68. order='C3',
  69. )
  70. buttons[7] = dict(
  71. value=7,
  72. symbol='7️⃣',
  73. order='B1',
  74. )
  75. buttons[8] = dict(
  76. value=8,
  77. symbol='8️⃣',
  78. order='B2',
  79. )
  80. buttons[9] = dict(
  81. value=9,
  82. symbol='9️⃣',
  83. order='B3',
  84. )
  85. buttons['plus'] = dict(
  86. value='+',
  87. symbol='➕️',
  88. order='B4',
  89. )
  90. buttons['minus'] = dict(
  91. value='-',
  92. symbol='➖',
  93. order='C4',
  94. )
  95. buttons['times'] = dict(
  96. value='*',
  97. symbol='✖️',
  98. order='D4',
  99. )
  100. buttons['divided'] = dict(
  101. value='/',
  102. symbol='➗',
  103. order='E4',
  104. )
  105. buttons['point'] = dict(
  106. value='.',
  107. symbol='.',
  108. order='E2',
  109. )
  110. buttons['000'] = dict(
  111. value='*1000',
  112. symbol='0️⃣0️⃣0️⃣',
  113. order='E3',
  114. )
  115. buttons['enter'] = dict(
  116. value='\n',
  117. symbol='✅',
  118. order='F1',
  119. )
  120. buttons['del'] = dict(
  121. value='del',
  122. symbol='⬅️',
  123. order='F2',
  124. )
  125. return buttons
  126. calc_buttons = get_calc_buttons()
  127. def get_calculator_keyboard():
  128. return make_inline_keyboard(
  129. [
  130. make_button(
  131. text=button['symbol'],
  132. prefix='calc:///',
  133. delimiter='|',
  134. data=[button['value']]
  135. )
  136. for button in sorted(calc_buttons.values(), key=lambda b: b['order'])
  137. ],
  138. 4
  139. )
  140. async def _calculate_button(bot: Bot,
  141. language: str,
  142. data: List[Union[int, str]]):
  143. result, text, reply_markup = '', '', None
  144. if len(data) == 1:
  145. input_value = data[0]
  146. if input_value == 'del':
  147. pass
  148. elif input_value == 'info':
  149. pass
  150. elif input_value in [button['value'] for button in calc_buttons.values()]:
  151. pass
  152. else:
  153. pass # Error!
  154. # Edit the update with the button if a new text is specified
  155. if not text:
  156. return result
  157. return dict(
  158. text=result,
  159. edit=dict(
  160. text=text,
  161. reply_markup=reply_markup
  162. )
  163. )
  164. async def _calculate_command(bot: Bot,
  165. update: dict,
  166. language: str,
  167. command_name: str = 'calc'):
  168. reply_markup = None
  169. if 'reply_to_message' in update:
  170. update = update['reply_to_message']
  171. command_aliases = [command_name]
  172. if command_name in bot.commands:
  173. command_aliases += list(
  174. bot.commands[command_name]['language_labelled_commands'].values()
  175. ) + bot.commands[command_name]['aliases']
  176. text = get_cleaned_text(bot=bot,
  177. update=update,
  178. replace=command_aliases)
  179. if not text:
  180. text = bot.get_message(
  181. 'useful_tools', 'calculate_command', 'instructions',
  182. language=language
  183. )
  184. reply_markup = get_calculator_keyboard()
  185. else:
  186. text = 'pass'
  187. await bot.send_message(text=text,
  188. update=update,
  189. reply_markup=reply_markup)
  190. async def _length_command(bot: Bot, update: dict, user_record: OrderedDict):
  191. message_text = get_cleaned_text(
  192. update=update,
  193. bot=bot,
  194. replace=[
  195. alias
  196. for alias in bot.messages[
  197. 'useful_tools'
  198. ][
  199. 'length_command'
  200. ][
  201. 'language_labelled_commands'
  202. ].values()
  203. ]
  204. )
  205. if message_text:
  206. text = bot.get_message(
  207. 'useful_tools', 'length_command', 'result',
  208. user_record=user_record, update=update,
  209. n=len(message_text)
  210. )
  211. elif 'reply_to_message' not in update:
  212. text = bot.get_message(
  213. 'useful_tools', 'length_command', 'instructions',
  214. user_record=user_record, update=update
  215. )
  216. else:
  217. text = bot.get_message(
  218. 'useful_tools', 'length_command', 'result',
  219. user_record=user_record, update=update,
  220. n=len(update['reply_to_message']['text'])
  221. )
  222. update = update['reply_to_message']
  223. reply_to_message_id = update['message_id']
  224. return dict(
  225. chat_id=update['chat']['id'],
  226. text=text,
  227. parse_mode='HTML',
  228. reply_to_message_id=reply_to_message_id
  229. )
  230. async def _message_info_command(bot: Bot, update: dict, language: str):
  231. """Provide information about selected update.
  232. Selected update: the message `update` is sent in reply to. If `update` is
  233. not a reply to anything, it gets selected.
  234. The update containing the command, if sent in reply, is deleted.
  235. """
  236. if 'reply_to_message' in update:
  237. selected_update = update['reply_to_message']
  238. else:
  239. selected_update = update
  240. await bot.send_message(
  241. text=bot.get_message(
  242. 'useful_tools', 'info_command', 'result',
  243. language=language,
  244. info=json.dumps(selected_update, indent=2)
  245. ),
  246. update=update,
  247. reply_to_message_id=selected_update['message_id'],
  248. )
  249. if selected_update != update:
  250. try:
  251. await bot.delete_message(update=update)
  252. except TelegramError:
  253. pass
  254. async def _ping_command(bot: Bot, update: dict):
  255. """Return `pong` only in private chat."""
  256. chat_id = bot.get_chat_id(update=update)
  257. if chat_id < 0:
  258. return
  259. return "<i>Pong!</i>"
  260. async def _when_command(bot: Bot, update: dict, language: str):
  261. reply_markup = None
  262. text = ''
  263. if 'reply_to_message' not in update:
  264. return bot.get_message(
  265. 'useful_tools', 'when_command', 'instructions',
  266. language=language
  267. )
  268. update = update['reply_to_message']
  269. date = (
  270. datetime.datetime.fromtimestamp(update['date'])
  271. if 'date' in update
  272. else None
  273. )
  274. text += bot.get_message(
  275. 'useful_tools', 'when_command', 'who_when',
  276. language=language,
  277. who=get_user(update['from']),
  278. when=date
  279. )
  280. if 'forward_date' in update:
  281. original_datetime = (
  282. datetime.datetime.fromtimestamp(update['forward_date'])
  283. if 'forward_from' in update
  284. else None
  285. )
  286. text += "\n\n" + bot.get_message(
  287. 'useful_tools', 'when_command', 'forwarded_message',
  288. language=language,
  289. who=get_user(update['forward_from']),
  290. when=original_datetime
  291. ) + "\n"
  292. text += bot.get_message(
  293. 'useful_tools', 'when_command', 'who_when',
  294. language=language,
  295. who=get_user(update['forward_from']),
  296. when=original_datetime
  297. )
  298. await bot.send_message(
  299. text=text,
  300. reply_markup=reply_markup,
  301. reply_to_message_id=update['message_id'],
  302. disable_notification=True,
  303. chat_id=update['chat']['id']
  304. )
  305. def init(telegram_bot: Bot, useful_tools_messages=None):
  306. """Define commands for `telegram_bot`.
  307. You may provide customized `useful_tools_messages` that will overwrite
  308. `default_useful_tools_messages`. Missing entries will be kept default.
  309. """
  310. if useful_tools_messages is None:
  311. useful_tools_messages = dict()
  312. useful_tools_messages = recursive_dictionary_update(
  313. default_useful_tools_messages,
  314. useful_tools_messages
  315. )
  316. telegram_bot.messages['useful_tools'] = useful_tools_messages
  317. @telegram_bot.command(command='/calc',
  318. aliases=None,
  319. reply_keyboard_button=None,
  320. show_in_keyboard=False,
  321. **{key: val for key, val
  322. in useful_tools_messages['calculate_command'].items()
  323. if key in ('description', 'help_section',
  324. 'language_labelled_commands')},
  325. authorization_level='everybody')
  326. async def calculate_command(bot, update, language):
  327. return await _calculate_command(bot=bot,
  328. update=update,
  329. language=language,
  330. command_name='calc')
  331. @telegram_bot.button(prefix='calc:///',
  332. separator='|',
  333. authorization_level='everybody')
  334. async def calculate_button(bot, language, data):
  335. return await _calculate_button(bot=bot, language=language, data=data)
  336. @telegram_bot.command(command='/info',
  337. aliases=None,
  338. reply_keyboard_button=None,
  339. show_in_keyboard=False,
  340. **{key: val for key, val
  341. in useful_tools_messages['info_command'].items()
  342. if key in ('description', 'help_section',
  343. 'language_labelled_commands')},
  344. authorization_level='everybody')
  345. async def message_info_command(bot, update, language):
  346. return await _message_info_command(bot=bot,
  347. update=update,
  348. language=language)
  349. @telegram_bot.command(command='/length',
  350. aliases=None,
  351. reply_keyboard_button=None,
  352. show_in_keyboard=False,
  353. **{key: val for key, val
  354. in useful_tools_messages['length_command'].items()
  355. if key in ('description', 'help_section',
  356. 'language_labelled_commands')},
  357. authorization_level='everybody')
  358. async def length_command(bot, update, user_record):
  359. return await _length_command(bot=bot, update=update, user_record=user_record)
  360. @telegram_bot.command(command='/ping',
  361. aliases=None,
  362. reply_keyboard_button=None,
  363. show_in_keyboard=False,
  364. **{key: val for key, val
  365. in useful_tools_messages['ping_command'].items()
  366. if key in ('description', 'help_section',
  367. 'language_labelled_commands')},
  368. authorization_level='everybody')
  369. async def ping_command(bot, update):
  370. return await _ping_command(bot=bot, update=update)
  371. @telegram_bot.command(command='/when',
  372. aliases=None,
  373. reply_keyboard_button=None,
  374. show_in_keyboard=False,
  375. **{key: val for key, val
  376. in useful_tools_messages['when_command'].items()
  377. if key in ('description', 'help_section',
  378. 'language_labelled_commands')},
  379. authorization_level='everybody')
  380. async def when_command(bot, update, language):
  381. return await _when_command(bot=bot, update=update, language=language)