utils.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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 == 0 or 5 <= number <= 20:
  91. return WordKind.SECOND
  92. last_digit = number % 10
  93. if last_digit == 0:
  94. return WordKind.SECOND
  95. if last_digit == 1:
  96. return WordKind.FIRST
  97. if 5 <= last_digit <= 9:
  98. return WordKind.SECOND
  99. return WordKind.THIRD
  100. def get_word_for(word, number):
  101. return f'{number} {WORDS_TABLE[word][get_word_kind(number)]}'
  102. def calculate_age(date):
  103. now = datetime.now().date()
  104. birthday_date = date.replace(year=now.year)
  105. if now > birthday_date:
  106. birthday_date = date.replace(year=now.year + 1)
  107. delta = birthday_date - now
  108. age = (birthday_date - date).days // 365
  109. age_now = age - 1
  110. return Age(
  111. days_until=delta.days,
  112. age=age,
  113. age_now=age_now,
  114. date_string=date.strftime('%d.%m.%Y')
  115. )
  116. DELIMITERS = {
  117. MessageEntityBold: '**',
  118. MessageEntityItalic: '__',
  119. MessageEntityStrike: '~~',
  120. MessageEntityCode: '`',
  121. MessageEntityPre: '`',
  122. MessageEntityUnderline: '\ue000',
  123. MessageEntitySpoiler: '\ue001',
  124. MessageEntityUrl: '\ue002',
  125. MessageEntityMention: '\ue003',
  126. MessageEntityBotCommand: '\ue003'
  127. }
  128. class LookupTable:
  129. def __init__(self):
  130. self._start = {}
  131. self._end = {}
  132. def insert(self, entity):
  133. delimiter = DELIMITERS.get(type(entity))
  134. if not delimiter:
  135. return
  136. start = entity.offset
  137. end = entity.offset + entity.length
  138. if start in self._start:
  139. self._start[start].append(delimiter)
  140. else:
  141. self._start[start] = [delimiter]
  142. if end in self._end:
  143. self._end[end].insert(0, delimiter)
  144. else:
  145. self._end[end] = [delimiter]
  146. def _lookup(self, position):
  147. if position in self._start:
  148. return ''.join(self._start[position])
  149. elif position in self._end:
  150. return ''.join(self._end[position])
  151. def process(self, text):
  152. text = list(text) + ['']
  153. result = ''
  154. for position, character in zip(range(len(text)), text):
  155. delimiter = self._lookup(position)
  156. if delimiter:
  157. result += delimiter
  158. result += character
  159. return result
  160. def unparse(text, entities):
  161. table = LookupTable()
  162. for entity in entities:
  163. table.insert(entity)
  164. return table.process(text)