commands.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import re
  2. from io import BytesIO
  3. from sys import executable
  4. from struct import pack
  5. from asyncio import create_subprocess_shell
  6. from asyncio.subprocess import PIPE
  7. from datetime import datetime
  8. from ujson import dumps
  9. from tortoise.exceptions import IntegrityError
  10. from telethon.errors import MessageEmptyError
  11. from telethon.utils import get_display_name, get_peer_id
  12. from aiofiles.os import (
  13. remove,
  14. path
  15. )
  16. from aiohttp import ClientSession
  17. from emoji import is_emoji
  18. from cairosvg import svg2png
  19. from actions import (
  20. find_action,
  21. create_action,
  22. delete_action,
  23. add_gif,
  24. add_sticker,
  25. add_admin,
  26. delete_admin,
  27. add_or_update_birthday,
  28. get_birthdays,
  29. add_server,
  30. delete_server,
  31. add_allowed,
  32. delete_allowed,
  33. list_servers,
  34. get_server_ip
  35. )
  36. from utils import (
  37. make_temporary_filename,
  38. make_cache_filename,
  39. parse_kind,
  40. get_user_name,
  41. calculate_age,
  42. unparse,
  43. remove_ansi_escapes
  44. )
  45. class Handler:
  46. def __init__(self, handler, is_restricted=False, is_public=False):
  47. self.handler = handler
  48. self.is_restricted = is_restricted
  49. self.is_public = is_public
  50. async def newadmin_handler(bot, event, command):
  51. if command.argc < 1:
  52. await event.reply('Пожалуйста, укажите пользователя!')
  53. return
  54. try:
  55. target = await bot.get_entity(command.args[0])
  56. except ValueError:
  57. await event.reply('Недопустимое имя пользователя!')
  58. return
  59. try:
  60. await add_admin(target)
  61. except IntegrityError:
  62. await event.reply('Данный пользователь уже является администратором!')
  63. return
  64. await event.reply('Готово!~~')
  65. async def deladmin_handler(bot, event, command):
  66. if command.argc < 1:
  67. await event.reply('Пожалуйста, укажите пользователя!')
  68. return
  69. try:
  70. target = await bot.get_entity(command.args[0])
  71. except ValueError:
  72. await event.reply('Недопустимое имя пользователя!')
  73. return
  74. try:
  75. await delete_admin(target)
  76. except IndexError:
  77. await event.reply('Данный пользователь не является администратором!')
  78. return
  79. await event.reply('Готово!~~')
  80. async def newaction_handler(bot, event, command):
  81. if command.argc < 3:
  82. await event.reply('Пожалуйста, укажите тип, имя и шаблон действия!')
  83. return
  84. try:
  85. kind = parse_kind(command.args[0])
  86. except ValueError:
  87. await event.reply('Неверный тип действия!!!')
  88. return
  89. try:
  90. await create_action(command.args[1], ' '.join(command.args[2:]), kind)
  91. except SyntaxError:
  92. await event.reply('Недопустимое имя действия!!!')
  93. return
  94. except IntegrityError:
  95. await event.reply('Действие с таким названием уже существует!')
  96. return
  97. await event.reply('Действие создано!')
  98. async def delaction_handler(bot, event, command):
  99. if command.argc < 1:
  100. await event.reply('Пожалуйста, укажите имя действия!')
  101. return
  102. try:
  103. await delete_action(command.args[0])
  104. except SyntaxError:
  105. await event.reply('Недопустимое имя действия!!!')
  106. return
  107. except NameError:
  108. await event.reply('Действия с таким названием не существует!')
  109. return
  110. await event.reply('Действие удалено!')
  111. async def addgif_handler(bot, event, command):
  112. if command.argc < 1:
  113. await event.reply('Пожалуйста, укажите имя действия!')
  114. return
  115. gif = await event.get_reply_message()
  116. if not gif or not gif.gif:
  117. await event.reply('Пожалуйста, добавьте GIF!')
  118. return
  119. try:
  120. action = await find_action(command.args[0])
  121. await add_gif(action, gif.file.id)
  122. except SyntaxError:
  123. await event.reply('Недопустимое имя действия!!!')
  124. return
  125. except NameError:
  126. await event.reply('Нет такого действия!')
  127. return
  128. await event.reply('Готово!~~')
  129. async def addserver_handler(bot, event, command):
  130. if command.argc < 2:
  131. await event.reply('Пожалуйста, укажите имя и адрес сервера!')
  132. return
  133. try:
  134. await add_server(command.args[0], command.args[1])
  135. except SyntaxError:
  136. await event.reply('Недопустимое имя сервера!!')
  137. return
  138. except ValueError:
  139. await event.reply('Пожалуйста, введите корректный IPv4-/IPv6-адрес!')
  140. return
  141. except IntegrityError:
  142. await event.reply('Данный сервер уже был добавлен ранее!')
  143. return
  144. await event.reply('Готово!~~')
  145. async def delserver_handler(bot, event, command):
  146. if command.argc < 1:
  147. await event.reply('Пожалуйста, укажите имя сервера!')
  148. return
  149. try:
  150. await delete_server(command.args[0])
  151. except SyntaxError:
  152. await event.reply('Недопустимое имя сервера!!')
  153. return
  154. except IndexError:
  155. await event.reply('Сервер с таким именем не найден!')
  156. return
  157. await event.reply('Готово!~~')
  158. async def allow_handler(bot, event, command):
  159. try:
  160. await add_allowed(get_peer_id(event.peer_id))
  161. except IntegrityError:
  162. await event.reply('Данный чат уже добавлен в список разрешённых!')
  163. return
  164. await event.reply('Готово!~~')
  165. async def disallow_handler(bot, event, command):
  166. try:
  167. await delete_allowed(get_peer_id(event.peer_id))
  168. except IndexError:
  169. await event.reply('Данный чат не найден в списке разрешённых!!')
  170. return
  171. await event.reply('Готово!~~')
  172. # Very, very, VERY evil code...
  173. async def make_message_shot(bot, message):
  174. if message.sender is None:
  175. sender_id = message.peer_id.channel_id
  176. full_name = await bot.get_entity(sender_id)
  177. full_name = full_name.title
  178. else:
  179. sender_id = message.sender.id
  180. full_name = get_display_name(message.sender)
  181. output_path = make_temporary_filename('png')
  182. avatar_path = await make_cache_filename(sender_id, 'png')
  183. if not await path.isfile(avatar_path):
  184. await bot.download_profile_photo(sender_id, file=avatar_path, download_big=True)
  185. # TO-DO: make it better.
  186. mproc = await create_subprocess_shell(
  187. f'mogrify -format png {avatar_path}'
  188. )
  189. await mproc.communicate()
  190. if not await path.isfile(avatar_path):
  191. avatar_path = './resources/placeholder.png'
  192. data = bytes()
  193. data += pack('I', len(output_path))
  194. data += bytes(output_path, encoding='ASCII')
  195. data += pack('I', len(avatar_path))
  196. data += bytes(avatar_path, encoding='ASCII')
  197. username = bytes(full_name, encoding='UTF-8')
  198. data += pack('I', len(username))
  199. data += username
  200. data += pack('I', sender_id % 7)
  201. text = bytes(unparse(message.raw_text, [entity for entity, _ in message.get_entities_text()]), encoding='UTF-8')
  202. data += pack('I', len(text))
  203. data += text
  204. proc = await create_subprocess_shell(
  205. './makeshot/makeshot',
  206. stdin=PIPE
  207. )
  208. await proc.communicate(input=data)
  209. pproc = await create_subprocess_shell(
  210. f'pngcrush -reduce -ow {output_path}'
  211. )
  212. await pproc.communicate()
  213. return output_path
  214. async def save_handler(bot, event, command):
  215. message = await event.get_reply_message()
  216. if not message:
  217. await event.reply('Пожалуйста, укажите сообщение для сохранения!')
  218. return
  219. emoji = '⚡'
  220. if command.argc >= 1:
  221. emoji = command.args[0]
  222. if len(emoji) not in range(1, 6)\
  223. or not all(map(is_emoji, emoji)):
  224. await event.reply('Указан некорректный эмодзи!!!')
  225. return
  226. path = await make_message_shot(bot, message)
  227. try:
  228. file = await add_sticker(bot, path, emoji)
  229. await bot.send_file(
  230. message.peer_id,
  231. file=file,
  232. reply_to=message
  233. )
  234. finally:
  235. await remove(path)
  236. async def bday_handler(bot, event, command):
  237. if command.argc >= 1:
  238. try:
  239. date = datetime.strptime(' '.join(command.args), '%d.%m.%Y')
  240. except ValueError:
  241. await event.reply('Дата не может быть распознана. Пожалуйста, введите свой день рождения в следующем формате: 01.01.1970 (день, месяц, год).')
  242. return
  243. if date >= datetime.now():
  244. await event.reply('День рождения не может быть в будущем...')
  245. return
  246. if await add_or_update_birthday(get_peer_id(event.peer_id), event.sender, date):
  247. await event.reply('День рождения успешно добавлен!!!')
  248. else:
  249. await event.reply('День рождения успешно обновлён!!!')
  250. return
  251. birthdays = await get_birthdays(get_peer_id(event.peer_id))
  252. if not birthdays:
  253. await event.reply('Пока пусто...')
  254. return
  255. birthdays = map(lambda birthday: (birthday.user_id, calculate_age(birthday.date)), birthdays)
  256. birthdays = sorted(birthdays, key=lambda birthday: birthday[1].days_until)
  257. birthdays_list = ''
  258. for user_id, age in birthdays:
  259. try:
  260. entity = await bot.get_entity(user_id)
  261. except ValueError:
  262. await bot.get_participants(await event.get_chat())
  263. try:
  264. entity = await bot.get_entity(user_id)
  265. except ValueError:
  266. continue
  267. birthdays_list += f'{get_user_name(entity)} ❯ {age.age_now} ➔ {age.age} ❯ {age.date_string} ❯ {age.days_until}\n'
  268. await event.reply(f'Дни рождения:\n\n{birthdays_list}')
  269. async def vpn_handler(bot, event, command):
  270. if command.argc < 1:
  271. await event.reply(f'Пожалуйста, укажите имя сервера! Доступные сервера: {await list_servers()}.')
  272. return
  273. try:
  274. ip = await get_server_ip(command.args[0])
  275. except SyntaxError:
  276. await event.reply('Недопустимое имя сервера!!')
  277. return
  278. except IndexError:
  279. await event.reply('Сервер с таким именем не найден!')
  280. return
  281. if event.sender is None:
  282. sender_id = event.peer_id.channel_id
  283. else:
  284. sender_id = event.sender.id
  285. async with ClientSession() as session:
  286. try:
  287. async with session.post(
  288. f'http://{ip}:9217/api/obtain',
  289. data={'user_id': sender_id}
  290. ) as resp:
  291. data = await resp.json()
  292. profile = data['profile']
  293. except:
  294. await event.reply('Произошла ошибка при попытке обращения к API сервера… :(')
  295. return
  296. try:
  297. await bot.send_message(
  298. await bot.get_entity(sender_id),
  299. f'Ваш файл конфигурации WireGuard (сервер {command.args[0]}):\n\n```{profile}```'
  300. )
  301. except:
  302. await event.reply('Произошла ошибка при отправке файла конфигурации в Ваши личные сообщения… :с')
  303. return
  304. await event.reply('Готово!!~~ Файл конфигурации WireGuard отправлен в Ваши личные сообщения!')
  305. async def run_handler(bot, event, command):
  306. if command.argc < 1:
  307. async with ClientSession() as session:
  308. try:
  309. async with session.get(
  310. 'https://farlands.txlyre.website/langs'
  311. ) as resp:
  312. text = await resp.read()
  313. text = text.decode('UTF-8')
  314. await event.reply(f'Доступные языки:\n`{text}`')
  315. except:
  316. await event.reply('Произошла ошибка при попытке обращения к API… :(')
  317. return
  318. match = re.match(r'^(\w+)(?:\s|\n)((?:\n|.)*)$', command.args_string)
  319. if not match:
  320. await event.reply('Пожалуйста, не оставляйте ввод пустым!')
  321. return
  322. language_name, text = match.groups()
  323. if text.startswith('```') and text.endswith('```'):
  324. text = text[3:-3]
  325. text = text.replace('\xA0', ' ') # i hate telegram
  326. async with ClientSession() as session:
  327. try:
  328. async with session.post(
  329. f'https://farlands.txlyre.website/run/{language_name}',
  330. data=text
  331. ) as resp:
  332. if resp.status in (404, 500):
  333. info = await resp.json()
  334. await event.reply(f'Произошла ошибка при попытке обращения к API… :(\nОтвет API: {info["detail"]}')
  335. return
  336. elif resp.status != 200:
  337. await event.reply('Сервер API временно недоступен. Пожалуйста, попробуйте ещё раз чуть позже.')
  338. return
  339. text = await resp.read()
  340. text = text.decode('UTF-8')[:4096]
  341. except:
  342. await event.reply('Произошла ошибка при попытке обращения к API… :(')
  343. return
  344. text = remove_ansi_escapes(text).strip()
  345. text = filter(lambda c: (c in ' \t\n' or ord(c) >= 32) and ord(c) not in range(128, 159), text)
  346. text = ''.join(text)
  347. text = text.replace('`', '')
  348. if not text:
  349. await event.reply('<пусто>')
  350. return
  351. try:
  352. try:
  353. await event.reply(f'```\n{text}```')
  354. except ValueError:
  355. await event.reply(text, parse_mode=None)
  356. except MessageEmptyError:
  357. await event.reply('<Telegram не смог декодировать текст сообщения>')
  358. async def sylvy_handler(bot, event, command):
  359. if command.argc < 1:
  360. await event.reply('Пожалуйста, не оставляйте ввод пустым!')
  361. return
  362. async with ClientSession() as session:
  363. try:
  364. async with session.post(
  365. f'https://sylvy-engine.txlyre.website/api/compute',
  366. json={'program': command.args_string, 'stringify': True}
  367. ) as resp:
  368. data = await resp.json()
  369. if data['status'] != 'ok':
  370. await event.reply(f'Ошибка API Sylvy: {data["data"]["message"]}')
  371. return
  372. result = data['data']['result']
  373. except:
  374. await event.reply('Произошла ошибка при попытке обращения к API Sylvy… :(')
  375. return
  376. if result['status'] == 'TIMEOUT':
  377. await event.reply(f'Максимальное время исполнения истекло (более тридцати секунд)!!!')
  378. return
  379. elif result['status'] != 'SUCCESS':
  380. await event.reply(f'Ошибка исполнения!!!\n\n```{result["stdout"]}```')
  381. return
  382. if result['plots']:
  383. plots = []
  384. for plot in result['plots']:
  385. buffer = BytesIO()
  386. svg2png(
  387. bytestring=plot,
  388. write_to=buffer
  389. )
  390. plots.append(buffer.getvalue())
  391. await bot.send_file(
  392. event.peer_id,
  393. file=plots,
  394. reply_to=event
  395. )
  396. return
  397. text = ''
  398. if result['stdout']:
  399. text += result['stdout']
  400. text += '\n'
  401. text += result['output']
  402. text = map(lambda line: '| ' + line, text.split('\n'))
  403. text = '\n'.join(text)
  404. await event.reply(f'```\n{text}```')
  405. COMMANDS = {
  406. 'newadmin': Handler(newadmin_handler, is_restricted=True),
  407. 'deladmin': Handler(deladmin_handler, is_restricted=True),
  408. 'newaction': Handler(newaction_handler, is_restricted=True),
  409. 'delaction': Handler(delaction_handler, is_restricted=True),
  410. 'addgif': Handler(addgif_handler, is_restricted=True),
  411. 'addserver': Handler(addserver_handler, is_restricted=True),
  412. 'delserver': Handler(delserver_handler, is_restricted=True),
  413. 'allow': Handler(allow_handler, is_restricted=True),
  414. 'disallow': Handler(disallow_handler, is_restricted=True),
  415. 'save': Handler(save_handler),
  416. 'bday': Handler(bday_handler),
  417. 'vpn': Handler(vpn_handler),
  418. 'sylvy': Handler(sylvy_handler, is_public=True),
  419. 'run': Handler(run_handler, is_public=True),
  420. }