1.0.13: Include command prefix in inline `info`. Fix `--no-web`. Suggest to save modules to filesystem

pull/1/head
hikari.ftg 2022-03-31 18:24:00 +00:00
parent 6dfaaebf0d
commit c40b8e016e
8 changed files with 225 additions and 77 deletions

View File

@ -18,15 +18,20 @@ def compat(code: str) -> str:
r"^( *)from \.\.inline import (.+)$",
r"\1from ..inline.types import \2",
re.sub(
r"^( *)from \.\.inline import rand, ?(.+)$",
r"\1from ..inline.types import \2\n\1from ..utils import rand",
r"^( *)from \.\.inline import rand[^,]*$",
"\1from ..utils import rand",
re.sub(
r"^( *)from \.\.inline import (.+), ?rand[^,]+$",
r"^( *)from \.\.inline import rand, ?(.+)$",
r"\1from ..inline.types import \2\n\1from ..utils import rand",
re.sub(
r"^( *)from \.\.inline import (.+), ?rand, ?(.+)$",
r"\1from ..inline.types import \2, \3\n\1from ..utils import rand",
line.replace("GeekInlineQuery", "InlineQuery"),
r"^( *)from \.\.inline import (.+), ?rand[^,]*$",
r"\1from ..inline.types import \2\n\1from ..utils import rand",
re.sub(
r"^( *)from \.\.inline import (.+), ?rand, ?(.+)$",
r"\1from ..inline.types import \2, \3\n\1from ..utils import rand",
line.replace("GeekInlineQuery", "InlineQuery"),
flags=re.M,
),
flags=re.M,
),
flags=re.M,

View File

@ -34,22 +34,12 @@ import inspect
import logging
import os
import sys
import json
from . import utils, security
from .translations.dynamic import Strings
from .inline.core import InlineManager
from .types import Module, LoadError, ModuleConfig # noqa: F401
def use_fs_for_modules():
try:
with open("config.json", "r") as f:
config = json.loads(f.read())
except Exception:
return False
return config.get("use_fs_for_modules", False)
from importlib.machinery import ModuleSpec
def test(*args, **kwargs):
@ -179,33 +169,43 @@ class Modules:
and x[-3:] == ".py"
and x[0] != "_"
and ("OKTETO" in os.environ or x != "okteto.py")
and (not db.get("hikka", "disable_quickstart", False) or x != "quickstart.py")
and (
not db.get("hikka", "disable_quickstart", False)
or x != "quickstart.py"
)
),
os.listdir(os.path.join(utils.get_base_dir(), MODULES_NAME)),
)
]
if use_fs_for_modules():
mods += [
os.path.join(LOADED_MODULES_DIR, mod)
for mod in filter(
lambda x: (len(x) > 3 and x[-3:] == ".py" and x[0] != "_"),
os.listdir(LOADED_MODULES_DIR),
)
]
mods += [
os.path.join(LOADED_MODULES_DIR, mod)
for mod in filter(
lambda x: (len(x) > 3 and x[-3:] == ".py" and x[0] != "_"),
os.listdir(LOADED_MODULES_DIR),
)
]
logging.debug(mods)
for mod in mods:
try:
module_name = f"{__package__}.{MODULES_NAME}.{os.path.basename(mod)[:-3]}"
module_name = (
f"{__package__}.{MODULES_NAME}.{os.path.basename(mod)[:-3]}"
)
logging.debug(module_name)
spec = importlib.util.spec_from_file_location(module_name, mod)
self.register_module(spec, module_name)
except BaseException as e:
logging.exception(f"Failed to load module %s due to {e}:", mod)
def register_module(self, spec, module_name, origin="<core>"):
def register_module(
self,
spec: ModuleSpec,
module_name: str,
origin: str = "<core>",
save_fs: bool = False,
) -> Module:
"""Register single module from importlib spec"""
from .compat import uniborg
@ -232,7 +232,7 @@ class Modules:
cls_name = ret.__class__.__name__
if use_fs_for_modules():
if save_fs:
path = os.path.join(LOADED_MODULES_DIR, f"{cls_name}.py")
if not os.path.isfile(path) and origin == "<string>":
@ -243,7 +243,7 @@ class Modules:
return ret
def register_commands(self, instance):
def register_commands(self, instance: Module) -> None:
"""Register commands from instance"""
for command in instance.commands.copy():
# Verify that command does not already exist, or,
@ -264,7 +264,7 @@ class Modules:
self.commands.update({command.lower(): instance.commands[command]})
def register_watcher(self, instance):
def register_watcher(self, instance: Module) -> None:
"""Register watcher from instance"""
try:
if instance.watcher:
@ -280,7 +280,7 @@ class Modules:
except AttributeError:
pass
def complete_registration(self, instance):
def complete_registration(self, instance: Module) -> None:
"""Complete registration of instance"""
instance.allmodules = self
instance.hikka = True
@ -299,7 +299,7 @@ class Modules:
self.modules += [instance]
def dispatch(self, command):
def dispatch(self, command: str) -> tuple:
"""Dispatch command to appropriate module"""
change = str.maketrans(ru_keys + en_keys, en_keys + ru_keys)
try:
@ -319,13 +319,13 @@ class Modules:
except KeyError:
return command, None
def send_config(self, db, babel, skip_hook=False):
def send_config(self, db, babel, skip_hook: bool = False) -> None:
"""Configure modules"""
for mod in self.modules:
self.send_config_one(mod, db, babel, skip_hook)
@staticmethod
def send_config_one(mod, db, babel=None, skip_hook=False):
def send_config_one(mod, db, babel=None, skip_hook: bool = False) -> None:
"""Send config to single instance"""
if hasattr(mod, "config"):
modcfg = db.get(mod.__module__, "__config__", {})
@ -413,7 +413,7 @@ class Modules:
if not self._initial_registration and self.added_modules:
await self.added_modules(self)
def get_classname(self, name):
def get_classname(self, name: str) -> str:
return next(
(
module.__class__.__module__
@ -423,7 +423,7 @@ class Modules:
name,
)
def unload_module(self, classname):
def unload_module(self, classname: str) -> bool:
"""Remove module and all stuff from it"""
worked = []
to_remove = []
@ -433,12 +433,11 @@ class Modules:
worked += [module.__module__]
name = module.__class__.__name__
if use_fs_for_modules():
path = os.path.join(LOADED_MODULES_DIR, f"{name}.py")
path = os.path.join(LOADED_MODULES_DIR, f"{name}.py")
if os.path.isfile(path):
os.remove(path)
logging.debug(f"Removed {name} file")
if os.path.isfile(path):
os.remove(path)
logging.debug(f"Removed {name} file")
logging.debug("Removing module for unload %r", module)
self.modules.remove(module)

View File

@ -105,9 +105,6 @@ def save_config_key(key, value):
return True
save_config_key("use_fs_for_modules", get_config_key("use_fs_for_modules"))
def gen_port():
if "OKTETO" in os.environ:
return 8080
@ -327,19 +324,16 @@ class Hikka:
def _init_web(self) -> None:
"""Initialize web"""
if web_available:
self.web = (
core.Web(
data_root=self.arguments.data_root,
api_token=self.api_token,
proxy=self.proxy,
connection=self.conn,
)
if getattr(self.arguments, "web", True)
else None
)
else:
if web_available and not getattr(self.arguments, "disable_web", False):
self.web = None
return
self.web = core.Web(
data_root=self.arguments.data_root,
api_token=self.api_token,
proxy=self.proxy,
connection=self.conn,
)
def _get_token(self) -> None:
while self.api_token is None:
@ -430,10 +424,6 @@ class Hikka:
client.start(
phone=raise_auth
if self.web
and (
not hasattr(self.arguments, "web")
or self.arguments.web is not False
)
else lambda: input("Phone: ")
)
client.phone = phone

View File

@ -81,6 +81,7 @@ class HikkaInfoMod(loader.Module):
f'<b>🤴 Owner: <a href="tg://user?id={self._me.id}">{utils.escape_html(get_display_name(self._me))}</a></b>\n\n'
f"<b>🔮 Version: </b><i>{'.'.join(list(map(str, list(main.__version__))))}</i>\n"
f"<b>🧱 Build: </b><a href=\"https://github.com/hikariatama/Hikka/commit/{ver}\">{ver[:8] or 'Unknown'}</a>\n"
f"<b>📼 Command prefix: </b>«<code>{utils.escape_html((self._db.get(main.__name__, 'command_prefix', False) or '.')[0] )}</code>»\n"
f"<b>{upd}</b>\n"
f"<b>{utils.get_named_platform()}</b>\n"
),

