- New installation banner
- Add automatic blob->raw convertion in loader
pull/1/head
hikariatama 2022-06-05 12:58:08 +00:00
parent 993b005adf
commit dc7434e036
4 changed files with 124 additions and 169 deletions

View File

@ -29,6 +29,7 @@
# scope: inline
import asyncio
import contextlib
import importlib
import inspect
import logging
@ -36,6 +37,7 @@ import os
import re
import ast
import sys
import time
import uuid
from collections import ChainMap
from importlib.machinery import ModuleSpec
@ -52,66 +54,15 @@ from ..inline.types import InlineCall
logger = logging.getLogger(__name__)
VALID_URL = r"[-[\]_.~:/?#@!$&'()*+,;%<=>a-zA-Z0-9]+"
VALID_PIP_PACKAGES = re.compile(
r"^\s*# ?requires:(?: ?)((?:{url} )*(?:{url}))\s*$".format(url=VALID_URL),
r"^\s*# ?requires:(?: ?)((?:{url} )*(?:{url}))\s*$".format(
url=r"[-[\]_.~:/?#@!$&'()*+,;%<=>a-zA-Z0-9]+"
),
re.MULTILINE,
)
USER_INSTALL = "PIP_TARGET" not in os.environ and "VIRTUAL_ENV" not in os.environ
GIT_REGEX = re.compile(
r"^https?://github\.com((?:/[a-z0-9-]+){2})(?:/tree/([a-z0-9-]+)((?:/[a-z0-9-]+)*))?/?$",
flags=re.IGNORECASE,
)
def unescape_percent(text):
i = 0
ln = len(text)
is_handling_percent = False
out = ""
while i < ln:
char = text[i]
if char == "%" and not is_handling_percent:
is_handling_percent = True
i += 1
continue
if char == "d" and is_handling_percent:
out += "."
is_handling_percent = False
i += 1
continue
out += char
is_handling_percent = False
i += 1
return out
def get_git_api(url):
m = GIT_REGEX.search(url)
if m is None:
return None
branch = m.group(2)
path_ = m.group(3)
api_url = f"https://api.github.com/repos{m.group(1)}/contents"
if path_ is not None and len(path_) > 0:
api_url += path_
if branch:
api_url += f"?ref={branch}"
return api_url
@loader.tds
class LoaderMod(loader.Module):
@ -129,7 +80,7 @@ class LoaderMod(loader.Module):
"provide_module": "<b>⚠️ Provide a module to load</b>",
"bad_unicode": "<b>🚫 Invalid Unicode formatting in module</b>",
"load_failed": "<b>🚫 Loading failed. See logs for details</b>",
"loaded": "<b>🔭 Module </b><code>{}</code>{}<b> loaded {}</b>{}{}{}",
"loaded": "<b>🔭 Module </b><code>{}</code>{}<b> loaded {}</b>{}{}{}\n\n{}",
"no_class": "<b>What class needs to be unloaded?</b>",
"unloaded": "<b>🧹 Module unloaded.</b>",
"not_unloaded": "<b>🚫 Module not unloaded.</b>",
@ -159,6 +110,7 @@ class LoaderMod(loader.Module):
"add_repo_config_doc": "Additional repos to load from",
"share_link_doc": "Share module link in result message of .dlmod",
"modlink": "\n🌍 <b>Link: </b><code>{}</code>",
"blob_link": "🚸 <b>Do not use `blob` links to download modules. Consider switching to `raw` instead</b>",
}
strings_ru = {
@ -173,7 +125,7 @@ class LoaderMod(loader.Module):
"provide_module": "<b>⚠️ Укажи модуль для загрузки</b>",
"bad_unicode": "<b>🚫 Неверная кодировка модуля</b>",
"load_failed": "<b>🚫 Загрузка не увенчалась успехом. Смотри логи.</b>",
"loaded": "<b>🔭 Модуль </b><code>{}</code>{}<b> загружен {}</b>{}{}{}",
"loaded": "<b>🔭 Модуль </b><code>{}</code>{}<b> загружен {}</b>{}{}{}\n\n{}",
"no_class": "<b>А что выгружать то?</b>",
"unloaded": "<b>🧹 Модуль выгружен.</b>",
"not_unloaded": "<b>🚫 Модуль не выгружен.</b>",
@ -203,13 +155,14 @@ class LoaderMod(loader.Module):
"_cls_doc": "Загружает модули",
"share_link_doc": "Указывать ссылку на модуль после загрузки через .dlmod",
"modlink": "\n🌍 <b>Ссылка: </b><code>{}</code>",
"blob_link": "🚸 <b>Не используй `blob` ссылки для загрузки модулей. Лучше загружать из `raw`</b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"MODULES_REPO",
"https://mods.hikariatama.ru/",
"https://mods.hikariatama.ru",
lambda: self.strings("repo_config_doc"),
validator=loader.validators.Link(),
),
@ -217,17 +170,17 @@ class LoaderMod(loader.Module):
"ADDITIONAL_REPOS",
# Currenly the trusted developers are specified
[
"https://github.com/hikariatama/host/raw/master/",
"https://github.com/MoriSummerz/ftg-mods/raw/main/",
"https://gitlab.com/CakesTwix/friendly-userbot-modules/-/raw/master/",
"https://github.com/hikariatama/host/raw/master",
"https://github.com/MoriSummerz/ftg-mods/raw/main",
"https://github.com/iamnalinor/FTG-modules/raw/main",
"https://gitlab.com/CakesTwix/friendly-userbot-modules/-/raw/master",
],
lambda: self.strings("add_repo_config_doc"),
validator=loader.validators.Series(validator=loader.validators.Link()),
),
loader.ConfigValue(
"share_link",
False,
lambda: self.strings("share_link_doc"),
doc=lambda: self.strings("share_link_doc"),
validator=loader.validators.Boolean(),
),
)
@ -283,22 +236,12 @@ class LoaderMod(loader.Module):
async def dlpresetcmd(self, message: Message) -> None:
"""Set modules preset"""
args = utils.get_args(message)
if not args:
await utils.answer(message, self.strings("select_preset"))
return
try:
await self.get_repo_list(args[0])
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
await utils.answer(message, self.strings("no_preset"))
return
raise
await self.get_repo_list(args[0])
self.set("chosen_preset", args[0])
self.set("loaded_modules", {})
await utils.answer(message, self.strings("preset_loaded"))
await self.allmodules.commands["restart"](
@ -306,7 +249,7 @@ class LoaderMod(loader.Module):
)
async def _get_modules_to_load(self):
preset = self.get("chosen_preset", None)
preset = self.get("chosen_preset")
if preset != "disable":
possible_mods = (await self.get_repo_list(preset)).values()
@ -318,15 +261,27 @@ class LoaderMod(loader.Module):
return todo
async def _get_repo(self, repo: str, preset: str) -> str:
repo = repo.strip("/")
preset_id = f"{repo}/{preset}"
if self._links_cache.get(preset_id, {}).get("exp", 0) >= time.time():
return self._links_cache[preset_id]["data"]
res = await utils.run_sync(
requests.get,
f'{repo.strip("/")}/{preset}.txt',
f"{repo}/{preset}.txt",
)
if not str(res.status_code).startswith("2"):
logger.debug(f"Can't load {repo=}, {preset=}, {res.status_code=}")
return ""
return []
return res.text
self._links_cache[preset_id] = {
"exp": time.time() + 5 * 60,
"data": [link for link in res.text.strip().splitlines() if link],
}
return self._links_cache[preset_id]["data"]
async def get_repo_list(self, preset: Optional[str] = None):
if preset is None or preset == "none":
@ -334,15 +289,8 @@ class LoaderMod(loader.Module):
return {
repo: {
f"Preset_mod_{repo_id}_{i}": f'{repo.strip("/")}/{link}.py'
for i, link in enumerate(
set(
filter(
lambda x: x,
(await self._get_repo(repo, preset)).split("\n"),
)
)
)
f"Mod/{repo_id}/{i}": f'{repo.strip("/")}/{link}.py'
for i, link in enumerate(set(await self._get_repo(repo, preset)))
}
for repo_id, repo in enumerate(
[self.config["MODULES_REPO"]] + self.config["ADDITIONAL_REPOS"]
@ -360,24 +308,38 @@ class LoaderMod(loader.Module):
del links[self.config["MODULES_REPO"]]
return main_repo + converter(links)
async def _find_link(self, module_name: str) -> Union[str, bool]:
links = await self.get_links_list()
return next(
(
link
for link in links
if link.lower().endswith(f"/{module_name.lower()}.py")
),
False,
)
async def download_and_install(
self,
module_name: str,
message: Optional[Message] = None,
):
try:
blob_link = False
module_name = module_name.strip()
if urlparse(module_name).netloc:
url = module_name
if re.match(
r"^(https:\/\/github\.com\/.*?\/.*?\/blob\/.*\.py)|"
r"(https:\/\/gitlab\.com\/.*?\/.*?\/-\/blob\/.*\.py)$",
url,
):
url = url.replace("/blob/", "/raw/")
blob_link = True
else:
links = await self.get_links_list()
url = await self._find_link(module_name)
try:
url = next(
link
for link in links
if link.lower().endswith(f"/{module_name.lower()}.py")
)
except Exception:
if not url:
if message is not None:
await utils.answer(message, self.strings("no_module"))
@ -398,6 +360,7 @@ class LoaderMod(loader.Module):
message,
module_name,
url,
blob_link=blob_link,
)
except Exception:
logger.exception(f"Failed to load {module_name}")
@ -421,10 +384,7 @@ class LoaderMod(loader.Module):
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)
await self.load_module(doc, call, origin=path_ or "<string>", save_fs=save)
@loader.owner
async def loadmodcmd(self, message: Message) -> None:
@ -526,6 +486,7 @@ class LoaderMod(loader.Module):
origin: Optional[str] = "<string>",
did_requirements: Optional[bool] = False,
save_fs: Optional[bool] = False,
blob_link: Optional[bool] = False,
) -> None:
if any(
line.replace(" ", "") == "#scope:ffmpeg" for line in doc.splitlines()
@ -543,10 +504,7 @@ class LoaderMod(loader.Module):
return
if re.search(r"# ?scope: ?hikka_min", doc):
ver = re.search(
r"# ?scope: ?hikka_min ([0-9]+\.[0-9]+\.[0-9]+)",
doc,
).group(1)
ver = re.search(r"# ?scope: ?hikka_min ((\d+\.){2}\d+)", doc).group(1)
ver_ = tuple(map(int, ver.split(".")))
if main.__version__ < ver_:
if isinstance(message, Message):
@ -580,6 +538,8 @@ class LoaderMod(loader.Module):
else ""
)
blob_link = self.strings("blob_link") if blob_link else ""
if name is None:
try:
node = ast.parse(doc)
@ -625,7 +585,7 @@ class LoaderMod(loader.Module):
lambda x: not x.startswith(("-", "_", ".")),
map(
str.strip,
VALID_PIP_PACKAGES.search(doc)[1].split(" "),
VALID_PIP_PACKAGES.search(doc)[1].split(),
),
)
)
@ -681,19 +641,10 @@ class LoaderMod(loader.Module):
importlib.invalidate_caches()
return await self.load_module(
doc,
message,
name,
origin,
True,
save_fs,
) # Try again
return await self.load_module(utils.get_kwargs()) # Try again
except loader.LoadError as e:
try:
with contextlib.suppress(ValueError):
self.allmodules.modules.remove(instance) # skipcq: PYL-E0601
except ValueError:
pass
if message:
await utils.answer(message, f"🚫 <b>{utils.escape_html(str(e))}</b>")
@ -725,20 +676,16 @@ class LoaderMod(loader.Module):
from_dlmod=bool(message),
)
except loader.LoadError as e:
try:
with contextlib.suppress(ValueError):
self.allmodules.modules.remove(instance)
except ValueError:
pass
if message:
await utils.answer(message, f"🚫 <b>{utils.escape_html(str(e))}</b>")
return
except loader.SelfUnload as e:
logging.debug(f"Unloading {instance}, because it raised `SelfUnload`")
try:
with contextlib.suppress(ValueError):
self.allmodules.modules.remove(instance)
except ValueError:
pass
if message:
await utils.answer(message, f"🚫 <b>{utils.escape_html(str(e))}</b>")
@ -768,23 +715,23 @@ class LoaderMod(loader.Module):
if instance.__doc__:
modhelp += f"<i>\n {utils.escape_html(inspect.getdoc(instance))}</i>\n"
loaded_msg = lambda: self.strings("loaded").format(
modname.strip(),
version,
utils.ascii_face(),
modhelp,
developer,
self.strings("modlink").format(origin)
if origin != "<string>" and self.config["share_link"]
else "",
blob_link,
)
if any(
line.replace(" ", "") == "#scope:disable_onload_docs"
for line in doc.splitlines()
):
await utils.answer(
message,
self.strings("loaded").format(
modname.strip(),
version,
utils.ascii_face(),
modhelp,
developer,
self.strings("modlink").format(origin)
if origin != "<string>" and self.config["share_link"]
else "",
),
)
await utils.answer(message, loaded_msg())
return
for _name, fun in sorted(
@ -817,32 +764,9 @@ class LoaderMod(loader.Module):
)
try:
await utils.answer(
message,
self.strings("loaded").format(
modname.strip(),
version,
utils.ascii_face(),
modhelp,
developer,
self.strings("modlink").format(origin)
if origin != "<string>" and self.config["share_link"]
else "",
),
)
await utils.answer(message, loaded_msg())
except telethon.errors.rpcerrorlist.MediaCaptionTooLongError:
await message.reply(
self.strings("loaded").format(
modname.strip(),
version,
utils.ascii_face(),
modhelp,
developer,
self.strings("modlink").format(origin)
if origin != "<string>" and self.config["share_link"]
else "",
)
)
await message.reply(loaded_msg())
@loader.owner
async def unloadmodcmd(self, message: Message) -> None:
@ -913,6 +837,8 @@ class LoaderMod(loader.Module):
self._client = client
self._fully_loaded = False
self._links_cache = {}
self.allmodules.add_aliases(self.lookup("settings").get("aliases", {}))
main.hikka.ready.set()
@ -932,6 +858,7 @@ class LoaderMod(loader.Module):
)
asyncio.ensure_future(self._update_modules())
asyncio.ensure_future(self.get_repo_list("full"))
@loader.loop(interval=3, wait_before=True, autostart=True)
async def _modules_config_autosaver(self):
@ -944,7 +871,7 @@ class LoaderMod(loader.Module):
continue
delattr(mod.config._config[option], "_save_marker")
self._db.setdefault(mod.__class__.__name__, {},).setdefault(
self._db.setdefault(mod.__class__.__name__, {}).setdefault(
"__config__", {}
)[option] = config.value
self._db.save()

View File

@ -85,6 +85,7 @@
</div>
<div class="wrapper">
<div class="blur main finish_block">
<div id="installation_icon"></div>
<div class="title">Hikka is installed</div>
<div class="description">
{% if heroku %}

View File

@ -162,6 +162,18 @@ body {
top: 0;
height: 50vh;
}
.title {
height: auto;
line-height: auto;
}
.finish_block, .auth {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
@keyframes floating {
@ -248,3 +260,8 @@ label {
box-sizing: border-box;
position: absolute;
}
#installation_icon {
height: 200px;
margin-bottom: 35px;
}

View File

@ -1,16 +1,16 @@
function auth(callback) {
bodymovin.loadAnimation({
container: document.getElementById("tg_icon"),
renderer: 'canvas',
loop: true,
autoplay: true,
path: 'https://raw.githubusercontent.com/hikariatama/Hikka/master/assets/noface.json',
rendererSettings: {
clearCanvas: true,
}
});
$(".main").fadeOut(500, () => {
$(".auth").hide().fadeIn(500);
bodymovin.loadAnimation({
container: document.getElementById("tg_icon"),
renderer: 'canvas',
loop: true,
autoplay: true,
path: 'https://raw.githubusercontent.com/hikariatama/Hikka/master/assets/noface.json',
rendererSettings: {
clearCanvas: true,
}
});
fetch("/web_auth", {
method: "POST",
credentials: "include",
@ -80,6 +80,16 @@ function finish_login() {
.then(() => {
window.expanse = true;
$(".installation").fadeOut(2000, () => {
bodymovin.loadAnimation({
container: document.getElementById("installation_icon"),
renderer: 'canvas',
loop: true,
autoplay: true,
path: 'https://assets1.lottiefiles.com/animated_stickers/lf_tgs_j7miwfxd.json',
rendererSettings: {
clearCanvas: true,
}
});
$(".finish_block").fadeIn(300);
});
})