diff --git a/hikka/inline/core.py b/hikka/inline/core.py
index 6ad7d67..7f4fb36 100644
--- a/hikka/inline/core.py
+++ b/hikka/inline/core.py
@@ -105,7 +105,15 @@ class InlineManager(Gallery, Form, BotInteractions, Events, TokenObtainment):
if form["ttl"] < time.time():
del self._forms[form_uid]
- await asyncio.sleep(10)
+ for gallery_uid, gallery in self._galleries.copy().items():
+ if gallery["ttl"] < time.time():
+ del self._galleries[gallery_uid]
+
+ for map_uid, config in self._custom_map.copy().items():
+ if config["ttl"] < time.time():
+ del self._custom_map[map_uid]
+
+ await asyncio.sleep(5)
async def _register_manager(
self,
diff --git a/hikka/inline/events.py b/hikka/inline/events.py
index 20d46d5..f4c96eb 100644
--- a/hikka/inline/events.py
+++ b/hikka/inline/events.py
@@ -175,7 +175,7 @@ class Events(InlineUnit):
await query.answer("You are not allowed to press this button!")
return
- await self._custom_map[query.data]["handler"](query)
+ await self._custom_map[query.data]["handler"](query, *self._custom_map[query.data].get("args", []), **self._custom_map[query.data].get("kwargs", {}))
return
async def _chosen_inline_handler(
@@ -248,15 +248,18 @@ class Events(InlineUnit):
continue
# Retrieve docs from func
- doc = utils.escape_html(
- "\n".join(
- [
- line.strip()
- for line in inspect.getdoc(fun).splitlines()
- if not line.strip().startswith("@")
- ]
+ try:
+ doc = utils.escape_html(
+ "\n".join(
+ [
+ line.strip()
+ for line in inspect.getdoc(fun).splitlines()
+ if not line.strip().startswith("@")
+ ]
+ )
)
- )
+ except AttributeError:
+ doc = "π¦₯ No docs"
_help += f"πΉ @{self.bot_username} {name}
- {doc}\n"
diff --git a/hikka/inline/gallery.py b/hikka/inline/gallery.py
index c61e827..88a0c91 100644
--- a/hikka/inline/gallery.py
+++ b/hikka/inline/gallery.py
@@ -10,6 +10,8 @@ from aiogram.types import (
InlineQueryResultPhoto,
InlineQuery,
InlineQueryResultGif,
+ InlineQueryResultArticle,
+ InputTextMessageContent,
)
from aiogram.utils.exceptions import InvalidHTTPUrlContent, BadRequest, RetryAfter
@@ -22,6 +24,7 @@ import asyncio
import time
import functools
+
logger = logging.getLogger(__name__)
@@ -41,13 +44,14 @@ class Gallery(InlineUnit):
message: Union[Message, int],
next_handler: Union[FunctionType, List[str]],
caption: Union[str, FunctionType] = "",
+ *,
force_me: bool = True,
always_allow: Union[list, None] = None,
ttl: Union[int, bool] = False,
on_unload: Union[FunctionType, None] = None,
preload: Union[bool, int] = False,
gif: bool = False,
- reattempt: bool = False,
+ _reattempt: bool = False,
) -> Union[bool, str]:
"""
Processes inline gallery
@@ -127,15 +131,9 @@ class Gallery(InlineUnit):
btn_call_data = [utils.rand(16), utils.rand(16), utils.rand(16)]
try:
- if asyncio.iscoroutinefunction(next_handler):
- photo_url = await next_handler()
- elif getattr(next_handler, "__call__", False):
- photo_url = next_handler()
- else:
- raise Exception("Invalid type for `next_handler`")
-
- if not isinstance(photo_url, (str, list)):
- raise Exception("Got invalid result from `next_handler`")
+ photo_url = await self._call_photo(next_handler)
+ if not photo_url:
+ return False
except Exception:
logger.exception("Error while parsing first photo in gallery")
return False
@@ -168,6 +166,7 @@ class Gallery(InlineUnit):
),
"always_allow": always_allow,
"force_me": force_me,
+ "ttl": self._galleries[gallery_uid]["ttl"],
}
self._custom_map[btn_call_data[1]] = {
@@ -180,6 +179,7 @@ class Gallery(InlineUnit):
),
"always_allow": always_allow,
"force_me": force_me,
+ "ttl": self._galleries[gallery_uid]["ttl"],
}
self._custom_map[btn_call_data[2]] = {
@@ -193,10 +193,13 @@ class Gallery(InlineUnit):
),
"always_allow": always_allow,
"force_me": force_me,
+ "ttl": self._galleries[gallery_uid]["ttl"],
}
if isinstance(message, Message):
- await (message.edit if message.out else message.respond)("π©βπ€ Loading inline gallery...")
+ await (message.edit if message.out else message.respond)(
+ "π©βπ€ Loading inline gallery..."
+ )
try:
q = await self._client.inline_query(self.bot_username, gallery_uid)
@@ -211,7 +214,7 @@ class Gallery(InlineUnit):
del self._galleries[gallery_uid]
- if reattempt:
+ if _reattempt:
msg = (
"π« A problem occurred with inline bot "
"while processing query. Check logs for "
@@ -226,15 +229,15 @@ class Gallery(InlineUnit):
return False
return await self.gallery(
- caption,
- message,
- next_handler,
- force_me,
- always_allow,
- ttl,
- on_unload,
- preload,
- True,
+ caption=caption,
+ message=message,
+ next_handler=next_handler,
+ force_me=force_me,
+ always_allow=always_allow,
+ ttl=ttl,
+ on_unload=on_unload,
+ preload=preload,
+ _reattempt=True,
)
self._galleries[gallery_uid]["chat"] = utils.get_chat_id(m)
@@ -247,17 +250,137 @@ class Gallery(InlineUnit):
return gallery_uid
- async def _load_gallery_photos(self, gallery_uid: str) -> None:
- """Preloads photo. Should be called via ensure_future"""
- if asyncio.iscoroutinefunction(self._galleries[gallery_uid]["next_handler"]):
- photo_url = await self._galleries[gallery_uid]["next_handler"]()
- elif getattr(self._galleries[gallery_uid]["next_handler"], "__call__", False):
- photo_url = self._galleries[gallery_uid]["next_handler"]()
+ async def query_gallery(
+ self,
+ query: InlineQuery,
+ items: List[dict],
+ *,
+ force_me: bool = True,
+ always_allow: Union[list, None] = None,
+ ) -> None:
+ """
+ query
+ `InlineQuery` which should be answered with inline gallery
+ items
+ Array of dicts with inline results.
+ Each dict *must* has a:
+ - `title` - The title of the result
+ - `description` - Short description of the result
+ - `next_handler` - Inline gallery handler. Callback or awaitable
+ Each dict *could* has a:
+ - `caption` - Caption of photo. Defaults to `""`
+ force_me
+ Either this gallery buttons must be pressed only by owner scope or no
+ always_allow
+ Users, that are allowed to press buttons in addition to previous rules
+ """
+ if not isinstance(force_me, bool):
+ logger.error("Invalid type for `force_me`")
+ return False
+
+ if always_allow and not isinstance(always_allow, list):
+ logger.error("Invalid type for `always_allow`")
+ return False
+
+ if not always_allow:
+ always_allow = []
+
+ if (
+ not isinstance(items, list)
+ or not all(isinstance(i, dict) for i in items)
+ or not all(
+ "title" in i
+ and "description" in i
+ and "next_handler" in i
+ and (
+ callable(i["next_handler"])
+ or asyncio.iscoroutinefunction(i)
+ or isinstance(i, list)
+ )
+ and isinstance(i["title"], str)
+ and isinstance(i["description"], str)
+ for i in items
+ )
+ ):
+ logger.error("Invalid `items` specified in query gallery")
+ return False
+
+ result = []
+ for i in items:
+ if "thumb_handler" not in i:
+ photo_url = await self._call_photo(i["next_handler"])
+ if not photo_url:
+ return False
+
+ if isinstance(photo_url, list):
+ photo_url = photo_url[0]
+
+ if not isinstance(photo_url, str):
+ logger.error("Invalid result from `next_handler`")
+ continue
+ else:
+ photo_url = await self._call_photo(i["thumb_handler"])
+ if not photo_url:
+ return False
+
+ if isinstance(photo_url, list):
+ photo_url = photo_url[0]
+
+ if not isinstance(photo_url, str):
+ logger.error("Invalid result from `thumb_handler`")
+ continue
+
+ id_ = utils.rand(16)
+
+ self._custom_map[id_] = {
+ "handler": i["next_handler"],
+ "always_allow": always_allow,
+ "force_me": force_me,
+ "caption": i.get("caption", ""),
+ "ttl": round(time.time()) + 120,
+ }
+
+ result += [
+ InlineQueryResultArticle(
+ id=utils.rand(20),
+ title=i["title"],
+ description=i["description"],
+ input_message_content=InputTextMessageContent(
+ f"π©βπ€ Loading Hikka gallery...\n#id: {id_}",
+ "HTML",
+ disable_web_page_preview=True,
+ ),
+ thumb_url=photo_url,
+ thumb_width=128,
+ thumb_height=128,
+ )
+ ]
+
+ await query.answer(result, cache_time=0)
+
+ async def _call_photo(self, callback: FunctionType) -> Union[str, bool]:
+ """Parses photo url from `callback`. Returns url on success, otherwise `False`"""
+ if asyncio.iscoroutinefunction(callback):
+ photo_url = await callback()
+ elif getattr(callback, "__call__", False):
+ photo_url = callback()
+ elif isinstance(callback, str):
+ photo_url = callback
+ elif isinstance(callback, list):
+ photo_url = callback[0]
else:
- raise Exception("Invalid type for `next_handler`")
+ logger.error("Invalid type for `next_handler`")
+ return False
if not isinstance(photo_url, (str, list)):
- raise Exception("Got invalid result from `next_handler`")
+ logger.error("Got invalid result from `next_handler`")
+ return False
+
+ return photo_url
+
+ async def _load_gallery_photos(self, gallery_uid: str) -> None:
+ """Preloads photo. Should be called via ensure_future"""
+ photo_url = await self._call_photo(self._galleries[gallery_uid]["next_handler"])
self._galleries[gallery_uid]["photos"] += (
[photo_url] if isinstance(photo_url, str) else photo_url
@@ -385,7 +508,7 @@ class Gallery(InlineUnit):
reply_markup=self._gallery_markup(btn_call_data),
)
except (InvalidHTTPUrlContent, BadRequest):
- logger.exception("Error fetching photo content, attempting load next one")
+ logger.debug("Error fetching photo content, attempting load next one")
del self._galleries[gallery_uid]["photos"][
self._galleries[gallery_uid]["current_index"]
]
@@ -407,7 +530,11 @@ class Gallery(InlineUnit):
self._galleries[gallery_uid]["current_index"]
]
except IndexError:
- logger.error(f"Got IndexError in `_get_next_photo`. {self._galleries[gallery_uid]['current_index']=} / {len(self._galleries[gallery_uid]['photos'])=}")
+ logger.error(
+ "Got IndexError in `_get_next_photo`. "
+ f"{self._galleries[gallery_uid]['current_index']=} / "
+ f"{len(self._galleries[gallery_uid]['photos'])=}"
+ )
return self._galleries[gallery_uid]["photos"][0]
def _get_caption(self, gallery_uid: str) -> str:
diff --git a/hikka/inline/types.py b/hikka/inline/types.py
index 78ae9cf..173d22d 100644
--- a/hikka/inline/types.py
+++ b/hikka/inline/types.py
@@ -1,8 +1,16 @@
-from aiogram.types import Message as AiogramMessage, InlineQuery as AiogramInlineQuery
+from aiogram.types import (
+ Message as AiogramMessage,
+ InlineQuery as AiogramInlineQuery,
+ InlineQueryResultArticle,
+ InputTextMessageContent,
+)
+
+from .. import utils
class InlineCall:
"""Modified version of original Aiogram CallbackQuery"""
+
def __init__(self):
self.delete = None
self.unload = None
@@ -12,18 +20,21 @@ class InlineCall:
class InlineUnit:
"""InlineManager extension type. For internal use only"""
+
def __init__(self):
"""Made just for type specification"""
class BotMessage(AiogramMessage):
"""Modified version of original Aiogram Message"""
+
def __init__(self):
super().__init__()
class InlineQuery:
"""Modified version of original Aiogram InlineQuery"""
+
def __init__(self, inline_query: AiogramInlineQuery) -> None:
self.inline_query = inline_query
@@ -33,6 +44,9 @@ class InlineQuery:
if attr.startswith("__") and attr.endswith("__"):
continue # Ignore magic attrs
+ if hasattr(self, attr):
+ continue # Do not override anything
+
try:
setattr(self, attr, getattr(inline_query, attr))
except AttributeError:
@@ -44,3 +58,22 @@ class InlineQuery:
if len(self.inline_query.query.split()) > 1
else ""
)
+
+ async def e404(self) -> None:
+ await self.answer(
+ [
+ InlineQueryResultArticle(
+ id=utils.rand(20),
+ title="π« 404",
+ description="No results found",
+ input_message_content=InputTextMessageContent(
+ "πΆβπ«οΈ There is nothing here...",
+ parse_mode="HTML",
+ ),
+ thumb_url="https://img.icons8.com/external-justicon-flat-justicon/344/external-404-error-responsive-web-design-justicon-flat-justicon.png",
+ thumb_width=128,
+ thumb_height=128,
+ )
+ ],
+ cache_time=0,
+ )
diff --git a/hikka/modules/inline_stuff.py b/hikka/modules/inline_stuff.py
new file mode 100644
index 0000000..1f713a3
--- /dev/null
+++ b/hikka/modules/inline_stuff.py
@@ -0,0 +1,55 @@
+# β β β βββ βββ βββ β βββ βββ βββ βββββ βββ
+# βββ β β β βββ βββ β β βββ β βββ β β β βββ
+#
+# Β© Copyright 2022
+#
+# https://t.me/hikariatama
+#
+# π Licensed under the CC BY-NC-ND 4.0
+# π https://creativecommons.org/licenses/by-nc-nd/4.0
+
+# scope: inline
+# scope: hikka_only
+# meta developer: @hikariatama
+
+from .. import loader, utils
+from telethon.tl.types import Message
+from aiogram.types import CallbackQuery
+import logging
+import re
+
+logger = logging.getLogger(__name__)
+
+
+@loader.tds
+class InlineStuffMod(loader.Module):
+ """Provides support for inline stuff"""
+
+ strings = {"name": "InlineStuff"}
+
+ async def client_ready(self, client, db) -> None:
+ self._db = db
+ self._client = client
+ self._bot_id = (await self.inline.bot.get_me()).id
+
+ async def inline__close(self, call: CallbackQuery) -> None:
+ await call.delete()
+
+ async def watcher(self, message: Message) -> None:
+ if (
+ not getattr(message, "out", False)
+ or not getattr(message, "via_bot_id", False)
+ or message.via_bot_id != self._bot_id
+ or "Loading Hikka gallery..." not in getattr(message, "raw_text", "")
+ ):
+ return
+
+ id_ = re.search(r"#id: ([a-zA-Z0-9]+)", message.raw_text).group(1)
+
+ await self.inline.gallery(
+ message=utils.get_chat_id(message),
+ next_handler=self.inline._custom_map[id_]["handler"],
+ caption=self.inline._custom_map[id_].get("caption", ""),
+ )
+
+ await message.delete()
diff --git a/hikka/version.py b/hikka/version.py
index 867fe6a..151e378 100644
--- a/hikka/version.py
+++ b/hikka/version.py
@@ -1 +1 @@
-__version__ = (1, 0, 10)
+__version__ = (1, 0, 11)