View File

@ -338,6 +338,46 @@ class HikkaSettingsMod(loader.Module):
}
),
],
[
(
{
"text": "✅ Suggest FS for modules",
"callback": self.inline__setting,
"args": (
"disable_modules_fs",
True,
),
}
if not self._db.get(main.__name__, "disable_modules_fs", False)
else {
"text": "🚫 Suggest FS for modules",
"callback": self.inline__setting,
"args": (
"disable_modules_fs",
False,
),
}
),
(
{
"text": "✅ Always use FS for modules",
"callback": self.inline__setting,
"args": (
"permanent_modules_fs",
False,
),
}
if self._db.get(main.__name__, "permanent_modules_fs", False)
else {
"text": "🚫 Always use FS for modules",
"callback": self.inline__setting,
"args": (
"permanent_modules_fs",
True,
),
}
),
],
[
{
"text": "🔄 Restart",

View File

@ -26,6 +26,8 @@
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# scope: inline
import asyncio
import importlib
import inspect
@ -40,7 +42,8 @@ from importlib.machinery import ModuleSpec
import telethon
from telethon.tl.types import Message
import functools
from typing import Any
from typing import Any, Union
from aiogram.types import CallbackQuery
import requests
@ -169,6 +172,12 @@ class LoaderMod(loader.Module):
"version_incompatible": "🚫 <b>This module requires Hikka {}+\nPlease, update with </b><code>.update</code>",
"ffmpeg_required": "🚫 <b>This module requires FFMPEG, which is not installed</b>",
"developer": "\n\n🧑‍💻 <b>Developer: </b><code>{}</code>",
"module_fs": "💿 <b>Would you like to save this module to filesystem, so it won't get unloaded after restart?</b>",
"save": "💿 Save",
"no_save": "🚫 Don't save",
"save_for_all": "💽 Always save to fs",
"never_save": "🚫 Never save to fs",
"will_save_fs": "💽 Now all modules, loaded with .loadmod will be saved to filesystem",
}
def __init__(self):
@ -268,6 +277,30 @@ class LoaderMod(loader.Module):
except Exception:
logger.exception(f"Failed to load {module_name}")
async def _inline__load(
self,
call: CallbackQuery,
doc: str,
path_: Union[str, None],
mode: str,
) -> None:
save = False
if mode == "all_yes":
self._db.set(main.__name__, "permanent_modules_fs", True)
self._db.set(main.__name__, "disable_modules_fs", False)
await call.answer(self.strings("will_save_fs"))
save = True
elif mode == "all_no":
self._db.set(main.__name__, "disable_modules_fs", True)
self._db.set(main.__name__, "permanent_modules_fs", False)
elif mode == "once":
save = True
if path_ is not None:
await self.load_module(doc, call, origin=path_, save_fs=save)
else:
await self.load_module(doc, call, save_fs=save)
@loader.owner
async def loadmodcmd(self, message: Message) -> None:
"""Loads the module file"""
@ -291,20 +324,79 @@ class LoaderMod(loader.Module):
logger.debug("Loading external module...")
if message.file:
await message.edit("")
message = await message.respond("👩‍🎤")
try:
doc = doc.decode("utf-8")
except UnicodeDecodeError:
await utils.answer(message, self.strings("bad_unicode", message))
return
if (
not self._db.get(main.__name__, "disable_modules_fs", False)
and not self._db.get(main.__name__, "permanent_modules_fs", False)
):
await self.inline.form(
self.strings("module_fs"),
message=message,
reply_markup=[
[
{
"text": self.strings("save"),
"callback": self._inline__load,
"args": (doc, path_, "once"),
},
{
"text": self.strings("no_save"),
"callback": self._inline__load,
"args": (doc, path_, "no"),
},
],
[
{
"text": self.strings("save_for_all"),
"callback": self._inline__load,
"args": (doc, path_, "all_yes"),
}
],
[
{
"text": self.strings("never_save"),
"callback": self._inline__load,
"args": (doc, path_, "all_no"),
}
],
],
)
return
if path_ is not None:
await self.load_module(doc, message, origin=path_)
await self.load_module(
doc,
message,
origin=path_,
save_fs=self._db.get(main.__name__, "permanent_modules_fs", False)
and not self._db.get(main.__name__, "disable_modules_fs", False),
)
else:
await self.load_module(doc, message)
await self.load_module(
doc,
message,
save_fs=self._db.get(main.__name__, "permanent_modules_fs", False)
and not self._db.get(main.__name__, "disable_modules_fs", False),
)
async def load_module(
self, doc, message, name=None, origin="<string>", did_requirements=False
):
self,
doc: str,
message: Message,
name: Union[str, None] = None,
origin: str = "<string>",
did_requirements: bool = False,
save_fs: bool = False,
) -> None:
if any(
line.replace(" ", "") == "#scope:ffmpeg" for line in doc.splitlines()
) and os.system("ffmpeg -version"):
@ -322,12 +414,14 @@ class LoaderMod(loader.Module):
if re.search(r"# ?scope: ?hikka_min", doc):
ver = re.search(
r"# ?scope: ?hikka_min ([0-9]+\.[0-9]+\.[0-9]+)", doc
r"# ?scope: ?hikka_min ([0-9]+\.[0-9]+\.[0-9]+)",
doc,
).group(1)
ver_ = tuple(map(int, ver.split(".")))
if main.__version__ < ver_:
await utils.answer(
message, self.strings("version_incompatible").format(ver)
message,
self.strings("version_incompatible").format(ver),
)
return
@ -347,7 +441,7 @@ class LoaderMod(loader.Module):
try:
try:
spec = ModuleSpec(module_name, StringLoader(doc, origin), origin=origin)
instance = self.allmodules.register_module(spec, module_name, origin)
instance = self.allmodules.register_module(spec, module_name, origin, save_fs=save_fs)
except ImportError as e:
logger.info(
"Module loading failed, attemping dependency installation",
@ -359,7 +453,8 @@ class LoaderMod(loader.Module):
filter(
lambda x: x and x[0] not in ("-", "_", "."),
map(
str.strip, VALID_PIP_PACKAGES.search(doc)[1].split(" ")
str.strip,
VALID_PIP_PACKAGES.search(doc)[1].split(" "),
),
)
)
@ -374,14 +469,16 @@ class LoaderMod(loader.Module):
if did_requirements:
if message is not None:
await utils.answer(
message, self.strings("requirements_restart", message)
message,
self.strings("requirements_restart", message),
)
return True # save to database despite failure, so it will work after restart
if message is not None:
await utils.answer(
message, self.strings("requirements_installing", message)
message,
self.strings("requirements_installing", message),
)
pip = await asyncio.create_subprocess_exec(
@ -402,7 +499,8 @@ class LoaderMod(loader.Module):
if rc != 0:
if message is not None:
await utils.answer(
message, self.strings("requirements_failed", message)
message,
self.strings("requirements_failed", message),
)
return False
@ -410,7 +508,12 @@ class LoaderMod(loader.Module):
importlib.invalidate_caches()
return await self.load_module(
doc, message, name, origin, True
doc,
message,
name,
origin,
True,
save_fs,
) # Try again
except loader.LoadError as e:
if message:
@ -447,7 +550,10 @@ class LoaderMod(loader.Module):
try:
self.allmodules.send_config_one(instance, self._db, self.babel)
await self.allmodules.send_ready_one(
instance, self._client, self._db, self.allclients
instance,
self._client,
self._db,
self.allclients,
)
except loader.LoadError as e:
if message:

View File

@ -48,6 +48,10 @@ from telethon.tl.types import (
Chat,
)
from aiogram.types import CallbackQuery
from .inline.types import InlineCall
import random
from typing import Tuple, Union, List, Any
@ -228,8 +232,11 @@ def relocate_entities(
return entities
async def answer(message: Message, response: str, **kwargs) -> list:
async def answer(message: Union[Message, CallbackQuery], response: str, **kwargs) -> list:
"""Use this to give the response to a command"""
if isinstance(message, (CallbackQuery, InlineCall)):
return await message.edit(response)
if isinstance(message, list):
delete_job = asyncio.ensure_future(
message[0].client.delete_messages(message[0].input_chat, message[1:])

View File

@ -1 +1 @@
__version__ = (1, 0, 12)
__version__ = (1, 0, 13)