Queer European MD passionate about IT

administration_tools.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  1. """Administration tools for telegram bots.
  2. Usage:
  3. ```
  4. import davtelepot
  5. my_bot = davtelepot.Bot.get('my_token', 'my_database.db')
  6. davtelepot.admin_tools.init(my_bot)
  7. ```
  8. """
  9. # Standard library modules
  10. import asyncio
  11. import datetime
  12. import json
  13. # Third party modules
  14. import davtelepot
  15. from davtelepot import messages
  16. from davtelepot.utilities import (
  17. async_wrapper, Confirmator, extract, get_cleaned_text, get_user,
  18. escape_html_chars, line_drawing_unordered_list, make_button,
  19. make_inline_keyboard, remove_html_tags, send_part_of_text_file,
  20. send_csv_file
  21. )
  22. from sqlalchemy.exc import ResourceClosedError
  23. async def _forward_to(update, bot, sender, addressee, is_admin=False):
  24. if update['text'].lower() in ['stop'] and is_admin:
  25. with bot.db as db:
  26. admin_record = db['users'].find_one(
  27. telegram_id=sender
  28. )
  29. session_record = db['talking_sessions'].find_one(
  30. admin=admin_record['id'],
  31. cancelled=0
  32. )
  33. other_user_record = db['users'].find_one(
  34. id=session_record['user']
  35. )
  36. await end_session(
  37. bot=bot,
  38. other_user_record=other_user_record,
  39. admin_record=admin_record
  40. )
  41. else:
  42. bot.set_individual_text_message_handler(
  43. await async_wrapper(
  44. _forward_to,
  45. sender=sender,
  46. addressee=addressee,
  47. is_admin=is_admin
  48. ),
  49. sender
  50. )
  51. await bot.forward_message(
  52. chat_id=addressee,
  53. update=update
  54. )
  55. return
  56. def get_talk_panel(bot, update, user_record=None, text=''):
  57. """Return text and reply markup of talk panel.
  58. `text` may be:
  59. - `user_id` as string
  60. - `username` as string
  61. - `''` (empty string) for main menu (default)
  62. """
  63. users = []
  64. if len(text):
  65. with bot.db as db:
  66. if text.isnumeric():
  67. users = list(
  68. db['users'].find(id=int(text))
  69. )
  70. else:
  71. users = list(
  72. db.query(
  73. "SELECT * "
  74. "FROM users "
  75. "WHERE COALESCE( "
  76. " first_name || last_name || username, "
  77. " last_name || username, "
  78. " first_name || username, "
  79. " username, "
  80. " first_name || last_name, "
  81. " last_name, "
  82. " first_name "
  83. f") LIKE '%{text}%' "
  84. "ORDER BY LOWER( "
  85. " COALESCE( "
  86. " first_name || last_name || username, "
  87. " last_name || username, "
  88. " first_name || username, "
  89. " username, "
  90. " first_name || last_name, "
  91. " last_name, "
  92. " first_name "
  93. " ) "
  94. ") "
  95. "LIMIT 26"
  96. )
  97. )
  98. if len(text) == 0:
  99. text = (
  100. bot.get_message(
  101. 'talk',
  102. 'help_text',
  103. update=update,
  104. user_record=user_record,
  105. q=escape_html_chars(
  106. remove_html_tags(text)
  107. )
  108. )
  109. )
  110. reply_markup = make_inline_keyboard(
  111. [
  112. make_button(
  113. bot.get_message(
  114. 'talk', 'search_button',
  115. update=update, user_record=user_record
  116. ),
  117. prefix='talk:///',
  118. data=['search']
  119. )
  120. ],
  121. 1
  122. )
  123. elif len(users) == 0:
  124. text = (
  125. bot.get_message(
  126. 'talk',
  127. 'user_not_found',
  128. update=update,
  129. user_record=user_record,
  130. q=escape_html_chars(
  131. remove_html_tags(text)
  132. )
  133. )
  134. )
  135. reply_markup = make_inline_keyboard(
  136. [
  137. make_button(
  138. bot.get_message(
  139. 'talk', 'search_button',
  140. update=update, user_record=user_record
  141. ),
  142. prefix='talk:///',
  143. data=['search']
  144. )
  145. ],
  146. 1
  147. )
  148. else:
  149. text = "{header}\n\n{u}{etc}".format(
  150. header=bot.get_message(
  151. 'talk', 'select_user',
  152. update=update, user_record=user_record
  153. ),
  154. u=line_drawing_unordered_list(
  155. [
  156. get_user(user)
  157. for user in users[:25]
  158. ]
  159. ),
  160. etc=(
  161. '\n\n[...]'
  162. if len(users) > 25
  163. else ''
  164. )
  165. )
  166. reply_markup = make_inline_keyboard(
  167. [
  168. make_button(
  169. '👤 {u}'.format(
  170. u=get_user(
  171. {
  172. key: val
  173. for key, val in user.items()
  174. if key in (
  175. 'first_name',
  176. 'last_name',
  177. 'username'
  178. )
  179. }
  180. )
  181. ),
  182. prefix='talk:///',
  183. data=[
  184. 'select',
  185. user['id']
  186. ]
  187. )
  188. for user in users[:25]
  189. ],
  190. 2
  191. )
  192. return text, reply_markup
  193. async def _talk_command(bot, update, user_record):
  194. text = get_cleaned_text(
  195. update,
  196. bot,
  197. ['talk']
  198. )
  199. text, reply_markup = get_talk_panel(bot=bot, update=update,
  200. user_record=user_record, text=text)
  201. return dict(
  202. text=text,
  203. parse_mode='HTML',
  204. reply_markup=reply_markup,
  205. )
  206. async def start_session(bot, other_user_record, admin_record):
  207. """Start talking session between user and admin.
  208. Register session in database, so it gets loaded before message_loop starts.
  209. Send a notification both to admin and user, set custom parsers and return.
  210. """
  211. with bot.db as db:
  212. db['talking_sessions'].insert(
  213. dict(
  214. user=other_user_record['id'],
  215. admin=admin_record['id'],
  216. cancelled=0
  217. )
  218. )
  219. await bot.send_message(
  220. chat_id=other_user_record['telegram_id'],
  221. text=bot.get_message(
  222. 'talk', 'user_warning',
  223. user_record=other_user_record,
  224. u=get_user(admin_record)
  225. )
  226. )
  227. await bot.send_message(
  228. chat_id=admin_record['telegram_id'],
  229. text=bot.get_message(
  230. 'talk', 'admin_warning',
  231. user_record=admin_record,
  232. u=get_user(other_user_record)
  233. ),
  234. reply_markup=make_inline_keyboard(
  235. [
  236. make_button(
  237. bot.get_message(
  238. 'talk', 'stop',
  239. user_record=admin_record
  240. ),
  241. prefix='talk:///',
  242. data=['stop', other_user_record['id']]
  243. )
  244. ]
  245. )
  246. )
  247. bot.set_individual_text_message_handler(
  248. await async_wrapper(
  249. _forward_to,
  250. sender=other_user_record['telegram_id'],
  251. addressee=admin_record['telegram_id'],
  252. is_admin=False
  253. ),
  254. other_user_record['telegram_id']
  255. )
  256. bot.set_individual_text_message_handler(
  257. await async_wrapper(
  258. _forward_to,
  259. sender=admin_record['telegram_id'],
  260. addressee=other_user_record['telegram_id'],
  261. is_admin=True
  262. ),
  263. admin_record['telegram_id']
  264. )
  265. return
  266. async def end_session(bot, other_user_record, admin_record):
  267. """End talking session between user and admin.
  268. Cancel session in database, so it will not be loaded anymore.
  269. Send a notification both to admin and user, clear custom parsers
  270. and return.
  271. """
  272. with bot.db as db:
  273. db['talking_sessions'].update(
  274. dict(
  275. admin=admin_record['id'],
  276. cancelled=1
  277. ),
  278. ['admin']
  279. )
  280. await bot.send_message(
  281. chat_id=other_user_record['telegram_id'],
  282. text=bot.get_message(
  283. 'talk', 'user_session_ended',
  284. user_record=other_user_record,
  285. u=get_user(admin_record)
  286. )
  287. )
  288. await bot.send_message(
  289. chat_id=admin_record['telegram_id'],
  290. text=bot.get_message(
  291. 'talk', 'admin_session_ended',
  292. user_record=admin_record,
  293. u=get_user(other_user_record)
  294. ),
  295. )
  296. for record in (admin_record, other_user_record,):
  297. bot.remove_individual_text_message_handler(record['telegram_id'])
  298. return
  299. async def _talk_button(bot, update, user_record, data):
  300. telegram_id = user_record['telegram_id']
  301. command, *arguments = data
  302. result, text, reply_markup = '', '', None
  303. if command == 'search':
  304. bot.set_individual_text_message_handler(
  305. await async_wrapper(
  306. _talk_command,
  307. ),
  308. update
  309. )
  310. text = bot.get_message(
  311. 'talk', 'instructions',
  312. update=update, user_record=user_record
  313. )
  314. reply_markup = None
  315. elif command == 'select':
  316. if (
  317. len(arguments) < 1
  318. or type(arguments[0]) is not int
  319. ):
  320. result = "Errore!"
  321. else:
  322. with bot.db as db:
  323. other_user_record = db['users'].find_one(
  324. id=arguments[0]
  325. )
  326. admin_record = db['users'].find_one(
  327. telegram_id=telegram_id
  328. )
  329. await start_session(
  330. bot,
  331. other_user_record=other_user_record,
  332. admin_record=admin_record
  333. )
  334. elif command == 'stop':
  335. if (
  336. len(arguments) < 1
  337. or type(arguments[0]) is not int
  338. ):
  339. result = "Errore!"
  340. elif not Confirmator.get('stop_bots').confirm(telegram_id):
  341. result = bot.get_message(
  342. 'talk', 'end_session',
  343. update=update, user_record=user_record
  344. )
  345. else:
  346. with bot.db as db:
  347. other_user_record = db['users'].find_one(
  348. id=arguments[0]
  349. )
  350. admin_record = db['users'].find_one(
  351. telegram_id=telegram_id
  352. )
  353. await end_session(
  354. bot,
  355. other_user_record=other_user_record,
  356. admin_record=admin_record
  357. )
  358. text = "Session ended."
  359. reply_markup = None
  360. if text:
  361. return dict(
  362. text=result,
  363. edit=dict(
  364. text=text,
  365. parse_mode='HTML',
  366. reply_markup=reply_markup,
  367. disable_web_page_preview=True
  368. )
  369. )
  370. return result
  371. async def _restart_command(bot, update, user_record):
  372. with bot.db as db:
  373. db['restart_messages'].insert(
  374. dict(
  375. text=bot.get_message(
  376. 'admin', 'restart_command', 'restart_completed_message',
  377. update=update, user_record=user_record
  378. ),
  379. chat_id=update['chat']['id'],
  380. parse_mode='HTML',
  381. reply_to_message_id=update['message_id'],
  382. sent=None
  383. )
  384. )
  385. await bot.reply(
  386. update=update,
  387. text=bot.get_message(
  388. 'admin', 'restart_command', 'restart_scheduled_message',
  389. update=update, user_record=user_record
  390. )
  391. )
  392. bot.__class__.stop(message='=== RESTART ===', final_state=65)
  393. return
  394. async def _stop_command(bot, update, user_record):
  395. text = bot.get_message(
  396. 'admin', 'stop_command', 'text',
  397. update=update, user_record=user_record
  398. )
  399. reply_markup = make_inline_keyboard(
  400. [
  401. make_button(
  402. text=bot.get_message(
  403. 'admin', 'stop_button', 'stop_text',
  404. update=update, user_record=user_record
  405. ),
  406. prefix='stop:///',
  407. data=['stop']
  408. ),
  409. make_button(
  410. text=bot.get_message(
  411. 'admin', 'stop_button', 'cancel',
  412. update=update, user_record=user_record
  413. ),
  414. prefix='stop:///',
  415. data=['cancel']
  416. )
  417. ],
  418. 1
  419. )
  420. return dict(
  421. text=text,
  422. parse_mode='HTML',
  423. reply_markup=reply_markup
  424. )
  425. async def stop_bots(bot):
  426. """Stop bots in `bot` class."""
  427. await asyncio.sleep(2)
  428. bot.__class__.stop(message='=== STOP ===', final_state=0)
  429. return
  430. async def _stop_button(bot, update, user_record, data):
  431. result, text, reply_markup = '', '', None
  432. telegram_id = user_record['telegram_id']
  433. command = data[0] if len(data) > 0 else 'None'
  434. if command == 'stop':
  435. if not Confirmator.get('stop_bots').confirm(telegram_id):
  436. return bot.get_message(
  437. 'admin', 'stop_button', 'confirm',
  438. update=update, user_record=user_record
  439. )
  440. text = bot.get_message(
  441. 'admin', 'stop_button', 'stopping',
  442. update=update, user_record=user_record
  443. )
  444. result = text
  445. # Do not stop bots immediately, otherwise callback query
  446. # will never be answered
  447. asyncio.ensure_future(stop_bots(bot))
  448. elif command == 'cancel':
  449. text = bot.get_message(
  450. 'admin', 'stop_button', 'cancelled',
  451. update=update, user_record=user_record
  452. )
  453. result = text
  454. if text:
  455. return dict(
  456. text=result,
  457. edit=dict(
  458. text=text,
  459. parse_mode='HTML',
  460. reply_markup=reply_markup,
  461. disable_web_page_preview=True
  462. )
  463. )
  464. return result
  465. async def _send_bot_database(bot, update, user_record):
  466. if not all(
  467. [
  468. bot.db_url.endswith('.db'),
  469. bot.db_url.startswith('sqlite:///')
  470. ]
  471. ):
  472. return bot.get_message(
  473. 'admin', 'db_command', 'not_sqlite',
  474. update=update, user_record=user_record,
  475. db_type=bot.db_url.partition(':///')[0]
  476. )
  477. await bot.send_document(
  478. chat_id=user_record['telegram_id'],
  479. document_path=extract(bot.db.url, starter='sqlite:///'),
  480. caption=bot.get_message(
  481. 'admin', 'db_command', 'file_caption',
  482. update=update, user_record=user_record
  483. )
  484. )
  485. return bot.get_message(
  486. 'admin', 'db_command', 'db_sent',
  487. update=update, user_record=user_record
  488. )
  489. async def _query_command(bot, update, user_record):
  490. query = get_cleaned_text(
  491. update,
  492. bot,
  493. ['query', ]
  494. )
  495. query_id = None
  496. if len(query) == 0:
  497. return bot.get_message(
  498. 'admin', 'query_command', 'help',
  499. update=update, user_record=user_record
  500. )
  501. try:
  502. with bot.db as db:
  503. record = db.query(query)
  504. try:
  505. record = list(record)
  506. except ResourceClosedError:
  507. record = bot.get_message(
  508. 'admin', 'query_command', 'no_iterable',
  509. update=update, user_record=user_record
  510. )
  511. query_id = db['queries'].upsert(
  512. dict(
  513. query=query
  514. ),
  515. ['query']
  516. )
  517. if query_id is True:
  518. query_id = db['queries'].find_one(
  519. query=query
  520. )['id']
  521. result = json.dumps(record, indent=2)
  522. if len(result) > 500:
  523. result = (
  524. f"{result[:200]}\n" # First 200 characters
  525. f"[...]\n" # Interruption symbol
  526. f"{result[-200:]}" # Last 200 characters
  527. )
  528. except Exception as e:
  529. result = "{first_line}\n{e}".format(
  530. first_line=bot.get_message(
  531. 'admin', 'query_command', 'exception',
  532. update=update, user_record=user_record
  533. ),
  534. e=e
  535. )
  536. result = (
  537. "<b>{first_line}</b>\n".format(
  538. first_line=bot.get_message(
  539. 'admin', 'query_command', 'result',
  540. update=update, user_record=user_record
  541. )
  542. )
  543. + f"<code>{query}</code>\n\n"
  544. f"{result}"
  545. )
  546. if query_id:
  547. reply_markup = make_inline_keyboard(
  548. [
  549. make_button(
  550. text='CSV',
  551. prefix='db_query:///',
  552. data=['csv', query_id]
  553. )
  554. ],
  555. 1
  556. )
  557. else:
  558. reply_markup = None
  559. return dict(
  560. chat_id=update['chat']['id'],
  561. text=result,
  562. parse_mode='HTML',
  563. reply_markup=reply_markup
  564. )
  565. async def _query_button(bot, update, user_record, data):
  566. result, text, reply_markup = '', '', None
  567. command = data[0] if len(data) else 'default'
  568. error_message = bot.get_message(
  569. 'admin', 'query_button', 'error',
  570. user_record=user_record, update=update
  571. )
  572. if command == 'csv':
  573. if not len(data) > 1:
  574. return error_message
  575. if len(data) > 1:
  576. with bot.db as db:
  577. query_record = db['queries'].find_one(id=data[1])
  578. if query_record is None or 'query' not in query_record:
  579. return error_message
  580. await send_csv_file(
  581. bot=bot,
  582. chat_id=update['from']['id'],
  583. query=query_record['query'],
  584. file_name=bot.get_message(
  585. 'admin', 'query_button', 'file_name',
  586. user_record=user_record, update=update
  587. ),
  588. update=update,
  589. user_record=user_record
  590. )
  591. if text:
  592. return dict(
  593. text=result,
  594. edit=dict(
  595. text=text,
  596. reply_markup=reply_markup
  597. )
  598. )
  599. return result
  600. async def _log_command(bot, update, user_record):
  601. if bot.log_file_path is None:
  602. return bot.get_message(
  603. 'admin', 'log_command', 'no_log',
  604. update=update, user_record=user_record
  605. )
  606. # Always send log file in private chat
  607. chat_id = update['from']['id']
  608. text = get_cleaned_text(update, bot, ['log'])
  609. reversed_ = 'r' not in text
  610. text = text.strip('r')
  611. if text.isnumeric():
  612. limit = int(text)
  613. else:
  614. limit = 100
  615. if limit is None:
  616. sent = await bot.send_document(
  617. chat_id=chat_id,
  618. document_path=bot.log_file_path,
  619. caption=bot.get_message(
  620. 'admin', 'log_command', 'here_is_log_file',
  621. update=update, user_record=user_record
  622. )
  623. )
  624. else:
  625. sent = await send_part_of_text_file(
  626. bot=bot,
  627. update=update,
  628. user_record=user_record,
  629. chat_id=chat_id,
  630. file_path=bot.log_file_path,
  631. file_name=bot.log_file_name,
  632. caption=bot.get_message(
  633. 'admin', 'log_command', (
  634. 'log_file_last_lines'
  635. if reversed_
  636. else 'log_file_first_lines'
  637. ),
  638. update=update, user_record=user_record,
  639. lines=limit
  640. ),
  641. reversed_=reversed_,
  642. limit=limit
  643. )
  644. if isinstance(sent, Exception):
  645. return bot.get_message(
  646. 'admin', 'log_command', 'sending_failure',
  647. update=update, user_record=user_record,
  648. e=sent
  649. )
  650. return
  651. async def _errors_command(bot, update, user_record):
  652. # Always send errors log file in private chat
  653. chat_id = update['from']['id']
  654. if bot.errors_file_path is None:
  655. return bot.get_message(
  656. 'admin', 'errors_command', 'no_log',
  657. update=update, user_record=user_record
  658. )
  659. await bot.sendChatAction(chat_id=chat_id, action='upload_document')
  660. try:
  661. # Check that error log is not empty
  662. with open(bot.errors_file_path, 'r') as errors_file:
  663. for _ in errors_file:
  664. break
  665. else:
  666. return bot.get_message(
  667. 'admin', 'errors_command', 'empty_log',
  668. update=update, user_record=user_record
  669. )
  670. # Send error log
  671. sent = await bot.send_document(
  672. # Always send log file in private chat
  673. chat_id=chat_id,
  674. document_path=bot.errors_file_path,
  675. caption=bot.get_message(
  676. 'admin', 'errors_command', 'here_is_log_file',
  677. update=update, user_record=user_record
  678. )
  679. )
  680. # Reset error log
  681. with open(bot.errors_file_path, 'w') as errors_file:
  682. errors_file.write('')
  683. except Exception as e:
  684. sent = e
  685. # Notify failure
  686. if isinstance(sent, Exception):
  687. return bot.get_message(
  688. 'admin', 'errors_command', 'sending_failure',
  689. update=update, user_record=user_record,
  690. e=sent
  691. )
  692. return
  693. async def _maintenance_command(bot, update, user_record):
  694. maintenance_message = get_cleaned_text(update, bot, ['maintenance'])
  695. if maintenance_message.startswith('{'):
  696. maintenance_message = json.loads(maintenance_message)
  697. maintenance_status = bot.change_maintenance_status(
  698. maintenance_message=maintenance_message
  699. )
  700. if maintenance_status:
  701. return bot.get_message(
  702. 'admin', 'maintenance_command', 'maintenance_started',
  703. update=update, user_record=user_record,
  704. message=bot.maintenance_message
  705. )
  706. return bot.get_message(
  707. 'admin', 'maintenance_command', 'maintenance_ended',
  708. update=update, user_record=user_record
  709. )
  710. def get_maintenance_exception_criterion(bot, allowed_command):
  711. """Get a criterion to allow a type of updates during maintenance.
  712. `bot` : davtelepot.bot.Bot() instance
  713. `allowed_command` : str (command to be allowed during maintenance)
  714. """
  715. def criterion(update):
  716. if 'message' not in update:
  717. return False
  718. update = update['message']
  719. text = get_cleaned_text(update, bot, [])
  720. if (
  721. 'from' not in update
  722. or 'id' not in update['from']
  723. ):
  724. return False
  725. with bot.db as db:
  726. user_record = db['users'].find_one(
  727. telegram_id=update['from']['id']
  728. )
  729. if not bot.authorization_function(
  730. update=update,
  731. user_record=user_record,
  732. authorization_level=2
  733. ):
  734. return False
  735. return text == allowed_command.strip('/')
  736. return criterion
  737. async def _version_command(bot, update, user_record):
  738. try:
  739. _subprocess = await asyncio.create_subprocess_exec(
  740. 'git', 'rev-parse', 'HEAD',
  741. stdout=asyncio.subprocess.PIPE,
  742. stderr=asyncio.subprocess.STDOUT
  743. )
  744. stdout, _ = await _subprocess.communicate()
  745. last_commit = stdout.decode().strip()
  746. davtelepot_version = davtelepot.__version__
  747. except Exception as e:
  748. return f"{e}"
  749. return bot.get_message(
  750. 'admin', 'version_command', 'result',
  751. last_commit=last_commit,
  752. davtelepot_version=davtelepot_version,
  753. update=update, user_record=user_record
  754. )
  755. def init(telegram_bot, talk_messages=None, admin_messages=None):
  756. """Assign parsers, commands, buttons and queries to given `bot`."""
  757. if talk_messages is None:
  758. talk_messages = messages.default_talk_messages
  759. telegram_bot.messages['talk'] = talk_messages
  760. if admin_messages is None:
  761. admin_messages = messages.default_admin_messages
  762. telegram_bot.messages['admin'] = admin_messages
  763. db = telegram_bot.db
  764. if 'talking_sessions' not in db.tables:
  765. db['talking_sessions'].insert(
  766. dict(
  767. user=0,
  768. admin=0,
  769. cancelled=1
  770. )
  771. )
  772. allowed_during_maintenance = [
  773. get_maintenance_exception_criterion(telegram_bot, command)
  774. for command in ['stop', 'restart', 'maintenance']
  775. ]
  776. @telegram_bot.additional_task(when='BEFORE')
  777. async def load_talking_sessions():
  778. sessions = []
  779. for session in db.query(
  780. """SELECT *
  781. FROM talking_sessions
  782. WHERE NOT cancelled
  783. """
  784. ):
  785. sessions.append(
  786. dict(
  787. other_user_record=db['users'].find_one(
  788. id=session['user']
  789. ),
  790. admin_record=db['users'].find_one(
  791. id=session['admin']
  792. ),
  793. )
  794. )
  795. for session in sessions:
  796. await start_session(
  797. bot=telegram_bot,
  798. other_user_record=session['other_user_record'],
  799. admin_record=session['admin_record']
  800. )
  801. @telegram_bot.command(command='/talk', aliases=[], show_in_keyboard=False,
  802. description=admin_messages['talk_command']['description'],
  803. authorization_level='admin')
  804. async def talk_command(bot, update, user_record):
  805. return await _talk_command(bot, update, user_record)
  806. @telegram_bot.button(prefix='talk:///', separator='|', authorization_level='admin')
  807. async def talk_button(bot, update, user_record, data):
  808. return await _talk_button(bot, update, user_record, data)
  809. @telegram_bot.command(command='/restart', aliases=[], show_in_keyboard=False,
  810. description=admin_messages['restart_command']['description'],
  811. authorization_level='admin')
  812. async def restart_command(bot, update, user_record):
  813. return await _restart_command(bot, update, user_record)
  814. @telegram_bot.additional_task('BEFORE')
  815. async def send_restart_messages():
  816. """Send restart messages at restart."""
  817. for restart_message in db['restart_messages'].find(sent=None):
  818. asyncio.ensure_future(
  819. telegram_bot.send_message(
  820. **{
  821. key: val
  822. for key, val in restart_message.items()
  823. if key in (
  824. 'chat_id',
  825. 'text',
  826. 'parse_mode',
  827. 'reply_to_message_id'
  828. )
  829. }
  830. )
  831. )
  832. db['restart_messages'].update(
  833. dict(
  834. sent=datetime.datetime.now(),
  835. id=restart_message['id']
  836. ),
  837. ['id'],
  838. ensure=True
  839. )
  840. return
  841. @telegram_bot.command(command='/stop', aliases=[], show_in_keyboard=False,
  842. description=admin_messages['stop_command']['description'],
  843. authorization_level='admin')
  844. async def stop_command(bot, update, user_record):
  845. return await _stop_command(bot, update, user_record)
  846. @telegram_bot.button(prefix='stop:///', separator='|',
  847. description=admin_messages['stop_command']['description'],
  848. authorization_level='admin')
  849. async def stop_button(bot, update, user_record, data):
  850. return await _stop_button(bot, update, user_record, data)
  851. @telegram_bot.command(command='/db', aliases=[], show_in_keyboard=False,
  852. description=admin_messages['db_command']['description'],
  853. authorization_level='admin')
  854. async def send_bot_database(bot, update, user_record):
  855. return await _send_bot_database(bot, update, user_record)
  856. @telegram_bot.command(command='/query', aliases=[], show_in_keyboard=False,
  857. description=admin_messages['query_command']['description'],
  858. authorization_level='admin')
  859. async def query_command(bot, update, user_record):
  860. return await _query_command(bot, update, user_record)
  861. @telegram_bot.command(command='/select', aliases=[], show_in_keyboard=False,
  862. description=admin_messages['select_command']['description'],
  863. authorization_level='admin')
  864. async def select_command(bot, update, user_record):
  865. return await _query_command(bot, update, user_record)
  866. @telegram_bot.button(prefix='db_query:///', separator='|',
  867. description=admin_messages['query_command']['description'],
  868. authorization_level='admin')
  869. async def query_button(bot, update, user_record, data):
  870. return await _query_button(bot, update, user_record, data)
  871. @telegram_bot.command(command='/log', aliases=[], show_in_keyboard=False,
  872. description=admin_messages['log_command']['description'],
  873. authorization_level='admin')
  874. async def log_command(bot, update, user_record):
  875. return await _log_command(bot, update, user_record)
  876. @telegram_bot.command(command='/errors', aliases=[], show_in_keyboard=False,
  877. description=admin_messages['errors_command']['description'],
  878. authorization_level='admin')
  879. async def errors_command(bot, update, user_record):
  880. return await _errors_command(bot, update, user_record)
  881. for exception in allowed_during_maintenance:
  882. telegram_bot.allow_during_maintenance(exception)
  883. @telegram_bot.command(command='/maintenance', aliases=[], show_in_keyboard=False,
  884. description=admin_messages['maintenance_command']['description'],
  885. authorization_level='admin')
  886. async def maintenance_command(bot, update, user_record):
  887. return await _maintenance_command(bot, update, user_record)
  888. @telegram_bot.command(command='/version',
  889. aliases=[],
  890. reply_keyboard_button=admin_messages['version_command']['reply_keyboard_button'],
  891. show_in_keyboard=False,
  892. description=admin_messages['version_command']['description'],
  893. help_section=admin_messages['version_command']['help_section'],
  894. authorization_level='admin',)
  895. async def version_command(bot, update, user_record):
  896. return await _version_command(bot=bot, update=update, user_record=user_record)