form.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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 contextlib
  7. import copy
  8. import logging
  9. import os
  10. import random
  11. import time
  12. import traceback
  13. import typing
  14. from asyncio import Event
  15. from urllib.parse import urlparse
  16. import grapheme
  17. from aiogram.types import (
  18. InlineQuery,
  19. InlineQueryResultArticle,
  20. InlineQueryResultAudio,
  21. InlineQueryResultDocument,
  22. InlineQueryResultGif,
  23. InlineQueryResultLocation,
  24. InlineQueryResultPhoto,
  25. InlineQueryResultVideo,
  26. InputTextMessageContent,
  27. )
  28. from hikkatl.errors.rpcerrorlist import ChatSendInlineForbiddenError
  29. from hikkatl.extensions.html import CUSTOM_EMOJIS
  30. from hikkatl.tl.types import Message
  31. from .. import main, utils
  32. from ..types import HikkaReplyMarkup
  33. from .types import InlineMessage, InlineUnit
  34. logger = logging.getLogger(__name__)
  35. VERIFICATION_EMOJIES = list(
  36. grapheme.graphemes(
  37. "๐Ÿ‘จโ€๐Ÿซ๐Ÿ‘ฉโ€๐Ÿซ๐Ÿ‘จโ€๐ŸŽค๐Ÿง‘โ€๐ŸŽค๐Ÿ‘ฉโ€๐ŸŽค๐Ÿ‘จโ€๐ŸŽ“๐Ÿ‘ฉโ€๐ŸŽ“๐Ÿ‘ฉโ€๐Ÿณ๐Ÿ‘ฉโ€๐ŸŒพ๐Ÿ‘ฉโ€โš•๏ธ๐Ÿ•ต๏ธโ€โ™€๏ธ๐Ÿ’‚โ€โ™€๏ธ๐Ÿ‘ทโ€โ™‚๏ธ๐Ÿ‘ฎโ€โ™‚๏ธ๐Ÿ‘ด๐Ÿง‘โ€๐Ÿฆณ๐Ÿ‘ฉโ€๐Ÿฆณ๐Ÿ‘ฑโ€โ™€๏ธ๐Ÿ‘ฉโ€๐Ÿฆฐ๐Ÿ‘จโ€๐Ÿฆฑ๐Ÿ‘ฉโ€โš–๏ธ๐Ÿง™โ€โ™‚๏ธ๐Ÿงโ€โ™€๏ธ๐Ÿง›โ€โ™€๏ธ"
  38. "๐ŸŽ…๐Ÿงšโ€โ™‚๏ธ๐Ÿ™†โ€โ™€๏ธ๐Ÿ™โ€โ™‚๏ธ๐Ÿ‘ฉโ€๐Ÿ‘ฆ๐Ÿงถ๐Ÿชข๐Ÿชก๐Ÿงต๐Ÿฉฒ๐Ÿ‘–๐Ÿ‘•๐Ÿ‘š๐Ÿฆบ๐Ÿ‘—๐Ÿ‘™๐Ÿฉฑ๐Ÿ‘˜๐Ÿฅป๐Ÿฉด๐Ÿฅฟ๐Ÿงฆ๐Ÿฅพ๐Ÿ‘Ÿ๐Ÿ‘ž"
  39. "๐Ÿ‘ข๐Ÿ‘ก๐Ÿ‘ ๐Ÿช–๐Ÿ‘‘๐Ÿ’๐Ÿ‘๐Ÿ‘›๐Ÿ‘œ๐Ÿ’ผ๐ŸŒ‚๐Ÿฅฝ๐Ÿ•ถ๐Ÿ‘“๐Ÿงณ๐ŸŽ’๐Ÿถ๐Ÿฑ๐Ÿญ๐Ÿน๐Ÿฐ๐ŸฆŠ๐Ÿป๐Ÿท๐Ÿฎ"
  40. "๐Ÿฆ๐Ÿฏ๐Ÿจ๐Ÿปโ€โ„๏ธ๐Ÿผ๐Ÿฝ๐Ÿธ๐Ÿต๐Ÿ™ˆ๐Ÿ™‰๐Ÿ™Š๐Ÿ’๐Ÿฆ†๐Ÿฅ๐Ÿฃ๐Ÿค๐Ÿฆ๐Ÿง๐Ÿ”๐Ÿฆ…๐Ÿฆ‰๐Ÿฆ‡๐Ÿบ๐Ÿ—๐Ÿด"
  41. "๐Ÿฆ„๐Ÿœ๐Ÿž๐ŸŒ๐Ÿฆ‹๐Ÿ›๐Ÿชฑ๐Ÿ๐Ÿชฐ๐Ÿชฒ๐Ÿชณ๐ŸฆŸ๐Ÿฆ—๐Ÿ•ท๐Ÿ•ธ๐Ÿ™๐Ÿฆ•๐Ÿฆ–๐ŸฆŽ๐Ÿ๐Ÿข๐Ÿฆ‚๐Ÿฆ‘๐Ÿฆ๐Ÿฆž"
  42. "๐Ÿฆ€๐Ÿก๐Ÿ ๐ŸŸ๐Ÿ…๐ŸŠ๐Ÿฆญ๐Ÿฆˆ๐Ÿ‹๐Ÿณ๐Ÿฌ๐Ÿ†๐Ÿฆ“๐Ÿฆ๐Ÿฆง๐Ÿฆฃ๐Ÿ˜๐Ÿฆ›๐Ÿƒ๐Ÿฆฌ๐Ÿฆ˜๐Ÿฆ’๐Ÿซ๐Ÿช๐Ÿฆ"
  43. "๐Ÿ‚๐Ÿ„๐ŸŽ๐Ÿ–๐Ÿ๐Ÿ‘๐Ÿฆ™๐Ÿˆ๐Ÿ•โ€๐Ÿฆบ๐Ÿฆฎ๐Ÿฉ๐Ÿ•๐ŸฆŒ๐Ÿ๐Ÿˆโ€โฌ›๐Ÿชถ๐Ÿ“๐Ÿฆƒ๐Ÿฆค๐Ÿฆš๐Ÿฆœ๐Ÿฆก๐Ÿฆจ๐Ÿฆ๐Ÿ‡"
  44. "๐Ÿ•Š๐Ÿฆฉ๐Ÿฆข๐Ÿฆซ๐Ÿฆฆ๐Ÿฆฅ๐Ÿ๐Ÿ€๐Ÿฟ๐Ÿฆ”๐ŸŒณ๐ŸŒฒ๐ŸŒต๐Ÿฒ๐Ÿ‰๐Ÿพ๐ŸŽ‹๐Ÿ‚๐Ÿ๐Ÿ„๐Ÿš๐ŸŒพ๐Ÿชจ๐Ÿ’๐ŸŒท"
  45. "๐Ÿฅ€๐ŸŒบ๐ŸŒธ๐ŸŒป๐ŸŒž๐ŸŒœ๐ŸŒ˜๐ŸŒ—๐ŸŒŽ๐Ÿช๐Ÿ’ซโญ๏ธโœจโšก๏ธโ˜„๏ธ๐Ÿ’ฅโ˜€๏ธ๐ŸŒช๐Ÿ”ฅ๐ŸŒˆ๐ŸŒคโ›…๏ธโ„๏ธโ›„๏ธ๐ŸŒŠ"
  46. "โ˜‚๏ธ๐Ÿ๐ŸŽ๐Ÿ๐ŸŠ๐Ÿ‹๐ŸŒ๐Ÿ‰๐Ÿฅญ๐Ÿ‘๐Ÿ’๐Ÿˆ๐Ÿซ๐Ÿ“๐Ÿ‡๐Ÿ๐Ÿฅฅ๐Ÿฅ๐Ÿ…๐Ÿฅ‘๐Ÿฅฆ๐Ÿง”โ€โ™‚๏ธ"
  47. )
  48. )
  49. class Placeholder:
  50. """Placeholder"""
  51. class Form(InlineUnit):
  52. async def form(
  53. self,
  54. text: str,
  55. message: typing.Union[Message, int],
  56. reply_markup: typing.Optional[HikkaReplyMarkup] = None,
  57. *,
  58. force_me: bool = False,
  59. always_allow: typing.Optional[typing.List[int]] = None,
  60. manual_security: bool = False,
  61. disable_security: bool = False,
  62. ttl: typing.Optional[int] = None,
  63. on_unload: typing.Optional[callable] = None,
  64. photo: typing.Optional[str] = None,
  65. gif: typing.Optional[str] = None,
  66. file: typing.Optional[str] = None,
  67. mime_type: typing.Optional[str] = None,
  68. video: typing.Optional[str] = None,
  69. location: typing.Optional[str] = None,
  70. audio: typing.Optional[typing.Union[dict, str]] = None,
  71. silent: bool = False,
  72. ) -> typing.Union[InlineMessage, bool]:
  73. """
  74. Send inline form to chat
  75. :param text: Content of inline form. HTML markdown supported
  76. :param message: Where to send inline. Can be either `Message` or `int`
  77. :param reply_markup: List of buttons to insert in markup. List of dicts with keys: text, callback
  78. :param force_me: Either this form buttons must be pressed only by owner scope or no
  79. :param always_allow: Users, that are allowed to press buttons in addition to previous rules
  80. :param ttl: Time, when the form is going to be unloaded. Unload means, that the form
  81. buttons with inline queries and callback queries will become unusable, but
  82. buttons with type url will still work as usual. Pay attention, that ttl can't
  83. be bigger, than default one (1 day) and must be either `int` or `False`
  84. :param on_unload: Callback, called when form is unloaded and/or closed. You can clean up trash
  85. or perform another needed action
  86. :param manual_security: By default, Hikka will try to inherit inline buttons security from the caller (command)
  87. If you want to avoid this, pass `manual_security=True`
  88. :param disable_security: By default, Hikka will try to inherit inline buttons security from the caller (command)
  89. If you want to disable all security checks on this form in particular, pass `disable_security=True`
  90. :param photo: Attach a photo to the form. URL must be supplied
  91. :param gif: Attach a gif to the form. URL must be supplied
  92. :param file: Attach a file to the form. URL must be supplied
  93. :param mime_type: Only needed, if `file` field is not empty. Must be either 'application/pdf' or 'application/zip'
  94. :param video: Attach a video to the form. URL must be supplied
  95. :param location: Attach a map point to the form. List/tuple must be supplied (latitude, longitude)
  96. Example: (55.749931, 48.742371)
  97. โš ๏ธ If you pass this parameter, you'll need to pass empty string to `text` โš ๏ธ
  98. :param audio: Attach a audio to the form. Dict or URL must be supplied
  99. :param silent: Whether the form must be sent silently (w/o "Opening form..." message)
  100. :return: If form is sent, returns :obj:`InlineMessage`, otherwise returns `False`
  101. """
  102. with contextlib.suppress(AttributeError):
  103. _hikka_client_id_logging_tag = copy.copy(self._client.tg_id) # noqa: F841
  104. if reply_markup is None:
  105. reply_markup = []
  106. if always_allow is None:
  107. always_allow = []
  108. if not isinstance(text, str):
  109. logger.error(
  110. "Invalid type for `text`. Expected `str`, got `%s`",
  111. type(text),
  112. )
  113. return False
  114. text = self.sanitise_text(text)
  115. if not isinstance(silent, bool):
  116. logger.error(
  117. "Invalid type for `silent`. Expected `bool`, got `%s`",
  118. type(silent),
  119. )
  120. return False
  121. if not isinstance(manual_security, bool):
  122. logger.error(
  123. "Invalid type for `manual_security`. Expected `bool`, got `%s`",
  124. type(manual_security),
  125. )
  126. return False
  127. if not isinstance(disable_security, bool):
  128. logger.error(
  129. "Invalid type for `disable_security`. Expected `bool`, got `%s`",
  130. type(disable_security),
  131. )
  132. return False
  133. if not isinstance(message, (Message, int)):
  134. logger.error(
  135. "Invalid type for `message`. Expected `Message` or `int`, got `%s`",
  136. type(message),
  137. )
  138. return False
  139. if not isinstance(reply_markup, (list, dict)):
  140. logger.error(
  141. "Invalid type for `reply_markup`. Expected `list` or `dict`, got `%s`",
  142. type(reply_markup),
  143. )
  144. return False
  145. if photo and (not isinstance(photo, str) or not utils.check_url(photo)):
  146. logger.error(
  147. "Invalid type for `photo`. Expected `str` with URL, got `%s`",
  148. type(photo),
  149. )
  150. return False
  151. try:
  152. path = urlparse(photo).path
  153. ext = os.path.splitext(path)[1]
  154. except Exception:
  155. ext = None
  156. if photo is not None and ext in {".gif", ".mp4"}:
  157. gif = copy.copy(photo)
  158. photo = None
  159. if gif and (not isinstance(gif, str) or not utils.check_url(gif)):
  160. logger.error(
  161. "Invalid type for `gif`. Expected `str` with URL, got `%s`",
  162. type(gif),
  163. )
  164. return False
  165. if file and (not isinstance(file, str) or not utils.check_url(file)):
  166. logger.error(
  167. "Invalid type for `file`. Expected `str` with URL, got `%s`",
  168. type(file),
  169. )
  170. return False
  171. if file and not mime_type:
  172. logger.error(
  173. "You must pass `mime_type` along with `file` field\n"
  174. "It may be either 'application/zip' or 'application/pdf'"
  175. )
  176. return False
  177. if video and (not isinstance(video, str) or not utils.check_url(video)):
  178. logger.error(
  179. "Invalid type for `video`. Expected `str` with URL, got `%s`",
  180. type(video),
  181. )
  182. return False
  183. if isinstance(audio, str):
  184. audio = {"url": audio}
  185. if audio and (
  186. not isinstance(audio, dict)
  187. or "url" not in audio
  188. or not utils.check_url(audio["url"])
  189. ):
  190. logger.error(
  191. "Invalid type for `audio`. Expected `dict` with `url` key, got `%s`",
  192. type(audio),
  193. )
  194. return False
  195. if location and (
  196. not isinstance(location, (list, tuple))
  197. or len(location) != 2
  198. or not all(isinstance(item, float) for item in location)
  199. ):
  200. logger.error(
  201. (
  202. "Invalid type for `location`. Expected `list` or `tuple` with 2"
  203. " `float` items, got `%s`"
  204. ),
  205. type(location),
  206. )
  207. return False
  208. if [
  209. photo is not None,
  210. gif is not None,
  211. file is not None,
  212. video is not None,
  213. audio is not None,
  214. location is not None,
  215. ].count(True) > 1:
  216. logger.error("You passed two or more exclusive parameters simultaneously")
  217. return False
  218. reply_markup = self._validate_markup(reply_markup) or []
  219. if not isinstance(force_me, bool):
  220. logger.error(
  221. "Invalid type for `force_me`. Expected `bool`, got `%s`",
  222. type(force_me),
  223. )
  224. return False
  225. if not isinstance(always_allow, list):
  226. logger.error(
  227. "Invalid type for `always_allow`. Expected `list`, got `%s`",
  228. type(always_allow),
  229. )
  230. return False
  231. if not isinstance(ttl, int) and ttl:
  232. logger.error("Invalid type for `ttl`. Expected `int`, got `%s`", type(ttl))
  233. return False
  234. if isinstance(message, Message) and not silent:
  235. try:
  236. status_message = await (
  237. message.edit if message.out else message.respond
  238. )(
  239. (
  240. utils.get_platform_emoji()
  241. if self._client.hikka_me.premium and CUSTOM_EMOJIS
  242. else "๐ŸŒ˜"
  243. )
  244. + self.translator.getkey("inline.opening_form"),
  245. **({"reply_to": utils.get_topic(message)} if message.out else {}),
  246. )
  247. except Exception:
  248. status_message = None
  249. else:
  250. status_message = None
  251. unit_id = utils.rand(16)
  252. perms_map = None if manual_security else self._find_caller_sec_map()
  253. if not reply_markup and not ttl:
  254. logger.debug("Patching form reply markup with empty data")
  255. base_reply_markup = copy.deepcopy(reply_markup) or None
  256. reply_markup = self._validate_markup({"text": "ยญ", "data": "ยญ"})
  257. else:
  258. base_reply_markup = Placeholder()
  259. if (
  260. not any(
  261. any("callback" in button or "input" in button for button in row)
  262. for row in reply_markup
  263. )
  264. and not ttl
  265. ):
  266. logger.debug(
  267. "Patching form ttl to 10 minutes, because it doesn't contain any"
  268. " buttons"
  269. )
  270. ttl = 10 * 60
  271. self._units[unit_id] = {
  272. "type": "form",
  273. "text": text,
  274. "buttons": reply_markup,
  275. "caller": message,
  276. "chat": None,
  277. "message_id": None,
  278. "top_msg_id": utils.get_topic(message),
  279. "uid": unit_id,
  280. "on_unload": on_unload,
  281. "future": Event(),
  282. **({"photo": photo} if photo else {}),
  283. **({"video": video} if video else {}),
  284. **({"gif": gif} if gif else {}),
  285. **({"location": location} if location else {}),
  286. **({"audio": audio} if audio else {}),
  287. **({"location": location} if location else {}),
  288. **({"perms_map": perms_map} if perms_map else {}),
  289. **({"message": message} if isinstance(message, Message) else {}),
  290. **({"force_me": force_me} if force_me else {}),
  291. **({"disable_security": disable_security} if disable_security else {}),
  292. **({"ttl": round(time.time()) + ttl} if ttl else {}),
  293. **({"always_allow": always_allow} if always_allow else {}),
  294. }
  295. async def answer(msg: str):
  296. nonlocal message
  297. if isinstance(message, Message):
  298. await (message.edit if message.out else message.respond)(
  299. msg,
  300. **({} if message.out else {"reply_to": utils.get_topic(message)}),
  301. )
  302. else:
  303. await self._client.send_message(message, msg)
  304. try:
  305. m = await self._invoke_unit(unit_id, message)
  306. except ChatSendInlineForbiddenError:
  307. await answer(self.translator.getkey("inline.inline403"))
  308. except Exception:
  309. logger.exception("Can't send form")
  310. del self._units[unit_id]
  311. await answer(
  312. self.translator.getkey("inline.invoke_failed_logs").format(
  313. utils.escape_html(
  314. "\n".join(traceback.format_exc().splitlines()[1:])
  315. )
  316. )
  317. if self._db.get(main.__name__, "inlinelogs", True)
  318. else self.translator.getkey("inline.invoke_failed")
  319. )
  320. return False
  321. await self._units[unit_id]["future"].wait()
  322. del self._units[unit_id]["future"]
  323. self._units[unit_id]["chat"] = utils.get_chat_id(m)
  324. self._units[unit_id]["message_id"] = m.id
  325. if isinstance(message, Message) and message.out:
  326. await message.delete()
  327. if status_message and not message.out:
  328. await status_message.delete()
  329. inline_message_id = self._units[unit_id]["inline_message_id"]
  330. msg = InlineMessage(self, unit_id, inline_message_id)
  331. if not isinstance(base_reply_markup, Placeholder):
  332. await msg.edit(text, reply_markup=base_reply_markup)
  333. return msg
  334. async def _form_inline_handler(self, inline_query: InlineQuery):
  335. try:
  336. query = inline_query.query.split()[0]
  337. except IndexError:
  338. return
  339. for unit in self._units.copy().values():
  340. for button in utils.array_sum(unit.get("buttons", [])):
  341. if (
  342. "_switch_query" in button
  343. and "input" in button
  344. and button["_switch_query"] == query
  345. and inline_query.from_user.id
  346. in [self._me]
  347. + self._client.dispatcher.security._owner
  348. + unit.get("always_allow", [])
  349. ):
  350. await inline_query.answer(
  351. [
  352. InlineQueryResultArticle(
  353. id=utils.rand(20),
  354. title=button["input"],
  355. description=(
  356. self.translator.getkey("inline.keep_id").format(
  357. random.choice(VERIFICATION_EMOJIES)
  358. )
  359. ),
  360. input_message_content=InputTextMessageContent(
  361. (
  362. "๐Ÿ”„ <b>Transferring value to"
  363. " userbot...</b>\n<i>This message will be"
  364. " deleted automatically</i>"
  365. if inline_query.from_user.id == self._me
  366. else "๐Ÿ”„ <b>Transferring value to userbot...</b>"
  367. ),
  368. "HTML",
  369. disable_web_page_preview=True,
  370. ),
  371. )
  372. ],
  373. cache_time=60,
  374. )
  375. return
  376. if (
  377. inline_query.query not in self._units
  378. or self._units[inline_query.query]["type"] != "form"
  379. ):
  380. return
  381. form = self._units[inline_query.query]
  382. try:
  383. if "photo" in form:
  384. await inline_query.answer(
  385. [
  386. InlineQueryResultPhoto(
  387. id=utils.rand(20),
  388. title="Hikka",
  389. description="Hikka",
  390. caption=form.get("text"),
  391. parse_mode="HTML",
  392. photo_url=form["photo"],
  393. thumb_url=(
  394. "https://img.icons8.com/cotton/452/moon-satellite.png"
  395. ),
  396. reply_markup=self.generate_markup(
  397. form["uid"],
  398. ),
  399. )
  400. ],
  401. cache_time=0,
  402. )
  403. elif "gif" in form:
  404. await inline_query.answer(
  405. [
  406. InlineQueryResultGif(
  407. id=utils.rand(20),
  408. title="Hikka",
  409. caption=form.get("text"),
  410. parse_mode="HTML",
  411. gif_url=form["gif"],
  412. thumb_url=(
  413. "https://img.icons8.com/cotton/452/moon-satellite.png"
  414. ),
  415. reply_markup=self.generate_markup(
  416. form["uid"],
  417. ),
  418. )
  419. ],
  420. cache_time=0,
  421. )
  422. elif "video" in form:
  423. await inline_query.answer(
  424. [
  425. InlineQueryResultVideo(
  426. id=utils.rand(20),
  427. title="Hikka",
  428. description="Hikka",
  429. caption=form.get("text"),
  430. parse_mode="HTML",
  431. video_url=form["video"],
  432. thumb_url=(
  433. "https://img.icons8.com/cotton/452/moon-satellite.png"
  434. ),
  435. mime_type="video/mp4",
  436. reply_markup=self.generate_markup(
  437. form["uid"],
  438. ),
  439. )
  440. ],
  441. cache_time=0,
  442. )
  443. elif "file" in form:
  444. await inline_query.answer(
  445. [
  446. InlineQueryResultDocument(
  447. id=utils.rand(20),
  448. title="Hikka",
  449. description="Hikka",
  450. caption=form.get("text"),
  451. parse_mode="HTML",
  452. document_url=form["file"],
  453. mime_type=form["mime_type"],
  454. reply_markup=self.generate_markup(
  455. form["uid"],
  456. ),
  457. )
  458. ],
  459. cache_time=0,
  460. )
  461. elif "location" in form:
  462. await inline_query.answer(
  463. [
  464. InlineQueryResultLocation(
  465. id=utils.rand(20),
  466. latitude=form["location"][0],
  467. longitude=form["location"][1],
  468. title="Hikka",
  469. reply_markup=self.generate_markup(
  470. form["uid"],
  471. ),
  472. )
  473. ],
  474. cache_time=0,
  475. )
  476. elif "audio" in form:
  477. await inline_query.answer(
  478. [
  479. InlineQueryResultAudio(
  480. id=utils.rand(20),
  481. audio_url=form["audio"]["url"],
  482. caption=form.get("text"),
  483. parse_mode="HTML",
  484. title=form["audio"].get("title", "Hikka"),
  485. performer=form["audio"].get("performer"),
  486. audio_duration=form["audio"].get("duration"),
  487. reply_markup=self.generate_markup(
  488. form["uid"],
  489. ),
  490. )
  491. ],
  492. cache_time=0,
  493. )
  494. else:
  495. await inline_query.answer(
  496. [
  497. InlineQueryResultArticle(
  498. id=utils.rand(20),
  499. title="Hikka",
  500. input_message_content=InputTextMessageContent(
  501. form["text"],
  502. "HTML",
  503. disable_web_page_preview=True,
  504. ),
  505. reply_markup=self.generate_markup(inline_query.query),
  506. )
  507. ],
  508. cache_time=0,
  509. )
  510. except Exception as e:
  511. if form["uid"] in self._error_events:
  512. self._error_events[form["uid"]].set()
  513. self._error_events[form["uid"]] = e