Queer European MD passionate about IT

admin_tools.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. """WARNING: this is only a legacy module.
  2. For newer versions use `administration_tools.py`.
  3. ----------
  4. Administration tools for telegram bots.
  5. Usage:
  6. ```
  7. import davtelepot
  8. my_bot = davtelepot.Bot.get('my_token', 'my_database.db')
  9. davtelepot.admin_tools.init(my_bot)
  10. ```
  11. """
  12. # Third party modules
  13. from davtelepot.utilities import (
  14. async_wrapper, Confirmator, get_cleaned_text, get_user, escape_html_chars,
  15. extract, line_drawing_unordered_list, make_button, make_inline_keyboard,
  16. remove_html_tags
  17. )
  18. TALK_MESSAGES = dict(
  19. admin_session_ended=dict(
  20. en=(
  21. 'Session with user {u} ended.'
  22. ),
  23. it=(
  24. 'Sessione terminata con l\'utente {u}.'
  25. ),
  26. ),
  27. admin_warning=dict(
  28. en=(
  29. 'You are now talking to {u}.\n'
  30. 'Until you end this session, your messages will be '
  31. 'forwarded to each other.'
  32. ),
  33. it=(
  34. 'Sei ora connesso con {u}.\n'
  35. 'Finché non chiuderai la connessione, i messaggi che scriverai '
  36. 'qui saranno inoltrati a {u}, e ti inoltrerò i suoi.'
  37. ),
  38. ),
  39. end_session=dict(
  40. en=(
  41. 'End session?'
  42. ),
  43. it=(
  44. 'Chiudere la sessione?'
  45. ),
  46. ),
  47. help_text=dict(
  48. en='Press the button to search for user.',
  49. it='Premi il pulsante per scegliere un utente.'
  50. ),
  51. search_button=dict(
  52. en="🔍 Search for user",
  53. it="🔍 Cerca utente",
  54. ),
  55. select_user=dict(
  56. en='Which user would you like to talk to?',
  57. it='Con quale utente vorresti parlare?'
  58. ),
  59. user_not_found=dict(
  60. en=(
  61. "Sory, but no user matches your query for\n"
  62. "<code>{q}</code>"
  63. ),
  64. it=(
  65. "Spiacente, ma nessun utente corrisponde alla ricerca per\n"
  66. "<code>{q}</code>"
  67. ),
  68. ),
  69. instructions=dict(
  70. en=(
  71. 'Write a part of name, surname or username of the user you want '
  72. 'to talk to.'
  73. ),
  74. it=(
  75. 'Scrivi una parte del nome, cognome o username dell\'utente con '
  76. 'cui vuoi parlare.'
  77. ),
  78. ),
  79. stop=dict(
  80. en=(
  81. 'End session'
  82. ),
  83. it=(
  84. 'Termina la sessione'
  85. ),
  86. ),
  87. user_session_ended=dict(
  88. en=(
  89. 'Session with admin {u} ended.'
  90. ),
  91. it=(
  92. 'Sessione terminata con l\'amministratore {u}.'
  93. ),
  94. ),
  95. user_warning=dict(
  96. en=(
  97. '{u}, admin of this bot, wants to talk to you.\n'
  98. 'Until this session is ended by {u}, your messages will be '
  99. 'forwarded to each other.'
  100. ),
  101. it=(
  102. '{u}, amministratore di questo bot, vuole parlare con te.\n'
  103. 'Finché non chiuderà la connessione, i messaggi che scriverai '
  104. 'qui saranno inoltrati a {u}, e ti inoltrerò i suoi.'
  105. ),
  106. ),
  107. # key=dict(
  108. # en='',
  109. # it='',
  110. # ),
  111. # key=dict(
  112. # en=(
  113. # ''
  114. # ),
  115. # it=(
  116. # ''
  117. # ),
  118. # ),
  119. )
  120. async def _forward_to(update, bot, sender, addressee, is_admin=False):
  121. if update['text'].lower() in ['stop'] and is_admin:
  122. with bot.db as db:
  123. admin_record = db['users'].find_one(
  124. telegram_id=sender
  125. )
  126. session_record = db['talking_sessions'].find_one(
  127. admin=admin_record['id'],
  128. cancelled=0
  129. )
  130. user_record = db['users'].find_one(
  131. id=session_record['user']
  132. )
  133. await end_session(
  134. bot=bot,
  135. user_record=user_record,
  136. admin_record=admin_record
  137. )
  138. else:
  139. bot.set_individual_text_message_handler(
  140. await async_wrapper(
  141. _forward_to,
  142. bot=bot,
  143. sender=sender,
  144. addressee=addressee,
  145. is_admin=is_admin
  146. ),
  147. sender
  148. )
  149. await bot.forward_message(
  150. chat_id=addressee,
  151. update=update
  152. )
  153. return
  154. def get_talk_panel(update, bot, text=''):
  155. """Return text and reply markup of talk panel.
  156. `text` may be:
  157. - `user_id` as string
  158. - `username` as string
  159. - `''` (empty string) for main menu (default)
  160. """
  161. users = []
  162. if len(text):
  163. with bot.db as db:
  164. if text.isnumeric():
  165. users = list(
  166. db['users'].find(id=int(text))
  167. )
  168. else:
  169. users = list(
  170. db.query(
  171. """SELECT *
  172. FROM users
  173. WHERE COALESCE(
  174. first_name || last_name || username,
  175. last_name || username,
  176. first_name || username,
  177. username,
  178. first_name || last_name,
  179. last_name,
  180. first_name
  181. ) LIKE '%{username}%'
  182. ORDER BY LOWER(
  183. COALESCE(
  184. first_name || last_name || username,
  185. last_name || username,
  186. first_name || username,
  187. username,
  188. first_name || last_name,
  189. last_name,
  190. first_name
  191. )
  192. )
  193. LIMIT 26
  194. """.format(
  195. username=text
  196. )
  197. )
  198. )
  199. if len(text) == 0:
  200. text = (
  201. bot.get_message(
  202. 'talk',
  203. 'help_text',
  204. update=update,
  205. q=escape_html_chars(
  206. remove_html_tags(text)
  207. )
  208. )
  209. )
  210. reply_markup = make_inline_keyboard(
  211. [
  212. make_button(
  213. bot.get_message(
  214. 'talk', 'search_button',
  215. update=update
  216. ),
  217. prefix='talk:///',
  218. data=['search']
  219. )
  220. ],
  221. 1
  222. )
  223. elif len(users) == 0:
  224. text = (
  225. bot.get_message(
  226. 'talk',
  227. 'user_not_found',
  228. update=update,
  229. q=escape_html_chars(
  230. remove_html_tags(text)
  231. )
  232. )
  233. )
  234. reply_markup = make_inline_keyboard(
  235. [
  236. make_button(
  237. bot.get_message(
  238. 'talk', 'search_button',
  239. update=update
  240. ),
  241. prefix='talk:///',
  242. data=['search']
  243. )
  244. ],
  245. 1
  246. )
  247. else:
  248. text = "{header}\n\n{u}{etc}".format(
  249. header=bot.get_message(
  250. 'talk', 'select_user',
  251. update=update
  252. ),
  253. u=line_drawing_unordered_list(
  254. [
  255. get_user(user)
  256. for user in users[:25]
  257. ]
  258. ),
  259. etc=(
  260. '\n\n[...]'
  261. if len(users) > 25
  262. else ''
  263. )
  264. )
  265. reply_markup = make_inline_keyboard(
  266. [
  267. make_button(
  268. '👤 {u}'.format(
  269. u=get_user(
  270. {
  271. key: val
  272. for key, val in user.items()
  273. if key in (
  274. 'first_name',
  275. 'last_name',
  276. 'username'
  277. )
  278. }
  279. )
  280. ),
  281. prefix='talk:///',
  282. data=[
  283. 'select',
  284. user['id']
  285. ]
  286. )
  287. for user in users[:25]
  288. ],
  289. 2
  290. )
  291. return text, reply_markup
  292. async def _talk_command(update, bot):
  293. text = get_cleaned_text(
  294. update,
  295. bot,
  296. ['talk']
  297. )
  298. text, reply_markup = get_talk_panel(update, bot, text)
  299. return dict(
  300. text=text,
  301. parse_mode='HTML',
  302. reply_markup=reply_markup,
  303. )
  304. async def start_session(bot, user_record, admin_record):
  305. """Start talking session between user and admin.
  306. Register session in database, so it gets loaded before message_loop starts.
  307. Send a notification both to admin and user, set custom parsers and return.
  308. """
  309. with bot.db as db:
  310. db['talking_sessions'].insert(
  311. dict(
  312. user=user_record['id'],
  313. admin=admin_record['id'],
  314. cancelled=0
  315. )
  316. )
  317. await bot.send_message(
  318. chat_id=user_record['telegram_id'],
  319. text=bot.get_message(
  320. 'talk', 'user_warning',
  321. user_record=user_record,
  322. u=get_user(admin_record)
  323. )
  324. )
  325. await bot.send_message(
  326. chat_id=admin_record['telegram_id'],
  327. text=bot.get_message(
  328. 'talk', 'admin_warning',
  329. user_record=admin_record,
  330. u=get_user(user_record)
  331. ),
  332. reply_markup=make_inline_keyboard(
  333. [
  334. make_button(
  335. bot.get_message(
  336. 'talk', 'stop',
  337. user_record=admin_record
  338. ),
  339. prefix='talk:///',
  340. data=['stop', user_record['id']]
  341. )
  342. ]
  343. )
  344. )
  345. bot.set_individual_text_message_handler(
  346. await async_wrapper(
  347. _forward_to,
  348. bot=bot,
  349. sender=user_record['telegram_id'],
  350. addressee=admin_record['telegram_id'],
  351. is_admin=False
  352. ),
  353. user_record['telegram_id']
  354. )
  355. bot.set_individual_text_message_handler(
  356. await async_wrapper(
  357. _forward_to,
  358. bot=bot,
  359. sender=admin_record['telegram_id'],
  360. addressee=user_record['telegram_id'],
  361. is_admin=True
  362. ),
  363. admin_record['telegram_id']
  364. )
  365. return
  366. async def end_session(bot, user_record, admin_record):
  367. """End talking session between user and admin.
  368. Cancel session in database, so it will not be loaded anymore.
  369. Send a notification both to admin and user, clear custom parsers
  370. and return.
  371. """
  372. with bot.db as db:
  373. db['talking_sessions'].update(
  374. dict(
  375. admin=admin_record['id'],
  376. cancelled=1
  377. ),
  378. ['admin']
  379. )
  380. await bot.send_message(
  381. chat_id=user_record['telegram_id'],
  382. text=bot.get_message(
  383. 'talk', 'user_session_ended',
  384. user_record=user_record,
  385. u=get_user(admin_record)
  386. )
  387. )
  388. await bot.send_message(
  389. chat_id=admin_record['telegram_id'],
  390. text=bot.get_message(
  391. 'talk', 'admin_session_ended',
  392. user_record=admin_record,
  393. u=get_user(user_record)
  394. ),
  395. )
  396. for record in (admin_record, user_record, ):
  397. bot.remove_individual_text_message_handler(record['telegram_id'])
  398. return
  399. async def _talk_button(update, bot):
  400. telegram_id = update['from']['id']
  401. command, *arguments = extract(update['data'], '///').split('|')
  402. result, text, reply_markup = '', '', None
  403. if command == 'search':
  404. bot.set_individual_text_message_handler(
  405. await async_wrapper(
  406. _talk_command,
  407. bot=bot
  408. ),
  409. update
  410. )
  411. text = bot.get_message(
  412. 'talk', 'instructions',
  413. update=update
  414. )
  415. reply_markup = None
  416. elif command == 'select':
  417. if (
  418. len(arguments) < 1
  419. or not arguments[0].isnumeric()
  420. ):
  421. result = "Errore!"
  422. else:
  423. with bot.db as db:
  424. user_record = db['users'].find_one(
  425. id=int(arguments[0])
  426. )
  427. admin_record = db['users'].find_one(
  428. telegram_id=telegram_id
  429. )
  430. await start_session(
  431. bot,
  432. user_record=user_record,
  433. admin_record=admin_record
  434. )
  435. elif command == 'stop':
  436. if (
  437. len(arguments) < 1
  438. or not arguments[0].isnumeric()
  439. ):
  440. result = "Errore!"
  441. elif not Confirmator.get('stop_bots').confirm(telegram_id):
  442. result = bot.get_message(
  443. 'talk', 'end_session',
  444. update=update,
  445. )
  446. else:
  447. with bot.db as db:
  448. user_record = db['users'].find_one(
  449. id=int(arguments[0])
  450. )
  451. admin_record = db['users'].find_one(
  452. telegram_id=telegram_id
  453. )
  454. await end_session(
  455. bot,
  456. user_record=user_record,
  457. admin_record=admin_record
  458. )
  459. text = "Session ended."
  460. reply_markup = None
  461. if text:
  462. return dict(
  463. text=result,
  464. edit=dict(
  465. text=text,
  466. parse_mode='HTML',
  467. reply_markup=reply_markup,
  468. disable_web_page_preview=True
  469. )
  470. )
  471. return result
  472. def init(bot):
  473. """Assign parsers, commands, buttons and queries to given `bot`."""
  474. if not hasattr(bot, 'messages'):
  475. bot.messages = dict()
  476. bot.messages['talk'] = TALK_MESSAGES
  477. with bot.db as db:
  478. if 'talking_sessions' not in db.tables:
  479. db['talking_sessions'].insert(
  480. dict(
  481. user=0,
  482. admin=0,
  483. cancelled=1
  484. )
  485. )
  486. @bot.additional_task(when='BEFORE')
  487. async def load_talking_sessions():
  488. sessions = []
  489. with bot.db as db:
  490. for session in db.query(
  491. """SELECT *
  492. FROM talking_sessions
  493. WHERE NOT cancelled
  494. """
  495. ):
  496. sessions.append(
  497. dict(
  498. user_record=db['users'].find_one(
  499. id=session['user']
  500. ),
  501. admin_record=db['users'].find_one(
  502. id=session['admin']
  503. ),
  504. )
  505. )
  506. for session in sessions:
  507. await start_session(
  508. bot=bot,
  509. user_record=session['user_record'],
  510. admin_record=session['admin_record']
  511. )
  512. @bot.command(command='/talk', aliases=[], show_in_keyboard=False,
  513. descr="Choose a user and forward messages to each other.",
  514. auth='admin')
  515. async def talk_command(update):
  516. return await _talk_command(update, bot)
  517. @bot.button(data='talk:///', auth='admin')
  518. async def talk_button(update):
  519. return await _talk_button(update, bot)
  520. return