commands.py 16 KB

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