mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Commited backup
This commit is contained in:
8
vsecoder/hikka_modules/.deepsource.toml
Normal file
8
vsecoder/hikka_modules/.deepsource.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
||||
8
vsecoder/hikka_modules/.gitignore
vendored
Normal file
8
vsecoder/hikka_modules/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
test.py
|
||||
assets/chromedriver
|
||||
sel.py
|
||||
ProSpam.py
|
||||
.vscode/settings.json
|
||||
kblayout.py
|
||||
.idea/
|
||||
206
vsecoder/hikka_modules/CheckMods.py
Normal file
206
vsecoder/hikka_modules/CheckMods.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/color/344/antivirus-scanner--v1.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/color/344/antivirus-scanner--v1.png&title=Check%20module&description=Module%20for%20check%20modules
|
||||
|
||||
__version__ = (3, 3, 0)
|
||||
|
||||
import logging
|
||||
import re
|
||||
import requests
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
checker_regex = {
|
||||
"critical": [
|
||||
{"command": "DeleteAccountRequest", "perms": "delete account"},
|
||||
{"command": "edit_2fa", "perms": "change 2FA password"},
|
||||
{"command": "get_me", "perms": "presumably get your profile account data"},
|
||||
{"command": "disconnect", "perms": "disconnect account"},
|
||||
{"command": "log_out", "perms": "disconnect account"},
|
||||
{"command": "ResetAuthorizationRequest", "perms": "kill account sessions"},
|
||||
{
|
||||
"command": "GetAuthorizationsRequest",
|
||||
"perms": "get telegram api_id and api_hash",
|
||||
},
|
||||
{"command": "AddRequest", "perms": "get telegram api_id and api_hash"},
|
||||
{"command": "pyarmor", "perms": "all(obfuscated script)"},
|
||||
{"command": "pyrogram", "perms": "another tg client"},
|
||||
{"command": "system", "perms": "presumably eval commands"},
|
||||
{"command": "eval", "perms": "presumably eval python code"},
|
||||
{"command": "exec", "perms": "presumably exec python code"},
|
||||
{
|
||||
"command": "sessions",
|
||||
"perms": "get all sessions data, delete sessoins, copy and send sessions",
|
||||
},
|
||||
{"command": "subprocess", "perms": "eval commands"},
|
||||
{"command": "torpy", "perms": "download viruses"},
|
||||
{"command": "httpimport", "perms": "import malicious scripts"},
|
||||
],
|
||||
"warn": [
|
||||
{"command": "list_sessions", "perms": "get all account sessions"},
|
||||
{"command": "LeaveChannelRequest", "perms": "leave channel and chats"},
|
||||
{"command": "JoinChannelRequest", "perms": "join channel and chats"},
|
||||
{
|
||||
"command": "ChannelAdminRights",
|
||||
"perms": "edit channel and chats users perms",
|
||||
},
|
||||
{"command": "EditBannedRequest", "perms": "kick and ban users"},
|
||||
{"command": "remove", "perms": "presumably remove files"},
|
||||
{"command": "rmdir", "perms": "presumably remove dirs"},
|
||||
{"command": "telethon", "perms": "telethon funcs"},
|
||||
{"command": "get_response", "perms": "get telegram messages"},
|
||||
],
|
||||
"council": [
|
||||
{"command": "requests", "perms": "send requests"},
|
||||
{"command": "get_entity", "perms": "get entities"},
|
||||
{"command": "get_dialogs", "perms": "get dialogs"},
|
||||
{"command": "os", "perms": "presumably get os info"},
|
||||
{"command": "sys", "perms": "presumably get sys info"},
|
||||
{"command": "import", "perms": "import modules"},
|
||||
{"command": "client", "perms": "all client functions"},
|
||||
{"command": "send_message", "perms": "send messages"},
|
||||
{"command": "send_file", "perms": "send files"},
|
||||
{"command": "TelegramClient", "perms": "create new session"},
|
||||
{"command": "download_file", "perms": "download telegram files"},
|
||||
{"command": "ModuleConfig", "perms": "create configs"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CheckModulesMod(loader.Module):
|
||||
"""Module for check modules"""
|
||||
|
||||
strings = {
|
||||
"name": "Check module",
|
||||
"cfg_lingva_url": (
|
||||
"Check the module for suspicious features, scam, and find out what the"
|
||||
" module has access to"
|
||||
),
|
||||
"answer": (
|
||||
"🔍 <b>Module check complete</b>:\n\n⛔️ Criticals:\n{0}\n🟡 Warns:\n{1}\n✅"
|
||||
" Councils:\n{2}"
|
||||
),
|
||||
"component": " ▪️ «<code>{0}</code>» in module have permissions on <i>{1}</i>",
|
||||
"error": (
|
||||
"Error!\n\n.checkmod <module_link>\n.checkmod"
|
||||
" https://raw.githubusercontent.com/vsecoder/hikka_modules/main/googleit.py"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"cfg_lingva_url": (
|
||||
"Проверьте модуль на подозрительные возможности, скам, и узнайте к чему"
|
||||
" есть доступ у модуля"
|
||||
),
|
||||
"answer": (
|
||||
"🔍 <b>Проверка модуля завершена</b>:\n\n⛔️ Критические:\n{0}\n🟡"
|
||||
" Предупреждения:\n{1}\n✅ Советы:\n{2}"
|
||||
),
|
||||
"component": " ▪️ «<code>{0}</code>» в модуле имеет разрешения на <i>{1}</i>",
|
||||
"error": (
|
||||
"Ошибка!\n\n.checkmod <module_link>\n.checkmod"
|
||||
" https://raw.githubusercontent.com/vsecoder/hikka_modules/main/googleit.py"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
async def check_m(self, args):
|
||||
string = args
|
||||
critical = ""
|
||||
warn = ""
|
||||
council = ""
|
||||
for command in checker_regex["critical"]:
|
||||
r = re.search(command["command"], string)
|
||||
if r is not None:
|
||||
critical = (
|
||||
critical
|
||||
+ self.strings["component"].format(
|
||||
command["command"], command["perms"]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
if not critical:
|
||||
critical = " ▪️ ➖\n"
|
||||
|
||||
for command in checker_regex["warn"]:
|
||||
r = re.search(command["command"], string)
|
||||
if r is not None:
|
||||
warn = (
|
||||
warn
|
||||
+ self.strings["component"].format(
|
||||
command["command"], command["perms"]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
if not warn:
|
||||
warn = " ▪️ ➖\n"
|
||||
|
||||
for command in checker_regex["council"]:
|
||||
r = re.search(command["command"], string)
|
||||
if r is not None:
|
||||
council = (
|
||||
council
|
||||
+ self.strings["component"].format(
|
||||
command["command"], command["perms"]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
if not council:
|
||||
council = " ▪️ ➖\n"
|
||||
|
||||
return self.strings["answer"].format(critical, warn, council, args)
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def checkmodcmd(self, message):
|
||||
"""
|
||||
<module_link> or "reply file" or "send file" - perform module check
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
with contextlib.suppress(Exception):
|
||||
r = await utils.run_sync(requests.get, args)
|
||||
string = r.text
|
||||
await utils.answer(message, await self.check_m(string))
|
||||
return
|
||||
|
||||
try:
|
||||
code_from_message = (
|
||||
await self._client.download_file(message.media, bytes)
|
||||
).decode("utf-8")
|
||||
except Exception:
|
||||
code_from_message = ""
|
||||
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
code_from_reply = (
|
||||
await self._client.download_file(reply.media, bytes)
|
||||
).decode("utf-8")
|
||||
except Exception:
|
||||
code_from_reply = ""
|
||||
|
||||
args = code_from_message or code_from_reply
|
||||
await utils.answer(message, await self.check_m(args))
|
||||
21
vsecoder/hikka_modules/LICENSE
Normal file
21
vsecoder/hikka_modules/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Всеволод
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
295
vsecoder/hikka_modules/Limoka.py
Normal file
295
vsecoder/hikka_modules/Limoka.py
Normal file
@@ -0,0 +1,295 @@
|
||||
# meta developer: @limokanews
|
||||
|
||||
from whoosh.index import create_in
|
||||
from whoosh.fields import TEXT, ID, Schema
|
||||
from whoosh.qparser import QueryParser, OrGroup
|
||||
from whoosh.query import FuzzyTerm, Wildcard
|
||||
|
||||
import aiohttp
|
||||
import random
|
||||
import logging
|
||||
import os
|
||||
import html
|
||||
import json
|
||||
|
||||
from telethon.types import Message
|
||||
from .. import utils, loader
|
||||
|
||||
|
||||
logger = logging.getLogger("Limoka")
|
||||
|
||||
|
||||
class Search:
|
||||
def __init__(self, query: str):
|
||||
self.schema = Schema(
|
||||
title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)
|
||||
)
|
||||
self.query = query
|
||||
|
||||
def search_module(self, content):
|
||||
if not os.path.exists("limoka_search"):
|
||||
os.makedirs("limoka_search")
|
||||
|
||||
ix = create_in("limoka_search", self.schema)
|
||||
writer = ix.writer()
|
||||
|
||||
for module_content in content:
|
||||
writer.add_document(
|
||||
title=module_content["id"],
|
||||
path=module_content["id"],
|
||||
content=module_content["content"],
|
||||
)
|
||||
writer.commit()
|
||||
|
||||
with ix.searcher() as searcher:
|
||||
parser = QueryParser("content", ix.schema, group=OrGroup)
|
||||
query = parser.parse(self.query)
|
||||
|
||||
fuzzy_query = FuzzyTerm("content", self.query, maxdist=1, prefixlength=2)
|
||||
wildcard_query = Wildcard("content", f"*{self.query}*")
|
||||
|
||||
results = searcher.search(query)
|
||||
|
||||
if not results:
|
||||
results = searcher.search(fuzzy_query)
|
||||
if not results:
|
||||
results = searcher.search(wildcard_query)
|
||||
|
||||
if results:
|
||||
best_match = results[0]
|
||||
return best_match["path"]
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class LimokaAPI:
|
||||
async def get_all_modules(self) -> dict:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
"https://git.vsecoder.dev/root/limoka/-/raw/main/modules.json"
|
||||
) as response:
|
||||
text = await response.text()
|
||||
return json.loads(text)
|
||||
|
||||
async def get_module_raw(self, module_path: str) -> str:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"https://git.vsecoder.dev/root/limoka/-/raw/main/{module_path}"
|
||||
) as response:
|
||||
return await response.text()
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Limoka(loader.Module):
|
||||
"""Hikka modules are now in one place with easy searching!"""
|
||||
|
||||
strings = {
|
||||
"name": "Limoka",
|
||||
"wait": (
|
||||
"Just wait"
|
||||
"\n<emoji document_id=5404630946563515782>🔍</emoji> A search is underway among {count} modules for the query: <code>{query}</code>"
|
||||
"\n"
|
||||
"\n<i>{fact}</i>"
|
||||
),
|
||||
"found": (
|
||||
"<emoji document_id=5413334818047940135>🔍</emoji> Found the module <b>{name}</b> by query: <b>{query}</b>"
|
||||
"\n"
|
||||
"\n<b><emoji document_id=5418376169055602355>ℹ️</emoji> Description:</b> {description}"
|
||||
"\n<b><emoji document_id=5418299289141004396>🧑💻</emoji> Developer:</b> {username}"
|
||||
"\n\n{commands}"
|
||||
"\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm https://git.vsecoder.dev/root/limoka/-/raw/main/{module_path}</code>"
|
||||
),
|
||||
"command_template": "{emoji} <code>{prefix}{command}</code> {description}\n",
|
||||
"emojis": {
|
||||
1: "<emoji document_id=5416037945909987712>1️⃣</emoji>",
|
||||
2: "<emoji document_id=5413855071731470617>2️⃣</emoji>",
|
||||
3: "<emoji document_id=5416068826724850291>3️⃣</emoji>",
|
||||
4: "<emoji document_id=5415843998071803071>4️⃣</emoji>",
|
||||
5: "<emoji document_id=5415684843763686989>5️⃣</emoji>",
|
||||
6: "<emoji document_id=5415975458430796879>6️⃣</emoji>",
|
||||
7: "<emoji document_id=5415769763857060166>7️⃣</emoji>",
|
||||
8: "<emoji document_id=5416006506749383505>8️⃣</emoji>",
|
||||
9: "<emoji document_id=5415963015910544694>9️⃣</emoji>",
|
||||
},
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Not found by query: <i>{query}</i></b>",
|
||||
"noargs": "<emoji document_id=5210952531676504517>❌</emoji> <b>No args</b>",
|
||||
"?": "<emoji document_id=5951895176908640647>🔎</emoji> Request too short / not found",
|
||||
"no_info": "No information",
|
||||
"facts": [
|
||||
"<emoji document_id=5472193350520021357>🛡</emoji> The limoka catalog is carefully moderated!",
|
||||
"<emoji document_id=5940434198413184876>🚀</emoji> Limoka performance allows you to search for modules quickly!",
|
||||
],
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"wait": (
|
||||
"Подождите"
|
||||
"\n<emoji document_id=5404630946563515782>🔍</emoji> Идёт поиск среди {count} модулей по запросу: <code>{query}</code>"
|
||||
"\n"
|
||||
"\n<i>{fact}</i>"
|
||||
),
|
||||
"found": (
|
||||
"<emoji document_id=5413334818047940135>🔍</emoji> Найден модуль <b>{name}</b> по запросу: <b>{query}</b>"
|
||||
"\n"
|
||||
"\n<b><emoji document_id=5418376169055602355>ℹ️</emoji> Описание:</b> {description}"
|
||||
"\n<b><emoji document_id=5418299289141004396>🧑💻</emoji> Разработчик:</b> {username}"
|
||||
"\n"
|
||||
"\n{commands}"
|
||||
"\n"
|
||||
"\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm https://git.vsecoder.dev/root/limoka/-/raw/main/{module_path}</code>"
|
||||
),
|
||||
"command_template": "{emoji} <code>{prefix}{command}</code> {description}\n",
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Не найдено по запросу: <i>{query}</i></b>",
|
||||
"noargs": "<emoji document_id=5210952531676504517>❌</emoji> <b>Нет аргументов</b>",
|
||||
"?": "<emoji document_id=5951895176908640647>🔎</emoji> Запрос слишком короткий / не найден",
|
||||
"no_info": "Нет информации",
|
||||
"facts": [
|
||||
"<emoji document_id=5472193350520021357>🛡</emoji> Каталог лимоки тщательно модерируется!",
|
||||
"<emoji document_id=5940434198413184876>🚀</emoji> Производительность лимоки позволяет вам искать модули с невероятной скоростью",
|
||||
],
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
def __init__(self):
|
||||
self.api = LimokaAPI()
|
||||
|
||||
@loader.command()
|
||||
async def limoka(self, message: Message):
|
||||
"""[query] - Search module"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if len(args) <= 1:
|
||||
return await utils.answer(message, self.strings["?"])
|
||||
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings["noargs"])
|
||||
|
||||
modules = await self.api.get_all_modules()
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["wait"].format(
|
||||
count=len(modules),
|
||||
fact=random.choice(self.strings["facts"]),
|
||||
query=args,
|
||||
),
|
||||
)
|
||||
|
||||
modules = await self.api.get_all_modules()
|
||||
|
||||
contents = []
|
||||
|
||||
for module_path, module_data in modules.items():
|
||||
contents.append(
|
||||
{
|
||||
"id": module_path,
|
||||
"content": module_data["name"],
|
||||
}
|
||||
)
|
||||
|
||||
for module_path, module_data in modules.items():
|
||||
contents.append(
|
||||
{
|
||||
"id": module_path,
|
||||
"content": module_data["description"],
|
||||
}
|
||||
)
|
||||
|
||||
for module_path, module_data in modules.items():
|
||||
for func in module_data["commands"]:
|
||||
for command, description in func.items():
|
||||
contents.append({"id": module_path, "content": command})
|
||||
contents.append({"id": module_path, "content": description})
|
||||
|
||||
searcher = Search(args.lower())
|
||||
try:
|
||||
result = searcher.search_module(contents)
|
||||
except IndexError:
|
||||
return await utils.answer(message, self.strings["?"])
|
||||
|
||||
module_path = result
|
||||
|
||||
if module_path is None or module_path == 0:
|
||||
return await utils.answer(message, self.strings["404"].format(query=args))
|
||||
|
||||
module_info = modules[module_path]
|
||||
|
||||
dev_username = module_info["meta"].get("developer", "Unknown")
|
||||
|
||||
commands = []
|
||||
command_count = 0
|
||||
end_count_cmds = False
|
||||
for func in module_info["commands"]:
|
||||
if end_count_cmds:
|
||||
break
|
||||
for command, description in func.items():
|
||||
if command_count == 9:
|
||||
commands.append("...")
|
||||
end_count_cmds = True
|
||||
break
|
||||
command_count += 1
|
||||
emoji = self.strings["emojis"].get(command_count, "")
|
||||
commands.append(
|
||||
self.strings["command_template"].format(
|
||||
prefix=self.get_prefix(),
|
||||
command=html.escape(command.replace("cmd", "")),
|
||||
emoji=emoji,
|
||||
description=(
|
||||
html.escape(description)
|
||||
if description
|
||||
else self.strings["no_info"]
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
name = module_info["name"]
|
||||
description = (
|
||||
html.escape(module_info["description"])
|
||||
if module_info["description"]
|
||||
else self.strings["no_info"]
|
||||
)
|
||||
banner = module_info["meta"]["banner"]
|
||||
|
||||
if description:
|
||||
translated_desc = await self._client.translate(
|
||||
message.peer_id,
|
||||
message,
|
||||
to_lang=self._db.get("hikka.translations", "lang", "en")[0:2],
|
||||
raw_text=description,
|
||||
entities=message.entities,
|
||||
)
|
||||
|
||||
try:
|
||||
await utils.answer_file(
|
||||
message,
|
||||
banner,
|
||||
self.strings["found"].format(
|
||||
query=args,
|
||||
name=name if name else self.strings["no_info"],
|
||||
description=(
|
||||
translated_desc if description else self.strings["no_info"]
|
||||
),
|
||||
username=dev_username,
|
||||
commands="".join(commands),
|
||||
prefix=self.get_prefix(),
|
||||
module_path=module_path.replace("\\", "/"),
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["found"].format(
|
||||
query=args,
|
||||
name=name if name else self.strings["no_info"],
|
||||
description=(
|
||||
translated_desc if description else self.strings["no_info"]
|
||||
),
|
||||
username=dev_username,
|
||||
commands="".join(commands),
|
||||
prefix=self.get_prefix(),
|
||||
module_path=module_path,
|
||||
),
|
||||
)
|
||||
162
vsecoder/hikka_modules/MangaSlider.py
Normal file
162
vsecoder/hikka_modules/MangaSlider.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/color/256/kakashi-hatake.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/color/256/kakashi-hatake.png&title=MangaSlider&description=Read%20manga%20in%20Telegram%20%F0%9F%91%8D
|
||||
|
||||
__version__ = (2, 0, 1)
|
||||
|
||||
import logging
|
||||
from aiogram.types import Message as AiogramMessage
|
||||
from .. import loader # type: ignore
|
||||
from ..inline.types import InlineCall # type: ignore
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class MangaSliderMod(loader.Module):
|
||||
strings = {"name": "MangaSlider"}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.__doc__ = (
|
||||
"Модуль для чтения манги 👨💻[beta]\n\n🔗 Ссылка:"
|
||||
f" t.me/{self.inline.bot_username}?start=manga\n\n"
|
||||
"В будущем ожидается или перенос в бота, "
|
||||
"или добавление полноценного функционала."
|
||||
)
|
||||
|
||||
async def requests(self, data):
|
||||
_api = "https://api.newmanga.org/"
|
||||
_storage = "https://storage.newmanga.org/"
|
||||
|
||||
_all_chapters = _api + "v3/branches/{}/chapters/all" # paste manga id
|
||||
_all_pages = _api + "v3/chapters/{}/pages" # paste chapter id
|
||||
|
||||
_image = (
|
||||
_storage + "origin_proxy/{}/{}/{}"
|
||||
) # paste disk name, chapter id and file name
|
||||
|
||||
chapters = requests.get(_all_chapters.format(data["name"])).json()
|
||||
charapter = chapters[data["chapter"]]
|
||||
charapter_id = charapter["id"]
|
||||
disk = charapter["origin"]
|
||||
tom = charapter["tom"]
|
||||
pages_count = charapter["pages"]
|
||||
|
||||
if data["page"] > pages_count:
|
||||
return {"error": "❗️ Это последняя страница"}
|
||||
|
||||
pages = requests.get(_all_pages.format(charapter_id)).json()
|
||||
page = pages["pages"][data["page"]]["slices"][0]["path"]
|
||||
|
||||
return {
|
||||
"image": _image.format(disk, charapter_id, page),
|
||||
"page": f"{data['page'] + 1}/{pages_count}",
|
||||
"chapter": f"{data['chapter'] + 1}/{len(chapters)}",
|
||||
"tom": tom,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
async def _markup(self, data):
|
||||
return self.inline.generate_markup(
|
||||
[
|
||||
[
|
||||
{
|
||||
"text": "◀️",
|
||||
"data": f"manga/undo/{data['name']}/{data['page']}/{data['chapter']}",
|
||||
},
|
||||
{
|
||||
"text": "▶️",
|
||||
"data": f"manga/next/{data['name']}/{data['page']}/{data['chapter']}",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "▶️ Следующая глава",
|
||||
"data": f"manga/next_chapter/{data['name']}/{data['page']}/{data['chapter']}",
|
||||
}
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
async def aiogram_watcher(self, message: AiogramMessage):
|
||||
if self._client._tg_id == message.chat.id and message.text:
|
||||
if message.text == "/start manga":
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
"""
|
||||
👨💻 <b>Привет, чтобы продолжить введи <code>/read</code> с параметром - номером манги, который можно получить с сайта https://newmanga.org, пример:</b>
|
||||
|
||||
▪️ Клинок, рассекающий демонов - https://newmanga.org/p/blade-of-demon-destruction/<code>4774</code>/r/85016
|
||||
Для чтения манги введите команду <code>/read 4774</code> что бы начать с первой главы
|
||||
""",
|
||||
)
|
||||
elif message.text.split(" ")[0] == "/read":
|
||||
args = message.text.split(" ")
|
||||
if len(args) != 2:
|
||||
return await self.inline.bot.send_message(
|
||||
self._tg_id, "❗️ Неправильно указан агрумент"
|
||||
)
|
||||
|
||||
page = 0
|
||||
data = {"name": args[1], "page": page, "chapter": 0}
|
||||
|
||||
_markup = await self._markup(data)
|
||||
|
||||
r = await self.requests(data)
|
||||
await self.inline.bot.send_photo(
|
||||
self._tg_id,
|
||||
r["image"],
|
||||
r["page"],
|
||||
reply_markup=_markup,
|
||||
)
|
||||
|
||||
async def feedback_callback_handler(self, call: InlineCall):
|
||||
if not call.data.startswith("manga"):
|
||||
return
|
||||
|
||||
args = call.data.replace("manga/", "").split("/")
|
||||
|
||||
data = {
|
||||
"name": args[1],
|
||||
"page": int(args[2]),
|
||||
"chapter": int(args[3]),
|
||||
}
|
||||
|
||||
if args[0] == "undo":
|
||||
if data["page"] == 0:
|
||||
return await self.inline.bot.answer_callback_query(
|
||||
call.id, "❗️ Это первая страница"
|
||||
)
|
||||
data["page"] -= 1
|
||||
elif args[0] == "next":
|
||||
data["page"] += 1
|
||||
elif args[0] == "next_chapter":
|
||||
data["page"] = 0
|
||||
data["chapter"] += 1
|
||||
|
||||
_markup = await self._markup(data)
|
||||
|
||||
r = await self.requests(data)
|
||||
|
||||
if r["error"]:
|
||||
return await self.inline.bot.answer_callback_query(call.id, r["error"])
|
||||
|
||||
text = f"<b>📚 Том</b>: {r['tom']}\n<b>📙 Глава:</b> {r['chapter']}\n<b>📄 Страница:</b> {r['page']}"
|
||||
|
||||
await self.inline.bot.send_photo(
|
||||
self._tg_id, r["image"], text, reply_markup=_markup
|
||||
)
|
||||
await self.inline.bot.delete_message(self._tg_id, call.message.message_id)
|
||||
1
vsecoder/hikka_modules/README.md
Normal file
1
vsecoder/hikka_modules/README.md
Normal file
@@ -0,0 +1 @@
|
||||
https://t.me/vsecoder_m
|
||||
90
vsecoder/hikka_modules/RussianRoulette.py
Normal file
90
vsecoder/hikka_modules/RussianRoulette.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/external-flaticons-lineal-color-flat-icons/344/external-roulette-casino-flaticons-lineal-color-flat-icons-3.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/external-flaticons-lineal-color-flat-icons/344/external-roulette-casino-flaticons-lineal-color-flat-icons-3.png&title=Russian%20roulette&description=Telegram%20Russian%20roulette%20game
|
||||
|
||||
__version__ = (2, 3, 2)
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import random
|
||||
from .. import loader, utils # type: ignore
|
||||
from telethon import functions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class RussianRouletteMod(loader.Module):
|
||||
"""Module for "Russian roulette" game"""
|
||||
|
||||
strings = {
|
||||
"name": "Russian roulette",
|
||||
"cfg_lingva_url": (
|
||||
"1/8 chance of destroying the account, are you taking a chance or are you"
|
||||
" afraid?)"
|
||||
),
|
||||
"answer": "😒 You're lucky, but only now...",
|
||||
"answer2": "😏 * the sound of a gunshot *",
|
||||
"answer3": "🤨 Were you seriously expecting account deletion?",
|
||||
"error": "😡 Ah, EMAE, the revolver broke...",
|
||||
"cfg_real": "If set to `True`, if you lose, your account will be deleted",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"cfg_lingva_url": "Шанс 1/8 на уничтожение аккаунта, рискнешь или боишься?)",
|
||||
"answer": "😒 Тебе повезло, но только сейчас...",
|
||||
"answer2": "😏 * звук выстрела *",
|
||||
"answer3": "🤨 Ты серьёзно ожидал удаление аккаунта?",
|
||||
"error": "😡 Ах, ЁМАЁ, сломался то, револьвер...",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"real",
|
||||
False,
|
||||
self.strings["cfg_real"],
|
||||
validator=loader.validators.Link(),
|
||||
)
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def revolvercmd(self, message):
|
||||
"""
|
||||
- to start "Russian roulette"
|
||||
"""
|
||||
try:
|
||||
roulette = [1]
|
||||
roulette.extend(0 for _ in range(7))
|
||||
result = random.choice(roulette)
|
||||
if result != 1:
|
||||
await utils.answer(message, self.strings["answer"])
|
||||
else:
|
||||
await utils.answer(message, self.strings["answer2"])
|
||||
await asyncio.sleep(3)
|
||||
await utils.answer(message, "gg")
|
||||
if self.config["real"]:
|
||||
self.client(
|
||||
functions.account.DeleteAccountRequest(
|
||||
reason="Lose in Russian roulette"
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
185
vsecoder/hikka_modules/accounttime.py
Normal file
185
vsecoder/hikka_modules/accounttime.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/fluency/344/timer.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/fluency/344/timer.png&title=Account%20Time&description=Get%20the%20account%20registration%20date%20and%20time!
|
||||
|
||||
__version__ = (2, 5, 0)
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Callable, Tuple
|
||||
import time
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
data = {
|
||||
"1000000": 1380326400,
|
||||
"2768409": 1383264000,
|
||||
"7679610": 1388448000,
|
||||
"11538514": 1391212000,
|
||||
"15835244": 1392940000,
|
||||
"23646077": 1393459000,
|
||||
"38015510": 1393632000,
|
||||
"44634663": 1399334000,
|
||||
"46145305": 1400198000,
|
||||
"54845238": 1411257000,
|
||||
"63263518": 1414454000,
|
||||
"101260938": 1425600000,
|
||||
"101323197": 1426204000,
|
||||
"111220210": 1429574000,
|
||||
"103258382": 1432771000,
|
||||
"103151531": 1433376000,
|
||||
"116812045": 1437696000,
|
||||
"122600695": 1437782000,
|
||||
"109393468": 1439078000,
|
||||
"112594714": 1439683000,
|
||||
"124872445": 1439856000,
|
||||
"130029930": 1441324000,
|
||||
"125828524": 1444003000,
|
||||
"133909606": 1444176000,
|
||||
"157242073": 1446768000,
|
||||
"143445125": 1448928000,
|
||||
"148670295": 1452211000,
|
||||
"152079341": 1453420000,
|
||||
"171295414": 1457481000,
|
||||
"181783990": 1460246000,
|
||||
"222021233": 1465344000,
|
||||
"225034354": 1466208000,
|
||||
"278941742": 1473465000,
|
||||
"285253072": 1476835000,
|
||||
"294851037": 1479600000,
|
||||
"297621225": 1481846000,
|
||||
"328594461": 1482969000,
|
||||
"337808429": 1487707000,
|
||||
"341546272": 1487782000,
|
||||
"352940995": 1487894000,
|
||||
"369669043": 1490918000,
|
||||
"400169472": 1501459000,
|
||||
"616816630": 1529625600,
|
||||
"727572658": 1543708800,
|
||||
"782000000": 1546300800,
|
||||
"925078064": 1563290000,
|
||||
"1974255900": 1634000000,
|
||||
"3318845111": 1618028800,
|
||||
"4317845111": 1620028800,
|
||||
"5336336790": 1646368100,
|
||||
"5396587273": 1648014800,
|
||||
"6020888206": 1675534800,
|
||||
"6057123350": 1676198350,
|
||||
"6554264430": 1695654800,
|
||||
}
|
||||
|
||||
|
||||
class Function:
|
||||
def __init__(self, order: int = 3):
|
||||
self.order = 3
|
||||
|
||||
self.x, self.y = self._unpack_data()
|
||||
self._func = self._fit_data()
|
||||
|
||||
def _unpack_data(self) -> Tuple[list, list]:
|
||||
x_data = np.array(list(map(int, data.keys())))
|
||||
y_data = np.array(list(data.values()))
|
||||
|
||||
return (x_data, y_data)
|
||||
|
||||
def _fit_data(self) -> Callable[[int], int]:
|
||||
fitted = np.polyfit(self.x, self.y, self.order)
|
||||
return np.poly1d(fitted)
|
||||
|
||||
def add_datapoint(self, pair: tuple):
|
||||
pair[0] = str(pair[0])
|
||||
|
||||
data.update([pair])
|
||||
|
||||
# update the model with new data
|
||||
# self.x, self.y = self._unpack_data()
|
||||
self._func = self._fit_data()
|
||||
|
||||
def func(self, tg_id: int) -> int:
|
||||
value = self._func(tg_id)
|
||||
current = time.time()
|
||||
|
||||
if value > current:
|
||||
value = current
|
||||
|
||||
return value
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AcTimeMod(loader.Module):
|
||||
"""Module for get account time"""
|
||||
|
||||
strings = {
|
||||
"name": "Account Time",
|
||||
"info": "Get the account registration date and time!",
|
||||
"error": "Error!",
|
||||
"answer": (
|
||||
"⏳ This account: {0}\n🕰 A registered: {1}\n\nP.S. The module script is"
|
||||
" trained with the number of requests from different ids, so the data can"
|
||||
" be refined"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"info": "Узнай дату регистрации аккаунта, и время, которое вы его используете!",
|
||||
"error": "Ошибка!",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
def time_format(self, unix_time: int, fmt="%Y-%m-%d") -> list:
|
||||
result = [str(datetime.utcfromtimestamp(unix_time).strftime(fmt))]
|
||||
|
||||
d = relativedelta(datetime.now(), datetime.utcfromtimestamp(unix_time))
|
||||
result.append(f"{d.years} years, {d.months} months, {d.days} days")
|
||||
|
||||
return result
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def actimecmd(self, message):
|
||||
"""
|
||||
- get the account registration date and time [beta]
|
||||
P.S. You can also send a command in response to a message
|
||||
"""
|
||||
try:
|
||||
interpolation = Function()
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if reply:
|
||||
date = self.time_format(
|
||||
unix_time=round(interpolation.func(int(reply.sender.id)))
|
||||
)
|
||||
else:
|
||||
date = self.time_format(
|
||||
unix_time=round(interpolation.func(int(message.from_id)))
|
||||
)
|
||||
|
||||
await utils.answer(message, self.strings["answer"].format(date[0], date[1]))
|
||||
except Exception as e:
|
||||
await utils.answer(message, f'{self.strings["error"]}\n\n{e}')
|
||||
if message.out:
|
||||
await asyncio.sleep(5)
|
||||
await message.delete()
|
||||
126
vsecoder/hikka_modules/ascii.py
Normal file
126
vsecoder/hikka_modules/ascii.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/color/344/asc.png
|
||||
# requires: Pillow
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/color/344/asc.png&title=AsciiMod&description=Module%20for%20convert%20image%20to%20ascii
|
||||
|
||||
__version__ = (0, 0, 1)
|
||||
|
||||
import logging
|
||||
from .. import loader, utils # type: ignore
|
||||
import imgkit # type: ignore
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from image2ascii.core import Image2ASCII # type: ignore
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AsciiMod(loader.Module):
|
||||
"""Module for convert image to ascii"""
|
||||
|
||||
strings = {
|
||||
"name": "AsciiMod",
|
||||
"loading_image": "⏳ Downloading image...",
|
||||
"converting_image": "⏳ Converting image...",
|
||||
"save_image": "⏳ Saving image...",
|
||||
"os_error": (
|
||||
"❗️ Install 'wkhtmltopdf'\n\n.terminal sudo apt install wkhtmltopdf"
|
||||
),
|
||||
"type_error": "❗️ Unknown image format!",
|
||||
"another_error": "❗️ Unknown error, please check logs!\n\n{}",
|
||||
"complete": "🖍 Look:",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"loading_image": "⏳ Скачивается изображение...",
|
||||
"converting_image": "⏳ Конвертируется изображение...",
|
||||
"save_image": "⏳ Сохраняется изображение...",
|
||||
"os_error": (
|
||||
"❗️ Установите 'wkhtmltopdf'\n\n.terminal sudo apt install wkhtmltopdf"
|
||||
),
|
||||
"type_error": "❗️ Неизвестный формат изображения!",
|
||||
"another_error": "❗️ Неизвестная ошибка, пожайлуйста проверьте логи!\n\n{}",
|
||||
"complete": "🖍 Смотри:",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"background",
|
||||
"black",
|
||||
lambda m: "Background",
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"color",
|
||||
"white",
|
||||
lambda m: "Color",
|
||||
),
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def asciicmd(self, message):
|
||||
"""
|
||||
<reply_to_image> - convert image to ascii
|
||||
"""
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
await utils.answer(message, self.strings["loading_image"])
|
||||
f = await self._client.download_media(message=reply, file="test.png")
|
||||
await utils.answer(message, self.strings["converting_image"])
|
||||
r = Image2ASCII("test.png").render()
|
||||
|
||||
background = self.config["background"]
|
||||
color = self.config["color"]
|
||||
|
||||
im = Image.open("test.png")
|
||||
width, height = im.size
|
||||
|
||||
options = {"crop-w": width, "crop-h": height, "encoding": "UTF-8"}
|
||||
|
||||
ascii = "".join(
|
||||
str(line).replace(" ", " ") + "<br>" for line in str(r).split("\n")
|
||||
)
|
||||
|
||||
await utils.answer(message, self.strings["save_image"])
|
||||
|
||||
with open("test.html", "w") as f:
|
||||
f.write(
|
||||
f'<html style="background: {background}; color:'
|
||||
f' {color}"><code>{ascii}</code></html>'
|
||||
)
|
||||
|
||||
try:
|
||||
imgkit.from_file("test.html", "out.jpg", options=options)
|
||||
|
||||
await self._client.send_file(
|
||||
utils.get_chat_id(message),
|
||||
open("out.jpg", "rb"),
|
||||
)
|
||||
await utils.answer(message, self.strings["complete"])
|
||||
except OSError:
|
||||
await utils.answer(message, self.strings["os_error"])
|
||||
except TypeError:
|
||||
await utils.answer(message, self.strings["type_error"])
|
||||
except Exception as e:
|
||||
await utils.answer(message, self.strings["another_error"].format(e))
|
||||
BIN
vsecoder/hikka_modules/assets/fone.jpg
Normal file
BIN
vsecoder/hikka_modules/assets/fone.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
34
vsecoder/hikka_modules/assets/profile.html
Normal file
34
vsecoder/hikka_modules/assets/profile.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" style="background-color: gray;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<title>Profile</title>
|
||||
</head>
|
||||
|
||||
<body style="width: 540px;height: 220px; background-image: url({});color: black;background-size: 100% 100%;">
|
||||
<div class="container">
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-4" style="padding: 6px;">
|
||||
<img src="data:image/png;base64, {}" class="img-thumbnail" alt="...">
|
||||
</div>
|
||||
<div class="col-8 row align-items-center justify-content-center" style="padding: 6px;">
|
||||
<div class="col-5" style="padding: 6px;">
|
||||
<h1>{}</h1>
|
||||
<p>Chats: {}</p>
|
||||
<p>Channels: {}</p>
|
||||
<p>Users: {}</p>
|
||||
<p>Bots: {}</p>
|
||||
</div>
|
||||
<div class="col-7" style="padding: 15px;">
|
||||
<p>{}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
vsecoder/hikka_modules/assets/spyduckmini.jpg
Normal file
BIN
vsecoder/hikka_modules/assets/spyduckmini.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
95
vsecoder/hikka_modules/biopage.py
Normal file
95
vsecoder/hikka_modules/biopage.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/external-filled-outline-wichaiwi/344/external-page-uxui-design-filled-outline-wichaiwi.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/external-filled-outline-wichaiwi/344/external-page-uxui-design-filled-outline-wichaiwi.png&title=BioPage&description=Module%20for%20create%20bio%20page
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
import logging
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class BioPageMod(loader.Module):
|
||||
"""Module for create bio page"""
|
||||
|
||||
strings = {
|
||||
"name": "Bio Page",
|
||||
"answer": (
|
||||
'📦 The configuration of the <b>BioPage</b> is set to <code>"{0}"</code>'
|
||||
),
|
||||
"error": "❗️ Error, check logs!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"answer": '📦 Конфигурация <b>BioPage</b> установлена <code>"{0}"</code>',
|
||||
"error": "❗️ Ошибка, проверьте логи!",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"toggle",
|
||||
"off",
|
||||
"Toggle bio page on/off",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"bio_url",
|
||||
"https://vsecoder.github.io/tg-web-app/",
|
||||
"Bio page url (restart required to apply)",
|
||||
validator=loader.validators.Link(),
|
||||
),
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._db = db
|
||||
self._client = client
|
||||
self.botfather = "@BotFather"
|
||||
|
||||
async def bot_conifg(self):
|
||||
if self.config["toggle"]:
|
||||
async with self._client.conversation(self.botfather) as conv:
|
||||
await conv.send_message("/setmenubutton")
|
||||
await conv.mark_read()
|
||||
await conv.send_message(f"@{self.inline.bot_username}")
|
||||
await conv.mark_read()
|
||||
await conv.send_message(self.config["bio_url"])
|
||||
await conv.mark_read()
|
||||
await conv.send_message("🔗 Bio")
|
||||
await conv.mark_read()
|
||||
else:
|
||||
async with self._client.conversation(self.botfather) as conv:
|
||||
await conv.send_message("/setmenubutton")
|
||||
await conv.mark_read()
|
||||
await conv.send_message(f"@{self.inline.bot_username}")
|
||||
await conv.mark_read()
|
||||
await conv.send_message("/empty")
|
||||
await conv.mark_read()
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def biotogglecmd(self, message):
|
||||
"""
|
||||
- toggle bio page(default: off)
|
||||
Based on... my code)
|
||||
"""
|
||||
self.config["toggle"] = not self.config["toggle"]
|
||||
await self.bot_conifg()
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["answer"].format("on" if self.config["toggle"] else "off"),
|
||||
)
|
||||
121
vsecoder/hikka_modules/calc.py
Normal file
121
vsecoder/hikka_modules/calc.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/color/344/calculate.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/color/344/calculate.png&title=Calc&description=Module%20for%20inline%20calc
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
import logging
|
||||
from telethon import TelegramClient
|
||||
from .. import loader # type: ignore
|
||||
from ..inline.types import InlineCall # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CalcMod(loader.Module):
|
||||
"""Module for inline calc"""
|
||||
|
||||
strings = {
|
||||
"name": "📟 Calc",
|
||||
"answer": "🧮 <b>Start calculating(press inline buttons):</b>",
|
||||
"calc": "🧮 <i>{0}</i>=<code>{1}</code>",
|
||||
"error": "❗️ Error!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"answer": "🧮 <b>Начните подсчитывать(нажимайте на инлайн кнопки):</b>",
|
||||
"error": "❗️ Ошибка!",
|
||||
}
|
||||
|
||||
async def client_ready(self, client: TelegramClient, db):
|
||||
self._db = db
|
||||
self._client = client
|
||||
|
||||
async def return_keyboard(self, expression):
|
||||
return [
|
||||
# 1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣0️⃣➗✖️➕➖🧮
|
||||
[
|
||||
{"text": "1️⃣", "callback": self.calc, "args": ["1", expression]},
|
||||
{"text": "2️⃣", "callback": self.calc, "args": ["2", expression]},
|
||||
{"text": "3️⃣", "callback": self.calc, "args": ["3", expression]},
|
||||
],
|
||||
[
|
||||
{"text": "4️⃣", "callback": self.calc, "args": ["4", expression]},
|
||||
{"text": "5️⃣", "callback": self.calc, "args": ["5", expression]},
|
||||
{"text": "6️⃣", "callback": self.calc, "args": ["6", expression]},
|
||||
],
|
||||
[
|
||||
{"text": "7️⃣", "callback": self.calc, "args": ["7", expression]},
|
||||
{"text": "8️⃣", "callback": self.calc, "args": ["8", expression]},
|
||||
{"text": "9️⃣", "callback": self.calc, "args": ["9", expression]},
|
||||
],
|
||||
[
|
||||
{"text": "➕", "callback": self.calc, "args": ["+", expression]},
|
||||
{"text": "0️⃣", "callback": self.calc, "args": ["0", expression]},
|
||||
{"text": "➖", "callback": self.calc, "args": ["-", expression]},
|
||||
],
|
||||
[
|
||||
{"text": "➗", "callback": self.calc, "args": ["/", expression]},
|
||||
{"text": "🔙", "callback": self.calc, "args": ["C", expression]},
|
||||
{"text": "✖️", "callback": self.calc, "args": ["*", expression]},
|
||||
],
|
||||
]
|
||||
|
||||
async def calc(self, message: InlineCall, press, expression):
|
||||
if expression == "0":
|
||||
expression = ""
|
||||
if press == "C":
|
||||
expression = expression[:-1]
|
||||
else:
|
||||
expression = expression + press
|
||||
|
||||
a = ["+", "-", "*", "/", "=", "C"]
|
||||
b = False
|
||||
for i in a:
|
||||
if press == i:
|
||||
b = True
|
||||
|
||||
if b is False and expression != "" and expression != "0":
|
||||
result = eval(expression)
|
||||
else:
|
||||
result = ""
|
||||
|
||||
text = self.strings["calc"].format(expression, result)
|
||||
|
||||
keyboard = await self.return_keyboard(expression)
|
||||
|
||||
await message.edit(text=text, reply_markup=keyboard)
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def calccmd(self, message):
|
||||
"""
|
||||
- init calc
|
||||
Based on... my code)
|
||||
"""
|
||||
|
||||
text = self.strings["answer"]
|
||||
|
||||
expression = "0"
|
||||
|
||||
keyboard = await self.return_keyboard(expression)
|
||||
|
||||
await message.delete()
|
||||
await self.inline.form(
|
||||
text=text,
|
||||
message=message,
|
||||
always_allow=[message.from_id],
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
110
vsecoder/hikka_modules/chatgptfree.py
Normal file
110
vsecoder/hikka_modules/chatgptfree.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
|
||||
version = (1, 0, 0)
|
||||
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ChatGPTfreeMod(loader.Module):
|
||||
"""
|
||||
Бесплатный модуль для ChatGPT
|
||||
https://t.me/Jarvis_IT_Assistant_bot
|
||||
Сначала запустите бота и отключите уведомления
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "ChatGPTfree",
|
||||
"loading": "🔄 Ваш запрос обрабатывается...",
|
||||
"no_args": "🚫 Не указан текст для обработки!",
|
||||
"start_text": "<b>🤖 ChatGPT:</b>\n",
|
||||
"context_text": "❕ Создался новый диалог. Предыдущие запросы удалены.",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.gpt_free = "@Jarvis_IT_Assistant_bot"
|
||||
|
||||
async def message_q(
|
||||
self,
|
||||
text: str,
|
||||
user_id: int,
|
||||
mark_read: bool = False,
|
||||
delete: bool = False,
|
||||
ignore_answer: bool = False,
|
||||
):
|
||||
"""Отправляет сообщение и возращает ответ"""
|
||||
async with self.client.conversation(user_id) as conv:
|
||||
msg = await conv.send_message(text)
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
response = await conv.get_response()
|
||||
if mark_read:
|
||||
await conv.mark_read()
|
||||
|
||||
if delete:
|
||||
await msg.delete()
|
||||
await response.delete()
|
||||
|
||||
if ignore_answer:
|
||||
return response
|
||||
|
||||
if "✅ Запрос отправлен" in response.text:
|
||||
continue
|
||||
|
||||
if "Ожидание ответа" in response.text:
|
||||
continue
|
||||
|
||||
return response
|
||||
|
||||
async def chatgptfreecmd(self, message: Message):
|
||||
"""
|
||||
{text} - обработать текст через ChatGPT
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings["no_args"])
|
||||
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
|
||||
response = await self.message_q(
|
||||
args, self.gpt_free, mark_read=True, delete=True, ignore_answer=False
|
||||
)
|
||||
|
||||
text = self.strings["start_text"] + response.text.replace(
|
||||
"/context", "<code>.contextgpt</code>"
|
||||
)
|
||||
|
||||
return await utils.answer(message, text)
|
||||
|
||||
async def contextgptcmd(self, message: Message):
|
||||
"""
|
||||
- сбросить диалог и начать новый
|
||||
"""
|
||||
await self.message_q(
|
||||
"/context", self.gpt_free, mark_read=True, delete=True, ignore_answer=True
|
||||
)
|
||||
return await utils.answer(message, self.strings["context_text"])
|
||||
158
vsecoder/hikka_modules/feedbackbot.py
Normal file
158
vsecoder/hikka_modules/feedbackbot.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/fluency/344/feedback.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/fluency/344/feedback.png&title=Feedback&description=Feedback%20bot%20for%20Hikka%20modules
|
||||
|
||||
__version__ = (3, 0, 1)
|
||||
|
||||
import logging
|
||||
import time
|
||||
from telethon.utils import get_display_name
|
||||
from aiogram.types import Message as AiogramMessage
|
||||
from .. import loader, utils # type: ignore
|
||||
from ..inline.types import InlineCall # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FeedbackBotMod(loader.Module):
|
||||
"""FeedbackBot"""
|
||||
strings = {
|
||||
"name": "📥 Feedback",
|
||||
"start": "✌️ Hi, I'm feedback bot as {}",
|
||||
"fb_message": "📝 Take to send message",
|
||||
"wait": "⏳ You can send next message in {} second(-s)",
|
||||
"start_feedback": (
|
||||
"📝 Write 1 message, and I'll send it to {}\n\n[{} per minute]"
|
||||
),
|
||||
"sent": "📩 Message sent",
|
||||
"banned": "🚫 You are banned",
|
||||
"user_banned": "🚫 {} is banned",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"start": "✌️ Привет, я бот обратной связи {}",
|
||||
"fb_message": "📝 Нажмите для отправки сообщения",
|
||||
"wait": "⏳ Вы можете отправить сообщение через {} секунд(-ы)",
|
||||
"start_feedback": "📝 Напишите сообщение, и я отправлю его {}\n\n[{} в минуту]",
|
||||
"sent": "📩 Сообщение отправлено",
|
||||
"banned": "🚫 Вы забанены",
|
||||
"user_banned": "🚫 {} забанен",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"ratelimit",
|
||||
"1",
|
||||
"Rate limit(in minutes)",
|
||||
validator=loader.validators.Integer(minimum=0),
|
||||
)
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
|
||||
self._name = utils.escape_html(get_display_name(await client.get_me()))
|
||||
|
||||
self._ratelimit = {}
|
||||
self._ban_list = []
|
||||
|
||||
self.__doc__ = (
|
||||
"Module from add feedback bot 👨💻\n\n"
|
||||
"📝 Dev: @vsecoder\n"
|
||||
"📥 Source: github.com/vsecoder/hikka_modules"
|
||||
f"🔗 Feedback link: t.me/{self.inline.bot_username}?start=feedback\n\n"
|
||||
'❌ Toggle in .security "✅ Everyone (inline)" to use'
|
||||
)
|
||||
|
||||
async def aiogram_watcher(self, message: AiogramMessage):
|
||||
if message.text == "/start feedback":
|
||||
if str(message.from_user.id) in map(str, self._ban_list):
|
||||
return await message.answer(self.strings["banned"])
|
||||
|
||||
_markup = self.inline.generate_markup(
|
||||
{"text": self.strings["fb_message"], "data": "fb_message"}
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
self.strings["start"].format(self._name),
|
||||
reply_markup=_markup,
|
||||
)
|
||||
|
||||
if self.inline.gs(message.from_user.id) == "fb_send_message":
|
||||
await self.inline.bot.forward_message(
|
||||
self._tg_id,
|
||||
message.chat.id,
|
||||
message.message_id,
|
||||
)
|
||||
_markup = self.inline.generate_markup(
|
||||
{"text": "🚫 Ban", "data": f"fb_ban/{message.from_user.id}"}
|
||||
)
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
f"{message.chat.id}",
|
||||
reply_markup=_markup,
|
||||
)
|
||||
await message.answer(self.strings["sent"])
|
||||
self._ratelimit[message.from_user.id] = (
|
||||
time.time() + self.config["ratelimit"] * 60
|
||||
)
|
||||
self.inline.ss(message.from_user.id, False)
|
||||
|
||||
@loader.inline_everyone
|
||||
async def feedback_callback_handler(self, call: InlineCall):
|
||||
if call.data == "fb_cancel":
|
||||
self.inline.ss(call.from_user.id, False)
|
||||
await self.inline.bot.delete_message(
|
||||
call.message.chat.id,
|
||||
call.message.message_id,
|
||||
)
|
||||
return
|
||||
|
||||
if call.data.split("/")[0] == "fb_ban":
|
||||
fb_ban_id = call.data.split("/")[1]
|
||||
if str(fb_ban_id) in str(self._ban_list):
|
||||
pass
|
||||
else:
|
||||
self._ban_list.append(fb_ban_id)
|
||||
await call.answer(self.strings["user_banned"].format(fb_ban_id))
|
||||
|
||||
if call.data != "fb_message":
|
||||
return
|
||||
|
||||
if str(call.from_user.id) in str(self._ban_list):
|
||||
await call.answer(
|
||||
self.strings["banned"],
|
||||
show_alert=True,
|
||||
)
|
||||
|
||||
if (
|
||||
call.from_user.id in self._ratelimit
|
||||
and self._ratelimit[call.from_user.id] > time.time()
|
||||
):
|
||||
await call.answer(
|
||||
self.strings["wait"].format(
|
||||
self._ratelimit[call.from_user.id] - time.time()
|
||||
),
|
||||
show_alert=True,
|
||||
)
|
||||
return
|
||||
|
||||
self.inline.ss(call.from_user.id, "fb_send_message")
|
||||
|
||||
await call.answer(
|
||||
self.strings["start_feedback"].format(self._name, self.config["ratelimit"]),
|
||||
)
|
||||
128
vsecoder/hikka_modules/formatter.py
Normal file
128
vsecoder/hikka_modules/formatter.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/fluency/344/pen-1.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/fluency/344/pen-1.png&title=FormatterMod&description=Module%20for%20prettifying%20the%20formatting%20of%20messages
|
||||
|
||||
__version__ = (1, 0, 1)
|
||||
|
||||
import logging
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
import datetime as dt
|
||||
import re
|
||||
from telethon.tl.types import Message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _copy_tl(o, **kwargs):
|
||||
d = o.to_dict()
|
||||
del d["_"]
|
||||
d.update(kwargs)
|
||||
return o.__class__(**d)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class FormatterMod(loader.Module):
|
||||
"""
|
||||
Module for prettifying the formatting of messages 🪛
|
||||
|
||||
📌 For example write:
|
||||
--------------------
|
||||
Hi, now is {now}, today is {today}, yesterday is {yesterday}, my id is {id}, username is @{username}...
|
||||
|
||||
⌨️ Keyboard:
|
||||
~
|
||||
📥 Modules $ https://t.me/vsecoder_m
|
||||
👨💻 Dev $ https://t.me/vsecoder
|
||||
--------------------
|
||||
|
||||
P.S. "~" is a separator for keyboard and message.
|
||||
"$" is a separator for button and link.
|
||||
|
||||
"""
|
||||
|
||||
strings = {"name": "Formatter"}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self.me = await client.get_me()
|
||||
self.html = await self.import_lib(
|
||||
"https://raw.githubusercontent.com/vsecoder/hikka_modules/main/libs/html2.py",
|
||||
suspend_on_error=True,
|
||||
)
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
if (
|
||||
not isinstance(message, Message)
|
||||
or not message.out
|
||||
or message.text.split()
|
||||
and message.text.split()[0].lower() in self.allmodules.commands
|
||||
or utils.remove_html(message.text).startswith(self.get_prefix())
|
||||
):
|
||||
return
|
||||
|
||||
text = message.text
|
||||
|
||||
keyboard = []
|
||||
if len(text.split("\n~\n")) == 2:
|
||||
for key in message.raw_text.split("\n~\n")[1].split("\n"):
|
||||
if len(key.split(" $ ")) == 2:
|
||||
button = key.split(" $ ")[0]
|
||||
CLEANR = re.compile(
|
||||
"<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});"
|
||||
)
|
||||
link = re.sub(CLEANR, "", key.split(" $ ")[1])
|
||||
keyboard.append([{"text": button, "url": link}])
|
||||
|
||||
text = text.split("\n~\n")[0]
|
||||
|
||||
""" Deleted because of bugs
|
||||
raw_cut_text = message.raw_text.split("\n~\n")
|
||||
logger.error(str(raw_cut_text))
|
||||
|
||||
entities = self.html.parse(message.text)[1]
|
||||
|
||||
for entity in entities.copy():
|
||||
if not hasattr(entity, "offset") or not hasattr(entity, "length"):
|
||||
continue
|
||||
|
||||
if entity.offset > len(raw_cut_text):
|
||||
entities.remove(entity)
|
||||
continue
|
||||
|
||||
if entity.offset + entity.length > len(raw_cut_text):
|
||||
entities[entities.index(entity)] = _copy_tl(
|
||||
entity,
|
||||
length=len(raw_cut_text) - entity.offset,
|
||||
)
|
||||
|
||||
text = self.html.unparse(raw_cut_text, entities)
|
||||
"""
|
||||
|
||||
formats = {
|
||||
"{now}": dt.datetime.now(),
|
||||
"{today}": dt.date.today(),
|
||||
"{yesterday}": dt.date.today() - dt.timedelta(days=1),
|
||||
"{id}": self._client.tg_id,
|
||||
"{username}": self.me.username,
|
||||
"{phone}": self.me.phone,
|
||||
"{msg}": message,
|
||||
}
|
||||
|
||||
for key, value in formats.items():
|
||||
text = text.replace(key, utils.escape_html(value))
|
||||
|
||||
if text and text != message.text or keyboard and keyboard != [[]]:
|
||||
await utils.answer(message, text, reply_markup=keyboard)
|
||||
23
vsecoder/hikka_modules/full.txt
Normal file
23
vsecoder/hikka_modules/full.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
CheckMods
|
||||
MangaSlider
|
||||
RussianRoulette
|
||||
accounttime
|
||||
ascii
|
||||
biopage
|
||||
calc
|
||||
chatgptfree
|
||||
feedbackbot
|
||||
formatter
|
||||
googleit
|
||||
hentaimanga
|
||||
hypixel
|
||||
lmfify
|
||||
mazemod
|
||||
monkeytype
|
||||
octocode
|
||||
profile
|
||||
quotes
|
||||
searx
|
||||
speechcensorship
|
||||
steam
|
||||
hh
|
||||
66
vsecoder/hikka_modules/googleit.py
Normal file
66
vsecoder/hikka_modules/googleit.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/bubbles/344/google-logo.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/bubbles/344/google-logo.png&title=GoogleIT&description=Google%20search%20module%20for%20userbot
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class GoogleItMod(loader.Module):
|
||||
"""Module for google search"""
|
||||
|
||||
strings = {
|
||||
"name": "Google it",
|
||||
"cfg_searc_url": "Searcher",
|
||||
"answer": "😒 I advise you to look in the search engine first: ",
|
||||
"error": "Error!\n .googleit | text",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"cfg_searc_url": "Поисковик",
|
||||
"answer": "😒 Советую для начала заглянуть в поисковик: ",
|
||||
"error": "Ошибка!\n \n .googleit | text",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
"search_url",
|
||||
"https://www.google.com/search?q={query}",
|
||||
self.strings["cfg_searc_url"],
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def googleitcmd(self, message):
|
||||
"""
|
||||
{text} - text to search
|
||||
"""
|
||||
args = message.text.replace(f"{self.get_prefix()}googleit ", "")
|
||||
if args:
|
||||
url = self.config["search_url"].format(query=args).replace(" ", "+")
|
||||
await utils.answer(message, f'{self.strings["answer"]}{url}')
|
||||
else:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
await asyncio.sleep(5)
|
||||
await message.delete()
|
||||
191
vsecoder/hikka_modules/hentaimanga.py
Normal file
191
vsecoder/hikka_modules/hentaimanga.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# █▀ █░█ ▄▀█ █▀▄ █▀█ █░█░█
|
||||
# ▄█ █▀█ █▀█ █▄▀ █▄█ ▀▄▀▄▀
|
||||
|
||||
# Copyright 2023 t.me/shadow_modules
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# meta developer: @shadow_modules, @toxicuse, @vsecoder
|
||||
# meta banner: https://i.imgur.com/8UYznku.jpeg
|
||||
|
||||
import requests # type: ignore
|
||||
from .. import loader, utils # type: ignore
|
||||
from telethon.tl.types import Message # type: ignore
|
||||
from ..inline.types import InlineCall # type: ignore
|
||||
|
||||
|
||||
async def request(url: str) -> dict:
|
||||
"""Manga handler"""
|
||||
return (await utils.run_sync(requests.get, url)).json()["data"]
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HentaiMangaMod(loader.Module):
|
||||
strings = {
|
||||
"name": "HentaiManga",
|
||||
"message": "<b>Title:</b> <code>{title}</code>\n<b>Pages:</b> {total}\n<b>Tags:</b> {tags}\n\n"
|
||||
"Command to get this manga: <code>.ghm {api} {id}</code>",
|
||||
"time": "<b>Wait...</b>",
|
||||
"warn-form": (
|
||||
"<b>⚠️ Attention!</b>\n<b>😰 This module is 18+\n"
|
||||
"✉️ In many chats it is prohibited</b>\n<b>✅ If you agree with what you can get"
|
||||
"ban - click on the button below</b>"
|
||||
),
|
||||
"yes": "✅ Yes",
|
||||
"no": "❌ No",
|
||||
"args_error": "<b>Not enough arguments</b>",
|
||||
"not_found": "<b>Not found</b>",
|
||||
}
|
||||
strings_ru = {
|
||||
"message": "<b>Название:</b> <code>{title}</code>\n<b>Страниц:</b> {total}\n<b>Теги:</b> {tags}\n\n"
|
||||
"Команда для получения этой манги: <code>.ghm {api} {id}</code>",
|
||||
"time": "<b>Ожидайте...</b>",
|
||||
"warn-form": (
|
||||
"<b>⚠️ Внимание!</b>\n<b>😰 Данный модуль 18+\n"
|
||||
"✉️ Во многих чатах он запрещен</b>\n<b>✅ Если вы согласны с тем что можете получить"
|
||||
" бан - нажмите на кнопку ниже</b>"
|
||||
),
|
||||
"yes": "✅ Да",
|
||||
"no": "❌ Нет",
|
||||
"args_error": "<b>Недостаточно аргументов</b>",
|
||||
"not_found": "<b>Не найдено</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"janda",
|
||||
"144.22.39.141:3333",
|
||||
"https://github.com/sinkaroid/jandapress",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.apis = {
|
||||
"3hentai": {
|
||||
"random": "http://{janda}/3hentai/random",
|
||||
"get": "http://{janda}/3hentai/get?book={id}",
|
||||
},
|
||||
"asmhentai": {
|
||||
"random": "http://{janda}/asmhentai/random",
|
||||
"get": "http://{janda}/asmhentai/get?book={id}",
|
||||
},
|
||||
"hentaifox": {
|
||||
"random": "http://{janda}/hentaifox/random",
|
||||
"get": "http://{janda}/hentaifox/get?book={id}",
|
||||
},
|
||||
# now not working
|
||||
# "hentai2read": {
|
||||
# "random": "https://{janda}/hentai2read/random",
|
||||
# "get": "https://{janda}/hentai2read/get?book={id}",
|
||||
# },
|
||||
# "nhentai": {
|
||||
# "random": "https://{janda}/nhentai/random",
|
||||
# "get": "https://{janda}/nhentai/get?book={id}",
|
||||
# },
|
||||
# "pururin": {
|
||||
# "random": "https://{janda}/pururin/random",
|
||||
# "get": "https://{janda}/pururin/get?book={id}",
|
||||
# },
|
||||
}
|
||||
|
||||
async def gallery(self, message: Message, mang: dict, api: str = "3hentai"):
|
||||
await self.inline.gallery(
|
||||
caption=self.strings["message"].format(
|
||||
title=mang["title"].replace("[", "").replace("]", ""),
|
||||
total=mang["total"],
|
||||
tags=", ".join(mang["tags"]),
|
||||
api=api,
|
||||
id=mang["id"],
|
||||
),
|
||||
message=message,
|
||||
next_handler=mang["image"],
|
||||
)
|
||||
|
||||
async def warn(self, message: Message):
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=self.strings["warn-form"],
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings["yes"],
|
||||
"callback": self.inline_call_answer,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": self.strings["no"],
|
||||
"callback": self.delete_module,
|
||||
"args": (message,),
|
||||
},
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
@loader.command(alias="rhm")
|
||||
async def rnd_hentai_mangacmd(self, message: Message):
|
||||
"""
|
||||
{hentai_api_name: optional} - рандомная хентай-манга
|
||||
"""
|
||||
|
||||
args = utils.get_args_raw(message).split(" ")
|
||||
|
||||
if not self.db.get(__name__, "warn", False):
|
||||
await self.warn(message)
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings["time"])
|
||||
|
||||
api = args[0] if args and args[0] in self.apis else "3hentai"
|
||||
|
||||
mang = await request(self.apis[api]["random"].format(janda=self.config["janda"]))
|
||||
|
||||
await self.gallery(message, mang, api)
|
||||
|
||||
@loader.command(alias="ghm")
|
||||
async def get_hentai_mangacmd(self, message: Message):
|
||||
"""
|
||||
{hentai_api_name} {id} - получить хентай-мангу
|
||||
"""
|
||||
|
||||
args = utils.get_args_raw(message).split(" ")
|
||||
|
||||
if len(args) < 2:
|
||||
return await utils.answer(message, self.strings["args_error"])
|
||||
|
||||
if not self.db.get(__name__, "warn", False):
|
||||
await self.warn(message)
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings["time"])
|
||||
|
||||
if args[0] not in self.apis:
|
||||
return await utils.answer(message, self.strings["args_error"])
|
||||
|
||||
mang = await request(self.apis[args[0]]["get"].format(id=args[1], janda=self.config['janda']))
|
||||
|
||||
if not mang:
|
||||
return await utils.answer(message, self.strings["not_found"])
|
||||
|
||||
await self.gallery(message, mang, args[0])
|
||||
|
||||
async def inline_call_answer(self, call: InlineCall):
|
||||
self.db.set(__name__, "warn", True)
|
||||
await call.delete()
|
||||
|
||||
@loader.owner
|
||||
async def delete_module(self, call: InlineCall, message):
|
||||
await call.delete()
|
||||
await self.invoke("unloadmod", "HentaiManga", message.peer_id)
|
||||
570
vsecoder/hikka_modules/hh.py
Normal file
570
vsecoder/hikka_modules/hh.py
Normal file
@@ -0,0 +1,570 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://avatars.githubusercontent.com/u/128410002
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://avatars.githubusercontent.com/u/128410002&title=HH&description=Hikkahost%20userbot%20manager%20module
|
||||
|
||||
import os
|
||||
import enum
|
||||
import aiohttp
|
||||
from aiohttp import ClientConnectorError
|
||||
from datetime import datetime, timezone
|
||||
from typing import Union, Optional, Tuple, List, Dict
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
|
||||
FLAGS = {
|
||||
"ad": "🇦🇩", # Андорра
|
||||
"ae": "🇦🇪", # ОАЭ
|
||||
"af": "🇦🇫", # Афганистан
|
||||
"ag": "🇦🇬", # Антигуа и Барбуда
|
||||
"ai": "🇦🇮", # Ангилья
|
||||
"al": "🇦🇱", # Албания
|
||||
"am": "🇦🇲", # Армения
|
||||
"ao": "🇦🇴", # Ангола
|
||||
"aq": "🇦🇶", # Антарктика
|
||||
"ar": "🇦🇷", # Аргентина
|
||||
"at": "🇦🇹", # Австрия
|
||||
"au": "🇦🇺", # Австралия
|
||||
"aw": "🇦🇼", # Аруба
|
||||
"ax": "🇦🇽", # Аландские острова
|
||||
"az": "🇦🇿", # Азербайджан
|
||||
"ba": "🇧🇦", # Босния и Герцеговина
|
||||
"bb": "🇧🇧", # Барбадос
|
||||
"bd": "🇧🇩", # Бангладеш
|
||||
"be": "🇧🇪", # Бельгия
|
||||
"bf": "🇧🇫", # Буркина-Фасо
|
||||
"bg": "🇧🇬", # Болгария
|
||||
"bh": "🇧🇭", # Бахрейн
|
||||
"bi": "🇧🇮", # Бурунди
|
||||
"bj": "🇧🇯", # Бенин
|
||||
"bl": "🇧🇱", # Сен-Бартельми
|
||||
"bm": "🇧🇲", # Бермудские острова
|
||||
"bn": "🇧🇳", # Бруней
|
||||
"bo": "🇧🇴", # Боливия
|
||||
"bq": "🇧🇶", # Бонэйр, Синт-Эстатиус и Саба
|
||||
"br": "🇧🇷", # Бразилия
|
||||
"bs": "🇧🇸", # Багамы
|
||||
"bt": "🇧🇹", # Бутан
|
||||
"bv": "🇧🇻", # остров Буве
|
||||
"bw": "🇧🇼", # Ботсвана
|
||||
"by": "🇧🇾", # Беларусь
|
||||
"bz": "🇧🇿", # Белиз
|
||||
"ca": "🇨🇦", # Канада
|
||||
"cc": "🇨🇨", # Кокосовые (Килинг) острова
|
||||
"cd": "🇨🇩", # Конго - Киншаса
|
||||
"cf": "🇨🇫", # Центральноафриканская Республика
|
||||
"cg": "🇨🇬", # Конго - Браззавиль
|
||||
"ch": "🇨🇭", # Швейцария
|
||||
"ci": "🇨🇮", # Кот-д’Ивуар
|
||||
"ck": "🇨🇰", # Острова Кука
|
||||
"cl": "🇨🇱", # Чили
|
||||
"cm": "🇨🇲", # Камерун
|
||||
"cn": "🇨🇳", # Китай
|
||||
"co": "🇨🇴", # Колумбия
|
||||
"cr": "🇨🇷", # Коста-Рика
|
||||
"cu": "🇨🇺", # Куба
|
||||
"cv": "🇨🇻", # Кабо-Верде
|
||||
"cw": "🇨🇼", # Кюрасао
|
||||
"cx": "🇨🇽", # остров Рождества
|
||||
"cy": "🇨🇾", # Кипр
|
||||
"cz": "🇨🇿", # Чехия
|
||||
"de": "🇩🇪", # Германия
|
||||
"dj": "🇩🇯", # Джибути
|
||||
"dk": "🇩🇰", # Дания
|
||||
"dm": "🇩🇲", # Доминика
|
||||
"do": "🇩🇴", # Доминиканская Республика
|
||||
"dz": "🇩🇿", # Алжир
|
||||
"ec": "🇪🇨", # Эквадор
|
||||
"ee": "🇪🇪", # Эстония
|
||||
"eg": "🇪🇬", # Египет
|
||||
"eh": "🇪🇭", # Западная Сахара
|
||||
"er": "🇪🇷", # Эритрея
|
||||
"es": "🇪🇸", # Испания
|
||||
"et": "🇪🇹", # Эфиопия
|
||||
"fi": "🇫🇮", # Финляндия
|
||||
"fj": "🇫🇯", # Фиджи
|
||||
"fk": "🇫🇰", # Фолклендские острова
|
||||
"fm": "🇫🇲", # Микронезия
|
||||
"fo": "🇫🇴", # Фарерские острова
|
||||
"fr": "🇫🇷", # Франция
|
||||
"ga": "🇬🇦", # Габон
|
||||
"gb": "🇬🇧", # Великобритания
|
||||
"gd": "🇬🇩", # Гренада
|
||||
"ge": "🇬🇪", # Грузия
|
||||
"gf": "🇬🇫", # Французская Гвиана
|
||||
"gg": "🇬🇬", # Гернси
|
||||
"gh": "🇬🇭", # Гана
|
||||
"gi": "🇬🇮", # Гибралтар
|
||||
"gl": "🇬🇱", # Гренландия
|
||||
"gm": "🇬🇲", # Гамбия
|
||||
"gn": "🇬🇳", # Гвинея
|
||||
"gp": "🇬🇵", # Гваделупа
|
||||
"gq": "🇬🇶", # Экваториальная Гвинея
|
||||
"gr": "🇬🇷", # Греция
|
||||
"gs": "🇬🇸", # Южная Георгия и Южные Сандвичевы острова
|
||||
"gt": "🇬🇹", # Гватемала
|
||||
"gu": "🇬🇺", # Гуам
|
||||
"gw": "🇬🇼", # Гвинея-Бисау
|
||||
"gy": "🇬🇾", # Гайана
|
||||
"hk": "🇭🇰", # Гонконг
|
||||
"hm": "🇭🇲", # остров Херд и острова Макдональд
|
||||
"hn": "🇭🇳", # Гондурас
|
||||
"hr": "🇭🇷", # Хорватия
|
||||
"ht": "🇭🇹", # Гаити
|
||||
"hu": "🇭🇺", # Венгрия
|
||||
"id": "🇮🇩", # Индонезия
|
||||
"ie": "🇮🇪", # Ирландия
|
||||
"il": "🇮🇱", # Израиль
|
||||
"im": "🇮🇲", # остров Мэн
|
||||
"in": "🇮🇳", # Индия
|
||||
"io": "🇮🇴", # Британская территория в Индийском океане
|
||||
"iq": "🇮🇶", # Ирак
|
||||
"ir": "🇮🇷", # Иран
|
||||
"is": "🇮🇸", # Исландия
|
||||
"it": "🇮🇹", # Италия
|
||||
"je": "🇯🇪", # Джерси
|
||||
"jm": "🇯🇲", # Ямайка
|
||||
"jo": "🇯🇴", # Иордания
|
||||
"jp": "🇯🇵", # Япония
|
||||
"ke": "🇰🇪", # Кения
|
||||
"kg": "🇰🇬", # Киргизия
|
||||
"kh": "🇰🇭", # Камбоджа
|
||||
"ki": "🇰🇮", # Кирибати
|
||||
"km": "🇰🇲", # Коморы
|
||||
"kn": "🇰🇳", # Сент-Китс и Невис
|
||||
"kp": "🇰🇵", # Корейская Народно-Демократическая Республика
|
||||
"kr": "🇰🇷", # Республика Корея
|
||||
"kw": "🇰🇼", # Кувейт
|
||||
"ky": "🇰🇾", # Каймановы острова
|
||||
"kz": "🇰🇿", # Казахстан
|
||||
"la": "🇱🇦", # Лаос
|
||||
"lb": "🇱🇧", # Ливан
|
||||
"lc": "🇱🇨", # Сент-Люсия
|
||||
"li": "🇱🇮", # Лихтенштейн
|
||||
"lk": "🇱🇰", # Шри-Ланка
|
||||
"lr": "🇱🇷", # Либерия
|
||||
"ls": "🇱🇸", # Лесото
|
||||
"lt": "🇱🇹", # Литва
|
||||
"lu": "🇱🇺", # Люксембург
|
||||
"lv": "🇱🇻", # Латвия
|
||||
"ly": "🇱🇾", # Ливия
|
||||
"my": "🇲🇾",
|
||||
"md": "🇲🇩",
|
||||
"mv": "🇲🇻",
|
||||
"mw": "🇲🇼",
|
||||
"mx": "🇲🇽",
|
||||
"my": "🇲🇾",
|
||||
"mz": "🇲🇿",
|
||||
"na": "🇳🇦",
|
||||
"nc": "🇳🇨",
|
||||
"ne": "🇳🇪",
|
||||
"nf": "🇳🇫",
|
||||
"ng": "🇳🇬",
|
||||
"ni": "🇳🇮",
|
||||
"nl": "🇳🇱",
|
||||
"no": "🇳🇴",
|
||||
"np": "🇳🇵",
|
||||
"nr": "🇳🇷",
|
||||
"nu": "🇳🇺",
|
||||
"nz": "🇳🇿",
|
||||
"om": "🇴🇲",
|
||||
"pa": "🇵🇦",
|
||||
"pe": "🇵🇪",
|
||||
"pf": "🇵🇫",
|
||||
"pg": "🇵🇬",
|
||||
"ph": "🇵🇭",
|
||||
"pk": "🇵🇰",
|
||||
"pl": "🇵🇱",
|
||||
"pm": "🇵🇲",
|
||||
"pn": "🇵🇳",
|
||||
"pr": "🇵🇷",
|
||||
"ps": "🇵🇸",
|
||||
"pt": "🇵🇹",
|
||||
"pw": "🇵🇼",
|
||||
"py": "🇵🇾",
|
||||
"qa": "🇶🇦",
|
||||
"re": "🇷🇪",
|
||||
"ro": "🇷🇴",
|
||||
"rs": "🇷🇸",
|
||||
"ru": "🇷🇺",
|
||||
"rw": "🇷🇼",
|
||||
"sa": "🇸🇦",
|
||||
"sb": "🇸🇧",
|
||||
"sc": "🇸🇨",
|
||||
"sd": "🇸🇩",
|
||||
"se": "🇸🇪",
|
||||
"sg": "🇸🇬",
|
||||
"sh": "🇸🇭",
|
||||
"si": "🇸🇮",
|
||||
"sj": "🇸🇯",
|
||||
"sk": "🇸🇰",
|
||||
"sl": "🇸🇱",
|
||||
"sm": "🇸🇲",
|
||||
"sn": "🇸🇳",
|
||||
"so": "🇸🇴",
|
||||
"sr": "🇸🇷",
|
||||
"ss": "🇸🇸",
|
||||
"st": "🇸🇹",
|
||||
"sv": "🇸🇻",
|
||||
"sx": "🇸🇽",
|
||||
"sy": "🇸🇾",
|
||||
"sz": "🇸🇿",
|
||||
"tc": "🇹🇨",
|
||||
"td": "🇹🇩",
|
||||
"tf": "🇹🇫",
|
||||
"tg": "🇹🇬",
|
||||
"th": "🇹🇭",
|
||||
"tj": "🇹🇯",
|
||||
"tk": "🇹🇰",
|
||||
"tl": "🇹🇱",
|
||||
"tm": "🇹🇲",
|
||||
"tn": "🇹🇳",
|
||||
"to": "🇹🇴",
|
||||
"tr": "🇹🇷",
|
||||
"tt": "🇹🇹",
|
||||
"tv": "🇹🇻",
|
||||
"tw": "🇹🇼",
|
||||
"tz": "🇹🇿",
|
||||
"ua": "🇺🇦",
|
||||
"ug": "🇺🇬",
|
||||
"um": "🇺🇲",
|
||||
"us": "🇺🇸",
|
||||
"va": "🇻🇦",
|
||||
"vc": "🇻🇨",
|
||||
"ve": "🇻🇪",
|
||||
"vg": "🇻🇬",
|
||||
"vi": "🇻🇮",
|
||||
"vn": "🇻🇳",
|
||||
"vu": "🇻🇺",
|
||||
"wf": "🇼🇫",
|
||||
"ws": "🇼🇸",
|
||||
"xk": "🇽🇰",
|
||||
"ye": "🇾🇪",
|
||||
"yt": "🇾🇹",
|
||||
"za": "🇿🇦",
|
||||
"zm": "🇿🇲",
|
||||
"zw": "🇿🇼",
|
||||
}
|
||||
|
||||
|
||||
class Error(enum.Enum):
|
||||
critical = 500
|
||||
not_found = 404
|
||||
unauthorized = 403
|
||||
unknown = 0
|
||||
|
||||
|
||||
class Host:
|
||||
def __init__(
|
||||
self,
|
||||
id: int,
|
||||
name: str,
|
||||
server_id: int,
|
||||
port: int,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
password_hash: str,
|
||||
rate: float,
|
||||
userbot: str,
|
||||
):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.server_id = server_id
|
||||
self.port = port
|
||||
self.start_date = datetime.strptime(start_date, "%Y-%m-%dT%H:%M:%S.%f%z")
|
||||
self.end_date = datetime.strptime(end_date, "%Y-%m-%dT%H:%M:%S.%f%z")
|
||||
self.userbot = userbot
|
||||
self.rate = rate
|
||||
|
||||
|
||||
class API:
|
||||
async def _request(
|
||||
self,
|
||||
url: str,
|
||||
method: str = "GET",
|
||||
params: Optional[Dict] = None,
|
||||
data: Optional[Dict] = None,
|
||||
headers: Optional[Dict] = None,
|
||||
) -> Union[Dict, List[Union[Dict, int]]]:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.request(
|
||||
method, url, params=params, data=data, headers=headers
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
answer = await response.json()
|
||||
|
||||
if "status_code" in answer:
|
||||
return [{"detail": answer["detail"]}, answer["status_code"]]
|
||||
|
||||
return answer if isinstance(answer, dict) else {"data": answer}
|
||||
|
||||
return [{"detail": await response.text()}, response.status]
|
||||
|
||||
except ClientConnectorError:
|
||||
return [{"detail": "Connection error"}, 500]
|
||||
|
||||
except Exception as e:
|
||||
return [{"detail": f"Unknown error: {e}"}, 500]
|
||||
|
||||
|
||||
class HostAPI(API):
|
||||
def __init__(self, url: str, token: str):
|
||||
self.auth_header = {"token": token}
|
||||
self._url = f"{url}/api/host"
|
||||
|
||||
async def check_answer(
|
||||
self, res: Union[Dict, List]
|
||||
) -> Tuple[bool, Union["Error", Dict]]:
|
||||
if isinstance(res, list):
|
||||
for error in Error:
|
||||
if error.value == res[1]:
|
||||
return False, error
|
||||
|
||||
return False, Error.unknown
|
||||
|
||||
return True, res
|
||||
|
||||
async def get_host(self, user_id: Union[str, int]) -> Union[Host, "Error"]:
|
||||
route = f"{self._url}/{user_id}"
|
||||
res = await self._request(route, method="GET", headers=self.auth_header)
|
||||
|
||||
answer = await self.check_answer(res)
|
||||
if not answer[0]:
|
||||
return answer[1]
|
||||
|
||||
host = res["host"]
|
||||
return Host(**host)
|
||||
|
||||
async def action(self, user_id, action):
|
||||
route = f"{self._url}/{user_id}"
|
||||
payload = {"action": action}
|
||||
|
||||
await self._request(
|
||||
route,
|
||||
method="PUT",
|
||||
params=payload,
|
||||
headers=self.auth_header,
|
||||
)
|
||||
|
||||
async def get_stats(self, user_id) -> Dict:
|
||||
return await self._request(
|
||||
f"{self._url}/{user_id}/stats", headers=self.auth_header
|
||||
)
|
||||
|
||||
async def get_status(self, user_id) -> Dict:
|
||||
return await self._request(
|
||||
f"{self._url}/{user_id}/status", headers=self.auth_header
|
||||
)
|
||||
|
||||
async def get_servers(self) -> List:
|
||||
return await self._request(
|
||||
"https://api.hikka.host/api/server/get/all-open"
|
||||
)
|
||||
|
||||
async def get_logs(
|
||||
self, tg_id: Union[str, int], lines: Union[str, int] = "all"
|
||||
) -> Dict:
|
||||
route = f"{self._url}/{tg_id}/logs/{lines}"
|
||||
return await self._request(route, method="GET", headers=self.auth_header)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HHMod(loader.Module):
|
||||
"""@hikkahost userbot manager module"""
|
||||
|
||||
strings = {
|
||||
"name": "HH",
|
||||
"info": (
|
||||
"<emoji document_id=5413334818047940135>👤</emoji> <b>Info for</b> <code>{id}</code>\n\n"
|
||||
"<emoji document_id=5418136591484865679>📶</emoji> <b>Status:</b> {status}\n"
|
||||
"<emoji document_id=5415992848753379520>⚙️</emoji> <b>Server:</b> {server}\n"
|
||||
"<emoji document_id=5416042764863293485>❤️</emoji> <b>The subscription expires after</b> <code>{days_end} days</code>\n"
|
||||
"{stats}\n"
|
||||
"{warns}"
|
||||
),
|
||||
"logs": "<emoji document_id=5411608069396254249>📄</emoji> All docker container logs from the userbot\n\n<i>In t.me/hikkahost_bot/hhapp logs more readable</i>",
|
||||
"stats": "<emoji document_id=5413394354884596702>💾</emoji> <b>Used now:</b> <code>{cpu_percent}%</code> CPU, <code>{memory}MB</code> RAM\n",
|
||||
"loading_info": "<emoji document_id=5416094132672156295>⌛️</emoji> Loading...",
|
||||
"no_apikey": "<emoji document_id=5411402525146370107>🚫</emoji> Not specified API Key, need get token:\n\n1. Go to the @hikkahost_bot\n2. Send /token\n3. Paste token to .config HH",
|
||||
"warn_sub_left": "<emoji document_id=5411402525146370107>🚫</emoji> <i>There are less than 5 days left until the end of the subscription</i>\n",
|
||||
"statuses": {
|
||||
"running": "🟢",
|
||||
"stopped": "🔴",
|
||||
},
|
||||
"server": "{flag} {name}",
|
||||
"not_hh": "Your userbot is not running on hikkahost, please, go to @hikkahost_bot",
|
||||
"restart": "<emoji document_id=5418136591484865679>🌘</emoji> Your bot user goes to reboot",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "HH",
|
||||
"info": (
|
||||
"<emoji document_id=5413334818047940135>👤</emoji> <b>Информация о</b> <code>{id}</code>\n\n"
|
||||
"<emoji document_id=5418136591484865679>📶</emoji> <b>Статус:</b> {status}\n"
|
||||
"<emoji document_id=5415992848753379520>⚙️</emoji> <b>Сервер:</b> {server}\n"
|
||||
"<emoji document_id=5416042764863293485>❤️</emoji> <b>Подписка истечёт через</b> <code>{days_end} дней</code>\n"
|
||||
"{stats}\n"
|
||||
"{warns}"
|
||||
),
|
||||
"logs": "<emoji document_id=5411608069396254249>📄</emoji> Все логи docker контейнера от hikka\n\n<i>В t.me/hikkahost_bot/hhapp логи более читабельны</i>",
|
||||
"stats": "<emoji document_id=5413394354884596702>💾</emoji> <b>Используется:</b> <code>{cpu_percent}%</code> CPU, <code>{memory}MB</code> RAM\n",
|
||||
"loading_info": "<emoji document_id=5416094132672156295>⌛️</emoji> Загрузка...",
|
||||
"no_apikey": "<emoji document_id=5411402525146370107>🚫</emoji> Не задан ключ API, нужно получить токен:\n\n1. Зайдите в @hikkahost_bot\n2. Отправьте /token\n3. Запишите токен в .config HH",
|
||||
"warn_sub_left": "<emoji document_id=5411402525146370107>🚫</emoji> <i>Менее чем через 5 дней подписка истечёт</i>\n",
|
||||
"statuses": {
|
||||
"running": "🟢",
|
||||
"stopped": "🔴",
|
||||
},
|
||||
"server": "{flag} {name}",
|
||||
"not_hh": "Ваш юзербот запущен не через hikkahost, пожалуйста, зайдите в @hikkahost_bot",
|
||||
"restart": "<emoji document_id=5418136591484865679>🌘</emoji> Ваш юзербот отправлен в перезагрузку",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"token",
|
||||
None,
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.host = True
|
||||
self.url = "https://api.hikka.host"
|
||||
|
||||
if "HIKKAHOST" not in os.environ:
|
||||
self.host = False
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id, self.strings("not_hh")
|
||||
)
|
||||
|
||||
self._client = client
|
||||
self._db = db
|
||||
self.me = await client.get_me()
|
||||
self.bot = "@hikkahost_bot"
|
||||
|
||||
@loader.command(
|
||||
en_doc=" - ub status",
|
||||
)
|
||||
async def hinfocmd(self, message):
|
||||
""" - статус юзербота"""
|
||||
message = await utils.answer(message, self.strings("loading_info"))
|
||||
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_apikey"))
|
||||
return
|
||||
|
||||
token = self.config["token"]
|
||||
user_id = token.split(":")[0]
|
||||
api = HostAPI(self.url, token)
|
||||
|
||||
host = await api.get_host(user_id)
|
||||
|
||||
if isinstance(host, Error):
|
||||
await utils.answer(message, str(host))
|
||||
return
|
||||
|
||||
status = await api.get_status(user_id)
|
||||
stats = (await api.get_stats(user_id))["stats"]
|
||||
working = True if status["status"] == "running" else False
|
||||
|
||||
if working:
|
||||
cpu_stats = stats["cpu_stats"]
|
||||
cpu_total_usage = cpu_stats['cpu_usage']['total_usage']
|
||||
system_cpu_usage = cpu_stats['system_cpu_usage']
|
||||
|
||||
ram_usage = round(stats["memory_stats"]["usage"] / (1024 * 1024), 2)
|
||||
cpu_percent = round((cpu_total_usage / system_cpu_usage) * 100.0, 2)
|
||||
|
||||
stats = self.strings["stats"].format(
|
||||
cpu_percent=cpu_percent, memory=ram_usage
|
||||
)
|
||||
else:
|
||||
stats = ""
|
||||
|
||||
end_date = host.end_date.replace(tzinfo=timezone.utc)
|
||||
warns = ""
|
||||
days_end = (end_date - datetime.now(timezone.utc)).days
|
||||
if days_end < 5:
|
||||
warns += self.strings["warn_sub_left"]
|
||||
|
||||
servers = (await api.get_servers())["data"]
|
||||
servers_dict = {s["id"]: s for s in servers}
|
||||
|
||||
server = servers_dict.get(host.server_id)
|
||||
server = self.strings["server"].format(
|
||||
flag=FLAGS[server["country_code"]],
|
||||
name=server["name"],
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["info"].format(
|
||||
id=user_id,
|
||||
warns=warns,
|
||||
stats=stats,
|
||||
server=server,
|
||||
days_end=days_end,
|
||||
status=self.strings["statuses"][status["status"]],
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
en_doc=" - ub logs",
|
||||
)
|
||||
async def hlogscmd(self, message):
|
||||
""" - логи юзербота"""
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_apikey"))
|
||||
return
|
||||
|
||||
token = self.config["token"]
|
||||
user_id = token.split(":")[0]
|
||||
api = HostAPI(self.url, token)
|
||||
data = await api.get_logs(user_id)
|
||||
|
||||
files_log = data["logs"].split("\\r\\n")
|
||||
|
||||
with open("logs.txt", "w") as log_file:
|
||||
for log in files_log:
|
||||
log_file.write(log + "\n")
|
||||
|
||||
await utils.answer_file(message, "logs.txt", self.strings("logs"))
|
||||
|
||||
@loader.command(
|
||||
en_doc=" - ub restart",
|
||||
)
|
||||
async def hrestartcmd(self, message):
|
||||
""" - перезагрузить юзербота"""
|
||||
await utils.answer(message, self.strings("restart"))
|
||||
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_apikey"))
|
||||
return
|
||||
|
||||
token = self.config["token"]
|
||||
user_id = token.split(":")[0]
|
||||
api = HostAPI(self.url, token)
|
||||
|
||||
data = await api.action(user_id, "restart")
|
||||
|
||||
if isinstance(data, Error):
|
||||
await utils.answer(message, str(data))
|
||||
return
|
||||
182
vsecoder/hikka_modules/hypixel.py
Normal file
182
vsecoder/hikka_modules/hypixel.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta desc: Module for getting information about minecraft Hypixel player
|
||||
# meta pic: https://img.icons8.com/cute-clipart/64/minecraft-logo.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/cute-clipart/64/minecraft-logo.png&title=Hypixel&description=Module%20for%20getting%20information%20about%20minecraft%20Hypixel%20player
|
||||
|
||||
__version__ = (1, 1, 1)
|
||||
|
||||
import logging
|
||||
import aiohttp
|
||||
from telethon import TelegramClient
|
||||
from telethon.tl.types import Message
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HypixelAPI:
|
||||
def __init__(self, token):
|
||||
self.token = token
|
||||
self.api = "https://api.hypixel.net"
|
||||
|
||||
self.uuid_link = "https://api.mojang.com/users/profiles/minecraft/{}"
|
||||
|
||||
async def _request(self, url: str, method: str = "GET") -> dict:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.request(method, url) as response:
|
||||
return await response.json()
|
||||
|
||||
async def get_uuid(self, nickname):
|
||||
link = self.uuid_link.format(nickname)
|
||||
return await self._request(link)
|
||||
|
||||
async def get_player_data(self, uuid):
|
||||
link = f"{self.api}/player?key={self.token}&uuid={uuid}"
|
||||
return await self._request(link)
|
||||
|
||||
async def recent_games(self, uuid):
|
||||
link = f"{self.api}/recentgames?key={self.token}&uuid={uuid}"
|
||||
return await self._request(link)
|
||||
|
||||
async def player_status(self, uuid):
|
||||
link = f"{self.api}/status?key={self.token}&uuid={uuid}"
|
||||
return await self._request(link)
|
||||
|
||||
def to_string(self, data: dict, last_games: dict, online: dict) -> str:
|
||||
nick = data["player"]["displayname"]
|
||||
data = data["player"]["stats"]
|
||||
last_game = last_games["games"][-1]
|
||||
return f"""{nick} {"🟢" if online["session"]["online"] else "🔴"}
|
||||
|
||||
🛌 Bedwars:
|
||||
Coins: {data["Bedwars"]["coins"]}
|
||||
Win Streak: {data["Bedwars"]["winstreak"]}
|
||||
Games: {data["Bedwars"]["wins_bedwars"]} wins, {data["Bedwars"]["losses_bedwars"]} losses
|
||||
|
||||
🏝 Skywars:
|
||||
Coins: {data["SkyWars"]["coins"]}
|
||||
Win Streak: {data["SkyWars"]["win_streak"]}
|
||||
Games: {data["SkyWars"]["wins"]} wins, {data["SkyWars"]["losses"]} losses
|
||||
Kills: {data["SkyWars"]["kills"]}
|
||||
|
||||
🔪 Murder Mystery:
|
||||
Coins: {data["MurderMystery"]["coins"]}
|
||||
Games: {data["MurderMystery"]["wins"]} wins in {data["MurderMystery"]["games"]} games
|
||||
Kills: {data["MurderMystery"]["kills"]}
|
||||
|
||||
👷 Build Battle:
|
||||
Wins: {data["BuildBattle"]["wins"]}
|
||||
Coins: {data["BuildBattle"]["coins"]}
|
||||
|
||||
⚔️ Duels:
|
||||
Wins: {data["Duels"]["wins"]} wins, {data["Duels"]["losses"]} losses
|
||||
Kills: {data["Duels"]["kills"]}
|
||||
Coins: {data["Duels"]["coins"]}
|
||||
|
||||
📄 Last game: {last_game['gameType']}
|
||||
"""
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HypixelMod(loader.Module):
|
||||
"""
|
||||
Module for getting information about minecraft Hypixel player (beta)
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Hypixel",
|
||||
"template": '<pre><code class="language-output">{}</code></pre>',
|
||||
"not_token": "Token not found in config!",
|
||||
"_cfg_token": "Yandex.Music account token",
|
||||
"_cfg_uuid": "Minecraft UUID",
|
||||
"_cfg_nickname": "Minecraft nickname",
|
||||
"guide": (
|
||||
"Получите токен и запишите в .config по "
|
||||
'<a href="https://developer.hypixel.net/dashboard">ссылке</a>!'
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"not_token": "Токен не найден в конфиге!",
|
||||
"_cfg_token": "Токен аккаунта Яндекс.Музыка",
|
||||
"_cfg_uuid": "Майнкрафт UUID",
|
||||
"_cfg_nickname": "Никнейм в игре",
|
||||
"guide": (
|
||||
"Get a token and write it in .config by "
|
||||
'<a href="https://developer.hypixel.net/dashboard">link</a>!'
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"HypixelToken",
|
||||
None,
|
||||
self.strings["_cfg_token"],
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"UUID",
|
||||
None,
|
||||
self.strings["_cfg_uuid"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"nickname",
|
||||
"Pain_4986",
|
||||
self.strings["_cfg_nickname"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
)
|
||||
|
||||
async def on_dlmod(self):
|
||||
if not self.get("guide_send", False):
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
self.strings["guide"],
|
||||
)
|
||||
self.set("guide_send", True)
|
||||
|
||||
async def client_ready(self, client: TelegramClient, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
@loader.command()
|
||||
async def statcmd(self, message: Message):
|
||||
"""Get stats about Hypixel player"""
|
||||
|
||||
token = self.config["HypixelToken"]
|
||||
if not token:
|
||||
return await utils.answer(message, self.strings["not_token"])
|
||||
|
||||
hypixel = HypixelAPI(token)
|
||||
|
||||
uuid = self.config["UUID"]
|
||||
|
||||
if not self.config["UUID"]:
|
||||
uuid = (await hypixel.get_uuid(self.config["nickname"]))["id"]
|
||||
self.config["UUID"] = uuid
|
||||
|
||||
try:
|
||||
data = await hypixel.get_player_data(uuid)
|
||||
last_games = await hypixel.recent_games(uuid)
|
||||
online = await hypixel.player_status(uuid)
|
||||
except Exception as e:
|
||||
return await utils.answer(message, str(e))
|
||||
|
||||
answer = hypixel.to_string(data, last_games, online)
|
||||
return await utils.answer(message, self.strings["template"].format(answer))
|
||||
277
vsecoder/hikka_modules/libs/html2.py
Normal file
277
vsecoder/hikka_modules/libs/html2.py
Normal file
@@ -0,0 +1,277 @@
|
||||
# this is a @hikariatama library, im modified this for my modules
|
||||
# thk @hikariatama <3
|
||||
|
||||
import struct
|
||||
from collections import deque
|
||||
from html import escape
|
||||
from html.parser import HTMLParser
|
||||
from typing import Iterable, List, Optional, Tuple
|
||||
|
||||
from telethon import helpers
|
||||
from telethon.tl.types import (
|
||||
MessageEntityBlockquote,
|
||||
MessageEntityBold,
|
||||
MessageEntityCode,
|
||||
MessageEntityEmail,
|
||||
MessageEntityItalic,
|
||||
MessageEntityMentionName,
|
||||
MessageEntityPre,
|
||||
MessageEntitySpoiler,
|
||||
MessageEntityStrike,
|
||||
MessageEntityTextUrl,
|
||||
MessageEntityUnderline,
|
||||
MessageEntityUrl,
|
||||
TypeMessageEntity,
|
||||
MessageEntityCustomEmoji,
|
||||
)
|
||||
|
||||
from .. import loader
|
||||
|
||||
|
||||
# Helpers from markdown.py
|
||||
def _add_surrogate(text):
|
||||
return "".join(
|
||||
"".join(chr(y) for y in struct.unpack("<HH", x.encode("utf-16le")))
|
||||
if (0x10000 <= ord(x) <= 0x10FFFF)
|
||||
else x
|
||||
for x in text
|
||||
)
|
||||
|
||||
|
||||
def _del_surrogate(text):
|
||||
return text.encode("utf-16", "surrogatepass").decode("utf-16")
|
||||
|
||||
|
||||
class HTMLToTelegramParser(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.text = ""
|
||||
self.entities = []
|
||||
self._building_entities = {}
|
||||
self._open_tags = deque()
|
||||
self._open_tags_meta = deque()
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
self._open_tags.appendleft(tag)
|
||||
self._open_tags_meta.appendleft(None)
|
||||
|
||||
attrs = dict(attrs)
|
||||
EntityType = None
|
||||
args = {}
|
||||
if tag in ["strong", "b"]:
|
||||
EntityType = MessageEntityBold
|
||||
elif tag in ["em", "i"]:
|
||||
EntityType = MessageEntityItalic
|
||||
elif tag in ["tg-spoiler"]:
|
||||
EntityType = MessageEntitySpoiler
|
||||
elif tag == "u":
|
||||
EntityType = MessageEntityUnderline
|
||||
elif tag in ["del", "s"]:
|
||||
EntityType = MessageEntityStrike
|
||||
elif tag == "blockquote":
|
||||
EntityType = MessageEntityBlockquote
|
||||
elif tag == "code":
|
||||
try:
|
||||
# If we're in the middle of a <pre> tag, this <code> tag is
|
||||
# probably intended for syntax highlighting.
|
||||
#
|
||||
# Syntax highlighting is set with
|
||||
# <code class='language-...'>codeblock</code>
|
||||
# inside <pre> tags
|
||||
pre = self._building_entities["pre"]
|
||||
try:
|
||||
pre.language = attrs["class"][len("language-") :]
|
||||
except KeyError:
|
||||
pass
|
||||
except KeyError:
|
||||
EntityType = MessageEntityCode
|
||||
# @vsecoder append start #
|
||||
elif tag == "emoji":
|
||||
try:
|
||||
# Emoji have document_id parameter
|
||||
# <emoji document_id="...">❤️</emoji>
|
||||
emoji = self._building_entities["emoji"]
|
||||
try:
|
||||
emoji.document_id = attrs["document_id"]
|
||||
except KeyError:
|
||||
pass
|
||||
except KeyError:
|
||||
EntityType = MessageEntityCode
|
||||
# @vsecoder append end #
|
||||
elif tag == "pre":
|
||||
EntityType = MessageEntityPre
|
||||
args["language"] = ""
|
||||
elif tag == "a":
|
||||
try:
|
||||
url = attrs["href"]
|
||||
except KeyError:
|
||||
return
|
||||
if url.startswith("mailto:"):
|
||||
url = url[len("mailto:") :]
|
||||
EntityType = MessageEntityEmail
|
||||
elif self.get_starttag_text() == url:
|
||||
EntityType = MessageEntityUrl
|
||||
else:
|
||||
EntityType = MessageEntityTextUrl
|
||||
args["url"] = url
|
||||
url = None
|
||||
self._open_tags_meta.popleft()
|
||||
self._open_tags_meta.appendleft(url)
|
||||
|
||||
if EntityType and tag not in self._building_entities:
|
||||
self._building_entities[tag] = EntityType(
|
||||
offset=len(self.text),
|
||||
# The length will be determined when closing the tag.
|
||||
length=0,
|
||||
**args,
|
||||
)
|
||||
|
||||
def handle_data(self, text):
|
||||
previous_tag = self._open_tags[0] if len(self._open_tags) > 0 else ""
|
||||
if previous_tag == "a":
|
||||
if url := self._open_tags_meta[0]:
|
||||
text = url
|
||||
|
||||
for tag, entity in self._building_entities.items():
|
||||
entity.length += len(text)
|
||||
|
||||
self.text += text
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
try:
|
||||
self._open_tags.popleft()
|
||||
self._open_tags_meta.popleft()
|
||||
except IndexError:
|
||||
pass
|
||||
if entity := self._building_entities.pop(tag, None):
|
||||
self.entities.append(entity)
|
||||
|
||||
|
||||
class HTMLParserLib(loader.Library):
|
||||
developer = "@hikariatama" # and @vsecoder
|
||||
version = (2, 0, 0)
|
||||
|
||||
def parse(self, html: str) -> Tuple[str, List[TypeMessageEntity]]:
|
||||
"""
|
||||
Parses the given HTML message and returns its stripped representation
|
||||
plus a list of the MessageEntity's that were found.
|
||||
:param html: the message with HTML to be parsed.
|
||||
:return: a tuple consisting of (clean message, [message entities]).
|
||||
"""
|
||||
if not html:
|
||||
return html, []
|
||||
|
||||
parser = HTMLToTelegramParser()
|
||||
parser.feed(_add_surrogate(html))
|
||||
text = helpers.strip_text(parser.text, parser.entities)
|
||||
return _del_surrogate(text), parser.entities
|
||||
|
||||
def unparse(
|
||||
self,
|
||||
text: str,
|
||||
entities: Iterable[TypeMessageEntity],
|
||||
_offset: int = 0,
|
||||
_length: Optional[int] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Performs the reverse operation to .parse(), effectively returning HTML
|
||||
given a normal text and its MessageEntity's.
|
||||
:param text: the text to be reconverted into HTML.
|
||||
:param entities: the MessageEntity's applied to the text.
|
||||
:return: a HTML representation of the combination of both inputs.
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
elif not entities:
|
||||
return escape(text)
|
||||
|
||||
text = _add_surrogate(text)
|
||||
if _length is None:
|
||||
_length = len(text)
|
||||
html = []
|
||||
last_offset = 0
|
||||
for i, entity in enumerate(entities):
|
||||
if entity.offset >= _offset + _length:
|
||||
break
|
||||
relative_offset = entity.offset - _offset
|
||||
if relative_offset > last_offset:
|
||||
html.append(escape(text[last_offset:relative_offset]))
|
||||
elif relative_offset < last_offset:
|
||||
continue
|
||||
|
||||
skip_entity = False
|
||||
length = entity.length
|
||||
|
||||
# If we are in the middle of a surrogate nudge the position by +1.
|
||||
# Otherwise we would end up with malformed text and fail to encode.
|
||||
# For example of bad input: "Hi \ud83d\ude1c"
|
||||
# https://en.wikipedia.org/wiki/UTF-16#U+010000_to_U+10FFFF
|
||||
while helpers.within_surrogate(text, relative_offset, length=_length):
|
||||
relative_offset += 1
|
||||
|
||||
while helpers.within_surrogate(
|
||||
text, relative_offset + length, length=_length
|
||||
):
|
||||
length += 1
|
||||
|
||||
entity_text = self.unparse(
|
||||
text=text[relative_offset : relative_offset + length],
|
||||
entities=entities[i + 1 :],
|
||||
_offset=entity.offset,
|
||||
_length=length,
|
||||
)
|
||||
entity_type = type(entity)
|
||||
|
||||
if entity_type == MessageEntityBold:
|
||||
html.append("<strong>{}</strong>".format(entity_text))
|
||||
elif entity_type == MessageEntityItalic:
|
||||
html.append("<em>{}</em>".format(entity_text))
|
||||
elif entity_type == MessageEntitySpoiler:
|
||||
html.append("<tg-spoiler>{}</tg-spoiler>".format(entity_text))
|
||||
elif entity_type == MessageEntityCode:
|
||||
html.append("<code>{}</code>".format(entity_text))
|
||||
elif entity_type == MessageEntityUnderline:
|
||||
html.append("<u>{}</u>".format(entity_text))
|
||||
elif entity_type == MessageEntityStrike:
|
||||
html.append("<del>{}</del>".format(entity_text))
|
||||
elif entity_type == MessageEntityBlockquote:
|
||||
html.append("<blockquote>{}</blockquote>".format(entity_text))
|
||||
elif entity_type == MessageEntityPre:
|
||||
if entity.language:
|
||||
html.append(
|
||||
"<pre>\n"
|
||||
" <code class='language-{}'>\n"
|
||||
" {}\n"
|
||||
" </code>\n"
|
||||
"</pre>".format(entity.language, entity_text)
|
||||
)
|
||||
else:
|
||||
html.append("<pre><code>{}</code></pre>".format(entity_text))
|
||||
# @vsecoder append start
|
||||
elif entity_type == MessageEntityCustomEmoji:
|
||||
html.append('<emoji document_id="{}">{}</emoji>'.format(entity.document_id, entity_text))
|
||||
# @vsecoder append end
|
||||
elif entity_type == MessageEntityEmail:
|
||||
html.append('<a href="mailto:{0}">{0}</a>'.format(entity_text))
|
||||
elif entity_type == MessageEntityUrl:
|
||||
html.append('<a href="{0}">{0}</a>'.format(entity_text))
|
||||
elif entity_type == MessageEntityTextUrl:
|
||||
html.append(
|
||||
'<a href="{}">{}</a>'.format(escape(entity.url), entity_text)
|
||||
)
|
||||
elif entity_type == MessageEntityMentionName:
|
||||
html.append(
|
||||
'<a href="tg://user?id={}">{}</a>'.format(
|
||||
entity.user_id, entity_text
|
||||
)
|
||||
)
|
||||
else:
|
||||
skip_entity = True
|
||||
last_offset = relative_offset + (0 if skip_entity else length)
|
||||
|
||||
while helpers.within_surrogate(text, last_offset, length=_length):
|
||||
last_offset += 1
|
||||
|
||||
html.append(escape(text[last_offset:]))
|
||||
return _del_surrogate("".join(html))
|
||||
|
||||
15
vsecoder/hikka_modules/libs/sub.py
Normal file
15
vsecoder/hikka_modules/libs/sub.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from .. import loader
|
||||
|
||||
|
||||
class CheckSubscribe(loader.Library):
|
||||
developer = "@vsecoder"
|
||||
version = (1, 0, 0)
|
||||
|
||||
async def check(self, client, channel_name="vsecoder_m"):
|
||||
try:
|
||||
channel = await client.get_entity(f"t.me/{channel_name}")
|
||||
if channel.title:
|
||||
return True
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
66
vsecoder/hikka_modules/lmfify.py
Normal file
66
vsecoder/hikka_modules/lmfify.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2024 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/bubbles/344/google-logo.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/bubbles/344/google-logo.png&title=LMFIFY&description=Let%20me%20find%20it%20for%20you%20in%20Google%20/%20Yandex
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class LMFIFYMod(loader.Module):
|
||||
"""Let me find it for you in Google / Yandex"""
|
||||
|
||||
strings = {
|
||||
"name": "LMFIFY",
|
||||
"cfg_searc_engine": "Searcher, http://yaforyou.ru/?= or https://track24.ru/google/?q=",
|
||||
"answer": "<b><emoji document_id=5276167890624585747>👩💻</emoji> Let me find it for you: </b><a href='{}'>👉click</a>",
|
||||
"error": "Need text!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"cfg_searc_url": "Поисковик, http://yaforyou.ru/?= или https://track24.ru/google/?q=",
|
||||
"answer": "<b><emoji document_id=5276167890624585747>👩💻</emoji> Дай-ка я найду: </b><a href='{}'>👉click</a>",
|
||||
"error": "Нужен текст!",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
"search_url",
|
||||
"https://track24.ru/google/?q={query}",
|
||||
self.strings["cfg_searc_engine"],
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def finditcmd(self, message):
|
||||
"""
|
||||
{text} - find it in search engine
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
url = self.config["search_url"].format(query=args).replace(" ", "+")
|
||||
await utils.answer(message, self.strings["answer"].format(url))
|
||||
else:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
await asyncio.sleep(5)
|
||||
await message.delete()
|
||||
261
vsecoder/hikka_modules/mazemod.py
Normal file
261
vsecoder/hikka_modules/mazemod.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/external-icongeek26-linear-colour-icongeek26/344/external-maze-game-development-icongeek26-linear-colour-icongeek26.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/external-icongeek26-linear-colour-icongeek26/344/external-maze-game-development-icongeek26-linear-colour-icongeek26.png&title=MazeMod&description=Telegram%20Maze%20Game
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
import logging
|
||||
import random
|
||||
from telethon import TelegramClient
|
||||
from .. import loader # type: ignore
|
||||
from ..inline.types import InlineCall # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Maze:
|
||||
def __init__(self, rowsNumber, columnsNumber):
|
||||
self.rowsNumber = rowsNumber
|
||||
self.columnsNumber = columnsNumber
|
||||
self.maze = []
|
||||
|
||||
def isEven(self, number):
|
||||
return number % 2 == 0
|
||||
|
||||
def getRandomFrom(self, array):
|
||||
return random.choice(array)
|
||||
|
||||
def setField(self, x, y, value):
|
||||
if x < 0 or x >= self.columnsNumber or y < 0 or y >= self.rowsNumber:
|
||||
return False
|
||||
|
||||
self.maze[y][x] = value
|
||||
|
||||
def getField(self, x, y):
|
||||
if x < 0 or x >= self.columnsNumber or y < 0 or y >= self.rowsNumber:
|
||||
return False
|
||||
|
||||
return self.maze[y][x]
|
||||
|
||||
def isMaze(self):
|
||||
for x in range(self.columnsNumber):
|
||||
for y in range(self.rowsNumber):
|
||||
if self.isEven(x) and self.isEven(y) and self.getField(x, y) == "⬜️":
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def moveTractor(self, tractor):
|
||||
directs = []
|
||||
if tractor["x"] > 0:
|
||||
directs.append("left")
|
||||
|
||||
n = self.columnsNumber - 2
|
||||
|
||||
if tractor["x"] < n:
|
||||
directs.append("right")
|
||||
|
||||
if tractor["y"] > 0:
|
||||
directs.append("up")
|
||||
|
||||
n = self.rowsNumber - 2
|
||||
|
||||
if tractor["y"] < n:
|
||||
directs.append("down")
|
||||
|
||||
direct = self.getRandomFrom(directs)
|
||||
|
||||
if direct == "left":
|
||||
if self.getField(tractor["x"] - 2, tractor["y"]) == "⬜️":
|
||||
self.setField(tractor["x"] - 1, tractor["y"], "⬛️")
|
||||
self.setField(tractor["x"] - 2, tractor["y"], "⬛️")
|
||||
tractor["x"] -= 2
|
||||
if direct == "right":
|
||||
if self.getField(tractor["x"] + 2, tractor["y"]) == "⬜️":
|
||||
self.setField(tractor["x"] + 1, tractor["y"], "⬛️")
|
||||
self.setField(tractor["x"] + 2, tractor["y"], "⬛️")
|
||||
tractor["x"] += 2
|
||||
if direct == "up":
|
||||
if self.getField(tractor["x"], tractor["y"] - 2) == "⬜️":
|
||||
self.setField(tractor["x"], tractor["y"] - 1, "⬛️")
|
||||
self.setField(tractor["x"], tractor["y"] - 2, "⬛️")
|
||||
tractor["y"] -= 2
|
||||
if direct == "down":
|
||||
if self.getField(tractor["x"], tractor["y"] + 2) == "⬜️":
|
||||
self.setField(tractor["x"], tractor["y"] + 1, "⬛️")
|
||||
self.setField(tractor["x"], tractor["y"] + 2, "⬛️")
|
||||
tractor["y"] += 2
|
||||
|
||||
def generate_map(self):
|
||||
for _ in range(self.rowsNumber):
|
||||
row = ["⬜️" for _ in range(self.columnsNumber)]
|
||||
self.maze.append(row)
|
||||
|
||||
evenColums = []
|
||||
for column in range(self.columnsNumber):
|
||||
if self.isEven(column):
|
||||
evenColums.append(column)
|
||||
|
||||
startX = 2
|
||||
startY = 2
|
||||
|
||||
tractor = {"x": startX, "y": startY}
|
||||
|
||||
self.setField(startX, startY, "⬛️")
|
||||
|
||||
while not self.isMaze():
|
||||
self.moveTractor(tractor)
|
||||
|
||||
return self.maze
|
||||
|
||||
|
||||
@loader.tds
|
||||
class MazeModMod(loader.Module):
|
||||
"""Module for play maze"""
|
||||
|
||||
strings = {
|
||||
"name": "MazeMod",
|
||||
"cfg_maze_width": "Maze width and height, default is 30x30",
|
||||
"answer": "🕹 <b>Click on the inline buttons to move:</b> <i>{0}</i>",
|
||||
"doc": "\n 🟦 - player \n ⬛️ - road\n ⬜️ - wall\n 🟩 - finish\n",
|
||||
"move": "▶️ Moved\n",
|
||||
"not_allowed": "💭 Not allowed\n",
|
||||
"win": "🎉 You win!",
|
||||
"error": "❗️ Error!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"answer": "🕹 <b>Нажимайте на инлайн кнопки что двигаться:</b> <i>{0}</i>",
|
||||
"doc": "\n 🟦 - игрок \n ⬛️ - дорога\n ⬜️ - стена\n 🟩 - финиш\n",
|
||||
"move": "▶️ Передвинулся\n",
|
||||
"not_allowed": "💭 Не разрешено\n",
|
||||
"win": "🎉 Вы победили!",
|
||||
"error": "❗️ Ошибка!",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
"maze_width",
|
||||
"10",
|
||||
self.strings["cfg_maze_width"],
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client: TelegramClient, db):
|
||||
self._db = db
|
||||
self._client = client
|
||||
|
||||
async def render(self, message: InlineCall, press, maze, player):
|
||||
text = self.strings["answer"].format(self.strings["not_allowed"])
|
||||
move = {"x": player["x"], "y": player["y"]}
|
||||
if press == "up":
|
||||
move["y"] = move["y"] - 1
|
||||
if press == "left":
|
||||
move["x"] = move["x"] - 1
|
||||
if press == "down":
|
||||
move["y"] = move["y"] + 1
|
||||
if press == "right":
|
||||
move["x"] = move["x"] + 1
|
||||
|
||||
if maze[move["y"]][move["x"]] == "⬜":
|
||||
pass
|
||||
elif maze[move["y"]][move["x"]] == "⬛️":
|
||||
maze[player["y"]][player["x"]] = "⬛️"
|
||||
player = move
|
||||
text = self.strings["answer"].format(self.strings["move"])
|
||||
elif maze[move["y"]][move["x"]] == "🟩":
|
||||
text = self.strings["win"]
|
||||
return await message.edit(text=text)
|
||||
|
||||
maze[player["y"]][player["x"]] = "🟦"
|
||||
|
||||
keyboard = [
|
||||
[
|
||||
{"text": "🔼", "callback": self.render, "args": ["up", maze, player]},
|
||||
],
|
||||
[
|
||||
{"text": "◀️", "callback": self.render, "args": ["left", maze, player]},
|
||||
{
|
||||
"text": "▶️",
|
||||
"callback": self.render,
|
||||
"args": ["right", maze, player],
|
||||
},
|
||||
],
|
||||
[
|
||||
{"text": "🔽", "callback": self.render, "args": ["down", maze, player]},
|
||||
],
|
||||
]
|
||||
|
||||
for column in maze:
|
||||
for row in column:
|
||||
for i in row:
|
||||
text = text + i
|
||||
text = text + "\n"
|
||||
|
||||
await message.edit(text=text, reply_markup=keyboard)
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def mazecmd(self, message):
|
||||
"""
|
||||
- generate maze and start play
|
||||
Based on... my code)
|
||||
"""
|
||||
size = int(self.config["maze_width"])
|
||||
generate = Maze(size, size)
|
||||
maze = generate.generate_map()
|
||||
|
||||
player = {"x": 0, "y": 0}
|
||||
maze[player["y"]][player["x"]] = "🟦"
|
||||
maze[size - 2][size - 2] = "🟩"
|
||||
|
||||
text = self.strings["answer"].format(self.strings["doc"])
|
||||
keyboard = [
|
||||
[
|
||||
{"text": "🔼", "callback": self.render, "args": ["up", maze, player]},
|
||||
],
|
||||
[
|
||||
{"text": "◀️", "callback": self.render, "args": ["left", maze, player]},
|
||||
{
|
||||
"text": "▶️",
|
||||
"callback": self.render,
|
||||
"args": ["right", maze, player],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "🔽",
|
||||
"callback": self.render,
|
||||
"args": [
|
||||
"down",
|
||||
maze,
|
||||
player,
|
||||
],
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
for column in maze:
|
||||
for row in column:
|
||||
for i in row:
|
||||
text = text + i
|
||||
text = text + "\n"
|
||||
|
||||
await message.delete()
|
||||
await self.inline.form(
|
||||
text=text,
|
||||
message=message,
|
||||
always_allow=[message.from_id],
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
165
vsecoder/hikka_modules/monkeytype.py
Normal file
165
vsecoder/hikka_modules/monkeytype.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
Thk @hikariatama
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta desc: Module for getting information about monkeytype.com stats
|
||||
# meta pic: https://img.icons8.com/stickers/100/keyboard.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/stickers/100/keyboard.png&title=MonkeyType&description=Module%20for%20getting%20information%20about%20monkeytype.com%20stats
|
||||
|
||||
__version__ = (1, 0, 1)
|
||||
|
||||
import logging
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class MonkeyTypeMod(loader.Module):
|
||||
"""
|
||||
Module for getting information about monkeytype.com stats
|
||||
|
||||
{15/30/60/120:times} - dividing tests by time (default: 15)
|
||||
Need only account username (not full link)!
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "MonkeyType",
|
||||
"error": "<emoji document_id=5467928559664242360>❗️</emoji> Error: \n{}",
|
||||
"loading": "<emoji document_id=5451732530048802485>⏳</emoji> Loading...",
|
||||
"template_message": (
|
||||
"<blockquote><emoji document_id=5879770735999717115>👤</emoji> <b>MonkeyType.com stats for {}:</b></blockquote>\n\n"
|
||||
"<emoji document_id=5877485980901971030>📊</emoji> Completed tests: <code>{}</code>\n"
|
||||
"<emoji document_id=5778202206922608769>🔄</emoji> All time typing: <code>{}s</code>\n\n"
|
||||
"<emoji document_id=5870684638195748414>🏆</emoji> <b>Personal bests:</b>\n"
|
||||
"{}"
|
||||
"<emoji document_id=5994378914636500516>📈</emoji> <b>XP</b>: <code>{}</code>"
|
||||
),
|
||||
"time": " <emoji document_id=5960751816084820359>⏲️</emoji> <i>{}s</i>: \n",
|
||||
"not_time": " <emoji document_id=5960751816084820359>⏲️</emoji> <i>{}s</i>: -\n\n",
|
||||
"template_time": (
|
||||
" <emoji document_id=5100434699503797219>🟠</emoji><code>{}</code>:\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> {}: <code>{}</code>\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> Accuracy: <code>{}%</code>\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> Consistency: <code>{}%</code>\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> Difficulty: <code>{}</code>\n\n"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"error": "<emoji document_id=5467928559664242360>❗️</emoji> Ошибка: \n{}",
|
||||
"loading": "<emoji document_id=5451732530048802485>⏳</emoji> Загрузка...",
|
||||
"template_message": (
|
||||
"<blockquote><emoji document_id=5879770735999717115>👤</emoji> <b>Статистика MonkeyType.com для {}:</b></blockquote>\n\n"
|
||||
"<emoji document_id=5877485980901971030>📊</emoji> Пройденных тестов: <code>{}</code>\n"
|
||||
"<emoji document_id=5778202206922608769>🔄</emoji> Всего времени печати: <code>{}s</code>\n\n"
|
||||
"<emoji document_id=5870684638195748414>🏆</emoji> <b>Лучшие результаты:</b>\n"
|
||||
"{}"
|
||||
"<emoji document_id=5994378914636500516>📈</emoji> <b>XP</b>: <code>{}</code>"
|
||||
),
|
||||
"time": " <emoji document_id=5960751816084820359>⏲️</emoji> <i>{}s</i>: \n",
|
||||
"not_time": " <emoji document_id=5960751816084820359>⏲️</emoji> <i>{}s</i>: -\n\n",
|
||||
"template_time": (
|
||||
" <emoji document_id=5100434699503797219>🟠</emoji><code>{}</code>:\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> {}: <code>{}</code>\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> Точность: <code>{}%</code>\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> Согласованность: <code>{}%</code>\n"
|
||||
" <emoji document_id=5098279308821005089>🔵</emoji> Сложность: <code>{}</code>\n\n"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"default_typing_speed",
|
||||
"wpm",
|
||||
"Which typing speed to use by default",
|
||||
validator=loader.validators.Choice(["cps", "wpm", "cpm", "wps"]),
|
||||
),
|
||||
)
|
||||
|
||||
async def request(self, username=""):
|
||||
url = "https://api.monkeytype.com/users/{}/profile"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url.format(username)) as response:
|
||||
if response.status != [500, 503]:
|
||||
return await response.json()
|
||||
|
||||
return {"message": "Server error"}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
|
||||
@loader.command(alias="mts")
|
||||
async def monkeytypestatscmd(self, message):
|
||||
"""
|
||||
{username} {15/30/60/120:times} - get monkeytype.com user stats
|
||||
"""
|
||||
args = utils.get_args_raw(message).split(" ")
|
||||
|
||||
if not args:
|
||||
return await utils.answer(
|
||||
message, self.strings["error"].format("Invalid args")
|
||||
)
|
||||
|
||||
time = args[1] if len(args) > 1 else "15"
|
||||
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
|
||||
data = await self.request(args[0])
|
||||
|
||||
if data["message"] != "Profile retrieved":
|
||||
return await utils.answer(
|
||||
message, self.strings["error"].format(data["message"])
|
||||
)
|
||||
|
||||
data = data["data"]
|
||||
|
||||
best = ""
|
||||
|
||||
if time in data["personalBests"]["time"]:
|
||||
best += self.strings["time"].format(time)
|
||||
for i in data["personalBests"]["time"][time]:
|
||||
typing_speeds = {
|
||||
"wpm": 1,
|
||||
"cpm": 5,
|
||||
"wps": 1 / 60,
|
||||
"cps": 5 / 60,
|
||||
}
|
||||
speed = round(
|
||||
i["wpm"] * typing_speeds[self.config["default_typing_speed"]], 2
|
||||
)
|
||||
best += self.strings["template_time"].format(
|
||||
i["language"],
|
||||
self.config["default_typing_speed"].upper(),
|
||||
speed,
|
||||
i["acc"],
|
||||
i["consistency"],
|
||||
i["difficulty"],
|
||||
)
|
||||
else:
|
||||
best += self.strings["not_time"].format(time)
|
||||
|
||||
answer = self.strings["template_message"].format(
|
||||
args[0],
|
||||
data["typingStats"]["completedTests"],
|
||||
round(data["typingStats"]["timeTyping"]),
|
||||
best,
|
||||
data["xp"],
|
||||
)
|
||||
|
||||
return await utils.answer(message, answer)
|
||||
130
vsecoder/hikka_modules/octocode.py
Normal file
130
vsecoder/hikka_modules/octocode.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/cotton/344/code.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/cotton/344/code.png&title=OctoCode&description=OctoCode%20is%20a%20module%20for%20octopussed%20code%20in%20Telegram
|
||||
|
||||
__version__ = (3, 0, 0)
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class OctoCodeMod(loader.Module):
|
||||
"""
|
||||
Module for octopussed code
|
||||
|
||||
https://github.com/charmbracelet/freeze based
|
||||
|
||||
To use, run this in .terminal:
|
||||
|
||||
wget https://github.com/charmbracelet/freeze/releases/download/v0.1.6/freeze_0.1.6_amd64.deb
|
||||
sudo dpkg -i freeze_0.1.6_amd64.deb
|
||||
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "OctoCode",
|
||||
"answer": "🐙 <b>Code</b> <i>octopussed</i>: ",
|
||||
"loading": "🐙 <b>Loading</b>...",
|
||||
"cfg_theme": "🦎 Change theme",
|
||||
"cfg_line_numbers": "🦎 Type True/False to manage a number of line numbers",
|
||||
"cfg_default_lang": "🦎 Enter the programming language to use by default",
|
||||
"error": "❗️ Error: {0}",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"answer": "🐙 <b>Код</b> <i>осьмоножен</i>: ",
|
||||
"loading": "🐙 <b>Загрузка</b>...",
|
||||
"cfg_theme": "🦎 Выбери тему",
|
||||
"cfg_line_numbers": "🦎 Введите True/False для управления ряда номеров строк",
|
||||
"cfg_default_lang": (
|
||||
"🦎 Введите язык программирования для использования по умолчанию"
|
||||
),
|
||||
"error": "❗️ Ошибка: {0}",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"theme",
|
||||
"monokai",
|
||||
self.strings["cfg_theme"],
|
||||
validator=loader.validators.Choice(
|
||||
["charm", "dracula", "github-dark", "monokai", "nord", "onedark"]
|
||||
),
|
||||
),
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def get_code(self, message):
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if message.media:
|
||||
await self._client.download_file(message.media, "file.txt")
|
||||
return "file.txt"
|
||||
|
||||
if reply:
|
||||
if reply.media:
|
||||
await self._client.download_file(reply.media, "file.txt")
|
||||
return "file.txt"
|
||||
|
||||
return
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def octocmd(self, message):
|
||||
"""
|
||||
"reply file" or "send file"
|
||||
Octopussed your code
|
||||
"""
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
|
||||
path = await self.get_code(message)
|
||||
|
||||
if not path:
|
||||
return await utils.answer(
|
||||
message, self.strings["error"].format("not file changed")
|
||||
)
|
||||
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"freeze",
|
||||
path,
|
||||
"-t",
|
||||
self.config["theme"],
|
||||
"-l",
|
||||
"py",
|
||||
)
|
||||
await process.wait()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
await utils.answer(message, self.strings["error"].format(e))
|
||||
return
|
||||
|
||||
await self._client.send_file(
|
||||
utils.get_chat_id(message),
|
||||
file=open("freeze.png", "rb"),
|
||||
reply_to=getattr(message, "reply_to_msg_id", None),
|
||||
)
|
||||
|
||||
await utils.answer(message, self.strings["answer"])
|
||||
108
vsecoder/hikka_modules/profile.py
Normal file
108
vsecoder/hikka_modules/profile.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/office/344/administrator-male--v1.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/office/344/administrator-male--v1.png&title=Profilemod&description=Telegram%20Profile%20Statistic
|
||||
|
||||
__version__ = (0, 0, 1)
|
||||
|
||||
import logging
|
||||
from .. import loader, utils # type: ignore
|
||||
from telethon import functions
|
||||
import imgkit # type: ignore
|
||||
import base64
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Profilemod(loader.Module):
|
||||
"""Module for get beautiful picture profile statistic"""
|
||||
|
||||
strings = {"name": "Profilemod"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"background",
|
||||
"https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/office/344/administrator-male--v1.png&title=.&description=.",
|
||||
"Url to background (540x220 is perfect)",
|
||||
validator=loader.validators.Link(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"html_template",
|
||||
"https://raw.githubusercontent.com/vsecoder/hikka_modules/main/assets/profile.html",
|
||||
"link to html template (if you don't know how, don't touch!)",
|
||||
validator=loader.validators.Link(),
|
||||
),
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def profilecmd(self, message):
|
||||
"""
|
||||
- get
|
||||
"""
|
||||
chats, channels, bots, users = 0, 0, 0, 0
|
||||
message = await utils.answer(message, "Geting profile info...")
|
||||
async for dialog in self._client.iter_dialogs(ignore_migrated=True):
|
||||
if dialog.is_group:
|
||||
chats += 1
|
||||
elif dialog.is_channel:
|
||||
channels += 1
|
||||
elif dialog.entity.bot:
|
||||
bots += 1
|
||||
else:
|
||||
users += 1
|
||||
|
||||
options = {"crop-w": 540, "crop-h": 220, "encoding": "UTF-8"}
|
||||
|
||||
me = await self._client.get_me()
|
||||
desc = await self._client(functions.users.GetFullUserRequest(me.id))
|
||||
|
||||
message = await utils.answer(message, "Downloading profile photo...")
|
||||
await self._client.download_profile_photo("me", "profile.jpg")
|
||||
message = await utils.answer(message, "Converting profile photo...")
|
||||
base64EncodedStr = base64.b64encode(open("profile.jpg", "rb").read()).decode(
|
||||
"utf-8"
|
||||
)
|
||||
|
||||
message = await utils.answer(message, "Formating info to template...")
|
||||
with open("profile.html", "w") as f:
|
||||
template = requests.get(self.config["html_template"]).text
|
||||
f.write(
|
||||
template.format(
|
||||
self.config["background"],
|
||||
base64EncodedStr,
|
||||
f"@{me.username}",
|
||||
chats,
|
||||
channels,
|
||||
users,
|
||||
bots,
|
||||
desc.full_user.about,
|
||||
)
|
||||
)
|
||||
|
||||
message = await utils.answer(message, "Converting to image...")
|
||||
imgkit.from_file("profile.html", "profile.jpg", options=options)
|
||||
message = await utils.answer(message, "Complete:")
|
||||
|
||||
await self._client.send_file(
|
||||
utils.get_chat_id(message),
|
||||
open("profile.jpg", "rb"),
|
||||
)
|
||||
708
vsecoder/hikka_modules/quotes.py
Normal file
708
vsecoder/hikka_modules/quotes.py
Normal file
@@ -0,0 +1,708 @@
|
||||
__version__ = (0, 0, 7)
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
Thk @Fl1yd, based on his module
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/sf-black-filled/64/quote.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/sf-black-filled/64/quote.png&title=Quotes&description=Quote%20a%20message%20using%20vsecoder%20API
|
||||
# requires: pydub speechrecognition python-ffmpeg
|
||||
# scope: ffmpeg
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
import asyncio
|
||||
import logging
|
||||
from time import gmtime
|
||||
from typing import List, Union
|
||||
import speech_recognition as sr
|
||||
from pydub import AudioSegment
|
||||
|
||||
import requests
|
||||
import telethon # type: ignore
|
||||
from telethon.tl import types # type: ignore
|
||||
from telethon.tl.patched import Message # type: ignore
|
||||
from telethon.tl.types import PeerUser, PeerChat, PeerChannel, PeerBlocked # type: ignore
|
||||
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EntityPayload:
|
||||
def __init__(
|
||||
self,
|
||||
type_: str,
|
||||
offset: int,
|
||||
length: int,
|
||||
url: Union[str, None] = None,
|
||||
user: Union[dict, None] = None,
|
||||
language: Union[str, None] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.type = type_
|
||||
self.offset = offset
|
||||
self.length = length
|
||||
self.url = url
|
||||
self.user = user
|
||||
self.language = language
|
||||
self._ = kwargs
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"_": "MessageEntity",
|
||||
"type": self.type,
|
||||
"offset": self.offset,
|
||||
"length": self.length,
|
||||
"url": self.url,
|
||||
"user": self.user,
|
||||
"language": self.language,
|
||||
("custom_emoji_id" if self.type == "custom_emoji" else None): (
|
||||
str(self._["document_id"]) if self.type == "custom_emoji" else None
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class UserPayload:
|
||||
def __init__(
|
||||
self,
|
||||
id_: int,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
username: Union[str, None],
|
||||
language_code: str,
|
||||
title: Union[str, None],
|
||||
emoji_status: Union[str, None],
|
||||
photo: Union[dict, None],
|
||||
type_: str,
|
||||
):
|
||||
self.id = id_
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.username = username
|
||||
self.language_code = language_code
|
||||
self.title = title
|
||||
self.emoji_status = emoji_status
|
||||
self.photo = photo
|
||||
self.type = type_
|
||||
self.name = f"{self.first_name or ''} {self.last_name or ''}"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"first_name": self.first_name,
|
||||
"last_name": self.last_name,
|
||||
"username": self.username,
|
||||
"language_code": self.language_code,
|
||||
"title": self.title,
|
||||
"emoji_status": self.emoji_status,
|
||||
"photo": self.photo,
|
||||
"type": self.type,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
|
||||
class MessagePayload:
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
entities: Union[List[EntityPayload], None],
|
||||
chat_id: int,
|
||||
avatar: bool,
|
||||
from_: UserPayload,
|
||||
reply: Union[dict, None],
|
||||
media: Union[dict, None] = None,
|
||||
media_type: Union[str, None] = None,
|
||||
voice: Union[str, None] = None,
|
||||
is_forward: Union[bool, None] = False,
|
||||
via_bot: Union[bool, None] = None,
|
||||
):
|
||||
self.text = text
|
||||
self.media = media
|
||||
self.media_type = media_type
|
||||
self.voice = {"waveform": voice} if type(voice) == list else None
|
||||
self.entities = entities
|
||||
self.chat_id = chat_id
|
||||
self.avatar = avatar
|
||||
self.from_ = from_
|
||||
self.reply = reply
|
||||
|
||||
if is_forward:
|
||||
self.from_.name = f"Forwarded from {self.from_.name}"
|
||||
|
||||
if via_bot:
|
||||
self.from_.name = f"via @{via_bot}"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"text": self.text,
|
||||
"media": {"base64": self.media} if self.media else None,
|
||||
"mediaType": self.media_type,
|
||||
"voice": self.voice,
|
||||
"entities": [entity.to_dict() for entity in self.entities]
|
||||
if self.entities
|
||||
else None,
|
||||
"chatId": self.chat_id,
|
||||
"avatar": self.avatar,
|
||||
"from": self.from_.to_dict(),
|
||||
"replyMessage": self.reply,
|
||||
}
|
||||
|
||||
|
||||
class QuotePayload:
|
||||
def __init__(
|
||||
self,
|
||||
messages: List[MessagePayload],
|
||||
type_: str = "quote",
|
||||
background: str = "",
|
||||
**kwargs,
|
||||
):
|
||||
self.type = type_
|
||||
self.messages = messages
|
||||
self.background = background
|
||||
self._ = kwargs
|
||||
|
||||
def to_dict(self):
|
||||
r = {
|
||||
"type": self.type,
|
||||
"format": "webp",
|
||||
"width": 512,
|
||||
"height": 768,
|
||||
"scale": 2,
|
||||
"messages": [message.to_dict() for message in self.messages],
|
||||
**self._,
|
||||
}
|
||||
|
||||
if self.background:
|
||||
r["backgroundColor"] = self.background
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def get_message_media(message: Message):
|
||||
return (
|
||||
message.photo
|
||||
or message.sticker
|
||||
or message.video
|
||||
or message.video_note
|
||||
or message.gif
|
||||
or message.web_preview
|
||||
if message and message.media
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def get_entities(entities: types.TypeMessageEntity):
|
||||
# coded by @droox
|
||||
r = [] # EntityPayload
|
||||
if entities:
|
||||
for entity in entities:
|
||||
entity = entity.to_dict()
|
||||
entity["type"] = entity.pop("_").replace("MessageEntity", "").lower()
|
||||
if entity["type"] == "customemoji":
|
||||
entity["type"] = "custom_emoji"
|
||||
type_ = entity["type"]
|
||||
offset = entity["offset"]
|
||||
length = entity["length"]
|
||||
del entity["type"], entity["offset"], entity["length"]
|
||||
|
||||
r.append(
|
||||
EntityPayload(
|
||||
type_,
|
||||
offset,
|
||||
length,
|
||||
**entity,
|
||||
)
|
||||
)
|
||||
return r
|
||||
|
||||
|
||||
def get_message_text(message: Message, reply: bool = False, voice_text: str = ""):
|
||||
mb = 1024 * 1024
|
||||
if message.photo and reply:
|
||||
return "📷 Photo"
|
||||
elif message.sticker and reply:
|
||||
return f"{message.file.emoji} Sticker"
|
||||
elif message.video_note and reply:
|
||||
return "📹 Video note"
|
||||
elif message.video and reply:
|
||||
return "📹 Video"
|
||||
elif message.gif and reply:
|
||||
return "🖼 GIF"
|
||||
elif message.poll and reply:
|
||||
return "📊 Questioning"
|
||||
elif message.geo and reply:
|
||||
return "📍 Geolocation"
|
||||
elif message.contact and reply:
|
||||
return "👤 Contact"
|
||||
elif message.voice:
|
||||
duration = strftime(message.voice.attributes[0].duration)
|
||||
size = (
|
||||
message.voice.size / 1024
|
||||
if message.voice.size < mb
|
||||
else (message.voice.size / 1024 / 1024)
|
||||
)
|
||||
return f"▶️ {duration}, {size:.1f} {('KB' if message.voice.size < mb else 'MB')}\n{voice_text}"
|
||||
elif message.audio:
|
||||
audio_attributes = message.audio.attributes[0]
|
||||
duration = strftime(audio_attributes.duration)
|
||||
return f"🎧 Music: {duration} | {audio_attributes.performer} - {audio_attributes.title}"
|
||||
elif type(message.media) == types.MessageMediaDocument and not get_message_media(
|
||||
message
|
||||
):
|
||||
media = message.media.document.size
|
||||
size = media / 1024 if media < mb else (media / 1024 / 1024)
|
||||
return (
|
||||
f"💾 File: {message.file.name}, {size:.1f} {('KB' if media < mb else 'MB')}"
|
||||
)
|
||||
elif type(message.media) == types.MessageMediaDice:
|
||||
return f"{message.media.emoticon} {message.media.value} points"
|
||||
elif type(message) == types.MessageService:
|
||||
return f"Service message: {message.action.to_dict()['_']}"
|
||||
else:
|
||||
return message.raw_text
|
||||
|
||||
|
||||
def strftime(time: Union[int, float]):
|
||||
t = gmtime(time)
|
||||
return (
|
||||
f"{t.tm_hour:02d}:" if t.tm_hour > 0 else ""
|
||||
) + f"{t.tm_min:02d}:{t.tm_sec:02d}"
|
||||
|
||||
|
||||
def decode_waveform(wf):
|
||||
if not wf:
|
||||
return [0 for _ in range(0, 20)]
|
||||
bits_count = len(wf) * 8
|
||||
values_count = bits_count // 5
|
||||
|
||||
if not values_count:
|
||||
return []
|
||||
|
||||
last_idx = values_count - 1
|
||||
|
||||
result = []
|
||||
for i in range(last_idx):
|
||||
j = i * 5
|
||||
byte_idx = j // 8
|
||||
bit_shift = j % 8
|
||||
result.append((wf[byte_idx] >> bit_shift) & 0b11111)
|
||||
|
||||
last_byte_idx = (last_idx * 5) // 8
|
||||
last_bit_shift = (last_idx * 5) % 8
|
||||
last_value = (
|
||||
wf[last_byte_idx]
|
||||
if last_byte_idx == len(wf) - 1
|
||||
else int.from_bytes(wf[last_byte_idx : last_byte_idx + 2], "little")
|
||||
)
|
||||
result.append((last_value >> last_bit_shift) & 0b11111)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_reply(message: Message) -> Union[dict, None]:
|
||||
reply_name = reply_text = None
|
||||
if not message.fwd_from:
|
||||
if reply := await message.get_reply_message():
|
||||
reply_name = telethon.utils.get_display_name(reply.sender)
|
||||
reply_text = get_message_text(reply, True)
|
||||
|
||||
return (
|
||||
{
|
||||
"chatId": message.chat_id,
|
||||
"text": reply_text,
|
||||
"name": reply_name,
|
||||
}
|
||||
if reply_name
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class QuotesMod(loader.Module):
|
||||
"""
|
||||
Quotes by @vsecoder [beta]
|
||||
|
||||
Now doesn't work stickers, gifs, video.
|
||||
(Fake stories later)
|
||||
|
||||
Thk t.me/Fl1yd, based on his SQuotes module
|
||||
Thk t.me/hikariatama, recognize from VTT module
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Quotes",
|
||||
"no_reply": "<b>[Quotes]</b> No reply",
|
||||
"api_error": "<b>[Quotes]</b> API error",
|
||||
"no_args_or_reply": "<b>[Quotes]</b> No args or reply",
|
||||
"args_error": (
|
||||
"<b>[Quotes]</b> An error ocurred while parsing args. Request was:"
|
||||
" <code>{}</code>"
|
||||
),
|
||||
}
|
||||
|
||||
async def client_ready(self, client: telethon.TelegramClient, db: dict) -> None:
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.api_endpoint = "http://q.api.vsecoder.dev/generate"
|
||||
self.settings = self.get_settings()
|
||||
|
||||
async def qcmd(self, message: Message) -> None:
|
||||
"""
|
||||
<reply> [quantity] [!story] [!rec] [color] - Create nice quote from message(-s)
|
||||
"""
|
||||
|
||||
args: List[str] = utils.get_args(message)
|
||||
if not await message.get_reply_message():
|
||||
await utils.answer(message, self.strings["no_reply"])
|
||||
return
|
||||
|
||||
stories = "!story" in args
|
||||
recognize = "!rec" in args
|
||||
[count] = [int(arg) for arg in args if arg.isdigit() and int(arg) > 0] or [1]
|
||||
bg_color = self.settings["bg_color"]
|
||||
payload = QuotePayload(
|
||||
await self.quote_parse_messages(message, count, recognize),
|
||||
("stories" if stories else "quote"),
|
||||
**({"background": bg_color}),
|
||||
)
|
||||
|
||||
await self.send_quote(message, payload, stories)
|
||||
|
||||
async def fqcmd(self, message: Message) -> None:
|
||||
"""
|
||||
<@ or id> <text> -r <@ or id> <text> ... - Create fake quote
|
||||
"""
|
||||
|
||||
args: List[str] = utils.get_args(message)
|
||||
|
||||
stories = False # "!story" in args
|
||||
[bg_color] = [self.settings["bg_color"]]
|
||||
|
||||
msgs = await self.fake_quote_parse_messages(message)
|
||||
|
||||
if not msgs:
|
||||
await utils.answer(message, self.strings["args_error"].format(args))
|
||||
return
|
||||
|
||||
payload = QuotePayload(
|
||||
msgs,
|
||||
("stories" if stories else "quote"),
|
||||
**({"background": bg_color}),
|
||||
)
|
||||
|
||||
await self.send_quote(message, payload, stories)
|
||||
|
||||
async def send_quote(
|
||||
self, message: Message, payload: QuotePayload, stories: bool = False
|
||||
) -> None:
|
||||
r = await self._api_request(payload.to_dict())
|
||||
if r.status_code != 200:
|
||||
await utils.answer(message, self.strings["api_error"])
|
||||
return
|
||||
|
||||
quote = r.json()["image"]
|
||||
img_data = quote.encode()
|
||||
content = base64.b64decode(img_data)
|
||||
quote = io.BytesIO(content)
|
||||
quote.name = f'Quote.{"png" if stories else "webp"}'
|
||||
|
||||
await utils.answer(message, quote, force_document=stories)
|
||||
await (
|
||||
message[0] if isinstance(message, (list, tuple, set)) else message
|
||||
).delete()
|
||||
|
||||
async def quote_parse_messages(
|
||||
self, message: Message, count: int, recognize: bool = False
|
||||
) -> Union[List[MessagePayload], bool]:
|
||||
payloads = []
|
||||
messages = [
|
||||
msg
|
||||
async for msg in self.client.iter_messages(
|
||||
message.chat_id,
|
||||
count,
|
||||
reverse=True,
|
||||
add_offset=1,
|
||||
offset_id=(await message.get_reply_message()).id,
|
||||
)
|
||||
]
|
||||
|
||||
if len(messages) > self.settings["max_messages"]:
|
||||
await utils.answer(
|
||||
message,
|
||||
f"<b>[Quotes]</b> Maximum messages count is {self.settings['max_messages']}",
|
||||
)
|
||||
return False
|
||||
|
||||
payloads.extend(await self.parse_messages(messages, recognize))
|
||||
|
||||
return payloads
|
||||
|
||||
async def fake_quote_parse_messages(
|
||||
self, message: Message
|
||||
) -> Union[List[MessagePayload], bool]:
|
||||
# example text: @vsecoder hi my friend -r @enestasy7 hi ; <!story> (optional)
|
||||
payloads = []
|
||||
messages = []
|
||||
args: List[str] = utils.get_args_raw(message).split(" -r ")
|
||||
|
||||
for arg in args:
|
||||
name, text = arg.split(" ", 1)
|
||||
if not name or not text:
|
||||
return False
|
||||
|
||||
try:
|
||||
user = await self.client.get_entity(
|
||||
int(name) if name.isdigit() else name
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
messages.append(
|
||||
Message(
|
||||
id=0,
|
||||
message=text,
|
||||
from_id=user.id,
|
||||
date=message.date,
|
||||
out=False,
|
||||
media=None,
|
||||
reply_to=None,
|
||||
fwd_from=None,
|
||||
via_bot_id=None,
|
||||
reply_markup=None,
|
||||
entities=None,
|
||||
views=None,
|
||||
edit_date=None,
|
||||
post_author=None,
|
||||
restriction_reason=None,
|
||||
ttl_period=None,
|
||||
)
|
||||
)
|
||||
|
||||
payloads.extend(await self.parse_messages(messages))
|
||||
|
||||
return payloads
|
||||
|
||||
async def get_entity(self, message: Message) -> UserPayload:
|
||||
chat = message.peer_id
|
||||
peer = message.from_id or chat
|
||||
fwd = message.fwd_from
|
||||
|
||||
if fwd:
|
||||
peer = fwd.from_id
|
||||
name = fwd.post_author or fwd.from_name
|
||||
|
||||
t = type(peer)
|
||||
|
||||
if t is int:
|
||||
uid = peer
|
||||
elif t is PeerUser:
|
||||
uid = peer.user_id
|
||||
elif t is PeerChannel:
|
||||
uid = peer.channel_id
|
||||
elif t is PeerChat:
|
||||
uid = peer.chat_id
|
||||
elif t is PeerBlocked:
|
||||
uid = peer.peer_id
|
||||
elif not peer:
|
||||
uid = int(hashlib.shake_256(name.encode("utf-8")).hexdigest(6), 16)
|
||||
|
||||
entity = None
|
||||
try:
|
||||
entity = await self.client.get_entity(peer)
|
||||
except Exception:
|
||||
entity = await message.get_chat()
|
||||
|
||||
if t is PeerChannel or t is PeerChat:
|
||||
formated_entity = UserPayload(
|
||||
entity.id,
|
||||
entity.title,
|
||||
"",
|
||||
"",
|
||||
"ru",
|
||||
None,
|
||||
None,
|
||||
{"small_file_id": entity.photo.photo_id} if entity.photo else None,
|
||||
"private",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
formated_entity = UserPayload(
|
||||
entity.id,
|
||||
entity.first_name,
|
||||
entity.last_name,
|
||||
entity.username
|
||||
if entity.username
|
||||
else entity.usernames[0].username
|
||||
if entity.usernames
|
||||
else "",
|
||||
"ru",
|
||||
None,
|
||||
str(entity.emoji_status.document_id) if entity.premium else None,
|
||||
{"small_file_id": entity.photo.photo_id} if entity.photo else None,
|
||||
"private",
|
||||
)
|
||||
except:
|
||||
formated_entity = UserPayload(
|
||||
0,
|
||||
fwd.from_name if fwd else "Unknown",
|
||||
"",
|
||||
None,
|
||||
"ru",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"private",
|
||||
)
|
||||
|
||||
return formated_entity
|
||||
|
||||
async def recognize(self, message: Message):
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
file = os.path.join(
|
||||
tmpdir,
|
||||
"audio.mp3" if message.audio else "audio.ogg",
|
||||
)
|
||||
|
||||
data = await message.download_media(bytes)
|
||||
|
||||
with open(file, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
song = AudioSegment.from_file(
|
||||
file, format="mp3" if message.audio else "ogg"
|
||||
)
|
||||
song.export(os.path.join(tmpdir, "audio.wav"), format="wav")
|
||||
|
||||
r = sr.Recognizer()
|
||||
|
||||
with sr.AudioFile(os.path.join(tmpdir, "audio.wav")) as source:
|
||||
audio_data = r.record(source)
|
||||
text = await utils.run_sync(
|
||||
r.recognize_google, audio_data, language="ru-RU"
|
||||
)
|
||||
return text
|
||||
except Exception:
|
||||
logger.exception("Can't recognize")
|
||||
return "Can't recognize"
|
||||
|
||||
async def parse_messages(
|
||||
self, messages: List[Message], recognize: bool = False
|
||||
) -> List[MessagePayload]:
|
||||
payloads = []
|
||||
|
||||
for message in messages:
|
||||
media = get_message_media(message)
|
||||
base64_media = None
|
||||
if media:
|
||||
base64_media = base64.b64encode(
|
||||
await self.client.download_file(media)
|
||||
).decode()
|
||||
voice_text = ""
|
||||
if message.voice and recognize:
|
||||
voice_text = "Recognize:\n" + str(await self.recognize(message))
|
||||
text = get_message_text(message, False, voice_text)
|
||||
entities = get_entities(message.entities)
|
||||
from_ = await self.get_entity(message)
|
||||
|
||||
reply = await get_reply(message)
|
||||
"""
|
||||
text: str,
|
||||
entities: Union[List[EntityPayload], None],
|
||||
chat_id: int,
|
||||
avatar: bool,
|
||||
from_: UserPayload,
|
||||
reply: Union[dict, None],
|
||||
media: Union[dict, None] = None,
|
||||
media_type: Union[str, None] = None,
|
||||
voice: Union[str, None] = None,
|
||||
is_forward: Union[bool, None] = False,
|
||||
via_bot: Union[bool, None] = None,
|
||||
"""
|
||||
|
||||
payloads.append(
|
||||
MessagePayload(
|
||||
text,
|
||||
entities,
|
||||
message.chat_id,
|
||||
True,
|
||||
from_,
|
||||
reply,
|
||||
base64_media,
|
||||
None,
|
||||
decode_waveform(message.voice.attributes[0].waveform)
|
||||
if message.voice
|
||||
else None,
|
||||
True if message.fwd_from else False,
|
||||
message.via_bot.username if message.via_bot else None,
|
||||
)
|
||||
)
|
||||
|
||||
return payloads
|
||||
|
||||
async def sqsetcmd(self, message: Message) -> None:
|
||||
"""<bg_color/max_messages> <value> - Configure Quotes (text color automatically adjust to the background)"""
|
||||
args: List[str] = utils.get_args_raw(message).split(maxsplit=1)
|
||||
if not args:
|
||||
return await utils.answer(
|
||||
message,
|
||||
(
|
||||
"<b>[Quotes]</b> Settings:\n\nMax messages"
|
||||
" (<code>max_messages</code>):"
|
||||
f" {self.settings['max_messages']}\nBackground color"
|
||||
f" (<code>bg_color</code>): {self.settings['bg_color']}"
|
||||
),
|
||||
)
|
||||
|
||||
if args[0] == "reset":
|
||||
self.get_settings(True)
|
||||
await utils.answer(message, "<b>[Quotes]</b> Settings has been reset")
|
||||
return
|
||||
|
||||
if len(args) < 2:
|
||||
await utils.answer(message, "<b>[Quotes]</b> Insufficient args")
|
||||
return
|
||||
|
||||
mods = ["max_messages", "bg_color"]
|
||||
if args[0] not in mods:
|
||||
await utils.answer(message, f"<b>[Quotes]</b> Unknown param")
|
||||
return
|
||||
elif args[0] == "max_messages":
|
||||
if not args[1].isdigit():
|
||||
await utils.answer(message, "<b>[Quotes]</b> Number is expected")
|
||||
return
|
||||
|
||||
self.settings[args[0]] = int(args[1])
|
||||
|
||||
else:
|
||||
self.settings[args[0]] = args[1]
|
||||
|
||||
self.db.set("Quotes", "settings", self.settings)
|
||||
return await utils.answer(
|
||||
message, f"<b>[Quotes]</b> Param {args[0]} value is now {args[1]}"
|
||||
)
|
||||
|
||||
def get_settings(self, force: bool = False):
|
||||
settings: dict = self.db.get("Quotes", "settings", {})
|
||||
if not settings or force:
|
||||
settings.update({"max_messages": 15, "bg_color": "#162330"})
|
||||
self.db.set("Quotes", "settings", settings)
|
||||
|
||||
return settings
|
||||
|
||||
async def _api_request(self, data: dict):
|
||||
# logger.error(data)
|
||||
return await utils.run_sync(requests.post, self.api_endpoint, json=data)
|
||||
217
vsecoder/hikka_modules/searx.py
Normal file
217
vsecoder/hikka_modules/searx.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
Thk @fleef
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSCO9v08B8wLGwL4UMxZzlf7tNOvsRvWQjMypjq5uyvxhAa03NbOO40DY1m-Rr4aYeK7WE&usqp=CAU
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSCO9v08B8wLGwL4UMxZzlf7tNOvsRvWQjMypjq5uyvxhAa03NbOO40DY1m-Rr4aYeK7WE&usqp=CAU&title=SearX&description=Telegram%20SearX%20Engine
|
||||
|
||||
|
||||
__version__ = (1, 0, 1)
|
||||
|
||||
import logging
|
||||
import json
|
||||
import urllib3
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
urllib3.disable_warnings()
|
||||
|
||||
engines = (
|
||||
"bing_images",
|
||||
"mediawiki",
|
||||
"searchcode_code",
|
||||
"yahoo_news",
|
||||
"semantic_scholar",
|
||||
"btdigg",
|
||||
"nyaa",
|
||||
"1337x",
|
||||
"bing_news",
|
||||
"reddit",
|
||||
"startpage",
|
||||
"apkmirror",
|
||||
"bandcamp",
|
||||
"genius",
|
||||
"wolframalpha_noapi",
|
||||
"torrentz",
|
||||
"youtube_noapi",
|
||||
"archlinux",
|
||||
"vimeo",
|
||||
"sepiasearch",
|
||||
"fdroid",
|
||||
"piratebay",
|
||||
"soundcloud",
|
||||
"bing",
|
||||
"frinkiac",
|
||||
"ina",
|
||||
"google_videos",
|
||||
"openstreetmap",
|
||||
"pdbe",
|
||||
"rumble",
|
||||
"openverse",
|
||||
"ebay",
|
||||
"tvmaze",
|
||||
"mediathekviewweb",
|
||||
"onesearch",
|
||||
"mixcloud",
|
||||
"duckduckgo",
|
||||
"bing_videos",
|
||||
"duckduckgo_images",
|
||||
"pubmed",
|
||||
"yahoo",
|
||||
"github",
|
||||
"microsoft_academic",
|
||||
"digg",
|
||||
"google_images",
|
||||
"tineye",
|
||||
"google_scholar",
|
||||
"framalibre",
|
||||
"duckduckgo_definitions",
|
||||
"xpath",
|
||||
"currency_convert",
|
||||
"gentoo",
|
||||
"translated",
|
||||
"unsplash",
|
||||
"json_engine",
|
||||
"invidious",
|
||||
"google",
|
||||
"kickass",
|
||||
"etools",
|
||||
"dictzone",
|
||||
"photon",
|
||||
"yggtorrent",
|
||||
"deezer",
|
||||
"duden",
|
||||
"seznam",
|
||||
"gigablast",
|
||||
"deviantart",
|
||||
"wikidata",
|
||||
"tokyotoshokan",
|
||||
"flickr_noapi",
|
||||
"peertube",
|
||||
"qwant",
|
||||
"stackexchange",
|
||||
"imdb",
|
||||
"wordnik",
|
||||
"loc",
|
||||
"www1x",
|
||||
"solidtorrents",
|
||||
"google_news",
|
||||
"sjp",
|
||||
"wikipedia",
|
||||
"dailymotion",
|
||||
"arxiv",
|
||||
"yandex",
|
||||
)
|
||||
|
||||
engines_str = "| "
|
||||
for engine in engines:
|
||||
engines_str += f"{engine} | "
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SearXMod(loader.Module):
|
||||
"""Module for multi search"""
|
||||
|
||||
strings = {
|
||||
"name": "SearX",
|
||||
"cfg_engine": f"Search engine, all: \n{engines_str}",
|
||||
"cfg_searx_link": "SearX link, get from https://searx.space/",
|
||||
"error": "<emoji document_id=5467928559664242360>❗️</emoji> Error: \n{}",
|
||||
"loading": "<emoji document_id=5381942305081010778>⏳</emoji> Loading...",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"cfg_engine": f"Поисковик, все: \n{engines_str}",
|
||||
"cfg_searx_link": "Ссылка на SearX, получить можно на https://searx.space/",
|
||||
"error": "<emoji document_id=5467928559664242360>❗️</emoji> Ошибка: \n{}",
|
||||
"loading": "<emoji document_id=5381942305081010778>⏳</emoji> Загрузка...",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"engine",
|
||||
"duckduckgo",
|
||||
self.strings["cfg_engine"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"searx_link",
|
||||
"https://searx.thegpm.org/",
|
||||
self.strings["cfg_searx_link"],
|
||||
validator=loader.validators.Link(),
|
||||
),
|
||||
)
|
||||
|
||||
async def request(
|
||||
self, session, query: str, engine: str = "yandex", count_results: int = 3
|
||||
):
|
||||
if engine not in engines:
|
||||
return self.strings["error"].format("This engine is not found")
|
||||
if not query:
|
||||
return self.strings["error"].format("Specify a request")
|
||||
|
||||
def_params = dict(
|
||||
category_general="1",
|
||||
q=query,
|
||||
language="ru-RU",
|
||||
format="json",
|
||||
engines=engine,
|
||||
)
|
||||
|
||||
url = self.config["searx_link"]
|
||||
|
||||
start_time = datetime.now()
|
||||
|
||||
raw_results = json.loads(
|
||||
session.request("GET", url, fields={**def_params}).data.decode("UTF-8")
|
||||
)["results"]
|
||||
|
||||
len_raw_result = len(raw_results)
|
||||
raw_results = (
|
||||
raw_results[:2]
|
||||
if len_raw_result < count_results
|
||||
else raw_results[:count_results]
|
||||
)
|
||||
|
||||
pretty_result = "".join(
|
||||
f" 💡: <i>{result['title']}</i>\n 🔗: {result['url']}\n\n"
|
||||
for result in raw_results
|
||||
)
|
||||
|
||||
return (
|
||||
f"📟 <b>{engine}</b>\n\n{pretty_result}\n⏱: {datetime.now() - start_time}"
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
|
||||
async def searxcmd(self, message):
|
||||
"""
|
||||
{text} - search text in the internet
|
||||
|
||||
Based on SearX and t.me/fleef code
|
||||
"""
|
||||
args = utils.get_args_raw(message).split("&")
|
||||
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
session = urllib3.PoolManager()
|
||||
result = await self.request(session, args[0], self.config["engine"], 3)
|
||||
await utils.answer(message, result)
|
||||
103
vsecoder/hikka_modules/speechcensorship.py
Normal file
103
vsecoder/hikka_modules/speechcensorship.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# requires: git+https://github.com/vsecoder/py-censure
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
import logging
|
||||
from .. import loader, utils # type: ignore
|
||||
from censure import Censor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SpeechCensorshipMod(loader.Module):
|
||||
"""Module for censoring your speech"""
|
||||
|
||||
strings = {
|
||||
"name": "SpeechCensorship",
|
||||
"_cfg_replace_symbols": "Replace symbols for censoring",
|
||||
"_cfg_language": "Language",
|
||||
"turn": "<emoji document_id=5235698355119596341>😆</emoji> <b>Censorship mode {}</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cfg_replace_symbols": "Символы для замены мата",
|
||||
"_cfg_language": "Язык",
|
||||
"turn": "<emoji document_id=5235698355119596341>😆</emoji> <b>Режим цензуры {}</b>",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.get("censorship_work", False)
|
||||
self.me = await client.get_me()
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"replace_symbols",
|
||||
"#",
|
||||
lambda m: self.strings["_cfg_replace_symbols"],
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"language",
|
||||
["ru", "en"],
|
||||
lambda m: self.strings["_cfg_language"],
|
||||
validator=loader.validators.MultiChoice(["ru", "en"]),
|
||||
),
|
||||
)
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def censorshipcmd(self, message):
|
||||
"""
|
||||
Turn on/off censorship mode
|
||||
"""
|
||||
censorship_work = self.get("censorship_work", False)
|
||||
|
||||
work = "on" if not censorship_work else "off"
|
||||
self.set("censorship_work", not censorship_work)
|
||||
|
||||
await utils.answer(message, self.strings["turn"].format(work))
|
||||
|
||||
async def censor_task(self, text):
|
||||
languages = self.config["language"]
|
||||
replace_symbols = self.config["replace_symbols"]
|
||||
|
||||
censors = [Censor.get(lang) for lang in languages]
|
||||
|
||||
for censor in censors:
|
||||
text = censor.clean_line(text, beep=replace_symbols)[0]
|
||||
|
||||
return text
|
||||
|
||||
async def watcher(self, message):
|
||||
try:
|
||||
if self.me.id != message.from_id:
|
||||
return
|
||||
|
||||
censorship_work = self.get("censorship_work", False)
|
||||
|
||||
if not censorship_work:
|
||||
return
|
||||
|
||||
text = message.text
|
||||
|
||||
if text:
|
||||
censored = await self.censor_task(text)
|
||||
|
||||
if text != censored:
|
||||
await message.edit(censored)
|
||||
except:
|
||||
return
|
||||
197
vsecoder/hikka_modules/steam.py
Normal file
197
vsecoder/hikka_modules/steam.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/3d-fluency/94/steam.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/3d-fluency/94/steam.png&title=Steam&description=Module%20for%20get%20Steam%20account%20information
|
||||
|
||||
__version__ = (1, 0, 0)
|
||||
|
||||
import logging
|
||||
import aiohttp
|
||||
from datetime import datetime
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def minutes_to_hours(minutes: int) -> str:
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
return f'{hours}h {minutes}m'
|
||||
|
||||
|
||||
def get_created_ago(timestamp: int) -> str:
|
||||
created_at = datetime.fromtimestamp(timestamp)
|
||||
now = datetime.now()
|
||||
ago = now - created_at
|
||||
|
||||
return f"{ago.days // 365} years"
|
||||
|
||||
|
||||
class SteamSDK:
|
||||
def __init__(self, apikey: str, steamid: str):
|
||||
self.apikey = apikey
|
||||
self.steamid = steamid
|
||||
|
||||
self.api_url = "https://api.steampowered.com/{method}/"
|
||||
|
||||
self.profile_method = "ISteamUser/GetPlayerSummaries/v0002"
|
||||
self.recent_games_method = "IPlayerService/GetRecentlyPlayedGames/v0001"
|
||||
self.owned_games_method = "IPlayerService/GetOwnedGames/v0001"
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
url: str,
|
||||
params: dict,
|
||||
) -> dict:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.request("GET", url, params=params) as response:
|
||||
if response.status != 200:
|
||||
return {"code": response.status, "detail": response.reason}
|
||||
return await response.json()
|
||||
except aiohttp.ClientConnectorError:
|
||||
return {"detail": "Connection error", "code": 0}
|
||||
except Exception as e:
|
||||
return {"detail": f"Unknown error: {e}", "code": 0}
|
||||
|
||||
async def get_profile(self) -> dict:
|
||||
url = self.api_url.format(method=self.profile_method)
|
||||
params = {"key": self.apikey, "steamids": self.steamid}
|
||||
return await self._request(url, params)
|
||||
|
||||
async def get_recent_games(self) -> dict:
|
||||
url = self.api_url.format(method=self.recent_games_method)
|
||||
params = {"key": self.apikey, "steamid": self.steamid}
|
||||
return await self._request(url, params)
|
||||
|
||||
async def get_owned_games(self) -> dict:
|
||||
url = self.api_url.format(method=self.owned_games_method)
|
||||
params = {"key": self.apikey, "steamid": self.steamid}
|
||||
return await self._request(url, params)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SteamMod(loader.Module):
|
||||
"""
|
||||
Module for get Steam account information
|
||||
|
||||
Later (TODO):
|
||||
- achivments list
|
||||
- {STEAM} widget
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Steam",
|
||||
"profile": (
|
||||
'<a href="{avatar}"></a><emoji document_id=5328115953861408177>🎮</emoji> <b>{name}</b> <i>aka</i> '
|
||||
'<a href="https://steamcommunity.com/id/{username}/"><i>{username}</i></a> (created {created_ago} ago)\n'
|
||||
"<emoji document_id=5938413566624272793>🎮</emoji> Now {state}\n\n"
|
||||
"<emoji document_id=5879813604068298387>❗️</emoji> <b>More info:</b>\n"
|
||||
" <emoji document_id=5274034223886389748>❤️</emoji><i>Level:</i> <code>{level}</code>\n"
|
||||
" <emoji document_id=5274034223886389748>❤️</emoji><i>Total time spent:</i> <code>{time_spent}</code>\n"
|
||||
" <emoji document_id=5274034223886389748>❤️</emoji><i>Owned games:</i> <code>{games_count}</code>\n\n"
|
||||
"<emoji document_id=5843799474362652262>🔄</emoji> <b>Recently played in:</b>\n"
|
||||
"{games}"
|
||||
),
|
||||
"game": (
|
||||
' <emoji document_id=5274034223886389748>❤️</emoji><a href="https://steamcommunity.com/app/{game_id}">'
|
||||
"<i>{game}</i></a> (<code>{time_spent}</code>)\n"
|
||||
),
|
||||
"state_online": 'in "<i><a href="https://steamcommunity.com/app/{game_id}">{game}</a></i>"',
|
||||
"state_offline": "offline",
|
||||
"loading": "<emoji document_id=5323331656646407005>🎮</emoji> <b>Loading...</b>",
|
||||
"error_403": "<emoji document_id=5778527486270770928>❌</emoji> <b>Access denied, token is invalid or another error occurred</b>\nDEBUG INFO: <code>{error}</code>",
|
||||
"error_empty": "<emoji document_id=5778527486270770928>❌</emoji> <b>Empty API key or steamID64</b> (open <code> .config steam</code>)",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"apikey",
|
||||
"",
|
||||
"Get token from https://steamcommunity.com/dev/apikey",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"steamid",
|
||||
"",
|
||||
"Get steamID64 from https://steamid.io/lookup/",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
|
||||
async def steamcmd(self, message):
|
||||
"""
|
||||
- get steam profile
|
||||
"""
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
|
||||
if not self.config["apikey"] or not self.config["steamid"]:
|
||||
return await utils.answer(message, self.strings["error_empty"])
|
||||
|
||||
sdk = SteamSDK(self.config["apikey"], self.config["steamid"])
|
||||
|
||||
profile = await sdk.get_profile()
|
||||
owned_games = await sdk.get_owned_games()
|
||||
recent_games = await sdk.get_recent_games()
|
||||
|
||||
if "code" in profile:
|
||||
return await utils.answer(
|
||||
message,
|
||||
self.strings["error_403"].format(error=str(profile))
|
||||
)
|
||||
|
||||
profile = profile["response"]["players"][0]
|
||||
|
||||
if "gameid" in profile:
|
||||
game_id = profile["gameid"]
|
||||
game = profile["gameextrainfo"]
|
||||
state = self.strings["state_online"].format(game=game, game_id=game_id)
|
||||
else:
|
||||
state = self.strings["state_offline"]
|
||||
|
||||
games_count = owned_games["response"]["game_count"]
|
||||
created_ago = get_created_ago(profile["timecreated"])
|
||||
time_spent = sum(
|
||||
[game["playtime_forever"] for game in owned_games["response"]["games"]]
|
||||
)
|
||||
games = "".join(
|
||||
[
|
||||
self.strings["game"].format(
|
||||
game=x["name"],
|
||||
game_id=x["appid"],
|
||||
time_spent=minutes_to_hours(x["playtime_forever"]),
|
||||
)
|
||||
for x in recent_games["response"]["games"]
|
||||
]
|
||||
)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["profile"].format(
|
||||
avatar=profile["avatar"],
|
||||
name=profile["realname"] if "realname" in profile else "NoName",
|
||||
username=profile["personaname"],
|
||||
level=profile["communityvisibilitystate"],
|
||||
created_ago=created_ago,
|
||||
games_count=games_count,
|
||||
time_spent=minutes_to_hours(time_spent),
|
||||
state=state,
|
||||
games=games,
|
||||
),
|
||||
link_preview=True,
|
||||
)
|
||||
153
vsecoder/hikka_modules/vsecodertranslate.py
Normal file
153
vsecoder/hikka_modules/vsecodertranslate.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/external-vitaliy-gorbachev-lineal-color-vitaly-gorbachev/344/external-translate-online-learning-vitaliy-gorbachev-lineal-color-vitaly-gorbachev.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/external-vitaliy-gorbachev-lineal-color-vitaly-gorbachev/344/external-translate-online-learning-vitaliy-gorbachev-lineal-color-vitaly-gorbachev.png&title=VsecoderTranlate&description=Telegram%20Translate%20Bot
|
||||
|
||||
__version__ = (2, 2, 1)
|
||||
|
||||
import logging
|
||||
import translators as vt # type: ignore
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class VseTranslateMod(loader.Module):
|
||||
"""Traslate text"""
|
||||
strings = {
|
||||
"name": "💠 Vsecoder Translate",
|
||||
"invalid_args": "📥 Invalid arguments!",
|
||||
"answer": (
|
||||
"💠 <b>{}</b> <i>from:</i><b>[{}]</b>"
|
||||
" <i>to:</i><b>[{}]</b>\n\n<code>{}</code>"
|
||||
),
|
||||
"error": "📥 Error!",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"invalid_args": "📥 Неправильные аргументы!",
|
||||
"answer": (
|
||||
"💠 <b>{}</b> <i>с:</i><b>[{}]</b> <i>на:</i><b>[{}]</b>\n\n<code>{}</code>"
|
||||
),
|
||||
"error": "📥 Ошибка!",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"default_lang",
|
||||
"ru",
|
||||
"Which language to translate by default",
|
||||
validator=loader.validators.Choice(
|
||||
["ru", "en", "de", "fr", "es", "it", "pt", "ja", "zh", "ko"]
|
||||
),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"default_translator",
|
||||
"google",
|
||||
"Which translator to use by default",
|
||||
validator=loader.validators.Choice(
|
||||
["google", "yandex", "bing", "iciba"]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, _):
|
||||
self._client = client
|
||||
|
||||
async def translate(
|
||||
self,
|
||||
text: str,
|
||||
lang_from: str = "auto",
|
||||
lang_to: str = "ru",
|
||||
translator: str = "google",
|
||||
) -> dict:
|
||||
translators = {
|
||||
"google": vt.google,
|
||||
"yandex": vt.yandex,
|
||||
"bing": vt.bing,
|
||||
"iciba": vt.iciba,
|
||||
}
|
||||
|
||||
if translator not in translators:
|
||||
return {"error": self.strings["invalid_translator"]}
|
||||
|
||||
translater = translators[translator]
|
||||
return {
|
||||
"translator": translator,
|
||||
"from": lang_from,
|
||||
"to": lang_to,
|
||||
"text": translater(text, from_language=lang_from, to_language=lang_to),
|
||||
}
|
||||
|
||||
async def vsetranslatecmd(self, message):
|
||||
"""
|
||||
[from_language] [to_language] [text]
|
||||
.vsetranslate en ru Hello, world!
|
||||
"""
|
||||
args = utils.get_args(message)
|
||||
langs = ["auto", "ru", "en", "de", "fr", "es", "it", "pt", "ja", "zh", "ko"]
|
||||
translators = ["google", "yandex", "bing", "iciba"]
|
||||
text = message.text.replace(f"{self.get_prefix()}vsetranslate", "")
|
||||
t = ""
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings["invalid_args"])
|
||||
if args[0] not in langs: # .vsetranslate text
|
||||
t = await self.translate(
|
||||
text,
|
||||
translator=self.config["default_translator"],
|
||||
lang_to=self.config["default_lang"],
|
||||
)
|
||||
elif args[1] not in langs: # .vsetranslate from_language text
|
||||
text = message.text.replace(
|
||||
f"{self.get_prefix()}vsetranslate {args[0]}", ""
|
||||
)
|
||||
t = await self.translate(
|
||||
text,
|
||||
translator=self.config["default_translator"],
|
||||
lang_to=self.config["default_lang"],
|
||||
lang_from=args[0],
|
||||
)
|
||||
elif args[2] not in translators: # .vsetranslate from_language to_language text
|
||||
text = message.text.replace(
|
||||
f"{self.get_prefix()}vsetranslate {args[0]} {args[1]}", ""
|
||||
)
|
||||
t = await self.translate(
|
||||
text,
|
||||
translator=self.config["default_translator"],
|
||||
lang_to=args[1],
|
||||
lang_from=args[0],
|
||||
)
|
||||
else: # .vsetranslate from_language to_language translator text
|
||||
text = message.text.replace(
|
||||
f"{self.get_prefix()}vsetranslate {args[0]} {args[1]} {args[2]}", ""
|
||||
)
|
||||
t = await self.translate(
|
||||
text,
|
||||
translator=args[2],
|
||||
lang_to=args[1],
|
||||
lang_from=args[0],
|
||||
)
|
||||
try:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["answer"].format(
|
||||
t["translator"],
|
||||
t["from"],
|
||||
t["to"],
|
||||
t["text"],
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
72
vsecoder/hikka_modules/wikimod.py
Normal file
72
vsecoder/hikka_modules/wikimod.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
# meta developer: @vsecoder_m
|
||||
# meta pic: https://img.icons8.com/cute-clipart/344/wikipedia.png
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.icons8.com/cute-clipart/344/wikipedia.png&title=Wikipedia&description=Module%20for%20wikipedia%20search
|
||||
|
||||
__version__ = (1, 0, 0)
|
||||
|
||||
import logging
|
||||
import wikipedia # type: ignore
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class WikiMod(loader.Module):
|
||||
"""Module for wikipedia search"""
|
||||
|
||||
strings = {
|
||||
"name": "Wikipedia🌐",
|
||||
"answer": "🌐 <b>{0}</b><a href='{1}'>:</a>\n\n<i>{2}</i>\n\n{3}",
|
||||
"error": "❗️ <b>{0}</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"wiki_lang",
|
||||
"en",
|
||||
"Language of wikipedia",
|
||||
validator=loader.validators.Choice(["en", "ru"]),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
|
||||
async def wikicmd(self, message):
|
||||
"""
|
||||
<text> - search in wikipedia
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
try:
|
||||
wikipedia.set_lang(self.config["wiki_lang"])
|
||||
except Exception:
|
||||
wikipedia.set_lang("en")
|
||||
|
||||
try:
|
||||
page = wikipedia.page(args)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["answer"].format(
|
||||
utils.escape_html(page.title),
|
||||
page.images[0],
|
||||
utils.escape_html(page.summary),
|
||||
page.url,
|
||||
),
|
||||
)
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
await utils.answer(message, self.strings["error"].format(e))
|
||||
except wikipedia.exceptions.PageError as e:
|
||||
await utils.answer(message, self.strings["error"].format(e))
|
||||
455
vsecoder/hikka_modules/ymnow.py
Normal file
455
vsecoder/hikka_modules/ymnow.py
Normal file
@@ -0,0 +1,455 @@
|
||||
__version__ = (3, 1, 1)
|
||||
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# requires: yandex-music aiohttp
|
||||
# meta desc: Module for yandex music. Based on SpotifyNow, YaNow and WakaTime [beta]
|
||||
# meta pic: https://img.freepik.com/premium-vector/yandex-music-logo_578229-242.jpg
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.freepik.com/premium-vector/yandex-music-logo_578229-242.jpg&title=YMNow&description=Module%20for%20yandex%20music
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
import aiohttp
|
||||
import random
|
||||
import json
|
||||
import string
|
||||
from asyncio import sleep
|
||||
from yandex_music import ClientAsync
|
||||
from telethon import TelegramClient
|
||||
from telethon.tl.types import Message
|
||||
from telethon.errors.rpcerrorlist import FloodWaitError, MessageNotModifiedError
|
||||
from telethon.tl.functions.account import UpdateProfileRequest
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.getLogger("yandex_music").propagate = False
|
||||
|
||||
|
||||
# https://github.com/FozerG/YandexMusicRPC/blob/main/main.py#L133
|
||||
async def get_current_track(client, token):
|
||||
device_info = {
|
||||
"app_name": "Chrome",
|
||||
"type": 1,
|
||||
}
|
||||
|
||||
ws_proto = {
|
||||
"Ynison-Device-Id": "".join(
|
||||
[random.choice(string.ascii_lowercase) for _ in range(16)]
|
||||
),
|
||||
"Ynison-Device-Info": json.dumps(device_info),
|
||||
}
|
||||
|
||||
timeout = aiohttp.ClientTimeout(total=15, connect=10)
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.ws_connect(
|
||||
url="wss://ynison.music.yandex.ru/redirector.YnisonRedirectService/GetRedirectToYnison",
|
||||
headers={
|
||||
"Sec-WebSocket-Protocol": f"Bearer, v2, {json.dumps(ws_proto)}",
|
||||
"Origin": "http://music.yandex.ru",
|
||||
"Authorization": f"OAuth {token}",
|
||||
},
|
||||
timeout=10,
|
||||
) as ws:
|
||||
recv = await ws.receive()
|
||||
data = json.loads(recv.data)
|
||||
|
||||
if "redirect_ticket" not in data or "host" not in data:
|
||||
print(f"Invalid response structure: {data}")
|
||||
return {"success": False}
|
||||
|
||||
new_ws_proto = ws_proto.copy()
|
||||
new_ws_proto["Ynison-Redirect-Ticket"] = data["redirect_ticket"]
|
||||
|
||||
to_send = {
|
||||
"update_full_state": {
|
||||
"player_state": {
|
||||
"player_queue": {
|
||||
"current_playable_index": -1,
|
||||
"entity_id": "",
|
||||
"entity_type": "VARIOUS",
|
||||
"playable_list": [],
|
||||
"options": {"repeat_mode": "NONE"},
|
||||
"entity_context": "BASED_ON_ENTITY_BY_DEFAULT",
|
||||
"version": {
|
||||
"device_id": ws_proto["Ynison-Device-Id"],
|
||||
"version": 9021243204784341000,
|
||||
"timestamp_ms": 0,
|
||||
},
|
||||
"from_optional": "",
|
||||
},
|
||||
"status": {
|
||||
"duration_ms": 0,
|
||||
"paused": True,
|
||||
"playback_speed": 1,
|
||||
"progress_ms": 0,
|
||||
"version": {
|
||||
"device_id": ws_proto["Ynison-Device-Id"],
|
||||
"version": 8321822175199937000,
|
||||
"timestamp_ms": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"device": {
|
||||
"capabilities": {
|
||||
"can_be_player": True,
|
||||
"can_be_remote_controller": False,
|
||||
"volume_granularity": 16,
|
||||
},
|
||||
"info": {
|
||||
"device_id": ws_proto["Ynison-Device-Id"],
|
||||
"type": "WEB",
|
||||
"title": "Chrome Browser",
|
||||
"app_name": "Chrome",
|
||||
},
|
||||
"volume_info": {"volume": 0},
|
||||
"is_shadow": True,
|
||||
},
|
||||
"is_currently_active": False,
|
||||
},
|
||||
"rid": "ac281c26-a047-4419-ad00-e4fbfda1cba3",
|
||||
"player_action_timestamp_ms": 0,
|
||||
"activity_interception_type": "DO_NOT_INTERCEPT_BY_DEFAULT",
|
||||
}
|
||||
|
||||
async with session.ws_connect(
|
||||
url=f"wss://{data['host']}/ynison_state.YnisonStateService/PutYnisonState",
|
||||
headers={
|
||||
"Sec-WebSocket-Protocol": f"Bearer, v2, {json.dumps(new_ws_proto)}",
|
||||
"Origin": "http://music.yandex.ru",
|
||||
"Authorization": f"OAuth {token}",
|
||||
},
|
||||
timeout=10,
|
||||
method="GET",
|
||||
) as ws:
|
||||
await ws.send_str(json.dumps(to_send))
|
||||
recv = await asyncio.wait_for(ws.receive(), timeout=10)
|
||||
ynison = json.loads(recv.data)
|
||||
track_index = ynison["player_state"]["player_queue"][
|
||||
"current_playable_index"
|
||||
]
|
||||
if track_index == -1:
|
||||
print("No track is currently playing.")
|
||||
return {"success": False}
|
||||
track = ynison["player_state"]["player_queue"]["playable_list"][
|
||||
track_index
|
||||
]
|
||||
|
||||
await session.close()
|
||||
info = await client.tracks_download_info(track["playable_id"], True)
|
||||
track = await client.tracks(track["playable_id"])
|
||||
return {
|
||||
"paused": ynison["player_state"]["status"]["paused"],
|
||||
"duration_ms": ynison["player_state"]["status"]["duration_ms"],
|
||||
"progress_ms": ynison["player_state"]["status"]["progress_ms"],
|
||||
"entity_id": ynison["player_state"]["player_queue"]["entity_id"],
|
||||
"repeat_mode": ynison["player_state"]["player_queue"]["options"][
|
||||
"repeat_mode"
|
||||
],
|
||||
"entity_type": ynison["player_state"]["player_queue"]["entity_type"],
|
||||
"track": track,
|
||||
"info": info,
|
||||
"success": True,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e), "track": None}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class YmNowBetaMod(loader.Module):
|
||||
"""
|
||||
Module for yandex music. Based on SpotifyNow, YaNow and WakaTime. [BETA]
|
||||
|
||||
Now on Ynison API.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "YmNow",
|
||||
"no_token": "<b><emoji document_id=5843952899184398024>🚫</emoji> Specify a token in config!</b>",
|
||||
"playing": "<b><emoji document_id=5188705588925702510>🎶</emoji> Now playing: </b><code>{}</code><b> - </b><code>{}</code>\n<b>🕐 {}</b>",
|
||||
"no_args": "<b><emoji document_id=5843952899184398024>🚫</emoji> Provide arguments!</b>",
|
||||
"state": "🙂 <b>Widgets are now {}</b>\n{}",
|
||||
"tutorial": (
|
||||
"ℹ️ <b>To enable widget, send a message to a preffered chat with text"
|
||||
" </b><code>{YANDEXMUSIC}</code>"
|
||||
),
|
||||
"no_results": "<b><emoji document_id=5285037058220372959>☹️</emoji> No results found :(</b>",
|
||||
"autobioe": "<b>🔁 Autobio enabled</b>",
|
||||
"autobiod": "<b>🔁 Autobio disabled</b>",
|
||||
"_cfg_yandexmusictoken": "Yandex.Music account token",
|
||||
"_cfg_autobiotemplate": "Template for AutoBio",
|
||||
"_cfg_automesgtemplate": "Template for AutoMessage",
|
||||
"_cfg_update_interval": "Update interval",
|
||||
"guide": (
|
||||
'<a href="https://github.com/MarshalX/yandex-music-api/discussions/513#discussioncomment-2729781">'
|
||||
"Instructions for obtaining a Yandex.Music token</a>"
|
||||
),
|
||||
"configuring": "🙂 <b>Widget is ready and will be updated soon</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_token": "<b><emoji document_id=5843952899184398024>🚫</emoji> Укажи токен в конфиге!</b>",
|
||||
"playing": "<b><emoji document_id=5188705588925702510>🎶</emoji> Сейчас играет: </b><code>{}</code><b> - </b><code>{}</code>\n<b>🕐 {}</b>",
|
||||
"no_args": "<b><emoji document_id=5843952899184398024>🚫</emoji> Укажи аргументы!</b>",
|
||||
"state": "🙂 <b>Виджеты теперь {}</b>\n{}",
|
||||
"tutorial": (
|
||||
"ℹ️ <b>Чтобы включить виджет, отправь сообщение в нужный чат с текстом"
|
||||
" </b><code>{YANDEXMUSIC}</code>"
|
||||
),
|
||||
"no_results": "<b><emoji document_id=5285037058220372959>☹️</emoji> Ничего не найдено :(</b>",
|
||||
"autobioe": "<b>🔁 Autobio включен</b>",
|
||||
"autobiod": "<b>🔁 Autobio выключен</b>",
|
||||
"_cls_doc": "Модуль для Яндекс.Музыка. Основан на SpotifyNow, YaNow и WakaTime. [BETA]",
|
||||
"_cfg_yandexmusictoken": "Токен аккаунта Яндекс.Музыка",
|
||||
"_cfg_autobiotemplate": "Шаблон для AutoBio, параметры: {artists}, {track}, {time}",
|
||||
"_cfg_automesgtemplate": "Шаблон для AutoMessage, параметры: {artists}, {track}, {time}, {link}",
|
||||
"_cfg_update_interval": "Интервал обновления виджета",
|
||||
"guide": (
|
||||
'<a href="https://github.com/MarshalX/yandex-music-api/discussions/513#discussioncomment-2729781">'
|
||||
"Инструкция по получению токена Яндекс.Музыка</a>"
|
||||
),
|
||||
"configuring": "🙂 <b>Виджет готов и скоро будет обновлен</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"YandexMusicToken",
|
||||
None,
|
||||
lambda: self.strings["_cfg_yandexmusictoken"],
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"AutoBioTemplate",
|
||||
"🎧 {artists} - {track} / {time}",
|
||||
lambda: self.strings["_cfg_autobiotemplate"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"AutoMessageTemplate",
|
||||
"🎧 {artists} - {track} / {time} {link}",
|
||||
lambda: self.strings["_cfg_automesgtemplate"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"update_interval",
|
||||
300,
|
||||
lambda: self.strings["_cfg_update_interval"],
|
||||
validator=loader.validators.Integer(minimum=100),
|
||||
),
|
||||
)
|
||||
|
||||
async def on_dlmod(self):
|
||||
if not self.get("guide_send", False):
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
self.strings["guide"],
|
||||
)
|
||||
self.set("guide_send", True)
|
||||
|
||||
async def client_ready(self, client: TelegramClient, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
self._premium = getattr(await self.client.get_me(), "premium", False)
|
||||
|
||||
self.set("widgets", list(map(tuple, self.get("widgets", []))))
|
||||
|
||||
self._task = asyncio.ensure_future(self._parse())
|
||||
|
||||
if self.get("autobio", False):
|
||||
self.autobio.start()
|
||||
|
||||
@loader.command()
|
||||
async def ynowcmd(self, message: Message):
|
||||
"""Get now playing track"""
|
||||
|
||||
if not self.config["YandexMusicToken"]:
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
try:
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
await client.init()
|
||||
except: # noqa: E722
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
res = await get_current_track(client, self.config["YandexMusicToken"])
|
||||
|
||||
if not res["success"]:
|
||||
await utils.answer(message, self.strings["no_results"])
|
||||
return
|
||||
|
||||
track = res["track"][0] # type: ignore
|
||||
|
||||
link = res["info"][0]["direct_link"] # type: ignore
|
||||
title = track["title"]
|
||||
artists = [artist["name"] for artist in track["artists"]]
|
||||
duration_ms = int(track["duration_ms"])
|
||||
|
||||
caption = self.strings["playing"].format(
|
||||
utils.escape_html(", ".join(artists)),
|
||||
utils.escape_html(title),
|
||||
f"{duration_ms // 1000 // 60:02}:{duration_ms // 1000 % 60:02}",
|
||||
)
|
||||
lnk = track["id"]
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=caption,
|
||||
reply_markup={
|
||||
"text": "song.link",
|
||||
"url": f"https://song.link/ya/{lnk}",
|
||||
},
|
||||
silent=True,
|
||||
audio={
|
||||
"url": link,
|
||||
"title": utils.escape_html(title),
|
||||
"performer": utils.escape_html(", ".join(artists)),
|
||||
},
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def ybio(self, message: Message):
|
||||
"""Show now playing track in your bio"""
|
||||
|
||||
if not self.config["YandexMusicToken"]:
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
try:
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
await client.init()
|
||||
except: # noqa: E722
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
current = self.get("autobio", False)
|
||||
new = not current
|
||||
self.set("autobio", new)
|
||||
|
||||
if new:
|
||||
await utils.answer(message, self.strings["autobioe"])
|
||||
self.autobio.start()
|
||||
else:
|
||||
await utils.answer(message, self.strings["autobiod"])
|
||||
self.autobio.stop()
|
||||
|
||||
@loader.command()
|
||||
async def automsgcmd(self, message: Message):
|
||||
"""Toggle YandexMusic widgets' updates"""
|
||||
state = not self.get("state", False)
|
||||
self.set("state", state)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["state"].format(
|
||||
"on" if state else "off", self.strings("tutorial") if state else ""
|
||||
),
|
||||
)
|
||||
|
||||
@loader.loop(interval=60)
|
||||
async def autobio(self):
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
|
||||
await client.init()
|
||||
|
||||
res = await get_current_track(client, self.config["YandexMusicToken"])
|
||||
|
||||
track = res["track"][0] # type: ignore
|
||||
|
||||
title = track["title"]
|
||||
artists = [artist["name"] for artist in track["artists"]]
|
||||
duration_ms = int(track["duration_ms"])
|
||||
|
||||
text = self.config["AutoBioTemplate"].format(
|
||||
artists=utils.escape_html(", ".join(artists)),
|
||||
track=utils.escape_html(title),
|
||||
time=f"{duration_ms // 1000 // 60:02}:{duration_ms // 1000 % 60:02}",
|
||||
)
|
||||
|
||||
try:
|
||||
await self.client(
|
||||
UpdateProfileRequest(about=text[: 140 if self._premium else 70])
|
||||
)
|
||||
except FloodWaitError as e:
|
||||
logger.info(f"Sleeping {e.seconds}")
|
||||
await sleep(e.seconds)
|
||||
return
|
||||
|
||||
async def _parse(self, do_not_loop: bool = False):
|
||||
while True:
|
||||
for widget in self.get("widgets", []):
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
|
||||
await client.init()
|
||||
|
||||
res = await get_current_track(client, self.config["YandexMusicToken"])
|
||||
|
||||
track = res["track"][0] # type: ignore
|
||||
|
||||
title = track["title"]
|
||||
artists = [artist["name"] for artist in track["artists"]]
|
||||
duration_ms = int(track["duration_ms"])
|
||||
|
||||
try:
|
||||
await self._client.edit_message(
|
||||
*widget[:2],
|
||||
self.config["AutoMessageTemplate"].format(
|
||||
artists=utils.escape_html(", ".join(artists)),
|
||||
track=utils.escape_html(title),
|
||||
time=f"{duration_ms // 1000 // 60:02}:{duration_ms // 1000 % 60:02}",
|
||||
link=f"https://song.link/ya/{track['id']}",
|
||||
),
|
||||
)
|
||||
except MessageNotModifiedError:
|
||||
pass
|
||||
except FloodWaitError:
|
||||
pass
|
||||
except Exception:
|
||||
logger.debug("YmNow widget update failed")
|
||||
self.set(
|
||||
"widgets", list(set(self.get("widgets", [])) - set([widget]))
|
||||
)
|
||||
continue
|
||||
|
||||
if do_not_loop:
|
||||
break
|
||||
|
||||
await asyncio.sleep(int(self.config["update_interval"]))
|
||||
|
||||
async def on_unload(self):
|
||||
self._task.cancel()
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
try:
|
||||
if "{YANDEXMUSIC}" not in getattr(message, "text", "") or not message.out:
|
||||
return
|
||||
|
||||
chat_id = utils.get_chat_id(message)
|
||||
message_id = message.id
|
||||
|
||||
self.set(
|
||||
"widgets",
|
||||
self.get("widgets", []) + [(chat_id, message_id, message.text)], # type: ignore
|
||||
)
|
||||
|
||||
await utils.answer(message, self.strings["configuring"])
|
||||
await self._parse(do_not_loop=True)
|
||||
except Exception as e:
|
||||
logger.exception("Can't send widget")
|
||||
await utils.respond(message, self.strings["error"].format(e))
|
||||
339
vsecoder/hikka_modules/ymnowbeta.py
Normal file
339
vsecoder/hikka_modules/ymnowbeta.py
Normal file
@@ -0,0 +1,339 @@
|
||||
__version__ = (2, 1, 1)
|
||||
|
||||
"""
|
||||
_
|
||||
__ _____ ___ ___ ___ __| | ___ _ __
|
||||
\ \ / / __|/ _ \/ __/ _ \ / _` |/ _ \ '__|
|
||||
\ V /\__ \ __/ (_| (_) | (_| | __/ |
|
||||
\_/ |___/\___|\___\___/ \__,_|\___|_|
|
||||
|
||||
Copyleft 2022 t.me/vsecoder
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# meta developer: @vsecoder_m
|
||||
# requires: yandex-music aiohttp
|
||||
# meta desc: Module for yandex music. Based on SpotifyNow, YaNow and WakaTime [beta]
|
||||
# meta pic: https://img.freepik.com/premium-vector/yandex-music-logo_578229-242.jpg
|
||||
# meta banner: https://chojuu.vercel.app/api/banner?img=https://img.freepik.com/premium-vector/yandex-music-logo_578229-242.jpg&title=YMNow&description=Module%20for%20yandex%20music
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
import aiohttp
|
||||
import random
|
||||
import json
|
||||
import string
|
||||
from asyncio import sleep
|
||||
from yandex_music import ClientAsync
|
||||
from telethon import TelegramClient
|
||||
from telethon.tl.types import Message
|
||||
from telethon.errors.rpcerrorlist import FloodWaitError
|
||||
from telethon.tl.functions.account import UpdateProfileRequest
|
||||
from .. import loader, utils # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.getLogger("yandex_music").propagate = False
|
||||
|
||||
|
||||
# https://github.com/FozerG/YandexMusicRPC/blob/main/main.py#L133
|
||||
async def get_current_track(client, token):
|
||||
device_info = {
|
||||
"app_name": "Chrome",
|
||||
"type": 1,
|
||||
}
|
||||
|
||||
ws_proto = {
|
||||
"Ynison-Device-Id": "".join(
|
||||
[random.choice(string.ascii_lowercase) for _ in range(16)]
|
||||
),
|
||||
"Ynison-Device-Info": json.dumps(device_info),
|
||||
}
|
||||
|
||||
timeout = aiohttp.ClientTimeout(total=15, connect=10)
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.ws_connect(
|
||||
url="wss://ynison.music.yandex.ru/redirector.YnisonRedirectService/GetRedirectToYnison",
|
||||
headers={
|
||||
"Sec-WebSocket-Protocol": f"Bearer, v2, {json.dumps(ws_proto)}",
|
||||
"Origin": "http://music.yandex.ru",
|
||||
"Authorization": f"OAuth {token}",
|
||||
},
|
||||
timeout=10,
|
||||
) as ws:
|
||||
recv = await ws.receive()
|
||||
data = json.loads(recv.data)
|
||||
|
||||
if "redirect_ticket" not in data or "host" not in data:
|
||||
print(f"Invalid response structure: {data}")
|
||||
return {"success": False}
|
||||
|
||||
new_ws_proto = ws_proto.copy()
|
||||
new_ws_proto["Ynison-Redirect-Ticket"] = data["redirect_ticket"]
|
||||
|
||||
to_send = {
|
||||
"update_full_state": {
|
||||
"player_state": {
|
||||
"player_queue": {
|
||||
"current_playable_index": -1,
|
||||
"entity_id": "",
|
||||
"entity_type": "VARIOUS",
|
||||
"playable_list": [],
|
||||
"options": {"repeat_mode": "NONE"},
|
||||
"entity_context": "BASED_ON_ENTITY_BY_DEFAULT",
|
||||
"version": {
|
||||
"device_id": ws_proto["Ynison-Device-Id"],
|
||||
"version": 9021243204784341000,
|
||||
"timestamp_ms": 0,
|
||||
},
|
||||
"from_optional": "",
|
||||
},
|
||||
"status": {
|
||||
"duration_ms": 0,
|
||||
"paused": True,
|
||||
"playback_speed": 1,
|
||||
"progress_ms": 0,
|
||||
"version": {
|
||||
"device_id": ws_proto["Ynison-Device-Id"],
|
||||
"version": 8321822175199937000,
|
||||
"timestamp_ms": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"device": {
|
||||
"capabilities": {
|
||||
"can_be_player": True,
|
||||
"can_be_remote_controller": False,
|
||||
"volume_granularity": 16,
|
||||
},
|
||||
"info": {
|
||||
"device_id": ws_proto["Ynison-Device-Id"],
|
||||
"type": "WEB",
|
||||
"title": "Chrome Browser",
|
||||
"app_name": "Chrome",
|
||||
},
|
||||
"volume_info": {"volume": 0},
|
||||
"is_shadow": True,
|
||||
},
|
||||
"is_currently_active": False,
|
||||
},
|
||||
"rid": "ac281c26-a047-4419-ad00-e4fbfda1cba3",
|
||||
"player_action_timestamp_ms": 0,
|
||||
"activity_interception_type": "DO_NOT_INTERCEPT_BY_DEFAULT",
|
||||
}
|
||||
|
||||
async with session.ws_connect(
|
||||
url=f"wss://{data['host']}/ynison_state.YnisonStateService/PutYnisonState",
|
||||
headers={
|
||||
"Sec-WebSocket-Protocol": f"Bearer, v2, {json.dumps(new_ws_proto)}",
|
||||
"Origin": "http://music.yandex.ru",
|
||||
"Authorization": f"OAuth {token}",
|
||||
},
|
||||
timeout=10,
|
||||
method="GET",
|
||||
) as ws:
|
||||
await ws.send_str(json.dumps(to_send))
|
||||
recv = await asyncio.wait_for(ws.receive(), timeout=10)
|
||||
ynison = json.loads(recv.data)
|
||||
track_index = ynison["player_state"]["player_queue"][
|
||||
"current_playable_index"
|
||||
]
|
||||
if track_index == -1:
|
||||
print("No track is currently playing.")
|
||||
return {"success": False}
|
||||
track = ynison["player_state"]["player_queue"]["playable_list"][
|
||||
track_index
|
||||
]
|
||||
|
||||
await session.close()
|
||||
info = await client.tracks_download_info(track["playable_id"], True)
|
||||
track = await client.tracks(track["playable_id"])
|
||||
res = {
|
||||
"paused": ynison["player_state"]["status"]["paused"],
|
||||
"duration_ms": ynison["player_state"]["status"]["duration_ms"],
|
||||
"progress_ms": ynison["player_state"]["status"]["progress_ms"],
|
||||
"entity_id": ynison["player_state"]["player_queue"]["entity_id"],
|
||||
"repeat_mode": ynison["player_state"]["player_queue"]["options"][
|
||||
"repeat_mode"
|
||||
],
|
||||
"entity_type": ynison["player_state"]["player_queue"]["entity_type"],
|
||||
"track": track,
|
||||
"info": info,
|
||||
"success": True,
|
||||
}
|
||||
return res
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to get current track: {str(e)}")
|
||||
return {"success": False}
|
||||
|
||||
|
||||
@loader.tds
|
||||
class YmNowBetaMod(loader.Module):
|
||||
"""
|
||||
Module for yandex music. Based on SpotifyNow, YaNow and WakaTime. [BETA]
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "YmNowBeta",
|
||||
"no_token": "<b><emoji document_id=5843952899184398024>🚫</emoji> Specify a token in config!</b>",
|
||||
"playing": "<b><emoji document_id=5188705588925702510>🎶</emoji> Now playing: </b><code>{}</code><b> - </b><code>{}</code>\n<b>🕐 {}</b>",
|
||||
"no_args": "<b><emoji document_id=5843952899184398024>🚫</emoji> Provide arguments!</b>",
|
||||
"tutorial": (
|
||||
"ℹ️ <b>To enable widget, send a message to a preffered chat with text"
|
||||
" </b><code>{YANDEXMUSIC}</code>"
|
||||
),
|
||||
"no_results": "<b><emoji document_id=5285037058220372959>☹️</emoji> No results found :(</b>",
|
||||
"autobioe": "<b>🔁 Autobio enabled</b>",
|
||||
"autobiod": "<b>🔁 Autobio disabled</b>",
|
||||
"_cfg_yandexmusictoken": "Yandex.Music account token",
|
||||
"_cfg_autobiotemplate": "Template for AutoBio",
|
||||
"_cfg_automesgtemplate": "Template for AutoMessage",
|
||||
"_cfg_update_interval": "Update interval",
|
||||
"no_lyrics": "<b><emoji document_id=5843952899184398024>🚫</emoji> Track doesn't have lyrics.</b>",
|
||||
"guide": (
|
||||
'<a href="https://github.com/MarshalX/yandex-music-api/discussions/513#discussioncomment-2729781">'
|
||||
"Instructions for obtaining a Yandex.Music token</a>"
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"YandexMusicToken",
|
||||
None,
|
||||
lambda: self.strings["_cfg_yandexmusictoken"],
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"AutoBioTemplate",
|
||||
"🎧 {}",
|
||||
lambda: self.strings["_cfg_autobiotemplate"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
)
|
||||
|
||||
async def on_dlmod(self):
|
||||
if not self.get("guide_send", False):
|
||||
await self.inline.bot.send_message(
|
||||
self._tg_id,
|
||||
self.strings["guide"],
|
||||
)
|
||||
self.set("guide_send", True)
|
||||
|
||||
async def client_ready(self, client: TelegramClient, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self._premium = getattr(await self.client.get_me(), "premium", False)
|
||||
if self.get("autobio", False):
|
||||
self.autobio.start()
|
||||
|
||||
@loader.command()
|
||||
async def ynowcmd(self, message: Message):
|
||||
"""Get now playing track"""
|
||||
|
||||
if not self.config["YandexMusicToken"]:
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
try:
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
await client.init()
|
||||
except: # noqa: E722
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
res = await get_current_track(client, self.config["YandexMusicToken"])
|
||||
|
||||
track = res["track"]
|
||||
|
||||
if not track:
|
||||
await utils.answer(message, self.strings["no_results"])
|
||||
return
|
||||
|
||||
track = track[0] # type: ignore
|
||||
|
||||
link = res["info"][0]["direct_link"] # type: ignore
|
||||
title = track["title"]
|
||||
artists = [artist["name"] for artist in track["artists"]]
|
||||
duration_ms = int(track["duration_ms"])
|
||||
|
||||
caption = self.strings["playing"].format(
|
||||
utils.escape_html(", ".join(artists)),
|
||||
utils.escape_html(title),
|
||||
f"{duration_ms // 1000 // 60:02}:{duration_ms // 1000 % 60:02}",
|
||||
)
|
||||
lnk = track["id"]
|
||||
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
text=caption,
|
||||
reply_markup={
|
||||
"text": "song.link",
|
||||
"url": f"https://song.link/ya/{lnk}",
|
||||
},
|
||||
silent=True,
|
||||
audio={
|
||||
"url": link,
|
||||
"title": utils.escape_html(title),
|
||||
"performer": utils.escape_html(", ".join(artists)),
|
||||
},
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def ybio(self, message: Message):
|
||||
"""Show now playing track in your bio"""
|
||||
|
||||
if not self.config["YandexMusicToken"]:
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
try:
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
await client.init()
|
||||
except:
|
||||
await utils.answer(message, self.strings["no_token"])
|
||||
return
|
||||
|
||||
current = self.get("autobio", False)
|
||||
new = not current
|
||||
self.set("autobio", new)
|
||||
|
||||
if new:
|
||||
await utils.answer(message, self.strings["autobioe"])
|
||||
self.autobio.start()
|
||||
else:
|
||||
await utils.answer(message, self.strings["autobiod"])
|
||||
self.autobio.stop()
|
||||
|
||||
@loader.loop(interval=60)
|
||||
async def autobio(self):
|
||||
client = ClientAsync(self.config["YandexMusicToken"])
|
||||
|
||||
await client.init()
|
||||
|
||||
res = await get_current_track(client, self.config["YandexMusicToken"])
|
||||
|
||||
track = res["track"]
|
||||
|
||||
track = track[0] # type: ignore
|
||||
|
||||
title = track["title"]
|
||||
artists = [artist["name"] for artist in track["artists"]]
|
||||
duration_ms = int(track["duration_ms"])
|
||||
|
||||
text = self.config["AutoBioTemplate"].format(
|
||||
f"{', '.join(artists)} - {title} | {duration_ms // 1000 // 60:02}:{duration_ms // 1000 % 60:02}",
|
||||
)
|
||||
|
||||
try:
|
||||
await self.client(
|
||||
UpdateProfileRequest(about=text[: 140 if self._premium else 70])
|
||||
)
|
||||
except FloodWaitError as e:
|
||||
logger.info(f"Sleeping {e.seconds}")
|
||||
await sleep(e.seconds)
|
||||
return
|
||||
Reference in New Issue
Block a user