utils.py 4.6 KB

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