utils.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from uuid import uuid4
  2. from enum import IntEnum
  3. from datetime import datetime
  4. from ipaddress import ip_address
  5. from collections import namedtuple
  6. from aiofiles.os import mkdir
  7. from aiofiles.os import path
  8. from telethon.utils import get_display_name
  9. from telethon.tl.types import (
  10. MessageEntityBold,
  11. MessageEntityItalic,
  12. MessageEntityStrike,
  13. MessageEntityCode,
  14. MessageEntityPre,
  15. MessageEntitySpoiler,
  16. MessageEntityUnderline,
  17. MessageEntityUrl,
  18. MessageEntityMention,
  19. MessageEntityBotCommand
  20. )
  21. Command = namedtuple(
  22. 'Command',
  23. 'name argc args args_string'
  24. )
  25. Age = namedtuple(
  26. 'Age',
  27. 'days_until age age_now date_string'
  28. )
  29. class Kind(IntEnum):
  30. CANNOT_APPLY_TO_SELF = 0
  31. CAN_APPLY_TO_SELF = 1
  32. NO_TARGET = 2
  33. NO_TARGET_MAYBE = 3
  34. class WordKind(IntEnum):
  35. FIRST = 0 # «год» / «день».
  36. SECOND = 1 # «лет» / «дней».
  37. THIRD = 2 # «года» / «дня».
  38. WORDS_TABLE = {
  39. 'год': ('год', 'лет', 'года'),
  40. 'день': ('день', 'дней', 'дня')
  41. }
  42. def parse_command(text):
  43. text = text.strip()
  44. if not text.startswith('/'):
  45. raise ValueError
  46. text = text.split(' ')
  47. if len(text) < 1:
  48. raise ValueError
  49. command = text[0][1:].lower()
  50. if '@' in command:
  51. command = command.split('@')
  52. command = command[0]
  53. args = text[1:]
  54. argc = len(args)
  55. args_string = ' '.join(args)
  56. return Command(
  57. name=command,
  58. argc=argc,
  59. args=args,
  60. args_string=args_string
  61. )
  62. def get_user_name(user):
  63. full_name = get_display_name(user)
  64. if not full_name:
  65. full_name = user.username
  66. if not full_name:
  67. full_name = '?'
  68. return full_name
  69. def get_link_to_user(user):
  70. full_name = get_user_name(user)
  71. if user.username:
  72. return f'[{full_name}](@{user.username})'
  73. return f'[{full_name}](tg://user?id={user.id})'
  74. def is_valid_name(name):
  75. return name.isidentifier()
  76. def make_temporary_filename(ext):
  77. uid = uuid4().hex
  78. return f'tmp_{uid}.{ext}'
  79. CACHE_DIR = './cache'
  80. async def make_cache_filename(id, ext):
  81. if not await path.isdir(CACHE_DIR):
  82. await mkdir(CACHE_DIR)
  83. return f'{CACHE_DIR}/{id}.{ext}'
  84. def parse_kind(kind):
  85. kind = int(kind)
  86. if kind < 0 or kind > 3:
  87. raise ValueError
  88. return kind
  89. # Bruh?
  90. def get_word_kind(number):
  91. if number >= 100:
  92. number %= 100
  93. if number == 0 or 5 <= number <= 20:
  94. return WordKind.SECOND
  95. last_digit = number % 10
  96. if last_digit == 0:
  97. return WordKind.SECOND
  98. if last_digit == 1:
  99. return WordKind.FIRST
  100. if 5 <= last_digit <= 9:
  101. return WordKind.SECOND
  102. return WordKind.THIRD
  103. def get_word_for(word, number):
  104. return f'{number} {WORDS_TABLE[word][get_word_kind(number)]}'
  105. def calculate_age(date):
  106. now = datetime.now().date()
  107. birthday_date = date.replace(year=now.year)
  108. if now > birthday_date:
  109. birthday_date = date.replace(year=now.year + 1)
  110. delta = birthday_date - now
  111. age = (birthday_date - date).days // 365
  112. age_now = age - 1
  113. return Age(
  114. days_until=delta.days,
  115. age=age,
  116. age_now=age_now,
  117. date_string=date.strftime('%d.%m.%Y')
  118. )
  119. DELIMITERS = {
  120. MessageEntityBold: '**',
  121. MessageEntityItalic: '__',
  122. MessageEntityStrike: '~~',
  123. MessageEntityCode: '`',
  124. MessageEntityPre: '`',
  125. MessageEntityUnderline: '\ue000',
  126. MessageEntitySpoiler: '\ue001',
  127. MessageEntityUrl: '\ue002',
  128. MessageEntityMention: '\ue003',
  129. MessageEntityBotCommand: '\ue003'
  130. }
  131. class LookupTable:
  132. def __init__(self):
  133. self._start = {}
  134. self._end = {}
  135. def insert(self, entity):
  136. delimiter = DELIMITERS.get(type(entity))
  137. if not delimiter:
  138. return
  139. start = entity.offset
  140. end = entity.offset + entity.length
  141. if start in self._start:
  142. self._start[start].append(delimiter)
  143. else:
  144. self._start[start] = [delimiter]
  145. if end in self._end:
  146. self._end[end].insert(0, delimiter)
  147. else:
  148. self._end[end] = [delimiter]
  149. def _lookup(self, position):
  150. if position in self._start:
  151. return ''.join(self._start[position])
  152. elif position in self._end:
  153. return ''.join(self._end[position])
  154. def process(self, text):
  155. text = list(text) + ['']
  156. result = ''
  157. for position, character in zip(range(len(text)), text):
  158. delimiter = self._lookup(position)
  159. if delimiter:
  160. result += delimiter
  161. result += character
  162. return result
  163. def unparse(text, entities):
  164. table = LookupTable()
  165. for entity in entities:
  166. table.insert(entity)
  167. return table.process(text)
  168. def is_valid_ip(ip):
  169. try:
  170. ip_address(ip)
  171. except:
  172. return False
  173. return True