mirror of https://github.com/coddrago/Heroku
1.0.13: Include command prefix in inline `info`. Fix `--no-web`. Suggest to save modules to filesystem
parent
6dfaaebf0d
commit
c40b8e016e
|
@ -17,11 +17,14 @@ def compat(code: str) -> str:
|
|||
re.sub(
|
||||
r"^( *)from \.\.inline import (.+)$",
|
||||
r"\1from ..inline.types import \2",
|
||||
re.sub(
|
||||
r"^( *)from \.\.inline import rand[^,]*$",
|
||||
"\1from ..utils import rand",
|
||||
re.sub(
|
||||
r"^( *)from \.\.inline import rand, ?(.+)$",
|
||||
r"\1from ..inline.types import \2\n\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, ?(.+)$",
|
||||
|
@ -34,6 +37,8 @@ def compat(code: str) -> str:
|
|||
flags=re.M,
|
||||
),
|
||||
flags=re.M,
|
||||
),
|
||||
flags=re.M,
|
||||
)
|
||||
for line in code.splitlines()
|
||||
]
|
||||
|
|
|
@ -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,13 +169,15 @@ 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(
|
||||
|
@ -198,14 +190,22 @@ class Modules:
|
|||
|
||||
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,7 +433,6 @@ class Modules:
|
|||
worked += [module.__module__]
|
||||
|
||||
name = module.__class__.__name__
|
||||
if use_fs_for_modules():
|
||||
path = os.path.join(LOADED_MODULES_DIR, f"{name}.py")
|
||||
|
||||
if os.path.isfile(path):
|
||||
|
|
|
@ -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(
|
||||
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,
|
||||
)
|
||||
if getattr(self.arguments, "web", True)
|
||||
else None
|
||||
)
|
||||
else:
|
||||
self.web = None
|
||||
|
||||
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
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:])
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = (1, 0, 12)
|
||||
__version__ = (1, 0, 13)
|
||||
|
|
Loading…
Reference in New Issue