validators.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. # ©️ Dan Gazizullin, 2021-2023
  2. # This file is a part of Hikka Userbot
  3. # 🌐 https://github.com/hikariatama/Hikka
  4. # You can redistribute it and/or modify it under the terms of the GNU AGPLv3
  5. # 🔑 https://www.gnu.org/licenses/agpl-3.0.html
  6. import functools
  7. import re
  8. import typing
  9. import grapheme
  10. from emoji import get_emoji_unicode_dict
  11. from . import utils
  12. from .translations import SUPPORTED_LANGUAGES, translator
  13. ConfigAllowedTypes = typing.Union[tuple, list, str, int, bool, None]
  14. ALLOWED_EMOJIS = set(get_emoji_unicode_dict("en").values())
  15. class ValidationError(Exception):
  16. """
  17. Is being raised when config value passed can't be converted properly
  18. Must be raised with string, describing why value is incorrect
  19. It will be shown in .config, if user tries to set incorrect value
  20. """
  21. class Validator:
  22. """
  23. Class used as validator of config value
  24. :param validator: Sync function, which raises `ValidationError` if passed
  25. value is incorrect (with explanation) and returns converted
  26. value if it is semantically correct.
  27. ⚠️ If validator returns `None`, value will always be set to `None`
  28. :param doc: Docstrings for this validator as string, or dict in format:
  29. {
  30. "en": "docstring",
  31. "ru": "докстрингом",
  32. "fr": "chaîne de documentation",
  33. "it": "docstring",
  34. "de": "Dokumentation",
  35. "tr": "dökümantasyon",
  36. "uz": "hujjat",
  37. "es": "documentación",
  38. "kk": "құжат",
  39. }
  40. Use instrumental case with lowercase
  41. :param _internal_id: Do not pass anything here, or things will break
  42. """
  43. def __init__(
  44. self,
  45. validator: callable,
  46. doc: typing.Optional[typing.Union[str, dict]] = None,
  47. _internal_id: typing.Optional[int] = None,
  48. ):
  49. self.validate = validator
  50. if isinstance(doc, str):
  51. doc = {lang: doc for lang in SUPPORTED_LANGUAGES}
  52. self.doc = doc
  53. self.internal_id = _internal_id
  54. class Boolean(Validator):
  55. """
  56. Any logical value to be passed
  57. `1`, `"1"` etc. will be automatically converted to bool
  58. """
  59. def __init__(self):
  60. super().__init__(
  61. self._validate,
  62. translator.getdict("validators.boolean"),
  63. _internal_id="Boolean",
  64. )
  65. @staticmethod
  66. def _validate(value: ConfigAllowedTypes, /) -> bool:
  67. true = ["True", "true", "1", 1, True, "yes", "Yes", "on", "On", "y", "Y"]
  68. false = ["False", "false", "0", 0, False, "no", "No", "off", "Off", "n", "N"]
  69. if value not in true + false:
  70. raise ValidationError("Passed value must be a boolean")
  71. return value in true
  72. class Integer(Validator):
  73. """
  74. Checks whether passed argument is an integer value
  75. :param digits: Digits quantity, which must be passed
  76. :param minimum: Minimal number to be passed
  77. :param maximum: Maximum number to be passed
  78. """
  79. def __init__(
  80. self,
  81. *,
  82. digits: typing.Optional[int] = None,
  83. minimum: typing.Optional[int] = None,
  84. maximum: typing.Optional[int] = None,
  85. ):
  86. _signs = (
  87. translator.getdict("validators.positive")
  88. if minimum is not None and minimum == 0
  89. else (
  90. translator.getdict("validators.negative")
  91. if maximum is not None and maximum == 0
  92. else {}
  93. )
  94. )
  95. _digits = (
  96. translator.getdict("validators.digits", digits=digits)
  97. if digits is not None
  98. else {}
  99. )
  100. if minimum is not None and minimum != 0:
  101. doc = (
  102. {
  103. lang: text.format(
  104. sign=_signs.get(lang, ""),
  105. digits=_digits.get(lang, ""),
  106. minimum=minimum,
  107. )
  108. for lang, text in translator.getdict(
  109. "validators.integer_min"
  110. ).items()
  111. }
  112. if maximum is None and maximum != 0
  113. else {
  114. lang: text.format(
  115. sign=_signs.get(lang, ""),
  116. digits=_digits.get(lang, ""),
  117. minimum=minimum,
  118. maximum=maximum,
  119. )
  120. for lang, text in translator.getdict(
  121. "validators.integer_range"
  122. ).items()
  123. }
  124. )
  125. elif maximum is None and maximum != 0:
  126. doc = {
  127. lang: text.format(
  128. sign=_signs.get(lang, ""), digits=_digits.get(lang, "")
  129. )
  130. for lang, text in translator.getdict("validators.integer").items()
  131. }
  132. else:
  133. doc = {
  134. lang: text.format(
  135. sign=_signs.get(lang, ""),
  136. digits=_digits.get(lang, ""),
  137. maximum=maximum,
  138. )
  139. for lang, text in translator.getdict("validators.integer_max").items()
  140. }
  141. super().__init__(
  142. functools.partial(
  143. self._validate,
  144. digits=digits,
  145. minimum=minimum,
  146. maximum=maximum,
  147. ),
  148. doc,
  149. _internal_id="Integer",
  150. )
  151. @staticmethod
  152. def _validate(
  153. value: ConfigAllowedTypes,
  154. /,
  155. *,
  156. digits: int,
  157. minimum: int,
  158. maximum: int,
  159. ) -> typing.Union[int, None]:
  160. try:
  161. value = int(str(value).strip())
  162. except ValueError:
  163. raise ValidationError(f"Passed value ({value}) must be a number")
  164. if minimum is not None and value < minimum:
  165. raise ValidationError(f"Passed value ({value}) is lower than minimum one")
  166. if maximum is not None and value > maximum:
  167. raise ValidationError(f"Passed value ({value}) is greater than maximum one")
  168. if digits is not None and len(str(value)) != digits:
  169. raise ValidationError(
  170. f"The length of passed value ({value}) is incorrect "
  171. f"(Must be exactly {digits} digits)"
  172. )
  173. return value
  174. class Choice(Validator):
  175. """
  176. Check whether entered value is in the allowed list
  177. :param possible_values: Allowed values to be passed to config param
  178. """
  179. def __init__(
  180. self,
  181. possible_values: typing.List[ConfigAllowedTypes],
  182. /,
  183. ):
  184. super().__init__(
  185. functools.partial(self._validate, possible_values=possible_values),
  186. translator.getdict(
  187. "validators.choice",
  188. possible=" / ".join(list(map(str, possible_values))),
  189. ),
  190. _internal_id="Choice",
  191. )
  192. @staticmethod
  193. def _validate(
  194. value: ConfigAllowedTypes,
  195. /,
  196. *,
  197. possible_values: typing.List[ConfigAllowedTypes],
  198. ) -> ConfigAllowedTypes:
  199. if value not in possible_values:
  200. raise ValidationError(
  201. f"Passed value ({value}) is not one of the following:"
  202. f" {' / '.join(list(map(str, possible_values)))}"
  203. )
  204. return value
  205. class MultiChoice(Validator):
  206. """
  207. Check whether every entered value is in the allowed list
  208. :param possible_values: Allowed values to be passed to config param
  209. """
  210. def __init__(
  211. self,
  212. possible_values: typing.List[ConfigAllowedTypes],
  213. /,
  214. ):
  215. possible = " / ".join(list(map(str, possible_values)))
  216. super().__init__(
  217. functools.partial(self._validate, possible_values=possible_values),
  218. translator.getdict("validators.multichoice", possible=possible),
  219. _internal_id="MultiChoice",
  220. )
  221. @staticmethod
  222. def _validate(
  223. value: typing.List[ConfigAllowedTypes],
  224. /,
  225. *,
  226. possible_values: typing.List[ConfigAllowedTypes],
  227. ) -> typing.List[ConfigAllowedTypes]:
  228. if not isinstance(value, (list, tuple)):
  229. value = [value]
  230. for item in value:
  231. if item not in possible_values:
  232. raise ValidationError(
  233. f"One of passed values ({item}) is not one of the following:"
  234. f" {' / '.join(list(map(str, possible_values)))}"
  235. )
  236. return list(set(value))
  237. class Series(Validator):
  238. """
  239. Represents the series of value (simply `list`)
  240. :param separator: With which separator values must be separated
  241. :param validator: Internal validator for each sequence value
  242. :param min_len: Minimal number of series items to be passed
  243. :param max_len: Maximum number of series items to be passed
  244. :param fixed_len: Fixed number of series items to be passed
  245. """
  246. def __init__(
  247. self,
  248. validator: typing.Optional[Validator] = None,
  249. min_len: typing.Optional[int] = None,
  250. max_len: typing.Optional[int] = None,
  251. fixed_len: typing.Optional[int] = None,
  252. ):
  253. def trans(lang: str) -> str:
  254. return validator.doc.get(lang, validator.doc["en"])
  255. _each = (
  256. {
  257. lang: text.format(each=trans(lang))
  258. for lang, text in translator.getdict("validators.each").items()
  259. }
  260. if validator is not None
  261. else {}
  262. )
  263. if fixed_len is not None:
  264. _len = translator.getdict("validators.fixed_len", fixed_len=fixed_len)
  265. elif min_len is None:
  266. if max_len is None:
  267. _len = {}
  268. else:
  269. _len = translator.getdict("validators.max_len", max_len=max_len)
  270. elif max_len is not None:
  271. _len = translator.getdict(
  272. "validators.len_range", min_len=min_len, max_len=max_len
  273. )
  274. else:
  275. _len = translator.getdict("validators.min_len", min_len=min_len)
  276. super().__init__(
  277. functools.partial(
  278. self._validate,
  279. validator=validator,
  280. min_len=min_len,
  281. max_len=max_len,
  282. fixed_len=fixed_len,
  283. ),
  284. {
  285. lang: text.format(each=_each.get(lang, ""), len=_len.get(lang, ""))
  286. for lang, text in translator.getdict("validators.series").items()
  287. },
  288. _internal_id="Series",
  289. )
  290. @staticmethod
  291. def _validate(
  292. value: ConfigAllowedTypes,
  293. /,
  294. *,
  295. validator: typing.Optional[Validator] = None,
  296. min_len: typing.Optional[int] = None,
  297. max_len: typing.Optional[int] = None,
  298. fixed_len: typing.Optional[int] = None,
  299. ) -> typing.List[ConfigAllowedTypes]:
  300. if not isinstance(value, (list, tuple, set)):
  301. value = str(value).split(",")
  302. if isinstance(value, (tuple, set)):
  303. value = list(value)
  304. if min_len is not None and len(value) < min_len:
  305. raise ValidationError(
  306. f"Passed value ({value}) contains less than {min_len} items"
  307. )
  308. if max_len is not None and len(value) > max_len:
  309. raise ValidationError(
  310. f"Passed value ({value}) contains more than {max_len} items"
  311. )
  312. if fixed_len is not None and len(value) != fixed_len:
  313. raise ValidationError(
  314. f"Passed value ({value}) must contain exactly {fixed_len} items"
  315. )
  316. value = [item.strip() if isinstance(item, str) else item for item in value]
  317. if isinstance(validator, Validator):
  318. for i, item in enumerate(value):
  319. try:
  320. value[i] = validator.validate(item)
  321. except ValidationError:
  322. raise ValidationError(
  323. f"Passed value ({value}) contains invalid item"
  324. f" ({str(item).strip()}), which must be {validator.doc['en']}"
  325. )
  326. value = list(filter(lambda x: x, value))
  327. return value
  328. class Link(Validator):
  329. """Valid url must be specified"""
  330. def __init__(self):
  331. super().__init__(
  332. lambda value: self._validate(value),
  333. translator.getdict("validators.link"),
  334. _internal_id="Link",
  335. )
  336. @staticmethod
  337. def _validate(value: ConfigAllowedTypes, /) -> str:
  338. try:
  339. if not utils.check_url(value):
  340. raise Exception("Invalid URL")
  341. except Exception:
  342. raise ValidationError(f"Passed value ({value}) is not a valid URL")
  343. return value
  344. class String(Validator):
  345. """
  346. Checks for length of passed value and automatically converts it to string
  347. :param length: Exact length of string
  348. :param min_len: Minimal length of string
  349. :param max_len: Maximum length of string
  350. """
  351. def __init__(
  352. self,
  353. length: typing.Optional[int] = None,
  354. min_len: typing.Optional[int] = None,
  355. max_len: typing.Optional[int] = None,
  356. ):
  357. if length is not None:
  358. doc = translator.getdict("validators.string_fixed_len", length=length)
  359. else:
  360. if min_len is None:
  361. if max_len is None:
  362. doc = translator.getdict("validators.string")
  363. else:
  364. doc = translator.getdict(
  365. "validators.string_max_len", max_len=max_len
  366. )
  367. elif max_len is not None:
  368. doc = translator.getdict(
  369. "validators.string_len_range", min_len=min_len, max_len=max_len
  370. )
  371. else:
  372. doc = translator.getdict("validators.string_min_len", min_len=min_len)
  373. super().__init__(
  374. functools.partial(
  375. self._validate,
  376. length=length,
  377. min_len=min_len,
  378. max_len=max_len,
  379. ),
  380. doc,
  381. _internal_id="String",
  382. )
  383. @staticmethod
  384. def _validate(
  385. value: ConfigAllowedTypes,
  386. /,
  387. *,
  388. length: typing.Optional[int],
  389. min_len: typing.Optional[int],
  390. max_len: typing.Optional[int],
  391. ) -> str:
  392. if (
  393. isinstance(length, int)
  394. and len(list(grapheme.graphemes(str(value)))) != length
  395. ):
  396. raise ValidationError(
  397. f"Passed value ({value}) must be a length of {length}"
  398. )
  399. if (
  400. isinstance(min_len, int)
  401. and len(list(grapheme.graphemes(str(value)))) < min_len
  402. ):
  403. raise ValidationError(
  404. f"Passed value ({value}) must be a length of at least {min_len}"
  405. )
  406. if (
  407. isinstance(max_len, int)
  408. and len(list(grapheme.graphemes(str(value)))) > max_len
  409. ):
  410. raise ValidationError(
  411. f"Passed value ({value}) must be a length of up to {max_len}"
  412. )
  413. return str(value)
  414. class RegExp(Validator):
  415. """
  416. Checks if value matches the regex
  417. :param regex: Regex to match
  418. :param flags: Flags to pass to re.compile
  419. :param description: Description of regex
  420. """
  421. def __init__(
  422. self,
  423. regex: str,
  424. flags: typing.Optional[re.RegexFlag] = None,
  425. description: typing.Optional[typing.Union[dict, str]] = None,
  426. ):
  427. if not flags:
  428. flags = 0
  429. try:
  430. re.compile(regex, flags=flags)
  431. except re.error as e:
  432. raise Exception(f"{regex} is not a valid regex") from e
  433. if description is None:
  434. doc = translator.getdict("validators.regex", regex=regex)
  435. else:
  436. if isinstance(description, str):
  437. doc = {"en": description}
  438. else:
  439. doc = description
  440. super().__init__(
  441. functools.partial(self._validate, regex=regex, flags=flags),
  442. doc,
  443. _internal_id="RegExp",
  444. )
  445. @staticmethod
  446. def _validate(
  447. value: ConfigAllowedTypes,
  448. /,
  449. *,
  450. regex: str,
  451. flags: typing.Optional[re.RegexFlag],
  452. ) -> str:
  453. if not re.match(regex, str(value), flags=flags):
  454. raise ValidationError(f"Passed value ({value}) must follow pattern {regex}")
  455. return str(value)
  456. class Float(Validator):
  457. """
  458. Checks whether passed argument is a float value
  459. :param minimum: Minimal number to be passed
  460. :param maximum: Maximum number to be passed
  461. """
  462. def __init__(
  463. self,
  464. minimum: typing.Optional[float] = None,
  465. maximum: typing.Optional[float] = None,
  466. ):
  467. _signs = (
  468. translator.getdict("validators.positive")
  469. if minimum is not None and minimum == 0
  470. else (
  471. translator.getdict("validators.negative")
  472. if maximum is not None and maximum == 0
  473. else {}
  474. )
  475. )
  476. if minimum is not None and minimum != 0:
  477. doc = (
  478. {
  479. lang: text.format(sign=_signs.get(lang, ""), minimum=minimum)
  480. for lang, text in translator.getdict("validators.float_min").items()
  481. }
  482. if maximum is None and maximum != 0
  483. else {
  484. lang: text.format(
  485. sign=_signs.get(lang, ""), minimum=minimum, maximum=maximum
  486. )
  487. for lang, text in translator.getdict(
  488. "validators.float_range"
  489. ).items()
  490. }
  491. )
  492. elif maximum is None and maximum != 0:
  493. doc = {
  494. lang: text.format(sign=_signs.get(lang, ""))
  495. for lang, text in translator.getdict("validators.float").items()
  496. }
  497. else:
  498. doc = {
  499. lang: text.format(sign=_signs.get(lang, ""), maximum=maximum)
  500. for lang, text in translator.getdict("validators.float_max").items()
  501. }
  502. super().__init__(
  503. functools.partial(
  504. self._validate,
  505. minimum=minimum,
  506. maximum=maximum,
  507. ),
  508. doc,
  509. _internal_id="Float",
  510. )
  511. @staticmethod
  512. def _validate(
  513. value: ConfigAllowedTypes,
  514. /,
  515. *,
  516. minimum: typing.Optional[float] = None,
  517. maximum: typing.Optional[float] = None,
  518. ) -> float:
  519. try:
  520. value = float(str(value).strip().replace(",", "."))
  521. except ValueError:
  522. raise ValidationError(f"Passed value ({value}) must be a float")
  523. if minimum is not None and value < minimum:
  524. raise ValidationError(f"Passed value ({value}) is lower than minimum one")
  525. if maximum is not None and value > maximum:
  526. raise ValidationError(f"Passed value ({value}) is greater than maximum one")
  527. return value
  528. class TelegramID(Validator):
  529. def __init__(self):
  530. super().__init__(
  531. self._validate,
  532. "Telegram ID",
  533. _internal_id="TelegramID",
  534. )
  535. @staticmethod
  536. def _validate(value: ConfigAllowedTypes, /) -> int:
  537. e = ValidationError(f"Passed value ({value}) is not a valid telegram id")
  538. try:
  539. value = int(str(value).strip())
  540. except Exception:
  541. raise e
  542. if str(value).startswith("-100"):
  543. value = int(str(value)[4:])
  544. if value > 2**64 - 1 or value < 0:
  545. raise e
  546. return value
  547. class Union(Validator):
  548. def __init__(self, *validators):
  549. doc = translator.getdict("validators.union")
  550. def case(x: str) -> str:
  551. return x[0].upper() + x[1:]
  552. for validator in validators:
  553. for key in doc:
  554. doc[key] += f"- {case(validator.doc.get(key, validator.doc['en']))}\n"
  555. for key, value in doc.items():
  556. doc[key] = value.strip()
  557. super().__init__(
  558. functools.partial(self._validate, validators=validators),
  559. doc,
  560. _internal_id="Union",
  561. )
  562. @staticmethod
  563. def _validate(
  564. value: ConfigAllowedTypes,
  565. /,
  566. *,
  567. validators: list,
  568. ) -> ConfigAllowedTypes:
  569. for validator in validators:
  570. try:
  571. return validator.validate(value)
  572. except ValidationError:
  573. pass
  574. raise ValidationError(f"Passed value ({value}) is not valid")
  575. class NoneType(Validator):
  576. def __init__(self):
  577. super().__init__(
  578. self._validate,
  579. translator.getdict("validators.empty"),
  580. _internal_id="NoneType",
  581. )
  582. @staticmethod
  583. def _validate(value: ConfigAllowedTypes, /) -> None:
  584. if not value:
  585. raise ValidationError(f"Passed value ({value}) is not None")
  586. return None
  587. class Hidden(Validator):
  588. def __init__(self, validator: typing.Optional[Validator] = None):
  589. if not validator:
  590. validator = String()
  591. super().__init__(
  592. functools.partial(self._validate, validator=validator),
  593. validator.doc,
  594. _internal_id="Hidden",
  595. )
  596. @staticmethod
  597. def _validate(
  598. value: ConfigAllowedTypes,
  599. /,
  600. *,
  601. validator: Validator,
  602. ) -> ConfigAllowedTypes:
  603. return validator.validate(value)
  604. class Emoji(Validator):
  605. """
  606. Checks whether passed argument is a valid emoji
  607. :param quantity: Number of emojis to be passed
  608. :param min_len: Minimum number of emojis
  609. :param max_len: Maximum number of emojis
  610. """
  611. def __init__(
  612. self,
  613. length: typing.Optional[int] = None,
  614. min_len: typing.Optional[int] = None,
  615. max_len: typing.Optional[int] = None,
  616. ):
  617. if length is not None:
  618. doc = translator.getdict("validators.emoji_fixed_len", length=length)
  619. elif min_len is not None and max_len is not None:
  620. doc = translator.getdict(
  621. "validators.emoji_len_range", min_len=min_len, max_len=max_len
  622. )
  623. elif min_len is not None:
  624. doc = translator.getdict("validators.emoji_min_len", min_len=min_len)
  625. elif max_len is not None:
  626. doc = translator.getdict("validators.emoji_max_len", max_len=max_len)
  627. else:
  628. doc = translator.getdict("validators.emoji")
  629. super().__init__(
  630. functools.partial(
  631. self._validate,
  632. length=length,
  633. min_len=min_len,
  634. max_len=max_len,
  635. ),
  636. doc,
  637. _internal_id="Emoji",
  638. )
  639. @staticmethod
  640. def _validate(
  641. value: ConfigAllowedTypes,
  642. /,
  643. *,
  644. length: typing.Optional[int],
  645. min_len: typing.Optional[int],
  646. max_len: typing.Optional[int],
  647. ) -> str:
  648. value = str(value)
  649. passed_length = len(list(grapheme.graphemes(value)))
  650. if length is not None and passed_length != length:
  651. raise ValidationError(f"Passed value ({value}) is not {length} emojis long")
  652. if (
  653. min_len is not None
  654. and max_len is not None
  655. and (passed_length < min_len or passed_length > max_len)
  656. ):
  657. raise ValidationError(
  658. f"Passed value ({value}) is not between {min_len} and {max_len} emojis"
  659. " long"
  660. )
  661. if min_len is not None and passed_length < min_len:
  662. raise ValidationError(
  663. f"Passed value ({value}) is not at least {min_len} emojis long"
  664. )
  665. if max_len is not None and passed_length > max_len:
  666. raise ValidationError(
  667. f"Passed value ({value}) is not no more than {max_len} emojis long"
  668. )
  669. if any(emoji not in ALLOWED_EMOJIS for emoji in grapheme.graphemes(value)):
  670. raise ValidationError(
  671. f"Passed value ({value}) is not a valid string with emojis"
  672. )
  673. return value
  674. class EntityLike(RegExp):
  675. def __init__(self):
  676. super().__init__(
  677. regex=r"^(?:@|https?://t\.me/)?(?:[a-zA-Z0-9_]{5,32}|[a-zA-Z0-9_]{1,32}\?[a-zA-Z0-9_]{1,32})$",
  678. description=translator.getdict("validators.entity_like"),
  679. )
  680. @staticmethod
  681. def _validate(
  682. value: ConfigAllowedTypes,
  683. /,
  684. *,
  685. regex: str,
  686. flags: typing.Optional[re.RegexFlag],
  687. ) -> typing.Union[str, int]:
  688. value = super()._validate(value, regex=regex, flags=flags)
  689. if value.isdigit():
  690. if value.startswith("-100"):
  691. value = value[4:]
  692. value = int(value)
  693. if value.startswith("https://t.me/"):
  694. value = value.split("https://t.me/")[1]
  695. if not value.startswith("@"):
  696. value = f"@{value}"
  697. return value