Compare commits

..

46 Commits

Author SHA1 Message Date
github-actions[bot]
d17cfa4132 Updated modules.json after parse 2026-06-18 02:53:20 2026-06-18 02:53:20 +00:00
github-actions[bot]
ea3567f38a Added and updated repositories 2026-06-18 02:52:50 2026-06-18 02:52:50 +00:00
2770ae1ccc fix: now supports video / gif baners 2026-06-12 12:15:31 +03:00
Macsim
c3390a5887 Merge pull request #315 from MuRuLOSE/update-submodules_d33e49b6967d97ab3dc83dc74e03537a26defd62
Update of repositories 2026-06-12 08:37:40
2026-06-12 11:59:03 +03:00
github-actions[bot]
2ff79975c2 Updated modules.json after parse 2026-06-12 08:37:15 2026-06-12 08:37:15 +00:00
github-actions[bot]
ab03f6ed94 Added and updated repositories 2026-06-12 08:36:40 2026-06-12 08:36:40 +00:00
d33e49b696 fix 2026-06-12 11:34:55 +03:00
04cc1dc4b3 New developer 2026-06-12 11:24:33 +03:00
6b6afb7493 fix: Ported fix from stable, in python version below 3.12 you can't use " with f-strings 2026-06-08 09:46:37 +03:00
Macsim
2ed246b9ad Merge pull request #301 from MuRuLOSE/update-submodules_d279789b37a939b3d9ececce6b4d0e1992293c23
Update of repositories 2026-05-31 02:48:09
2026-06-01 00:23:40 +03:00
github-actions[bot]
837784206f Updated modules.json after parse 2026-05-31 02:47:46 2026-05-31 02:47:46 +00:00
github-actions[bot]
811beb2b74 Added and updated repositories 2026-05-31 02:47:15 2026-05-31 02:47:16 +00:00
Zahar Vanilovv
d279789b37 Merge pull request #273 from MuRuLOSE/update-submodules_63944822139e8b6869fe2250ad8c650e9db06765
Update of repositories 2026-05-03 02:11:45
2026-05-03 05:14:12 +03:00
github-actions[bot]
74dfe4caf8 Updated modules.json after parse 2026-05-03 02:11:26 2026-05-03 02:11:26 +00:00
github-actions[bot]
18b8247e21 Added and updated repositories 2026-05-03 02:10:53 2026-05-03 02:10:53 +00:00
6394482213 fix: banners now will be visible no matter what 2026-04-25 09:23:54 +03:00
43a0e578c1 fix: 1
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 21:58:27 +03:00
3112e6d4bf python-magic is shit so its replaced with filetype due to magic requires its own binary in order to work 2026-04-24 21:43:29 +03:00
18bb817239 fix: if server does not return Content-Type in header we will try determine MIME-type offline
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 21:37:39 +03:00
24f1983e2a fix: not awaited update function 2026-04-24 21:16:36 +03:00
74638c14d0 feat: now you can see version if module updated
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 20:56:58 +03:00
Macsim
db62a5aee1 Merge pull request #264 from MuRuLOSE/update-submodules_49956ba865b533dca03b344d048f96465a6be62e
Update of repositories 2026-04-24 17:42:48
2026-04-24 20:43:54 +03:00
github-actions[bot]
4d2a2899f4 Updated modules.json after parse 2026-04-24 17:42:27 2026-04-24 17:42:27 +00:00
github-actions[bot]
2a27c56d83 Added and updated repositories 2026-04-24 17:41:57 2026-04-24 17:41:57 +00:00
49956ba865 version bump 2026-04-24 20:33:45 +03:00
f0d2a28105 removed H.modules
feat: added force index update

Co-authored-by: Copilot <copilot@github.com>
2026-04-24 20:10:52 +03:00
13d091c56c removed H.Modules by request 2026-04-19 16:10:58 +03:00
d7cf406b78 feat: Added 'by developer' if meta developer in module and event if module new
fix: if some event happened with module, changes message will not be sended
2026-04-19 14:40:13 +03:00
Macsim
0f30a78990 Merge pull request #257 from MuRuLOSE/update-submodules_e50c7c1688b89901690bc01609544e5c3e238097
Update of repositories 2026-04-19 02:02:27
2026-04-19 14:16:50 +03:00
github-actions[bot]
16adfac8b5 Updated modules.json after parse 2026-04-19 02:02:03 2026-04-19 02:02:03 +00:00
github-actions[bot]
7ddb190b35 Added and updated repositories 2026-04-19 02:01:35 2026-04-19 02:01:35 +00:00
e50c7c1688 drop: debug log 2026-04-18 14:08:04 +03:00
f3682ed87a fix: version displaying wrong and some shit idk 2026-04-18 13:41:49 +03:00
be47e59d97 fix: some broken strings
feat: install button and new limoka version update notification
drop: watcher that installs module  from bot (temporary)
2026-04-18 13:19:24 +03:00
eb71e39fcf Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-04-18 11:01:48 +03:00
Macsim
7d713e36c0 Merge pull request #256 from MuRuLOSE/update-submodules_a424d6bac4fb0d52521fca595af24968162bcc87
Update of repositories 2026-04-18 01:50:07
2026-04-18 10:58:20 +03:00
github-actions[bot]
a8fde5e498 Updated modules.json after parse 2026-04-18 01:49:43 2026-04-18 01:49:43 +00:00
github-actions[bot]
4a03c6cb1a Added and updated repositories 2026-04-18 01:49:10 2026-04-18 01:49:10 +00:00
Macsim
a424d6bac4 Merge pull request #254 from MuRuLOSE/update-submodules_59564f07b5b57f12eec78ff6e8f0574c70e37f8b
Update of repositories 2026-04-16 02:02:29
2026-04-16 07:39:02 +03:00
github-actions[bot]
fc8344ca05 Updated modules.json after parse 2026-04-16 02:02:11 2026-04-16 02:02:11 +00:00
github-actions[bot]
3e62dc0b69 Added and updated repositories 2026-04-16 02:01:42 2026-04-16 02:01:42 +00:00
41f253b471 feat: deleted files now got dedicated notification too 2026-04-15 18:02:16 +03:00
Macsim
59564f07b5 Merge pull request #253 from MuRuLOSE/update-submodules_3a193cfb2f731c403bbc542d2408c6144d0bcda7
Update of repositories 2026-04-15 01:54:22
2026-04-15 17:35:17 +03:00
github-actions[bot]
dfe2ae1103 Updated modules.json after parse 2026-04-15 01:53:44 2026-04-15 01:53:44 +00:00
github-actions[bot]
4ca7279309 Added and updated repositories 2026-04-15 01:53:05 2026-04-15 01:53:05 +00:00
3a193cfb2f fix: blockquote not needed here 2026-04-14 18:29:41 +03:00
251 changed files with 79779 additions and 99583 deletions

View File

@@ -1,3 +0,0 @@
![amoremods](https://te.legra.ph/file/8600de18766a556b2f78e.jpg)
# amoremods
My mods for userbot

View File

@@ -1,53 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta pic: https://te.legra.ph/file/868a280910e7f61f6ab0e.png
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Abstract.jpg
from .. import utils, loader
chat = "@aeabstractbot"
class AbstractMod(loader.Module):
"""Write a beautiful summary on a notebook"""
strings = {
"name": "Abstract",
"processing": (
"<emoji document_id='6318766236746384900'>🕔</emoji> <b>Processing...</b>"
),
}
@loader.owner
@loader.command(ru_doc="<текст> - Создать конспект")
async def konspcmd(self, message):
"""<text> - Create summary"""
text = utils.get_args_raw(message)
message = await utils.answer(message, self.strings("processing"))
async with self._client.conversation(chat) as conv:
msgs = []
msgs += [await conv.send_message("/start")]
msgs += [await conv.get_response()]
msgs += [await conv.send_message(text)]
m = await conv.get_response()
await self._client.send_file(
message.peer_id,
m.media,
reply_to=message.reply_to_msg_id,
)
for msg in msgs + [m]:
await msg.delete()
if message.out:
await message.delete()
await self.client.delete_dialog(chat)

View File

@@ -1,33 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Activity.jpg
# requires: deep_translator
import requests
import deep_translator
from .. import loader, utils
def generate_activity():
return requests.get("http://api.farkhodovme.tk/activity/en").json()['activity']
class Activity(loader.Module):
"""Generate activity if you're bored"""
strings = {"name": "Activity", "activity": "⛩ <b>Activity:</b> <code>{}</code>", "lang": "en"}
strings_ru = {"activity": "⛩ <b>Занятие:</b> <code>{}</code>", "lang": "ru"}
strings_uz = {"activity": "⛩ <b>Harakat:</b> <code>{}</code>", "lang": "uz"}
@loader.command(ru_doc="Сгенерировать занятие", uz_doc="Harakat yaratish")
async def activity(self, message):
"""Generate activity"""
res = (deep_translator.GoogleTranslator(source="auto", target=self.strings["lang"]).translate(generate_activity()) if self.strings["lang"] != "en" else generate_activity())
txt = self.strings['activity'].format(res)
await utils.answer(message, txt)

View File

@@ -1,287 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/Aeconv.jpg?raw=true
# meta pic: https://cdn-icons-png.flaticon.com/512/5670/5670084.png
import re
import logging
from bs4 import BeautifulSoup as bs
from requests import get
from asyncio import sleep
from asyncio.exceptions import TimeoutError
from hikkatl.tl.types import Message
from hikkatl.errors.common import AlreadyInConversationError
from .. import utils, loader
from ..inline.types import InlineCall
logger = logging.getLogger(__name__)
@loader.tds
class Aeconv(loader.Module):
"""Easy and fast valute converter"""
bot = "@exchange_rates_vsk_bot"
strings = {
"name": "Aeconv",
"wait": "<emoji document_id=5346192260029489215>💵</emoji> <b>Converting...</b>",
"no_args": "<emoji document_id=5343820329980535275>🖕</emoji> <b>Where are the arguments?</b>",
"unsupported": "<emoji document_id=5307761055873638139>🚫</emoji> <b>Unsupported currency!</b>",
"converted": "<emoji document_id=6037400506823871682>💸</emoji> <b>Converted <code>{}</code></b>\n\n",
"already": "<emoji document_id=5348177037431414677>⚠️</emoji> <b>Wait until the bot responds!</b>",
"wrong_currency": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji><b> Wrong currency</b>",
"choose_currency": "📉 <b> Choose currency</b>",
"processing": "🕔 <b>Processing...</b>",
"done": "✅ <b>Done!</b>",
"already_in_conv": "⚠️ <b>Already in conversation!</b>",
}
strings_ru = {
"wait": "<emoji document_id=5346192260029489215>💵</emoji> <b>Конвертирую...</b>",
"no_args": "<emoji document_id=5343820329980535275>🖕</emoji> <b>Где аргументы?</b>",
"unsupported": "<emoji document_id=5307761055873638139>🚫</emoji> <b>Валюта не поддерживается!</b>",
"converted": "<emoji document_id=6037400506823871682>💸</emoji> <b>Сконвертирован <code>{}</code></b>\n\n",
"already": "<emoji document_id=5348177037431414677>⚠️</emoji> <b>Подожди пока бот ответит!</b>",
"wrong_currency": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji><b> Неправильная валюта</b>",
"choose_currency": "📉 <b> Выберите валюту</b>",
"processing": "🕔 Обрабатываю...",
"done": "<code>[Aeconv]</code> ✅ <b> Готово!</b>",
"already_in_conv": "⚠️ <b>Жди пока закончится процесс!</b>",
}
strings_uz = {
"wait": "<emoji document_id=5346192260029489215>💵</emoji> <b>Valyuta konvertatsiyasi...</b>",
"no_args": "<emoji document_id=5343820329980535275>🖕</emoji> <b>Argumetlar qayerda?</b>",
"unsupported": "<emoji document_id=5307761055873638139>🚫</emoji> <b>Valyuta qo'llab-quvvatlanmaydi!</b>",
"converted": "<emoji document_id=6037400506823871682>💸</emoji> <b>Konvertatsiya qilindi <code>{}</code></b>\n\n",
"already": "<emoji document_id=5348177037431414677>⚠️</emoji> <b>Bot javob berishini kuting!</b>",
"wrong_currency": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji><b> Noto'g'ri valyuta</b>",
"choose_currency": "📉 <b> Valyutani tanlang</b>",
"processing": "🕔 Qayta ishlayapman...",
"done": "<code>[Aeconv]</code> ✅ <b>Tayyor!</b>",
"already_in_conv": "⚠️ <b>Protsess tugaguncha kuting!</b>",
}
strings_de = { # i'm really sorry for translations, i'm not good at it
"wait": "<emoji document_id=5346192260029489215>💵</emoji> <b>Konvertiere...</b>",
"no_args": "<emoji document_id=5343820329980535275>🖕</emoji> <b>Wo sind die Argumente?</b>",
"unsupported": "<emoji document_id=5307761055873638139>🚫</emoji> <b>Nicht unterstützte Währung!</b>",
"converted": "<emoji document_id=6037400506823871682>💸</emoji> <b>Konvertiert <code>{}</code></b>\n\n",
"already": "<emoji document_id=5348177037431414677>⚠️</emoji> <b>Warten Sie, bis der Bot antwortet!</b>",
"wrong_currency": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji><b> Falsche Währung</b>",
"choose_currency": "📉 <b> Währung auswählen</b>",
"processing": "🕔 Verarbeitung...",
"done": "<code>[Aeconv]</code> ✅ <b>Fertig!</b>",
"already_in_conv": "⚠️ <b>Warten Sie, bis der Prozess beendet ist!</b>",
}
strings_tr = { # i'm really sorry for translations, i'm not good at it
"wait": "<emoji document_id=5346192260029489215>💵</emoji> <b>Dönüştürülüyor...</b>",
"no_args": "<emoji document_id=5343820329980535275>🖕</emoji> <b>Argümanlar nerede?</b>",
"unsupported": "<emoji document_id=5307761055873638139>🚫</emoji> <b>Desteklenmeyen para birimi!</b>",
"converted": "<emoji document_id=6037400506823871682>💸</emoji> <b>Dönüştürüldü <code>{}</code></b>\n\n",
"already": "<emoji document_id=5348177037431414677>⚠️</emoji> <b>Bot cevap verene kadar bekleyin!</b>",
"wrong_currency": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji><b> Yanlış para birimi</b>",
"choose_currency": "📉 <b> Para birimini seçin</b>",
"processing": "🕔 İşleniyor...",
"done": "<code>[Aeconv]</code> ✅ <b>Tamam!</b>",
"already_in_conv": "⚠️ <b>İşlem bitene kadar bekleyin!</b>",
}
strings_kk = { # i'm really sorry for translations, i'm not good at it
"wait": "<emoji document_id=5346192260029489215>💵</emoji> <b>Валюта айырбасталуда...</b>",
"no_args": "<emoji document_id=5343820329980535275>🖕</emoji> <b>Аргументтер қайда?</b>",
"unsupported": "<emoji document_id=5307761055873638139>🚫</emoji> <b>Валюта қолдау көрсетілмейді!</b>",
"converted": "<emoji document_id=6037400506823871682>💸</emoji> <b>Айырбасталды <code>{}</code></b>\n\n",
"already": "<emoji document_id=5348177037431414677>⚠️</emoji> <b>Бот жауап бергенге дейін күтіңіз!</b>",
"wrong_currency": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji><b> Дұрыс валюта емес</b>",
"choose_currency": "📉 <b> Валютаны таңдаңыз</b>",
"processing": "🕔 Қайта өңдеу...",
"done": "<code>[Aeconv]</code> ✅ <b>Тайық!</b>",
"already_in_conv": "⚠️ <b>Процесс аяқталғанда дейін күтіңіз!</b>",
}
custom_emojis = {
"🇬🇧": "<emoji document_id=6323589145717376403>🇬🇧</emoji>",
"🇺🇿": "<emoji document_id=6323430017179059570>🇺🇿</emoji>",
"🇺🇸": "<emoji document_id=6323374027985389586>🇺🇸</emoji>",
"🇷🇺": "<emoji document_id=6323139226418284334>🇷🇺</emoji>",
"🇰🇿": "<emoji document_id=6323135275048371614>🇰🇿</emoji>",
"🇪🇺": "<emoji document_id=6323217102765295143>🇪🇺</emoji>",
"🇺🇦": "<emoji document_id=6323289850921354919>🇺🇦</emoji>",
"🇹🇷": "<emoji document_id=6321003171678259486>🇹🇷</emoji>",
"🇵🇱": "<emoji document_id=6323602387101550101>🇵🇱</emoji>",
"🇰🇬": "<emoji document_id=6323615997852910673>🇰🇬</emoji>",
"bit": "<emoji document_id=6034931909945985955>💰</emoji>",
"eth": "<emoji document_id=5280647120607521572>🔹</emoji>",
"ton": "<emoji document_id=5863980370340351884>💰</emoji>"
}
currency_mapping = {
"EU": ("🇪🇺", "EUR"),
"GB": ("🇬🇧", "GBP"),
"UZ": ("🇺🇿", "UZS"),
"US": ("🇺🇸", "USD"),
"RU": ("🇷🇺", "RUB"),
"KZ": ("🇰🇿", "KZT"),
"UA": ("🇺🇦", "UAH"),
"PL": ("🇵🇱", "PLN"),
"TR": ("🇹🇷", "TRY"),
"KG": ("🇰🇬", "KGS")
}
currencies = [
"EUR", "GBP", "UZS", "USD", "RUB", "KZT", "UAH", "PLN", "TRY", "KGS", "TON", "ETH", "BTC"
]
currency_flags = {
"EUR": "🇪🇺",
"GBP": "🇬🇧",
"UZS": "🇺🇿",
"USD": "🇺🇸",
"RUB": "🇷🇺",
"KZT": "🇰🇿",
"UAH": "🇺🇦",
"PLN": "🇵🇱",
"TRY": "🇹🇷",
"KGS": "🇰🇬"
}
letters_stashing = {
"E": "cur_df",
"G": "cur_gh",
"P": "cur_nq",
"R": "cur_rs",
"S": "cur_rs",
"T": "cur_tu",
"U": "cur_tu",
}
def currencies_markup(self, argument: str = "") -> list:
return utils.chunks(
[
{
"text": f"{self.currency_flags[cur]} {cur}",
"callback": self.callback_4_currency,
"args": (cur,),
}
for cur in [
i
for i in self.currency_flags.keys()
if i.startswith(argument.upper())
]
if cur.startswith(argument.upper())
],
5,
)
async def client_ready(self, client, db):
await utils.dnd(client, self.bot, archive=True)
async def get_ton_in_rub(self, am, what: str = "uzs", cup: bool = False) -> str:
r = (
get(f"https://coinchefs.com/{what}/ton/{am}/")
if cup
else get(f"https://coinchefs.com/ton/{what}/{am}/")
)
soup = bs(r.text, "html.parser")
if result_div := soup.find('div', class_='convert-result'):
if result_text_div := result_div.find(
'div', class_='col-xs-10 col-sm-10 text-center result-text'
):
if value_element := result_text_div.b:
return value_element.get_text(strip=True)
else:
logger.debug("Value element not found")
else:
logger.debug("Result text div not found")
else:
logger.debug("Result div not found")
return None
async def callback_4_currency(self, call: InlineCall, currency: str):
try:
first_letter = currency[0]
await call.answer(self.strings["processing"], show_alert=True)
await call.delete()
async with self.client.conversation(self.bot) as conv:
m = await conv.send_message("/settings")
r = await conv.get_response()
await r.click(data=b'cur_menu')
await r.click(data=b'cur_curmenu')
await r.click(data=self.letters_stashing[first_letter])
await r.click(data=f"cur_{currency.upper()}")
await r.delete()
await m.delete()
await self.inline.bot.send_message(self.tg_id, self.strings["done"])
except AlreadyInConversationError:
await call.answer(self.strings["already_in_conv"], show_alert=True)
@loader.command(ru_doc="<количество> [валюта] должны быть разделены пробелом")
async def conv(self, message: Message):
"""<amount> [currency] should be separated by space"""
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings["no_args"])
return
# if args.split(" ")[1].upper() not in self.currencies:
# await utils.answer(message, self.strings["wrong_currency"])
# return
await utils.answer(message, self.strings["wait"])
if "ton".lower() in args.lower():
li_args = args.split(" ")
ex_ = await self.get_ton_in_rub(li_args[0])
try:
async with message.client.conversation(self.bot) as conv:
msg = await conv.send_message(args) if "ton".lower() not in args.lower() else await conv.send_message(ex_)
r = await conv.get_response()
res = r.text
text_ = ""
text_ += (
self.strings["converted"].format(args)
if "ton".lower() not in args.lower()
else self.strings["converted"].format(f"{li_args[0]} TON")
)
for emoji, currency, *_ in self.currency_mapping.values():
if match := re.findall(f"{emoji} ?(.*) {currency}", res):
text_ += (
f"<b>{self.custom_emojis.get(emoji)} {currency}:</b> "
f"<code>{match[0]}</code>\n"
)
if match := re.findall(r"(.*) BTC", res):
text_ += f"\n<b>{self.custom_emojis['bit']} BTC:</b> <code>{match[0]}</code>\n"
if match := re.findall(r"(.*) ETH", res):
text_ += f"<b>{self.custom_emojis['eth']} ETH:</b> <code>{match[0]}</code>\n"
if ex_ := await self.get_ton_in_rub(args.split(" ")[0], args.split(" ")[1].lower(), True):
text_ += f"<b>{self.custom_emojis['ton']} TON:</b> <code>{ex_.split(' = ')[1]}</code>\n"
await utils.answer(message, text_)
await msg.delete()
await r.delete()
except AlreadyInConversationError:
await utils.answer(message, self.strings["already"])
except TimeoutError:
await utils.answer(message, self.strings["unsupported"])
except IndexError:
await utils.answer(message, self.strings["no_args"])
@loader.command(ru_doc="[валюта] | без аргументов покажет список валют для включения/выключения")
async def controlvalute(self, message: Message):
"""[currency] | without arguments will show list of currencies for enable/disable"""
if args := utils.get_args_raw(message):
await utils.answer(message, self.strings["choose_currency"], reply_markup=self.currencies_markup(args))
else:
return await utils.answer(message, self.strings["choose_currency"], reply_markup=self.currencies_markup())

View File

@@ -1,225 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Alarm.jpg
import re
import pytz
import random
import logging
import asyncio
from datetime import datetime
from .. import utils, loader
logger = logging.getLogger(__name__)
day_to_weekday = {
"mon": 0,
"tue": 1,
"wed": 2,
"thu": 3,
"fri": 4,
"sat": 5,
"sun": 6,
"пн": 0,
"вт": 1,
"ср": 2,
"чт": 3,
"пт": 4,
"сб": 5,
"вс": 6,
}
@loader.tds
class AlarmMod(loader.Module):
"""Alarm module for remind you about something"""
strings = {
"name": "Alarm",
"set": "<emoji document_id=5870729937215819584>⏰</emoji> <b>Alarm set for <code>{}</code>!</b>",
"unset": "<emoji document_id=5213107179329953547>⏰</emoji> <b>Alarm for <code>{}</code> unset!</b>",
"unset_all": "<emoji document_id=5213107179329953547>⏰</emoji> <b>All alarms unset!</b>",
"list_item": (
"<emoji document_id=6334603778326529773>⏰</emoji> <b>Alarm for <code>{}</code>!</b> <code>#{}</code>"
"\n<emoji document_id=6334699757960693635>🕔</emoji> <b>Time:</b> <code>{}</code>"
"\n<emoji document_id=6334388660594542334>🔊</emoji> <b>Message:</b> <code>{}</code>"
),
"no_alarms": "<emoji document_id=5208549407280078951>🙅‍♂️</emoji> <b>No alarms!</b>",
"off_button": "✋ Off",
"notification": "⏰ <b>Alarm!</b>\n\n<code>{}</code>",
"turned_off": "✔️ <b>Alarm turned off!</b>",
"incorrect_time": "<emoji document_id=5371015453013450536>🖕</emoji> <b>Incorrect time!</b>",
"where_args": "<emoji document_id=5371015453013450536>🖕</emoji> <b>Where arguments?</b>",
"incorrect_args": "<emoji document_id=5371015453013450536>🖕</emoji> <b>Incorrect arguments! Write like this:</b> <code>.setalarm mon 12:00 text</code>",
"interval_doc": "Interval of sending notifications in seconds",
"time_zone_doc": "Time zone for alarms (for example, Europe/Moscow)",
}
strings_ru = {
"set": "<emoji document_id=5870729937215819584>⏰</emoji> <b>Напоминание установлено на <code>{}</code>!</b>",
"unset": "<emoji document_id=5213107179329953547>⏰</emoji> <b>Напоминание для <code>{}</code> отменено!</b>",
"unset_all": "<emoji document_id=5213107179329953547>⏰</emoji> <b>Все напоминания отменены!</b>",
"list_item": (
"<emoji document_id=6334603778326529773>⏰</emoji> <b>Напоминание для <code>{}</code>!</b> <code>#{}</code>"
"\n<emoji document_id=6334699757960693635>🕔</emoji> <b>Время:</b> <code>{}</code>"
"\n<emoji document_id=6334388660594542334>🔊</emoji> <b>Сообщение:</b> <code>{}</code>"
),
"no_alarms": "<emoji document_id=5208549407280078951>🙅‍♂️</emoji> <b>Нет напоминаний!</b>",
"off_button": "✋ Выключить",
"notification": "⏰ <b>Напоминание!</b>\n\n<code>{}</code>",
"turned_off": "✔️ <b>Напоминание выключено!</b>",
"incorrect_time": "<emoji document_id=5371015453013450536>🖕</emoji> <b>Неправильное время!</b>",
"where_args": "<emoji document_id=5371015453013450536>🖕</emoji> <b>Где аргументы?</b>",
"incorrect_args": "<emoji document_id=5371015453013450536>🖕</emoji> <b>Неправильные аргументы! Пиши так:</b> <code>.setalarm пн 12:00 текст</code>",
"interval_doc": "Интервал отправления напоминаний в секундах",
"time_zone_doc": "Часовой пояс для напоминаний (например, Europe/Moscow)",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"interval",
5,
lambda: self.strings("interval_doc"),
validator=loader.validators.Integer(minimum=1, maximum=60),
),
loader.ConfigValue(
"time_zone",
"Europe/Moscow",
lambda: self.strings("time_zone_doc"),
validator=loader.validators.RegExp(
r"^[\w/]+$",
)
),
)
@loader.command(ru_doc="<день недели> <время> <сообщение> - установить напоминание")
async def setalarm(self, message):
"""<day of the week> <time> <message> - set alarm"""
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings("where_args"))
try:
re_args = re.match(r"^(.+) (\d{1,2}):(\d{1,2}) (.*)$", args)
day = re_args.group(1).lower()
hour = int(re_args.group(2))
minute = int(re_args.group(3))
text = re_args.group(4)
except AttributeError:
return await utils.answer(message, self.strings("incorrect_args"))
if not (0 <= hour <= 23 and 0 <= minute <= 59):
return await utils.answer(message, self.strings("incorrect_time"))
if day not in day_to_weekday.keys():
text = f"<b>Wrong day of the week!</b>\n<b>Available days:</b> <code>{', '.join(day_to_weekday.keys())}</code>"
return await utils.answer(message, text)
id_ = random.randint(100, 999)
self.set(
"alarms",
{
**self.get("alarms", {}),
day: {
"hour": hour,
"minute": minute,
"text": text,
"id": id_,
"status": "on",
},
},
)
await utils.answer(message, self.strings("set").format(day))
@loader.command(ru_doc="получить список напоминаний")
async def alarms(self, message):
"""get alarms list"""
alarms = self.get("alarms", {})
if not alarms:
return await utils.answer(message, self.strings("no_alarms"))
text = ""
for day, alarm in alarms.items():
text += self.strings("list_item").format(
day,
f"{alarm['id']}",
f"{alarm['hour']}:{alarm['minute']}",
alarm["text"],
)
text += "\n\n"
await utils.answer(message, text)
@loader.command(ru_doc="<id> - отменить напоминание")
async def unsetalarm(self, message):
"""<id> - unset alarm"""
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings("where_time"))
if args.startswith("#"):
args = args[1:]
alarms = self.get("alarms", {})
for day, alarm in alarms.items():
if str(alarm["id"]) == args:
alarms.pop(day)
self.set("alarms", alarms)
return await utils.answer(
message, self.strings("unset").format(day)
)
await utils.answer(message, self.strings("unset").format(args))
@loader.command(ru_doc="отменить все напоминания")
async def unsetallalarms(self, message):
"""unset all alarms"""
self.set("alarms", {})
await utils.answer(message, self.strings("unset_all"))
@loader.loop(interval=2, autostart=True)
async def check_alarms(self):
alarms = self.get("alarms", {})
if not alarms:
return
now = datetime.now(tz=pytz.timezone(self.config["time_zone"]))
day = now.weekday()
hour = now.hour
minute = now.minute
for day_, alarm in alarms.items():
if (
day_to_weekday[day_] == day
and alarm["hour"] == hour
and alarm["minute"] == minute
):
while alarm["status"] == "on":
self._markup = self.inline.generate_markup(
{
"text": self.strings("off_button"),
"callback": self.off_alarm,
"args": (alarm["id"],),
}
)
await self.inline.bot.send_message(
self.tg_id,
self.strings("notification").format(alarm["text"]),
reply_markup=self._markup,
)
await asyncio.sleep(self.config["interval"])
break
async def off_alarm(self, call, id_):
alarms = self.get("alarms", {})
for day, alarm in alarms.items():
if alarm["id"] == id_:
alarm["status"] = "off"
self.set("alarms", alarms)
await call.edit(self.strings("turned_off"))
return False
await call.answer("Не найдено!")

View File

@@ -1,118 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/Amethyste.jpg?raw=true
from .. import utils, loader
from hikkatl.errors.common import AlreadyInConversationError
from telethon.tl.types import Message
@loader.tds
class Amethyste(loader.Module):
"""Generate memes image"""
strings = {
"name": "Amethyste",
"wait": "<emoji document_id=5328115567314346398>🫥</emoji> <b>Wait...</b>",
"already_open": "<emoji document_id=5330241494521487534>😹</emoji> <b>Conversation already opened Please wait.</b>",
"r_photo": "<emoji document_id=5298636457982826800>🖼</emoji> <b>Please reply to image.</b>",
"no_args": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Pls provide args</b>",
"not_found": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji> <b>Not found</b>",
}
strings_ru = {
"wait": "<emoji document_id=5328115567314346398>🫥</emoji> <b>Подождите...</b>",
"already_open": "<emoji document_id=5330241494521487534>😹</emoji> <b>Диалог уже открыт. Подождите.</b>",
"r_photo": "<emoji document_id=5298636457982826800>🖼</emoji> <b>Ответьте на фото.</b>",
"no_args": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Укажите аргументы</b>",
"not_found": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji> <b>Не найдено</b>",
}
strings_uz = {
"wait": "<emoji document_id=5328115567314346398>🫥</emoji> <b>Kuting...</b>",
"already_open": "<emoji document_id=5330241494521487534>😹</emoji> <b>Dialog allaqachon ochilgan. Iltimos, kuting.</b>",
"r_photo": "<emoji document_id=5298636457982826800>🖼</emoji> <b>Rasmga javob bering.</b>",
"no_args": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Argumetlarni ko'rsating</b>",
"not_found": "<emoji document_id=5345937796102104039>🤷‍♀️</emoji> <b>Topilmadi</b>",
}
_list = [
"3000years",
"approved",
"beautiful",
"brazzers",
"burn",
"challenger",
"circle",
"contrast",
"crush",
"ddungeon",
"dictator",
"distort",
"emboss",
"fire",
"frame",
"afusion",
"glitch",
"greyscale",
"instagram",
"invert",
"jail",
"magik",
"missionpassed",
"moustache",
"ps4",
"posterize",
"rejected",
"rip",
"scary",
"scrolloftruth",
"sepia",
"sharpen",
"sniper",
"thanos",
"trinity",
"triggered",
"unsharpen",
"utatoo",
"wanted",
"wasted",
]
async def amegencmd(self, message: Message):
"""Generate memes image"""
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
await utils.answer(message, self.strings["wait"])
if not args:
return await utils.answer(message, self.strings["no_args"])
elif not reply.photo:
return await utils.answer(message, self.strings["r_photo"])
elif args not in self._list:
return await utils.answer(message, self.strings["not_found"])
async with self.client.conversation("@aozoram_bot") as conv:
try:
msg = await conv.send_message("/start")
s = await conv.get_response()
f = await conv.send_file(file=reply)
m = await f.reply(f"/amegen {args}")
await conv.get_response() # wait for response
response = await conv.get_response()
await utils.answer_file(message, response.media)
await s.delete()
await msg.delete()
await m.delete()
except AlreadyInConversationError:
await utils.answer(message, self.strings["already_open"])
await self.client.delete_dialog("@aozoram_bot")
async def amelistcmd(self, message: Message):
"""List of memes"""
spis = "\n".join([f"• <code>{i}</code>" for i in self._list])
await utils.answer(message, f"<b>Available memes:</b>\n\n{spis}")

View File

@@ -1,258 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/AmoreInfo.jpg
import logging
import git
from telethon.tl.types import Message
from telethon.utils import get_display_name
from .. import loader, main, utils
import datetime
import time
logger = logging.getLogger(__name__)
@loader.tds
class AmoreindoMod(loader.Module):
"""Show userbot info"""
strings = {
"name": "AmoreInfo",
"owner": "Owner",
"version": "Version",
"build": "Build",
"prefix": "Prefix",
"time": "Time",
"platform": "Platform",
"uptime": "Uptime",
"up-to-date": "😌 Actual version",
"update_required": "😕 Outdated version </b><code>.update</code><b>",
"_cfg_cst_msg": "Custom message for info. May contain {me}, {version}, {build}, {prefix}, {platform}, {upd}, {time}, {uptime} keywords",
"_cfg_cst_btn": "Custom button for info. Leave empty to remove button",
"_cfg_cst_bnr": "Custom Banner for info.",
"_cfg_cst_frmt": "Custom fileformat for Banner info.",
"_cfg_banner": "Set `True` in order to disable an image banner",
"_cfg_time": "Use 1, -1, -3 etc.",
"_cfg_close": "Here you can change close button name",
}
strings_ru = {
"owner": "Владелец",
"version": "Версия",
"build": "Сборка",
"prefix": "Префикс",
"uptime": "Аптайм",
"platform": "Платформа",
"time": "Время",
"up-to-date": "😌 Актуальная версия",
"update_required": "😕 Требуется обновление </b><code>.update</code><b>",
}
strings_uz = {
"owner": "Egasi",
"version": "Versiya",
"build": "Yig'ish",
"prefix": "Prefix",
"uptime": "Uptime",
"platform": "Platforma",
"time": "Soat",
"up-to-date": "😌 Joriy versiya",
"update_required": "😕 Yangilanish talab qilinadi </b><code>.update</code><b>",
}
strings_de = {
"owner": "Besitzer",
"version": "Version",
"build": "Zusammenbau",
"prefix": "Präfix",
"uptime": "Betriebszeit",
"platform": "Plattform",
"time": "Die Zeit",
"up-to-date": "😌 Aktuelle Version",
"update_required": "😕 Aktualisierung erforderlich </b><code>.update</code><b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"custom_message",
"no",
doc=lambda: self.strings("_cfg_cst_msg"),
),
loader.ConfigValue(
"custom_button1",
["🏡 Modules", "https://t.me/amoremods"],
lambda: self.strings("_cfg_cst_btn"),
validator=loader.validators.Series(min_len=0, max_len=2),
),
loader.ConfigValue(
"custom_button2",
[],
lambda: self.strings("_cfg_cst_btn"),
validator=loader.validators.Series(min_len=0, max_len=2),
),
loader.ConfigValue(
"custom_button3",
[],
lambda: self.strings("_cfg_cst_btn"),
validator=loader.validators.Series(min_len=0, max_len=2),
),
loader.ConfigValue(
"custom_banner",
"https://te.legra.ph/file/64bde7bf6b8e377521134.mp4",
lambda: self.strings("_cfg_cst_bnr"),
),
loader.ConfigValue(
"disable_banner",
False,
lambda: self.strings("_cfg_banner"),
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"custom_format",
"gif",
lambda: self.strings("_cfg_cst_frmt"),
validator=loader.validators.Choice(["photo", "video", "gif"]),
),
loader.ConfigValue(
"timezone",
"+5",
lambda: self.strings("_cfg_time"),
),
loader.ConfigValue(
"close_btn",
"🔻Close",
lambda: self.strings("_cfg_close"),
),
)
async def client_ready(self, client, db):
self._db = db
self._client = client
self._me = await client.get_me()
def _render_info(self) -> str:
ver = utils.get_git_hash() or "Unknown"
try:
repo = git.Repo()
diff = repo.git.log(["HEAD..origin/master", "--oneline"])
upd = (
self.strings("update_required") if diff else self.strings("up-to-date")
)
except Exception:
upd = ""
me = f'<b><a href="tg://user?id={self._me.id}">{utils.escape_html(get_display_name(self._me))}</a></b>'
version = f'<i>{".".join(list(map(str, list(main.__version__))))}</i>'
build = f'<a href="https://github.com/hikariatama/Hikka/commit/{ver}">#{ver[:8]}</a>' # fmt: skip
prefix = f"«<code>{utils.escape_html(self.get_prefix())}</code>»"
platform = utils.get_named_platform()
uptime = utils.formatted_uptime()
offset = datetime.timedelta(hours=self.config["timezone"])
tz = datetime.timezone(offset)
time1 = datetime.datetime.now(tz)
time = time1.strftime("%H:%M:%S")
return (
"<b> </b>\n"
+ self.config["custom_message"].format(
me=me,
version=version,
build=build,
upd=upd,
prefix=prefix,
platform=platform,
uptime=uptime,
time=time,
)
if self.config["custom_message"] != "no"
else (
"<b>🎢 AmoreInfo </b>\n"
f'<b>🤴 {self.strings("owner")}: </b>{me}\n\n'
f"<b>🕶 {self.strings('version')}: </b>{version} {build}\n"
f"<b>{upd}</b>\n"
f"<b>⏳ {self.strings('uptime')}: {uptime}</b>\n\n"
f"<b>⌚ {self.strings('time')}: {time}</b>\n"
f"<b>📼 {self.strings('prefix')}: </b>{prefix}\n"
f"{platform}\n"
)
)
def _get_mark(self, int):
if int == 1:
return (
{
"text": self.config["custom_button1"][0],
"url": self.config["custom_button1"][1],
}
if self.config["custom_button1"]
else None
)
elif int == 2:
return (
{
"text": self.config["custom_button2"][0],
"url": self.config["custom_button2"][1],
}
if self.config["custom_button2"]
else None
)
elif int == 3:
return (
{
"text": self.config["custom_button3"][0],
"url": self.config["custom_button3"][1],
}
if self.config["custom_button3"]
else None
)
elif int == 4:
return (
{
"text": self.config["close_btn"],
"action": "close",
}
if self.config["close_btn"]
else None
)
@loader.owner
async def ainfocmd(self, message: Message):
"""Send userbot info"""
m1 = self._get_mark(1)
m2 = self._get_mark(2)
m3 = self._get_mark(3)
m4 = self._get_mark(4)
await self.inline.form(
message=message,
text=self._render_info(),
reply_markup=[
[
*([m1] if m1 else []),
],
[
*([m2] if m2 else []),
*([m3] if m3 else []),
],
[
*([m4] if m4 else []),
],
],
**{}
if self.config["disable_banner"]
else {self.config["custom_format"]: self.config["custom_banner"]},
)

View File

@@ -1,473 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/AnimeVoices.jpg
from .. import loader
@loader.tds
class AnimeVoicesMod(loader.Module):
"""🎤 Popular Anime Voices"""
strings = {"name": "AnimeVoices"}
async def smexkcmd(self, message):
"""Смех Канеки"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/9",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def smexycmd(self, message):
"""Смех Ягами"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/7",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def znaycmd(self, message):
"""Знай свое место ничтожество"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/35",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def madaracmd(self, message):
"""Учиха Мадара"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/24",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def sharingancmd(self, message):
"""Итачи Шаринган"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/VoiceAmore/29",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def itachicmd(self, message):
"""Учиха Итачи"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/26",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def imsasukecmd(self, message):
"""Учиха Саске"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/VoiceAmore/30",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def paincmd(self, message):
"""Познайте боль"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/15",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def rascmd(self, message):
"""Расширение территории"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/17",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def tenseicmd(self, message):
"""Shinra tensei"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/18",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def dazaicmd(self, message):
"""Dazai"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/3",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def gaycmd(self, message):
"""I'm gay"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/20",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def bankaicmd(self, message):
"""Bankai"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/21",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def satecmd(self, message):
"""Sate sate sate"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/5",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def yoaimocmd(self, message):
"""Yoaimo"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/11",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def ghoulcmd(self, message):
"""Я гуль"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/12",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def welawcmd(self, message):
"""Мы закон"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/13",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def dattebayocmd(self, message):
"""Даттебайо"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/14",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def hardlifecmd(self, message):
"""Жизнь такова"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/16",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def hanmacmd(self, message):
"""Я Ханма Шужи"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/25",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def surprisecmd(self, message):
"""Surprise MxtherFxcker"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/30",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def equalcmd(self, message):
"""Мы созданы равными"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/31",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def beautytreecmd(self, message):
"""Красота леса"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/32",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def bankaiicmd(self, message):
"""Bankai remix"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/33",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def yametecmd(self, message):
"""Фулл ямете кудасай"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/47",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def mafiacmd(self, message):
"""Просыпается мафия"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/48",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def sharinganncmd(self, message):
"""Sharingan remix"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/49",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def smexecmd(self, message):
"""Смех Эрен"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/50",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def narutocmd(self, message):
"""Naruto heroes"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/51",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def smexrcmd(self, message):
"""Смех рюк"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/52",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def ohayocmd(self, message):
"""Охаё"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/53",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def iamhungrycmd(self, message):
"""Есть хочу"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/54",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def amaterasucmd(self, message):
"""Аматерасу remix"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/55",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def owocmd(self, message):
"""Full OwO"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/56",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
async def ghoulrucmd(self, message):
"""Русский Tokyo Ghoul"""
reply = await message.get_reply_message()
await message.delete()
await message.client.send_file(
message.to_id,
"https://t.me/animevoicesbyamore/57",
voice_note=True,
reply_to=reply.id if reply else None,
)
return
#voices by @dziru

View File

@@ -1,283 +0,0 @@
# Friendly Telegram (telegram userbot)
# Copyright (C) 2018-2019 The Authors
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods, FTG
__version__ = (1, 1, 0)
import asyncio
import datetime
from telethon.tl import functions
from telethon.utils import get_display_name
from .. import loader, utils
@loader.tds
class AutoProfileMod(loader.Module):
"""Automatic stuff for your profile :P"""
strings = {
"name": "AutoProfile",
"invalid_args": (
"<b>Missing parameters, please read the <code>.aguide</code> <emoji document_id=5213468029597261187>✔️</emoji></b>"
),
"missing_time": (
"<b>Time was not specified in bio <emoji document_id=5215273032553078755>❎</emoji></b>"
),
"enabled_bio": (
"<b>Enabled bio clock <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"bio_not_enabled": (
"<b>Bio clock is not enabled <emoji document_id=5215273032553078755>❎</emoji></b>"
),
"disabled_bio": (
"<b>Disabled bio clock <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"enabled_name": (
"<b>Enabled name clock <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"name_not_enabled": (
"<b>Name clock is not enabled <emoji document_id=5215273032553078755>❎</emoji></b>"
),
"disabled_name": (
"<b>Name clock disabled <emoji document_id=5215273032553078755>❎</emoji></b>"
),
"_cfg_time": "Use timezone 1, -1, -3 etc.",
}
strings_uz = {
"invalid_args": (
"<b>to'g'ri argumetlar emas, <code > ni o'qing.aguide</code> <emoji document_id=5213468029597261187>✔️</emoji></b>"
),
"missing_time": (
"<b>vaqt bio-da o'rnatilmagan<emoji document_id=5215273032553078755 > ❎< / emoji></b>"
),
"enabled_bio": (
"<b>Bio soat muvaffaqiyatli o'rnatildi <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"bio_not_enabled": (
"<b>soat bio-ga o'rnatilmagan<emoji document_id=5215273032553078755 > ❎< / emoji > </b>"
),
"disabled_bio": (
"<b > Bio-dagi vaqt muvaffaqiyatli o'chirildi <emoji document_id = 5212932275376759608>✅</emoji></b>"
),
"enabled_name": (
"<b>soat taxallusga muvaffaqiyatli o'rnatildi <emoji document_id = 5212932275376759608>✅</emoji></b>"
),
"name_not_enabled": (
"<b>soat taxallusga o'rnatilmagan<emoji document_id=5215273032553078755 > ❎< / emoji > </b>"
),
"disabled_name": (
"<b>taxallusdagi vaqt muvaffaqiyatli o'chirildi <emoji document_id = 5212932275376759608>✅</emoji></b>"
),
"_cfg_time": "vaqt zonasidan foydalaning 1, -1, -3 va boshqalar.",
}
strings_ru = {
"invalid_args": (
"<b>Не правильные аргуметы, прочитай <code>.aguide</code> <emoji document_id=5213468029597261187>✔️</emoji></b>"
),
"missing_time": (
"<b>Время не было установлено в био<emoji document_id=5215273032553078755>❎</emoji></b>"
),
"enabled_bio": (
"<b>Био часы успешно установлены <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"bio_not_enabled": (
"<b>Часы не установлено в био<emoji document_id=5215273032553078755>❎</emoji></b>"
),
"disabled_bio": (
"<b>Время в био успешно отключен <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"enabled_name": (
"<b>Часы в ник успешно установлены <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"name_not_enabled": (
"<b>Часы не установлены в ник<emoji document_id=5215273032553078755>❎</emoji></b>"
),
"disabled_name": (
"<b>Время в нике успешно отключен <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"_cfg_time": "Используй таймзону 1, -1, -3 и тд.",
}
strings_de = {
"invalid_args": (
"<b>Sind nicht die richtigen Argumente, lies <code>.aguide</code> <emoji document_id=5213468029597261187>✔️</emoji></b>"
),
"missing_time": (
"<b>Die Zeit wurde nicht auf bio gesetzt<emoji document_id=5215273032553078755>❎</emoji></b>"
),
"enabled_bio": (
"<b>Bio-Uhr wurde erfolgreich installiert <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"bio_not_enabled": (
"<b>Die Uhr ist nicht auf bio eingestellt<emoji document_id=5215273032553078755>❎</emoji></b>"
),
"disabled_bio": (
"<b>Zeit in bio erfolgreich deaktiviert <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"enabled_name": (
"<b>Die Uhr wurde erfolgreich auf den Nickname gesetzt <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"name_not_enabled": (
"<b>Die Uhr ist nicht auf den Spitznamen<emoji document_id=5215273032553078755>❎</emoji></b> eingestellt"
),
"disabled_name": (
"<b>Nickzeit wurde erfolgreich deaktiviert <emoji document_id=5212932275376759608>✅</emoji></b>"
),
"_cfg_time": "Benutze die Zeitzone 1, -1, -3 usw.",
}
def __init__(self):
self.bio_enabled = False
self.name_enabled = False
self.raw_bio = None
self.raw_name = None
self.config = loader.ModuleConfig(
loader.ConfigValue(
"timezone",
"+5",
lambda: self.strings("_cfg_time"),
),
)
async def client_ready(self, client, db):
self.client = client
self._me = await client.get_me()
@loader.command(ru_doc="""Что-бы указать таймзону через конфиг""")
async def cfautoprofcmd(self, message):
"""To specify the timezone via the config"""
name = self.strings("name")
await self.allmodules.commands["config"](
await utils.answer(message,
f"{self.get_prefix()}config {name}")
)
@loader.command(ru_doc="""Автоматически изменяет биографию вашей учетной записи с учетом текущего времени, использования: .autobio 'сообщение, время как {time}'""")
async def autobiocmd(self, message):
"""Automatically changes your account's bio with current time, usage:
.autobio 'message, time as {time}'"""
msg = utils.get_args(message)
if len(msg) != 1:
return await utils.answer(message, self.strings("invalid_args", message))
raw_bio = msg[0]
if "{time}" not in raw_bio:
return await utils.answer(message, self.strings("missing_time", message))
self.bio_enabled = True
self.raw_bio = raw_bio
await self.allmodules.log("start_autobio")
await utils.answer(message, self.strings("enabled_bio", message))
while self.bio_enabled:
offset = datetime.timedelta(hours=self.config["timezone"])
tz = datetime.timezone(offset)
time1 = datetime.datetime.now(tz)
current_time = time1.strftime("%H:%M")
bio = raw_bio.format(time=current_time)
await self.client(functions.account.UpdateProfileRequest(about=bio))
await asyncio.sleep(60)
@loader.command(ru_doc="""Что-бы остановить время в био введи .stopautobio""")
async def stopautobiocmd(self, message):
"""Stop autobio cmd."""
if self.bio_enabled is False:
return await utils.answer(message, self.strings("bio_not_enabled", message))
self.bio_enabled = False
await self.allmodules.log("stop_autobio")
await utils.answer(message, self.strings("disabled_bio", message))
await self.client(
functions.account.UpdateProfileRequest(about=self.raw_bio.format(time=""))
)
@loader.command(ru_doc="""Автоматически изменяет имя вашей учетной записи с учетом текущего времени, использования: .autoname 'сообщение, время как {time}'""")
async def autonamecmd(self, message):
"""Automatically changes your Telegram name with current time, usage:
.autoname '<message, time as {time}>'"""
msg = utils.get_args(message)
if len(msg) != 1:
return await utils.answer(message, self.strings("invalid_args", message))
raw_name = msg[0]
if "{time}" not in raw_name:
return await utils.answer(message, self.strings("missing_time", message))
self.name_enabled = True
self.raw_name = raw_name
await self.allmodules.log("start_autoname")
await utils.answer(message, self.strings("enabled_name", message))
while self.name_enabled:
offset = datetime.timedelta(hours=self.config["timezone"])
tz = datetime.timezone(offset)
time1 = datetime.datetime.now(tz)
current_time = time1.strftime("%H:%M")
name = raw_name.format(time=current_time)
await self.client(functions.account.UpdateProfileRequest(first_name=name))
await asyncio.sleep(60)
@loader.command(ru_doc="""Что-бы остановить время в имени учетной записи введи .stopautoname""")
async def stopautonamecmd(self, message):
"""just write .stopautoname"""
if self.name_enabled is False:
return await utils.answer(
message, self.strings("name_not_enabled", message)
)
self.name_enabled = False
await self.allmodules.log("stop_autoname")
await utils.answer(message, self.strings("disabled_name", message))
await self.client(
functions.account.UpdateProfileRequest(
first_name=self.raw_name.format(time="")
)
)
@loader.command(ru_docs="""Доки ru/en""")
async def aguide(self, message):
"Just guide ru/en"
args = utils.get_args_raw(message)
args = args if args in {"en", "ru"} else "en"
time = "{time}"
nick = f'<a href="tg://user?id={self._me.id}">{utils.escape_html(get_display_name(self._me))}</a>'
pref = f"{utils.escape_html(self.get_prefix())}"
await utils.answer(
message,
f"<emoji document_id=5789581976176430614>💸</emoji> For example:\n\n<emoji document_id=5789667570579672963>💸</emoji> AutoName: <code>{pref}autoname '{nick} | {time}'</code>\n"
f"<emoji document_id=5789667570579672963>💸</emoji> AutoBio: <code>{pref}autobio 'smth | {time}'</code>\n"
if args == "en"
else (
f"<emoji document_id=5789581976176430614>💸</emoji> Например:\n\n<emoji document_id=5789667570579672963>💸</emoji> Авто Ник: <code>{pref}autoname '{nick} | {time}'</code>\n"
f"<emoji document_id=5789667570579672963>💸</emoji> Авто Био: <code>{pref}autobio 'что-то | {time}'</code>\n"
),
)

View File

@@ -1,124 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# If you don't like the module don't use it
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/besafe.jpg?raw=true
import logging
import requests
import ast
import re
from .. import loader, utils
logger = logging.getLogger(__name__)
__version__ = (1, 0, 0)
@loader.tds
class BeSafe(loader.Module):
"""
Check module before loading
"""
strings = {
"name": "BeSafe",
"no_args_or_reply": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>[BeSafe]</b> No link or reply to file",
"safe": "<emoji document_id=5203929938024999176>🛡</emoji> <b>Module is safe</b>",
"suspicious": "<emoji document_id=5325771498718241219>🔎</emoji> Module is suspicious\n\n<emoji document_id=6334443713485342501>⛩</emoji> <b>Suspicious imports:</b>\n",
'sus_keywords': "\n<emoji document_id=6334405093139416847>🔑</emoji> <b>Suspicous keywords:</b>"
}
strings_ru = {
"no_args_or_reply": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>[BeSafe]</b> Нет ссылки или реплея на модуль",
"safe": "<emoji document_id=5203929938024999176>🛡</emoji> <b>Модуль безопасен</b>",
"suspicious": "<emoji documentx_id=5325771498718241219>🔎</emoji> Модуль подозрительный\n\n<emoji document_id=6334443713485342501>⛩</emoji> <b>Подозрительные импорты:</b>\n",
'sus_keywords': "\n<emoji document_id=6334405093139416847>🔑</emoji> <b>Подозрительные ключевые слова:</b>"
}
def extract_imports(self, code):
code = code.lstrip('\ufeff') # крч удаление символа BOM, если он есть
try:
tree = ast.parse(code)
except SyntaxError as e:
if "invalid non-printable character" not in str(e):
raise
code = code.encode('utf-8-sig').decode('utf-8')
tree = ast.parse(code)
imports = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
imports.extend(name.name for name in node.names)
elif isinstance(node, ast.ImportFrom):
module_name = node.module
imports.extend(f"{module_name}.{name.name}" for name in node.names)
return imports
suspicious_imports = [
'glob',
'os',
'sys',
'telethon.tl.TLRequest',
'requests',
]
suspicious_keywords = [
r'0x418d4e0b',
r'0xf5b399ac',
r'w+z+mm+"A"+nk+u+h+lk',
r'b"\x0bN\x8dA"'
r'session',
r'TestingHikka_BOT' # временно будет тут
]
def extract_keywords(self, code):
words = []
for word in self.suspicious_keywords:
if r := re.findall(word, code):
words.append(r[0])
return words
@loader.command()
async def bs(self, message):
"""
BeSafe - <reply to module> or <link to module>
"""
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if args:
r = await utils.run_sync(requests.get, args)
string = r.text
elif reply:
code = (await self._client.download_file(reply.media, bytes)).decode("utf-8")
string = code
else:
await utils.answer(message, self.strings["no_args_or_reply"])
imports = self.extract_imports(string)
sus_imports = [f"▫️ <code>{imp}</code>" for imp in self.suspicious_imports if imp in imports]
sus_keywords = []
if sus_imports:
kw = self.extract_keywords(string)
sus_keywords = [f"▫️ <code>{k}</code>" for k in self.suspicious_keywords if k in kw]
if sus_imports or sus_keywords:
sus_list = sus_imports + [self.strings["sus_keywords"]] + sus_keywords
text = self.strings["suspicious"] + '\n'.join(sus_list)
else:
text = self.strings["safe"]
await utils.answer(message, text)

View File

@@ -1,229 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
import re
import asyncio
import random
from aiohttp import web
from .. import utils, loader
class WebCreator:
def __init__(self, name, tg_link, preview_name):
self.url = None
self.app = web.Application()
self.app.router.add_get("/", self.index)
self.name = name
self.tg_link = tg_link
self.preview_name = preview_name
async def index(self, request):
html_content = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<title>For {self.name}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1"></script>
</head>
<style>
body {{
margin: 0;
text-align: center;
background: #1d1d1d;
font: 22px 'Indie Flower', cursive, "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #fff;
overflow-x: hidden;
}}
#content {{
position: relative;
max-width: 470px;
margin: 0 auto;
line-height: 150%;
padding: 20px;
z-index: 1;
}}
h1 {{
font-size: 2.5rem;
color: #f7d066;
margin-top: 50px;
}}
p {{
font-size: 1.2rem;
margin-top: 20px;
}}
span {{
color: #f7467e;
}}
@media (max-width: 767px) {{
#content {{
max-width: 100%;
padding: 10px;
}}
}}
</style>
<body>
<div id="content">
<h1>Happy Birthday, {self.name}!</h1>
<p>Dear {self.name},<br>
On this special day, I wish you all the very best, all the joy you can ever have, and may you be blessed
abundantly today, tomorrow, and the days to come! May you have a fantastic birthday and many more to come...
HAPPY BIRTHDAY!!!!<br>
<span>With love, <a href="{self.tg_link}">{self.preview_name}</a></span>
</p>
</div>
<script>
function launchConfetti() {{
const duration = 2 * 1000;
const end = Date.now() + duration;
(function frame() {{
confetti({{
particleCount: 5,
angle: 60,
spread: 55,
origin: {{ x: 0 }},
colors: ['#f7d066', '#f7467e', '#fff']
}});
confetti({{
particleCount: 5,
angle: 120,
spread: 55,
origin: {{ x: 1 }},
colors: ['#f7d066', '#f7467e', '#fff']
}});
if (Date.now() < end) {{
requestAnimationFrame(frame);
}}
}})();
}}
window.onload = launchConfetti;
</script>
</body>
</html>
"""
return web.Response(text=html_content, content_type="text/html")
async def open_tunnel(self, port):
ssh_command = f"ssh -o StrictHostKeyChecking=no -R 80:localhost:{port} nokey@localhost.run"
process = await asyncio.create_subprocess_shell(
ssh_command,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
url = await self._extract_tunnel_url(process.stdout)
self.url = url or f"https://localhost:{port}"
return self.url
async def _extract_tunnel_url(self, stdout):
event = asyncio.Event()
url = None
async def read_output():
nonlocal url
while True:
line = await stdout.readline()
if not line:
break
decoded_line = line.decode()
match = re.search(r"tunneled.*?(https:\/\/.+)", decoded_line)
if match:
url = match[1]
break
event.set()
await read_output()
await event.wait()
return url
@loader.tds
class BirthdayWish(loader.Module):
"""Share warmth with your loved ones and give them this website to make their birthdays even more special and joyful."""
strings = {
"name": "BirthdayWish",
"provide_name": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>Please provide a name</b>",
"web_url": "<emoji document_id=5334643333488713810>🌐</emoji> <b>URL: {} | Expires in <code>{}</code> seconds</b>",
"expired": "<emoji document_id=5981043230160981261>⏱</emoji> <b>Url Expired</b>",
}
strings_ru = {
"provide_name": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>Пожалуйста, укажите имя</b>",
"web_url": "<emoji document_id=5334643333488713810>🌐</emoji> <b>URL: {} | Истекает через <code>{}</code> секунд</b>",
"expired": "<emoji document_id=5981043230160981261>⏱</emoji> <b>Url истек</b>",
}
def __init__(self):
self.wishes = {}
async def tunnel_handler(self, port):
creator = WebCreator(
name=self.name, tg_link=self.tg_link, preview_name=self.preview_name
)
runner = web.AppRunner(creator.app)
await runner.setup()
global site
site = web.TCPSite(runner, "127.0.0.1", port)
await site.start()
url = await creator.open_tunnel(port)
return url, runner
async def wishcmd(self, message):
"""Create Birthday web wishes args: <name> <time:seconds default(20)>"""
args = utils.get_args_raw(message).split(" ")
if args[0] == "":
return await utils.answer(message, self.strings("provide_name"))
text = args[0]
expiration_time = int(args[1]) if len(args) > 1 else 20
me = await message.client.get_me()
self.tg_link = f"https://t.me/{me.username}" or "https://t.me/Unknown"
self.preview_name = me.first_name
self.name = text
port = random.randint(1000, 9999)
url, runner = await self.tunnel_handler(port)
await utils.answer(
message, self.strings("web_url").format(url, expiration_time)
)
await asyncio.sleep(expiration_time)
await site.stop()
await runner.cleanup()
await utils.answer(message, self.strings("expired"))

View File

@@ -1,100 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta pic: https://te.legra.ph/file/7772a7dae6290f0a612a6.png
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Bull.jpg
import random
from .. import loader, utils
from ..inline.types import InlineCall
from ..inline.types import InlineQuery
bullr = (
"ТЫ ПОНИМАЕШЬ ЧТО Я ПИЗДАК В ТЕОЙ МАТРЕИ НА СВОЙ УЙ КАК МАКАРОНИНУ НАМОТАЛ БЛЯДЬ И НАЧАЛ РАСКРУЧИВАТЬ ЕЁ, ПОСЛЕ ЧЕГО ВЫКИНУЛ В КОСМОС, ЧТОБ ЕЁ ТАМ ИНОПЛАНЕТЯНЫ ХУЯМИ РВАЛИ?)",
"ТЫ ПОНИМАЕШЬ ЧТО Я ТВОЮ МАТЬ ОТПРАВИЛ СО СВОЕГО ЪХУЯ В НЕБО, ЧТОБ ОНА СВОИМ ПИЗДАКОМ ПРИНИМАЛА МИТЕОРИТНУЮ АТАКУ?)",
"ЕСЛИ ТЫ СЕЙЧАС ТАК И БУДЕШЬ ПРОДОЛЖАТЬ ПРОТИВОРЕЧИТЬ МОЕМУ ХУЮ, КАК ИМ КАК БЛЯДЬ НА НЛО ЗАХВОЧУ ТВОЁ ОЧКО И НАЧНУ ОПЫТЫ ПРОВОДИТЬ",
"ТЫ ПОНИМАЕШЬЧ ТО ВТОЯ МАТЬ МОЙ ХУЙ ЗАВЕРНУЛА В ПАКЕТИК ПОТОМУ ЧТО У ЭТОЙ БОМЖИХИ НЕБЫЛО ДЕНЕГ НА ПРЕЗИКИ, И ПОКЕТИК ПОРВАЛСЯ, И РОДИЛОСЬ ТАКОЕ ХУЙЛО КАК ТЫ",
"TЫ ПОНИМАЕШЬ ЧТО Я ТВОЮ МАТЬ СЛУЧАЙНО СВОИМ ХУЁМ СМЁЛ НАХУЙ СО СВОЕГО ПУТИ, И ОНА УЛЕТЕЛА НА РАДИУС ОБСТРЕЛА МОЕЙ ЗАЛУПЫ",
"АМЕБА ЕБАНАЯ СУКА) МАМАШКУ ТВОЮ ДЫРЯВИЛ ЧЕТ ) НАХУЙ ТВОЯ МАМАША КРИЧИТ КОГДА Я НАЧИНАЮ ЕБАТЬ ЕЕ)",
"АМЕБА ИЛИ ТЫ ОЛЕНЬ?) СЛЫШЬ ЕСЛИ ТЫ ПРОЛЬЕШЬ НА МОЙ ХУЙ СЛЕЗЫ , ТО ТЫ НЕ РАССЧИТЫВАЙ НА ТО , ЧТО ПОТОМ К ТЕБЕ ПРИДЕТ ФЕЯ И ПООБЕЩАЕТ ТЕБЕ ДОЛГУЮ И СЧАСТЛИВУЮ ЖИЗНЬ)",
"ОЛЕНЬ ТЫ ЕБАНЫЙ) МАТЬ ТВОЮ ЕБУ ЧЕТ ) ДАВАЙ ТЫ ЩАС ВОЗЬМЕШЬ МОЙ ХУЙ КАК ПЕРО И СЛОВНО КАК ПИСАТЕЛЬ СЕРЕБРЯНОГО ВЕКА НАПИШЕШЬ КАКОЙ НИБУДЬ РОМАН КОТОРЫЙ БУДЕТ ПО РАЗМЕРУ ПРИМЕРНО КАК МАСТЕР И МАРГАРИТА )",
"твоя мамка блядоебская кобыла и лезби",
"у тебя мать сраная шлюха",
"Я ТОВЮ МАМАШУ СВОИМ ХУЁМ РАСПЛЮЩИЛ, И ТЕПЕРЬ ОНА КАК ХОЯЧАЮ ПРУЖИТНКА БЛЯДЬ, ОТ СЕБЯ ВСЕ ХУИ ОТТАЛКИВАЕТ КРОМЕ МОЕГО, ДЛЯ МОЕГО ХУЯ ВСЕГДА ОТКРЫТ ДОСТУП В ЕЁ ПИЗДАК",
"ТЫ ПОНИМАЕШЬ ЧТО Я ТВОЮ МАТЬ БЛЯДЬ НАТЯНУЛ ПИЗДАКОМ НА ВЫСОКОВОЛЬТНУЮ ЭЛЕКТРО ВЫШКУ, И ОНА В СЕБЯ НАХУЙ ВЕСЬ ТОК ВТЯНУЛА, ТЕПЕРЬ ОНА БЛЯДЬ КАК ЭЛЕКТРО",
"Я КОГДА ВЫЕБАЛ ТВОЮ МАТЬ Я СВОЙ ХУЙ ПОСТАВИЛ К ЕЁ УХУ, ЧТОБ ОНА СЛЫШАЛА ПРИБОЯ СПЕРМЫ, А ПОТОМ ОНА ШИРОКО РАСКРЫЛА РОТ МЫ В ЕЁ ЕБЛЯТНИКЕ УСТРОИЛИ ОКЕАН",
"ТЫ ПОНИМАЕШЬ ЧТО Я В ПИЗДАК ТВОЕЙ МАТРЕИ СВОЙ ХУЙ ЗАСУНУ КАК БЛЯДЬ ШТТЕККЕР И ЕЁ ЗАРЯД ПОВЫСИЛСЯ КАК ОТ ЭНЕРГЕТИКА)",
"ТЫ ПОНИМАЕШЬ ЧТО ТВОЯ МАТЬ НА МОЁМ ХУЮ УСТРОИЛА БЛЯДЬ ТАНЦПОЛ, И НАЧАЛА СВОИМ ПОДРУГАМ ПРОДАВАТЬ НА НЕГО БИЛЕТЫ",
" ЕСЛИ ТЫ СЕЙЧАС НЕ НАЧНЁШЬ МНЕ ОТВЕЧАТЬ, Я ТЕБЕ НАХУЙ ХЁМ ПАЛЬЦЫ ПЕРЕЛОМАЮ, ОБРАЗИНА ТЫ ЕБАНАЯ)",
"ТЫ ПОНИМАЕШЬ ЧТО ВТОЯ МАМАШКА КАШЁЛКА ЕБАНАЯ, НА МОЙ ХУЙ ВЕШАЕТСЯ СВОИМ ПИЗДАКОМ КАК МАГНИТИК НА ХОЛОДИЛЬНИК, ПИДОПР ТЫ БЛЯДЬ ЕБАНЫЙ",
"ТЫ ПОНИМАЕШЬ ЧТО Я ТВОЕЙ МАТЕРИ ГОЛОВУ ХУЁМ КАК КОПЬЁМ ПРОБИЛ БЛЯДЬ И ЕЁ КУРИНЫЙ МОЗГ УМЕР НАХУЙ)) ИЗ-ЗА ЭТОГО ОНА ТЕБЯ ДАЖЕ И НЕ ВСПОМИНАЕТ)",
"ТЫ ПОНИМАЕШЬ ЧТО Я ВЫСТАВИЛ СВОЙ ХУЙ НА АВИТО, А ТВОЯ МАТЬ ПРОШЛА БЛЯДЬ БЕЗ ОЧЕРЕДИ И ККУПИЛА ЕГО, ОТДАВ СВОЮ ГНИЛУЮ ПОЧКУ?)",
"ТЫ ПОНИМАЕШЬ ЧТО ТВОЯ МАТЬ МОЙ ХУЙ НА НОЧЬ СЕБЕ В ПИЗДАК СУЁТ КАК ОБОГРЕВАТЕЛЬ НАХУЙ?)",
"ТЫ ПОНИМАЕШЬ ЧТО Я ПОКА ЧТО ЕБАЛ ТВОЮ МАМАШУ В СРАКУ, У НЕЁ ТАМ ЗАСОР СПЕРМЫ БЛЯДЬ ОБРАЗОВАЛСЯ И ЗАСОХ, ТЕПЕРЬ ОНА СРАТЬ НОРМАЛЬНО НЕ МОЖЕТ, ИДИ НАХУЙ СПАСАЙ ЭТУ ШЛЮХУ",
"ТЫ ПОНИМАЕШЬ ЧТО КОГДА Я ЕБУ ТВОЮ МАТЬ ЧТОБЫ ОНА НЕ ОРАЛА Я ЕЙ КЛЯП В РОТ СУЮ, НО ОДИН РАЗ СЛУЧИЛОСЬ ТАКОЕ, ЧТО КОГДА Я СВОИМ ХУЁМ ДАЛ ЕЙ ПО ПИЗДЕ ОНА ЭТОТ КЛЯП ПРОГЛАТИЛА, И НАЧАЛА ЗАДЫХАТЬСЯ, СПАСИ СВОЮ ШЛЮХА МАМКУ)",
"ТЫ ПОНИМАЕШЬ ЧТО МОЙ ХУЙ ВЗЛАМЫВАЕТ ОЧКО ТВОЕЙ МАТЕРИ КАК СЕЙФ НАХУЙ, И ОТ ТУДА НАЧИНАЮТ ВАЛИТЬСЯ САМОРОДКИ СПЕРМЫ?)",
"ТЫ ПОНИМАЕШЬ ЧТО Я В ПИЗДАКЕ ТВОЕЙ МАТЕРИ УСТРОИЛ ИЗВЕРЖЕНИЕ СВОЕГО ХУЯ НАХУЙ?",
"ТЫ ПОНИМАЕШЬ ЧТО Я ХУЁМ НАЧАЛ МОТАТЬ ПЕРЕД ТВОИМ ЕБАЛОИ И ТЕБЯ СЛУЧАЙНО НАХУЙ ЗАГИПНОТЕЗИРОВАЛ, И ТЫ ОБ ХУИ СТАЛ ГОЛОВОЙ БИТЬСЯ?)",
"ТЫ ПОНИМАЕШЬ ЧТО КОГДА Я ТВОЮ МАТЬ ОНА КАК ШЛЮХА ЛОЖИТСЯ НА СПИНКУ И НАЧИНАЕТ ПОСАСЫВАТЬ МОИ ЯЙЦА",
"ТЫ ПОНИМАЕШЬ ЧТО Я В ПИЗДАКЕ ТВОЕЙ МАТЕРИ ИЗ ЕЁ КЛИТОРНЫЙ СТЕН ВЫРЕЗАЮ РАКЕТНИЦУ СВОИМ ХУЁМ?",
"Я СЕЙЧАС СВОЕЙ СПЕРМОЙ ОБОЛЬЮ ТВОЙ ПИЗДАК КАК КЕРАСИНОМ, И ПУЩУ НА НЕГО ИСКРУ, ДОБЫТАЯ КОТОРАЯ БУДЕТ О ТВОИ ГНИЛЫЕ ЗУБКИ, И ТЫ СГОРИШЬ НАХУЙ)",
"ТЫ ПОНИМАЕШЬ ЧТО ТЫ ОТ МОЕЙ ЗАЛУПЫ ПРЯЧШЬСЯ В ПИЗДАКЕ СВОЕЙ МАТЕРИ КАК НАХУЙ В БУНКЕРЕ, А Я СВОИМ ХУЁМ ЕГО НА СКВОЗЬ ПРОШИЛ И ТЕБЕ В ЕБАЛО ПОПАЛ)) ",
"ТЫ ПОНИМАЕШЬ ЧТО Я ХУЁМ СТАЛ КАТАЛИЗИРОВАТЬ ПИЗДАК ТВОЕЙ МАТЕРИ НА РАЗДВИЖЕНИЕ ЕЁ ЖИРНЫХ НОГ?)",
)
def bullme():
iwfy = random.choice(bullr)
return iwfy
@loader.tds
class BullMod(loader.Module):
"""Bull пиз#а собеседнику"""
strings = {"name": "BullMod"}
@loader.inline_everyone
async def bull_inline_handler(self, query: InlineQuery):
"""Забулить кого-то жесткими матами про мать"""
aoa = bullme()
btn_a = [{"text": "🌀 Random", "callback": self.bulls}]
return {
"title": "Пошутить про маму",
"thumb": "https://te.legra.ph/file/b2a6c8d20e0034a534ac4.jpg",
"description": "Отправить...",
"message": f"<i>{aoa}</i>",
"reply_markup": btn_a,
}
async def bullcmd(self, message):
"""Забулить кого-то жесткими матами про мать"""
aoa = bullme()
await utils.answer(message, aoa)
async def bullicmd(self, message):
"""Забулить кого-то жесткими матами про мать (inline)"""
aoa = bullme()
await self.inline.form(
message=message,
text=aoa,
reply_markup=[
[{"text": "🌀 Random", "callback": self.bulls}],
]
)
async def bulls(self, call: InlineCall):
aoa = bullme()
await call.edit(
text=aoa,
reply_markup=[
[{"text": "🌀 Random", "callback": self.bulls}],
]
)

View File

@@ -1,158 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta pic: https://te.legra.ph/file/388e1b26a46a8c439e479.png
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Createlinks.jpg
from .. import loader, utils, security
@loader.tds
class AmorelinksMod(loader.Module):
"""Create links"""
strings = {
"name": "AmoreLinks",
"youtube": "🫂 <b>YouTube link special for you.</b>\n\n",
"google": "🫂 <b>Google link special for you.</b>\n\n",
"github": "🫂 <b>Github link special for you.</b>\n\n",
"pornhub": "🫂 <b>Pornhub link special for you.</b>\n\n",
"telegram": "🫂 <b>Telegram link special for you.</b>\n\n",
"4pda": "🫂 <b>4pda link special for you.</b>\n\n",
}
async def ytcmd(self, message):
"""<text> create YouTube link"""
text = utils.get_args_raw(message)
s = f"<b>✏ Input word: <code>{text}</code></b>"
if await self.allmodules.check_security(
message,
security.OWNER | security.SUDO,
):
try:
await self.inline.form(
self.strings("youtube", message) + s,
reply_markup=[
[{"text": "♨️ Link", "url": f"https://m.youtube.com/results?sp=mAEA&search_query={text}"}],
[{"text": "🔻 Close", "action": f"close"}],
],
message=message,
)
except Exception:
await utils.answer(message, self.strings("join", message))
async def gugcmd(self, message):
"""<text> create Google link"""
text = utils.get_args_raw(message)
s = f"<b>✏ Input word: <code>{text}</code></b>"
if await self.allmodules.check_security(
message,
security.OWNER | security.SUDO,
):
try:
await self.inline.form(
self.strings("google", message) + s,
reply_markup=[
[{"text": "🛰 Link", "url": f"https://www.google.com/search?q={text}"}],
[{"text": "🔻 Close", "action": f"close"}],
],
message=message,
)
except Exception:
await utils.answer(message, self.strings("join", message))
async def ghcmd(self, message):
"""<text> create Github link"""
text = utils.get_args_raw(message)
s = f"<b>✏ Input word: <code>{text}</code></b>"
if await self.allmodules.check_security(
message,
security.OWNER | security.SUDO,
):
try:
await self.inline.form(
self.strings("github", message) + s,
reply_markup=[
[{"text": "🛰 Link", "url": f"https://github.com/search?q={text}"}],
[{"text": "🔻 Close", "action": f"close"}],
],
message=message,
)
except Exception:
await utils.answer(message, self.strings("join", message))
async def phcmd(self, message):
"""<text> create PornHub link"""
text = utils.get_args_raw(message)
s = f"<b>✏ Input word: <code>{text}</code></b>"
if await self.allmodules.check_security(
message,
security.OWNER | security.SUDO,
):
try:
await self.inline.form(
self.strings("pornhub", message) + s,
reply_markup=[
[{"text": "🛰 Link", "url": f"https://rt.pornhub.com/video/search?search={text}"}],
[{"text": "🔻 Close", "action": f"close"}],
],
message=message,
)
except Exception:
await utils.answer(message, self.strings("join", message))
async def tgcmd(self, message):
"""<text> create Telegram link"""
text = utils.get_args_raw(message)
s = f"<b>✏ Input word: <code>{text}</code></b>"
if await self.allmodules.check_security(
message,
security.OWNER | security.SUDO,
):
try:
await self.inline.form(
self.strings("telegram", message) + s,
reply_markup=[
[{"text": "🛰 Link", "url": f"tg://search?query={text}"}],
[{"text": "🔻 Close", "action": f"close"}],
],
message=message,
)
except Exception:
await utils.answer(message, self.strings("join", message))
async def pdacmd(self, message):
"""<text> create 4pda link"""
text = utils.get_args_raw(message)
s = f"<b>✏ Input word: <code>{text}</code></b>"
if await self.allmodules.check_security(
message,
security.OWNER | security.SUDO,
):
try:
await self.inline.form(
self.strings("4pda", message) + s,
reply_markup=[
[{"text": "🛰 Link", "url": f"https://4pda.to/forum/index.php?act=search&source=all&forums=316&subforums=1&query={text}"}],
[{"text": "🔻 Close", "action": f"close"}],
],
message=message,
)
except Exception:
await utils.answer(message, self.strings("join", message))

View File

@@ -1,73 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/DTWR.jpg
from .. import loader, utils
from telethon.tl.types import Message
@loader.tds
class DTWRMod(loader.Module):
"""Module Don't tag wihout reason"""
strings = {
"name": "DTWR",
"text": "Your custom text",
"username": "Input you username without '@'",
}
strings_ru = {
"text": "Кастомный текст",
"username": "Введи свой юзернэйм без '@'",
}
strings_uz = {
"text": "Kastom text",
"username": "Usernameingizni kiriting, '@' siz"
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"Username",
"username",
doc=lambda: self.strings("username"),
),
loader.ConfigValue(
"custom_text",
"😫 Please don't tag me without reason",
doc=lambda: self.strings("text"),
),
)
@loader.command(ru_docs="Конфиг этого модуля")
async def cfgdtwrcmd(self, message):
"""This module config"""
name = self.strings("name")
await self.allmodules.commands["config"](
await utils.answer(message, f"{self.get_prefix()}config {name}")
)
@loader.tag("only_messages", "only_groups", "in")
async def watcher(self, message: Message):
reply = await message.get_reply_message()
tag = self.config['Username']
if tag.startswith('@') is False:
tag = f"@{tag}"
if reply:
return False
if message.text.lower() == tag:
await message.reply(self.config["custom_text"])
await self._client.send_read_acknowledge(
message.chat_id,
clear_mentions=True,
)

View File

@@ -1,48 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Facts.jpg
# channel @facti_p
from .. import loader, utils
from telethon import functions
from asyncio import sleep
import random
import datetime
chat = "@faktiru"
class FactsMod(loader.Module):
"""More Interesting Facts"""
strings = {
"name": "Facts",
"wait": "<emoji document_id=5472146462362048818>💡</emoji> Searching..."
}
strings_ru = {
"wait": "<emoji document_id=5472146462362048818>💡</emoji> Поиск..."
}
@loader.command(ru_docs="Интересные Факты")
async def afactscmd(self, message):
"""Intersting Facts"""
reply = await message.get_reply_message()
await utils.answer(message, self.strings["wait"])
result = await message.client(
functions.messages.GetHistoryRequest(
peer=chat, offset_id=0, offset_date=datetime.datetime.now(), add_offset=random.randint(0, 1000), limit=1, max_id=0, min_id=0, hash=0,
)
)
await sleep(0.30)
await message.delete()
await message.client.send_message(
message.to_id,
result.messages[0],
reply_to=reply.id if reply else None,
)

View File

@@ -1,67 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/Figlet.jpg?raw=true
import pyfiglet
import functools
from .. import loader, utils
class Figlet(loader.Module):
"""Creates Figlet Text"""
strings = {"name": "Figlet"}
style_to_font = {
"slant": "slant",
"3d": "3-d",
"5line": "5lineoblique",
"alpha": "alphabet",
"banner": "banner3-D",
"doh": "doh",
"iso": "isometric1",
"letter": "letters",
"allig": "alligator",
"dotm": "dotmatrix",
"bubble": "bubble",
"bulb": "bulbhead",
"digi": "digital"
}
@loader.command()
async def listfig(self, message):
"""List of figlet styles"""
keys_list = " , ".join(list(self.style_to_font.keys()))
await utils.answer(message, f"🚩 Available styles: {keys_list}")
@loader.command()
async def figlet(self, message):
"""Create figlet text, <style> | <args>"""
args = utils.get_args_raw(message).split(" | ")
if len(args) < 2:
await utils.answer(message, "Not enough arguments")
return
font = self.style_to_font.get(args[0], None)
if font is None:
await utils.answer(message, "There is no such style")
return
if not args[1]:
await utils.answer(message, "Text argument is empty")
return
result = await self.figlet_format_cached(args[1], font)
await utils.answer(message, f"<code>{result}<code>")
@staticmethod
@functools.lru_cache(maxsize=None)
async def figlet_format_cached(text, font):
return pyfiglet.figlet_format(text, font=font)

View File

@@ -1,48 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/fileext.jpg
from .. import loader, utils
from telethon.tl.types import Message
from bs4 import BeautifulSoup
import requests
async def search_extention(ext):
sample_url = "https://www.fileext.com/file-extension/{}.html"
response_api = requests.get(sample_url.format(ext))
if not response_api.ok:
return (
f"Error fetching details for {ext}. Status code: {response_api.status_code}"
)
soup = BeautifulSoup(response_api.content, "html.parser")
return soup.find_all("td", {"colspan": "3"})[-1].text
@loader.tds
class FileExtMod(loader.Module):
"""Get file extention details"""
strings = {
"name": "FileExt",
"no_args": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>No args passed</b>",
"response": "<emoji document_id=5467732133629926938>🔍</emoji> <b>File Extension</b>: <code>{}</code>\n<emoji document_id=5467919175160705819>🔍</emoji> <b>Description</b>: <code>{}</code>",
}
@loader.command()
async def fileext(self, message: Message):
"""Get file extention details"""
if args := utils.get_args_raw(message):
await utils.answer(
message,
self.strings("response").format(args, await search_extention(args)),
)
else:
await utils.answer(message, self.strings("no_args"))

View File

@@ -1,37 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/fragment_checker.jpg?raw=true
# requires: bs4
import requests
from bs4 import BeautifulSoup
from .. import loader, utils
class Fragment(loader.Module):
"""Show how much is the username in the Fragment.com"""
strings = {"name": "FragmentChecker"}
@loader.command()
async def fcheck(self, message):
"""check username in the Fragment.com"""
args = utils.get_args_raw(message)
response = requests.get(f"https://fragment.com/username/{args}")
if response.status_code == 200:
soup = BeautifulSoup(response.content, "html.parser")
elements = soup.select(".table-cell-value.tm-value.icon-before.icon-ton")
if elements:
text = elements[0].text.strip()
await utils.answer(message, f"<emoji document_id=5215219508670638513>💎</emoji> <b>Username Found!</b>\n<emoji document_id=5467626799556992380>✈️</emoji> <b>Username:</b> <code>{args}</code>\n<emoji document_id=5460720028288557729>🪙</emoji> <b>Cost:</b> <code>{text}</code> TON")
if not elements:
await utils.answer(message, f"<emoji document_id=5212926868012935693>❌</emoji> <b>Username <code>{args}</code> not found!</b>")

View File

@@ -1,40 +0,0 @@
autoprofile
animevoices
aeconv
amethyste
activity
phsticker
amoreinfo
abstract
dtwr
bull
meowvoices
mydiary
createlinks
imgbb
instsave
telegraphup
inlineping
poststealer
searchpic
funquotes
facts
hacker
premiuminfo
cartoonimg
trigger
recognition
universaltime
nytimer
figlet
fileext
my_usernames
fragment_checker
alarm
leta
speech
jutsu
usernamestealer
lexiwiz
birthdaywish
wakatime

View File

@@ -1,91 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Funquotes.jpg
__version__ = (1, 0, 0)
from telethon.tl.types import Message
from .. import loader, utils
@loader.tds
class InlineFunMod(loader.Module):
"""Create Fun quotes"""
strings = {
"name": "FunQuotes",
"where_text": "<emoji document_id='6041914500272098262'>🚫</emoji> <b>Provide a text to create sticker with</b>",
"processing": (
"<emoji document_id='6318766236746384900'>🕔</emoji> <b>Processing...</b>"
),
}
strings_ru = {
"where_text": "<emoji document_id='6041914500272098262'>🚫</emoji> <b>Укажи текст для создания стикера</b>",
"processing": (
"<emoji document_id='6318766236746384900'>🕔</emoji> <b>Обработка...</b>"
),
}
async def glaxcmd(self, message: Message):
"""<text> - Create Google search quote"""
text = utils.get_args_raw(message)
if not text:
await message.edit(self.strings("where_text"))
return
await message.edit(self.strings("processing"))
try:
query = await self._client.inline_query("@googlaxbot", text)
await message.respond(file=query[0].document)
except Exception as e:
await utils.answer(message, str(e))
return
if message.out:
await message.delete()
async def twitcmd(self, message: Message):
"""<text> - Create Twitter message quote"""
text = utils.get_args_raw(message)
if not text:
await message.edit(self.strings("where_text"))
return
await message.edit(self.strings("processing"))
try:
query = await self._client.inline_query("@TwitterStatusBot", text)
await message.respond(file=query[0].document)
except Exception as e:
await utils.answer(message, str(e))
return
if message.out:
await message.delete()
async def frogcmd(self, message: Message):
"""<text> - Create Frog text quote"""
text = utils.get_args_raw(message)
if not text:
await message.edit(self.strings("where_text"))
return
await message.edit(self.strings("processing"))
try:
query = await self._client.inline_query("@honka_says_bot", text + ".")
await message.respond(file=query[0].document)
except Exception as e:
await utils.answer(message, str(e))
return
if message.out:
await message.delete()

View File

@@ -1,69 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Hacker.jpg
__version__ = (1, 0, 0)
from .. import loader, utils
import requests
from PIL import Image,ImageFont,ImageDraw
import io
from textwrap import wrap
@loader.tds
class HackerMod(loader.Module):
"""Create hacker message stickers"""
strings = {
'name': 'Hacker',
'what': 'Reply to text or write text <emoji document_id="5467928559664242360">❗️</emoji>',
'processing': 'Processing <emoji document_id="6334710044407368265">🚀</emoji>'
}
@loader.owner
async def hackercmd(self, message):
"""Reply to text or write text"""
ufr = requests.get("https://0x0.st/opzq.ttf")
f = ufr.content
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not args:
if not reply:
await message.edit(self.strings('what', message))
return
else:
txt = reply.raw_text
else:
txt = utils.get_args_raw(message)
await message.edit(self.strings("processing"))
pic = requests.get("https://0x0.st/opzN.jpg")
pic.raw.decode_content = True
img = Image.open(io.BytesIO(pic.content)).convert("RGB")
W, H = img.size
txt = txt.replace("\n", "𓃐")
text = "\n".join(wrap(txt, 19))
t = text + "\n"
t = t.replace("𓃐","\n")
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(io.BytesIO(f), 32, encoding='UTF-8')
w, h = draw.multiline_textsize(t, font=font)
imtext = Image.new("RGBA", (w+10, h+10), (255, 250, 250, 1))
draw = ImageDraw.Draw(imtext)
draw.multiline_text((10, 10),t,(255, 255, 255),font=font, align='left')
imtext.thumbnail((339, 181))
w, h = 339, 181
img.paste(imtext, (10,10), imtext)
out = io.BytesIO()
out.name = "amore.webp"
img.save(out)
out.seek(0)
await message.client.send_file(message.to_id, out, reply_to=reply)
await message.delete()

View File

@@ -1,129 +0,0 @@
# ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# ⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠟⠛⠛⠛⠛⠛
# ⣶⣦⣤⣤⣤⣤⣤⣤⣬⣭⣭⣍⣉⡙⠛⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⣋⣩⣭⣥⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶
# ⣆⠀⠀⠀⢡⠁⠀⡀⠀⢸⠟⠻⣯⠙⠛⠷⣶⣬⡙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⢉⣥⣶⡟⠻⣙⡉⠀⢰⡆⠀⠀⣡⠀⣧⠀⠀⠀⢨
# ⠻⣦⠀⠀⠈⣇⣀⣧⣴⣿⣶⣶⣿⣷⠀⢀⡇⠉⠻⢶⣌⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣡⡶⠟⠉⠀⢣⠀⣿⠷⠀⠀⠀⠀⣿⡷⢀⠇⠀⠀⢠⣿
# ⣦⡈⢧⡀⠀⠘⢮⡙⠛⠉⠀⠄⠙⢿⣀⠞⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠈⠳⣄⠉⠓⠒⠚⠋⢀⡠⠋⠀⢀⣴⣏⣿
# ⣿⣿⣿⣛⣦⣀⠀⠙⠓⠦⠤⣤⠔⠛⠁⠀⠀⠀⠀⠀⢀⣀⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣤⣤⣤⣤⣤⣀⣀⣀⣀⢙⢓⣒⡒⠚⠋⢠⣤⢶⣟⣽⣿⣿
# ⣿⣿⣿⣿⣿⣿⣷⣦⠀⠀⣴⣿⣷⣶⣶⣶⣾⡖⢰⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣶⣾⣿⣿⣿⣿⣿⣿
# ⣿⣿⣿⣿⣿⣿⣿⣿⠀⢀⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# ⣿⣿⣿⣿⣿⣿⣿⡏⠀⢸⣿⣿⣿⣿⣿⣿⣿⠁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# ⣿⣿⣿⣿⣿⣿⣿⣷⠀⢸⣿⣿⣿⣿⣿⣿⣿⠀⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
#          https://t.me/amorescam
#
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
# █▀█ █ █ █ █▀█ █▀▄ █
#             © Copyright 2022
#
#          https://t.me/hikariatama
#
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# meta developer: @amoremods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Imgbb.jpg
import imghdr
import io
import random
import re
import requests
from telethon.errors.rpcerrorlist import YouBlockedUserError
from telethon.tl.types import Message
from .. import loader, utils
@loader.tds
class ImgbbUploader(loader.Module):
"""Upload you photo/video/gif to https://ibb.co"""
strings = {
"name": "Imgbb",
"noargs": "🚫 <b>File not specified</b>",
"err": "🚫 <b>Error uploading</b>",
"ban": "🎒 @Imgbb_com_bot pls unblock this bot",
"not_an_image": "🚫 <b>This platform only supports images </b>",
"uploading": "📤 <b>Uploading...</b>",
}
async def get_media(self, message: Message):
reply = await message.get_reply_message()
m = None
if reply and reply.media:
m = reply
elif message.media:
m = message
elif not reply:
await utils.answer(message, self.strings("noargs"))
return False
if not m:
file = io.BytesIO(bytes(reply.raw_text, "utf-8"))
file.name = "file.txt"
else:
file = io.BytesIO(await self._client.download_media(m, bytes))
file.name = (
m.file.name
or (
"".join(
[
random.choice("abcdefghijklmnopqrstuvwxyz1234567890")
for _ in range(16)
]
)
)
+ m.file.ext
)
return file
async def get_image(self, message: Message):
file = await self.get_media(message)
if not file:
return False
if imghdr.what(file) not in ["gif", "png", "jpg", "jpeg", "tiff", "bmp"]:
await utils.answer(message, self.strings("not_an_image"))
return False
return file
async def imgbbcmd(self, message: Message):
"""imgbb uploader"""
message = await utils.answer(message, self.strings("uploading"))
file = await self.get_image(message)
if not file:
return
chat = "@Imgbb_com_bot"
async with self._client.conversation(chat) as conv:
try:
m = await conv.send_message(file=file)
response = await conv.get_response()
except YouBlockedUserError:
await utils.answer(message, self.strings("ban"))
return
await m.delete()
await response.delete()
try:
url = (
re.search(
r'<meta property="og:image" data-react-helmet="true"'
r' content="(.*?)"',
(await utils.run_sync(requests.get, response.raw_text)).text,
)
.group(1)
.split("?")[0]
)
except Exception:
url = response.raw_text
await utils.answer(message, f"😸 Your file uploaded: <code>{url}</code>")

View File

@@ -1,83 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Inlineping.jpg
import logging
import time
from telethon.tl.types import Message
from .. import loader, utils
from ..inline.types import InlineCall, InlineQuery
logger = logging.getLogger(__name__)
@loader.tds
class PingerMod(loader.Module):
"""Inline Pinger For Test"""
strings = {
"name": "InlinePing",
"results_ping": "✨ <b>Telegram ping:</b> <code>{}</code> <b>ms</b>"
}
strings_ru = {"results_ping": "✨ <b>Телеграм пинг:</b> <code>{}</code> <b>ms</b>"}
strings_uz = {"results_ping": "✨ <b>Telegram ping:</b> <code>{}</code> <b>ms</b>"}
strings_de = {"results_ping": "✨ <b>Telegramm Ping:</b> <code>{}</code> <b>ms</b>"}
strings_ru = {"results_ping": "✨ <b>Скорость отклика Telegram:</b> <code>{}</code> <b>ms</b>"}
@loader.command(ru_doc="Проверить скорость отклика юзербота")
async def iping(self, message: Message):
"""Test your userbot ping"""
start = time.perf_counter_ns()
ping = self.strings("results_ping").format(
round((time.perf_counter_ns() - start) / 10**3, 3),
)
await self.inline.form(
ping,
reply_markup=[[{"text": "⏱️ PePing", "callback": self.ladno}]],
message=message,
)
async def ladno(self, call: InlineCall):
start = time.perf_counter_ns()
ping = self.strings("results_ping").format(
round((time.perf_counter_ns() - start) / 10**3, 3),
)
await call.edit(
ping,
reply_markup=[[{"text": "⏱️ PePing", "callback": self.ladno,}],]
)
async def ping_inline_handler(self, query: InlineQuery):
"""Test your userbot ping"""
start = time.perf_counter_ns()
ping = self.strings("results_ping").format(
round((time.perf_counter_ns() - start) / 10**3, 3),
)
button = [{
"text": "⏱️ PePing",
"callback": self.ladno
}]
return {
"title": "Ping",
"description": "Tap here",
"thumb": "https://te.legra.ph/file/5d8c7f1960a3e126d916a.jpg",
"message": ping,
"reply_markup": button,
}

View File

@@ -1,57 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta pic: https://te.legra.ph/file/0251f5d602a8f32cd7368.png
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Instsave.jpg
__version__ = (1, 0, 0)
from .. import utils, loader
chat = "@SaveAsBot"
class InstagramMod(loader.Module):
"""Download video from instagram without watermark"""
strings = {
"name": "InstSave",
"processing": (
"<emoji document_id='6318766236746384900'>🕔</emoji> <b>Processing...</b>"
),
"mods": (
"<b>Successfuly downloaded</b> <emoji document_id='6320882302708614449'>🚀</emoji></b>"
),
}
@loader.group_member
@loader.command(ru_doc="<линк> - Скачать видео из инстаграм")
async def instascmd(self, message):
"""instagram video/reels/photo url"""
text = utils.get_args_raw(message)
message = await utils.answer(message, self.strings("processing"))
async with self._client.conversation(chat) as conv:
msgs = []
msgs += [await conv.send_message("/start")]
msgs += [await conv.get_response()]
msgs += [await conv.send_message(text)]
m = await conv.get_response()
await self._client.send_file(
message.peer_id,
m.media,
caption=self.strings("mods"),
reply_to=message.reply_to_msg_id,
)
for msg in msgs + [m]:
await msg.delete()
if message.out:
await message.delete()
await self.client.delete_dialog(chat)

View File

@@ -1,355 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# requires: bs4 cloudscraper loguru tqdm lxml
# meta developer: @hikamorumods
import os
import pathlib
import shutil
import string
import random
import logging
from tqdm import tqdm
from dataclasses import dataclass
from bs4 import BeautifulSoup
from cloudscraper import create_scraper, CloudScraper
from telethon.tl.types import DocumentAttributeVideo
from aiogram.types import CallbackQuery
from .. import loader, utils
logger = logging.getLogger(__name__)
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36",
}
@dataclass
class Season:
title: str
episodes_urls: list[str]
def download_video(url: str, path, scraper: CloudScraper):
with scraper.get(url, stream=True) as r:
total_length = int(r.headers.get("Content-Length"))
with tqdm.wrapattr(r.raw, "read", total=total_length, desc="") as raw:
with open(path, "wb") as file:
shutil.copyfileobj(raw, file)
def remove_symbols(filename: str) -> str:
if not filename:
return filename
forbidden = '\\/*:?|"<>'
for symbol in forbidden:
filename.replace(symbol, "")
return filename
class JutSuD:
def loader(self, anime_url, season_from, episode_from, season_to, episode_to):
scraper = create_scraper(
delay=1,
browser={
"custom": "ScraperBot/1.0",
},
)
response = scraper.get(anime_url)
soup = BeautifulSoup(response.text, "lxml")
anime_title = soup.find("h1", {"class": "anime_padding_for_title"}).text
anime_title = (
anime_title.replace("Смотреть", "")
.replace("все серии", "")
.replace("и сезоны", "")
.strip()
)
seasons = [
Season(
title=season_title.text,
episodes_urls=[],
)
for season_title in soup.find_all("h2", class_=["the-anime-season"])
]
if not seasons:
seasons.append(
Season(
title=anime_title,
episodes_urls=[],
)
)
episodes_soup = soup.find_all(
"a",
class_=[
"short-btn black video the_hildi",
"short-btn green video the_hildi",
],
)
current_season_index = -1
current_episode_class = None
for ep in episodes_soup:
if ep["class"] != current_episode_class:
current_episode_class = ep["class"]
current_season_index += 1
url = "https://jut.su" + ep["href"]
seasons[current_season_index].episodes_urls.append(url)
for i, season in enumerate(seasons):
season_number = i + 1
if season_number < season_from or season_number > season_to:
continue
for j, episode_url in enumerate(season.episodes_urls):
episode_number = j + 1
if (season_number == season_from and episode_number < episode_from) or (
(season_number == season_to or season_number == len(seasons))
and episode_number > episode_to
):
continue
response = scraper.get(episode_url)
soup = BeautifulSoup(response.content, "lxml")
try:
episode_title = (
soup.find("div", {"class": "video_plate_title"}).find("h2").text
)
except AttributeError:
episode_title = soup.find("span", {"itemprop": "name"}).text
episode_title = (
episode_title.replace("Смотреть", "")
.replace(anime_title, "")
.strip()
)
video_url = soup.find("source")["src"]
name_video = random.choices("".join(string.ascii_letters), k=10)
video_path = pathlib.Path(f"{''.join(name_video)}.mp4")
episode_slug = f"{season.title} - {episode_title} [#{episode_number}]"
try:
download_video(url=video_url, path=video_path, scraper=scraper)
return video_path, episode_slug
except Exception as e:
logger.exception(e)
return False, False
def get_info(self, url):
scraper = create_scraper()
response = scraper.get(url, headers=HEADERS)
soup = BeautifulSoup(response.text, "lxml")
anime_title = soup.find("h1", {"class": "anime_padding_for_title"}).text
anime_title = (
anime_title.replace("Смотреть", "")
.replace("все серии", "")
.replace("и сезоны", "")
.strip()
)
seasons = [
Season(
title=season_title,
episodes_urls=[],
)
for season_title in soup.find_all("h2", class_=["the-anime-season"])
]
if not seasons:
seasons.append(
Season(
title=anime_title,
episodes_urls=[],
)
)
episodes_soup = soup.find_all(
"a",
class_=[
"short-btn black video the_hildi",
"short-btn green video the_hildi",
],
)
return anime_title, seasons, episodes_soup
@loader.tds
class Jutsu(loader.Module):
"""Download and get info about anime from jut.su"""
strings = {
"name": "Jutsu",
"info": (
"📺 <b>Anime info</b>\n\n"
"<b>Title:</b> {}\n"
"<b>Seasons:</b> {}\n"
"<b>Total episodes:</b> {}\n"
"<b>Link:</b> {}"
),
"download_button": "📥 Download",
"done": "✅ Download completed!",
"choose_season": "📺 <b>Choose season</b>",
"choose_episode": "🪶 <b>Choose episode</b>",
"wrong_url": "❌ Wrong url!",
"no_args": "❌ No args!",
"download": "📥 Downloading episode {}... (speed depends on your internet connection)",
"close": "❌ Close",
}
strings_ru = {
"info": (
"📺 <b>Информация о аниме</b>\n\n"
"<b>Название:</b> {}\n"
"<b>Сезонов:</b> {}\n"
"<b>Всего серий:</b> {}\n"
"<b>Ссылка:</b> {}"
),
"download_button": "📥 Скачать",
"done": "✅ Скачивание завершено!",
"choose_season": "📺 <b>Выберите сезон</b>",
"choose_episode": "🪶 <b>Выберите серию</b>",
"wrong_url": "❌ Неверная ссылка!",
"no_args": "❌ Нет аргументов!",
"download": "📥 Скачиваем серию {}... (скорость скачивание зависит от вашего интернета)",
"close": "❌ Закрыть",
}
async def client_ready(self, client, db):
asset_ch, _ = await utils.asset_channel(
self._client,
"JutSu downloads",
"Downloaded anime from JutSu will be sent here. (Hikamoru back?)",
avatar="https://i.pinimg.com/564x/0a/da/0b/0ada0bb575146736679f5ea7a78971b8.jpg",
)
self.chid = int(f"-100{asset_ch.id}")
async def download_(self, call, url, seasons, episodes_soup):
seasons = [season for season in range(1, len(seasons) + 1)]
kb = []
for mod_row in utils.chunks(seasons, 3):
row = [
{
"text": f"{season}",
"callback": self.season_,
"args": (season, episodes_soup, url),
}
for season in mod_row
]
kb += [row]
await call.edit(self.strings["choose_season"], reply_markup=kb)
async def season_(self, call, season, eps, url):
episodes = [episode for episode in range(1, len(eps) + 1)]
kb = []
for mod_row in utils.chunks(episodes, 3):
row = [
{
"text": f"{episode}",
"callback": self.episod_,
"args": (episode, season, url),
}
for episode in mod_row
]
kb += [row]
await call.edit(self.strings["choose_episode"], reply_markup=kb)
async def episod_(self, call: CallbackQuery, episode, episode_number, url):
await call.edit(self.strings["download"].format(episode_number))
try:
name, title = JutSuD().loader(
url, episode_number, episode, episode_number, episode
)
except TypeError:
await call.edit("There is not such a episode (This bug with button will be fixed soon)")
await self.client.send_file(
self.chid,
open(name, "rb"),
caption=self.strings["done"] + f"\n\n{title}",
filetype="video",
attributes=(DocumentAttributeVideo(0, 0, 0),),
)
await call.edit(self.strings["done"])
os.remove(name)
async def close_(self, call):
await call.delete()
@loader.command()
async def jutsud(self, message):
"""Download anime from jutsu - [url]"""
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings["no_args"])
return
if not args.startswith("https://jut.su"):
await utils.answer(message, self.strings["wrong_url"])
return
anime_title, seasons, episodes_soup = JutSuD().get_info(args)
await utils.answer(
message,
self.strings["info"].format(
anime_title, len(seasons), len(episodes_soup), args
),
reply_markup=[
[
{
"text": self.strings["download_button"],
"callback": self.download_,
"kwargs": {
"url": args,
"seasons": seasons,
"episodes_soup": episodes_soup,
},
},
{
"text": self.strings["close"],
"callback": self.close_,
},
]
],
)

View File

@@ -1,155 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/leta.jpg
# I don't care about other people's opinions, if you don't like it, don't use it. i will update this module in the future, if i have time.
import time
import logging
from .. import loader, utils
from telethon.errors import ChatAdminRequiredError
logger = logging.getLogger(__name__)
class Leta(loader.Module):
"""Customizable nightmode [Leta] for your group"""
strings = {
"name": "Leta",
"info": (
"🐈‍⬛ Heeey! I'm <b>Leta</b>! I'm a module for nightmode in your group.\n"
"📫 You can get acquainted with my settings using the command <code>.help Leta</code>."
),
"wrong_format": "<emoji document_id=5258419835922030550>🕔</emoji> <b>Enter the time in the format HH:MM</b>",
"day": "<emoji document_id=6332496306593859160>🌅</emoji> <b>Good morning!</b>\n<b>Night mode is disabled.</b>",
"night": "<emoji document_id=6334806423473489632>🌚</emoji> <b>Good night!</b>\n<b>Night mode is enabled.</b>",
"rm": "<emoji document_id=5021905410089550576>✅</emoji> <b>Removed nightmode.</b>",
"rm_notfound": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>Nightmode is not set.</b>",
"set": "<emoji document_id=5980930633298350051>✅</emoji> Time set to\n<emoji document_id=6334361735444563461>🌃</emoji>🌙</emoji> Night: <code>{}</code>\n<emoji document_id=6332496306593859160>🌅</emoji> Day: <code>{}</code>",
}
strings_ru = {
"info": (
"🐈‍⬛ Привет! Я <b>Leta</b>! Я модуль для ночного режима в вашей группе.\n"
"📫 Ознакомиться с моими настройками можно с помощью команды <code>.help Leta</code>."
),
"wrong_format": "<emoji document_id=5258419835922030550>🕔</emoji> <b>Введите время в формате HH:MM</b>",
"day": "<emoji document_id=6332496306593859160>🌅</emoji> <b>Доброе утро!</b>\n<b>Ночной режим отключен.</b>",
"night": "<emoji document_id=6334806423473489632>🌚</emoji> <b>Доброй ночи!</b>\n<b>Ночной режим включен.</b>",
"rm": "<emoji document_id=5021905410089550576>✅</emoji> <b>Удален ночной режим.</b>",
"rm_notfound": "<emoji document_id=5456652110143693064>🤷‍♂️</emoji> <b>Ночной режим не установлен.</b>",
"set": "<emoji document_id=5980930633298350051>✅</emoji> Время установлено на\n<emoji document_id=6334361735444563461>🌃</emoji>🌙</emoji> Ночь: <code>{}</code>\n<emoji document_id=6332496306593859160>🌅</emoji> День: <code>{}</code>",
}
def resolve_id(self, marked_id):
if marked_id >= 0:
return "user"
marked_id = -marked_id
marked_id -= 1000000000000
return "chat"
async def client_ready(self, client, db):
if not self.get("info", False):
await self.inline.bot.send_animation(
self._tg_id,
"https://0x0.st/Hpqm.mp4",
caption=self.strings("info"),
parse_mode="HTML",
)
self.set("info", True)
async def lettimecmd(self, message):
"""Set time - morning [HH:MM] evening [HH:MM]"""
args = utils.get_args_raw(message).split(" ")
resolving = self.resolve_id(message.chat_id)
if resolving != "chat":
return await utils.answer(message, "<b>Use this command in group</b>")
if not args:
return await utils.answer(message, self.strings("wrong_format"))
try:
dh, dm = args[0].split(":")
eh, em = args[1].split(":")
if int(dh) > 23 or int(dh) < 0 or int(dm) > 59 or int(dm) < 0 or int(eh) > 23 or int(eh) < 0 or int(em) > 59 or int(em) < 0:
return await utils.answer(message, self.strings("wrong_format"))
except Exception:
return await utils.answer(message, self.strings('wrong_format'))
day = args[0]
night = args[1]
self.set(
"ngs",
{
message.chat_id: {
"time": night,
"day": day,
"chat": message.chat_id
},
}
)
await utils.answer(message, self.strings("set").format(night, day))
async def letrmchatcmd(self, message):
"""Remove nightmode - chat-id"""
try:
args = int(utils.get_args_raw(message))
d = self.get("ngs", {})
logging.info(d)
if not args:
return await utils.answer(message, self.strings("rm_notfound"))
if args not in d:
return await utils.answer(message, self.strings("rm_notfound"))
del d[args]
self.set("ngs", d)
await utils.answer(message, self.strings("rm"))
except ValueError:
await utils.answer(message, self.strings("rm_notfound"))
@loader.loop(interval=60, autostart=True)
async def checker_loop_night(self):
"""Check time"""
ngs = self.get("ngs", {})
for i in ngs:
if ngs[i]["time"] == time.strftime("%H:%M"):
try:
await self.client.send_message(ngs[i]["chat"], self.strings("night"))
await self.client.edit_permissions(ngs[i]['chat'], send_messages=False)
except ChatAdminRequiredError:
await self.inline.bot.send_message(
self._tg_id,
f"👎 You don't have enough rights to change permissions in <code>{i['chat']}</code>",
parse_mode="HTML",
)
async def letchatscmd(self, message):
"""Get all chats with nightmode"""
ngs = self.get("ngs", {})
if not ngs:
return await utils.answer(message, "<b>There are no chats with nightmode</b>")
msg = "<b>Chats with nightmode:</b>\n"
for i in ngs:
msg += f"\n<code>{i}</code>"
await utils.answer(message, msg + "\n")
@loader.loop(interval=60, autostart=True)
async def checker_loop_day(self):
"""Check time"""
ngs = self.get("ngs", {})
for i in ngs:
if ngs[i]["day"] == time.strftime("%H:%M"):
try:
await self.client.edit_permissions(ngs[i]['chat'], send_messages=True)
await self.client.send_message(ngs[i]["chat"], self.strings("day"))
except ChatAdminRequiredError:
await self.inline.bot.send_message(
self._tg_id,
f"👎 You don't have enough rights to change permissions in <code>{i['chat']}</code>",
parse_mode="HTML",
)

View File

@@ -1,249 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# requires: bs4 aiohttp
# meta developer: @hikamorumods
import aiohttp
from bs4 import BeautifulSoup as bs
from .. import utils, loader
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
class English:
async def definition_get(self, word: str):
async with aiohttp.ClientSession() as session:
headers = {"User-Agent": user_agent}
async with session.get(
f"https://dictionary.cambridge.org/us/dictionary/english/{word}/",
headers=headers,
) as resp:
if resp.status != 200:
return f"Failed to retrieve data. Status code: {resp.status}"
soup = bs(await resp.text(), "html.parser")
if not (div_element := soup.find("div", class_="def ddef_d db")):
return "Definition not found"
text = div_element.get_text()
example_spans = soup.find_all("span", class_="eg deg")
examples = []
for ex in example_spans:
example_text = ex.get_text()
examples.append(example_text)
return {"definition": text.replace(":", ""), "examples": examples}
async def get_word_pronunciation_uk(self, word: str):
async with aiohttp.ClientSession() as session:
async with session.get(
f"https://dictionary.cambridge.org/dictionary/english/{word}",
headers={"User-Agent": user_agent},
) as resp:
if resp.status == 200:
soup = bs(await resp.text(), "html.parser")
try:
audio_tag = soup.find_all("audio", class_="hdn")[0]
pron_tag = soup.find_all("span", class_="ipa dipa lpr-2 lpl-1")[
0
]
audio_src = audio_tag.find("source", type="audio/mpeg")["src"]
return {
"audio": f"https://dictionary.cambridge.org/us{audio_src}",
"pron": pron_tag.get_text(),
}
except IndexError:
return False
async def get_word_pronunciation_us(self, word: str):
async with aiohttp.ClientSession() as session:
async with session.get(
f"https://dictionary.cambridge.org/dictionary/english/{word}",
headers={"User-Agent": user_agent},
) as resp:
if resp.status == 200:
soup = bs(await resp.text(), "html.parser")
try:
audio_tag = soup.find_all("audio", class_="hdn")[1]
audio_src = audio_tag.find("source")["src"]
pron_tag = soup.find_all("span", class_="ipa dipa lpr-2 lpl-1")[
1
]
return {
"audio": f"https://dictionary.cambridge.org{audio_src}",
"pron": pron_tag.get_text(),
}
except IndexError:
return False
async def thesaurus_synonyms(self, word: str):
url = f"https://thesaurus.plus/thesaurus/{word}"
async with aiohttp.ClientSession() as session:
async with session.get(url, headers={"User-Agent": user_agent}) as resp:
if resp.status == 200:
soup = bs(await resp.text(), "html.parser")
synonyms_list = []
synonyms_ul = soup.find_all("ul", class_="list")[1]
list_terms = synonyms_ul.find_all("li", class_="list_term")
for term in list_terms:
synonym = term.find("div", class_="p-2").get_text(strip=True)
synonyms_list.append(synonym)
return synonyms_list
async def thesaurus_antonyms(self, word: str):
url = f"https://thesaurus.plus/thesaurus/{word}"
async with aiohttp.ClientSession() as session:
async with session.get(url, headers={"User-Agent": user_agent}) as resp:
if resp.status == 200:
soup = bs(await resp.text(), "html.parser")
antonyms_list = []
antonyms_ul = soup.find_all("ul", class_="list")[0]
list_terms = antonyms_ul.find_all("li", class_="list_term")
for term in list_terms:
antonym = term.find("div", class_="p-2").get_text(strip=True)
antonyms_list.append(antonym)
return antonyms_list
@loader.tds
class LexiwizMod(loader.Module, English):
"""Lexical wizard - your english companion"""
strings = {
"name": "Lexiwiz",
"no_word": "🤷‍♂️ <b>No word provided</b>",
"no_definition": "😖 <b>Unfortunately, I couldn't find the definition of this word.</b>",
"definition": "📝 <b>Word:</b> <code>{}</code>\n\n🔆 <b>Definition:</b> <code>{}</code>\n\n📦 <b>Examples:</b>\n{}",
"Pronunciation": "{} <b>{} Pronunciation:</b> <code>{}</code>\n🔊 <a href='{}'>Listen</a>",
"no_synonyms": "😓 <b>Sorry, I couldn't find synonyms for this word.</b>",
"synonyms": "📝 <b>Synonyms for the word:</b> <code>{}</code>\n\n🔆 <b>Synonyms:</b> <code>{}</code>",
"no_antonyms": "😓 <b>Sorry, I couldn't find antonyms for this word.</b>",
"antonyms": "📝 <b>Antonyms for the word:</b> <code>{}</code>\n\n🔆 <b>Antonyms:</b> <code>{}</code>",
}
@loader.command()
async def getdef(self, message):
"""Get definition of a word"""
word = utils.get_args_raw(message)
if not word:
await utils.answer(message, self.strings("no_word"))
return
definition = await self.definition_get(word)
if definition == "Definition not found":
await utils.answer(message, self.strings("no_definition"))
if isinstance(definition, dict):
text = ""
_definition = definition["definition"]
_examples = definition["examples"]
for index, example in enumerate(_examples):
text += f"<b>{index}</b>. <i>{example}</i>\n"
await utils.answer(
message, self.strings("definition").format(word, _definition, text)
)
else:
await utils.answer(
message, self.strings("definiotion").format(word, definition, " ")
)
@loader.command()
async def getpron(self, message):
"""Get pronunciation of a word"""
word = utils.get_args_raw(message)
reply = await message.get_reply_message()
if not word:
await utils.answer(message, self.strings("no_word"))
return
uk = await self.get_word_pronunciation_uk(word)
us = await self.get_word_pronunciation_us(word)
if uk:
await message.delete()
await message.client.send_file(
message.to_id,
uk["audio"],
caption=self.strings("Pronunciation").format(
"🇬🇧",
"UK",
uk["pron"],
uk["audio"],
),
voice_note=True,
reply_to=reply.id if reply else None,
)
if us:
await message.delete()
await message.client.send_file(
message.to_id,
us["audio"],
caption=self.strings("Pronunciation").format(
"🇺🇸",
"US",
us["pron"],
us["audio"],
),
voice_note=True,
reply_to=reply.id if reply else None,
)
@loader.command()
async def getsyn(self, message):
"""Get synonyms of a word"""
word = utils.get_args_raw(message)
if not word:
await utils.answer(message, self.strings("no_word"))
return
synonyms = await self.thesaurus_synonyms(word)
if not synonyms:
await utils.answer(message, self.strings("no_synonyms"))
return
await utils.answer(
message, self.strings("synonyms").format(word, ", ".join(synonyms))
)
@loader.command()
async def getant(self, message):
"""Get antonyms of a word"""
word = utils.get_args_raw(message)
if not word:
await utils.answer(message, self.strings("no_word"))
return
antonyms = await self.thesaurus_antonyms(word)
if not antonyms:
await utils.answer(message, self.strings("no_antonyms"))
return
await utils.answer(
message, self.strings("antonyms").format(word, ", ".join(antonyms))
)

View File

@@ -1,27 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/my_usernames.jpg?raw=true
from telethon import functions
from telethon.tl.types import Channel
from .. import loader, utils
@loader.tds
class MyUsernames(loader.Module):
"""The usernames I own"""
strings = {"name": "My Usernames"}
@loader.command()
async def myusern(self, message):
"""A list of usernames that were created by me"""
result = await self.client(functions.channels.GetAdminedPublicChannelsRequest())
output_str = ""
for channel_obj in result.chats:
if isinstance(channel_obj, Channel) and channel_obj.username is not None:
output_str += f"<code>{channel_obj.title}</code> | <b>@{channel_obj.username}</b>\n"
await utils.answer(message, f"<b>💼 List usernames reserved by me</b>\n\n{output_str[:-3]}")

View File

@@ -1,296 +0,0 @@
__version__ = (1, 0, 0)
# ▀█▀ █ █ █▀█ █▀▄▀█ ▄▀█ █▀
# █  █▀█ █▄█ █ ▀ █ █▀█ ▄█
# https://t.me/netuzb
#
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta pic: https://te.legra.ph/file/4c1b4581de961df145a70.png
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Mydiary.jpg
# meta developer: @hikamoru & @wilsonmods
# scope: hikka_min 1.4.0
from .. import loader, utils
from telethon.tl.types import Message
from ..inline.types import InlineQuery
from ..inline.types import InlineCall
emoji_close = "🔻 "
emoji_back = "↙️ "
emoji_open = "💌 "
emoji_about = "🚨 "
@loader.tds
class PagesMod(loader.Module):
"""Diary page"""
strings = {
"name": "Diary",
"_about_module": "What does this module do? - here you can write about your day and write notes",
"_cfg_inline_banner": "Set `True` in order to disable an inline media banner.",
"_cfdiary_open_text": "enter a diary name or information about it",
"_cfdiary_second_text": "here you can write on dairy «text2»",
"_cfdiary_three_text": "here you can write on dairy «text3»",
"_cfdiary_four_text": "here you can write on dairy «text4»",
"_cfdiary_first_text": "here you can write on dairy «text1»",
"_cfdiary_second_text": "here you can write on dairy «text2»",
"_cfdiary_three_text": "here you can write on dairy «text3»",
"_cfdiary_four_text": "here you can write on dairy «text4»",
"_cfg_button_1_": "here you can change button name «day1»",
"_cfg_button_2_": "here you can change button name «day2»",
"_cfg_button_3_": "here you can change button name «day3»",
"_cfg_button_4_": "here you can change button name «day4»",
"x": emoji_close + "Close",
"back": emoji_back + "Back",
}
strings_ru = {
"_about_module": "Что делает этот модуль? - Ты ты можешь писать свои заметки или что делал сегодня",
"_cfg_inline_banner": "Установите `True`, чтобы отключить встроенный медиа-баннер",
"_cfdiary_open_text": "Введите название дневника или информацию о нем",
"_cfdiary_first_text": "здесь ты можешь написать написать дневник на «text1»",
"_cfdiary_second_text": "здесь ты можешь написать написать дневник на «text2»",
"_cfdiary_three_text": "здесь ты можешь написать написать дневник на «text3»",
"_cfdiary_four_text": "здесь ты можешь написать написать дневник на «text4»",
"_cfg_button_1_": "здесь ты можешь поменять название кнопки «day1»",
"_cfg_button_2_": "здесь ты можешь поменять название кнопки «day2»",
"_cfg_button_3_": "здесь ты можешь поменять название кнопки «day3»",
"_cfg_button_4_": "здесь ты можешь поменять название кнопки «day4»",
"x": emoji_close + "Закрыть",
"back": emoji_back + "Назад",
}
strings_uz = {
"_about_module": "Modul vazifasi nima?\n- Siz modul orqali bugungi kun rejangiz yoki eslatmani saqlab qoʻyishingiz mumkin.",
"_cfg_inline_banner": "Media-bannerni yopish uchun `True` rejimini yoqing",
"_cfdiary_open_text": "Kundalik nomini yoki unga bogʻliq maʼlumotni yozing",
"_cfdiary_first_text": "Bu yerda siz «text_numb_1» sozlashingiz mumkin",
"_cfdiary_second_text": "Bu yerda siz «text_numb_2» sozlashingiz mumkin",
"_cfdiary_three_text": "Bu yerda siz «text_numb_3» sozlashingiz mumkin",
"_cfdiary_four_text": "Bu yerda siz «text_numb_4» sozlashingiz mumkin",
"_cfg_button_1_": "Bu yerda siz «button_numb_1» tugmasini sozlashingiz mumkin",
"_cfg_button_2_": "Bu yerda siz «button_numb_2» tugmasini sozlashingiz mumkin",
"_cfg_button_3_": "Bu yerda siz «button_numb_3» tugmasini sozlashingiz mumkin",
"_cfg_button_4_": "Bu yerda siz «button_numb_4» tugmasini sozlashingiz mumkin",
"x": emoji_close + "Yopish",
"back": emoji_back + "Orqaga",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"open_text",
"Here is a caption for my diary",
doc=lambda: self.strings('_cfdiary_open_text'),
),
loader.ConfigValue(
"off_inline_banner",
False,
lambda: self.strings("_cfg_inline_banner"),
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"button_numb_1",
"Day 1",
doc=lambda: self.strings('_cfg_button_1_'),
),
loader.ConfigValue(
"button_numb_2",
"Day 2",
doc=lambda: self.strings('_cfg_button_2_'),
),
loader.ConfigValue(
"button_numb_3",
"Day 3",
doc=lambda: self.strings('_cfg_button_3_'),
),
loader.ConfigValue(
"button_numb_4",
"Day 4",
doc=lambda: self.strings('_cfg_button_4_'),
),
loader.ConfigValue(
"text_numb_1",
"Today i played football with my friends then i fall,",
doc=lambda: self.strings('_cfdiary_first_text'),
),
loader.ConfigValue(
"text_numb_2",
"Today i walked with my friends and i saw my best friend who was drawer",
doc=lambda: self.strings('_cfdiary_second_text'),
),
loader.ConfigValue(
"text_numb_3",
"What are you did today?",
doc=lambda: self.strings('_cfdiary_three_text'),
),
loader.ConfigValue(
"text_numb_4",
"What are you did today?",
doc=lambda: self.strings('_cfdiary_four_text'),
),
loader.ConfigValue(
"banner_numb_1",
"https://imgur.com/NqNGNOb",
lambda: f"here you can write on dairy photo1",
),
loader.ConfigValue(
"banner_numb_2",
"https://ibb.co/ZJ9hnfL",
lambda: f"here you can write on dairy photo2",
),
loader.ConfigValue(
"banner_numb_3",
"https://imgur.com/kITkUry",
lambda: f"here you can write on dairy photo3",
),
loader.ConfigValue(
"banner_numb_4",
"https://imgur.com/TOzh9u1",
lambda: f"here you can write on dairy photo3",
),
)
async def cfdiarycmd(self, message):
"""> Set up buttons for the module"""
name = self.strings("name")
await self.allmodules.commands["config"](
await utils.answer(message,
f"{self.get_prefix()}config {name}")
)
async def mydiarycmd(self, message: Message):
"""> Main the diary section"""
await self.inline.form(
text = self.config["open_text"],
message=message,
reply_markup=[
[{
"text": f"{emoji_open}Open diary",
"callback": self.page_one
}],
[{
"text": f"{emoji_about}About modules",
"callback": self._about_us
}]],
**{"photo": "https://te.legra.ph/file/64bb29a68030e118dfa21.jpg"},
)
async def mydiary_inline_handler(self, query: InlineQuery):
"""> Main the diary section"""
btn_a = [{
"text": f"{emoji_open}Open diary",
"callback": self.page_one
}],
btn_b = [{
"text": f"{emoji_about}About modules",
"callback": self._about_us
}],
msg_type = "message" if self.config["off_inline_banner"] else "caption"
return {
"title": "open diary",
"description": "open my own diary page",
msg_type: self.config['open_text'],
"photo": "https://te.legra.ph/file/64bb29a68030e118dfa21.jpg",
"thumb": (
"https://te.legra.ph/file/4c1b4581de961df145a70.png"
),
"reply_markup": btn_a + btn_b,
}
async def _back(self, call: InlineCall):
await call.edit(
text = self.config["open_text"],
reply_markup=[
[{
"text": f"{emoji_open}Open diary",
"callback": self.page_one
}],
[{
"text": f"{emoji_about}About modules",
"callback": self._about_us
}]
],
**{"photo": "https://te.legra.ph/file/64bb29a68030e118dfa21.jpg"},
)
async def _about_us(self, call: InlineCall):
await call.edit(
text = self.strings('_about_module'),
reply_markup=[
[
{
"text": self.strings("back"),
"callback": self._back
},
{
"text": self.strings("x"),
"action": "close"
},
]
],
)
async def page_one(self, call: InlineCall):
await call.edit(
text = self.config["text_numb_1"],
reply_markup=[
[{"text": self.config["button_numb_1"], "callback": self.page_one}, {"text": self.config["button_numb_2"], "callback": self.page_two}],
[{"text": self.config["button_numb_3"], "callback": self.page_three}, {"text": self.config["button_numb_4"], "callback": self.page_four}],
[{
"text": self.strings("x"),
"action": "close"
}]],
**{"photo": self.config["banner_numb_1"]},
)
async def page_two(self, call: InlineCall):
await call.edit(
text = self.config["text_numb_2"],
reply_markup=[
[{"text": self.config["button_numb_1"], "callback": self.page_one}, {"text": self.config["button_numb_2"], "callback": self.page_two}],
[{"text": self.config["button_numb_3"], "callback": self.page_three}, {"text": self.config["button_numb_4"], "callback": self.page_four}],
[{
"text": self.strings("x"),
"action": "close"
}]],
**{"photo": self.config["banner_numb_2"]},
)
async def page_three(self, call: InlineCall):
await call.edit(
text = self.config["text_numb_3"],
reply_markup=[
[{"text": self.config["button_numb_1"], "callback": self.page_one}, {"text": self.config["button_numb_2"], "callback": self.page_two}],
[{"text": self.config["button_numb_3"], "callback": self.page_three}, {"text": self.config["button_numb_4"], "callback": self.page_four}],
[{
"text": self.strings("x"),
"action": "close"
}]],
**{"photo": self.config["banner_numb_3"]},
)
async def page_four(self, call: InlineCall):
await call.edit(
text = self.config["text_numb_4"],
reply_markup=[
[{"text": self.config["button_numb_1"], "callback": self.page_one}, {"text": self.config["button_numb_2"], "callback": self.page_two}],
[{"text": self.config["button_numb_3"], "callback": self.page_three}, {"text": self.config["button_numb_4"], "callback": self.page_four}],
[{
"text": self.strings("x"),
"action": "close"
}]],
**{"photo": self.config["banner_numb_4"]},
)

View File

@@ -1,31 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Nytimer.jpg
from .. import loader, utils
import datetime
from time import strftime
@loader.tds
class NYMod(loader.Module):
"""Check how much is left until the new year"""
strings = {'name': 'NewYearTimer'}
async def nycmd(self, message):
"""Check date"""
now = datetime.datetime.today()
ng = datetime.datetime(int(strftime('%Y')) + 1, 1, 1)
d = ng - now
mm, ss = divmod(d.seconds, 60)
hh, mm = divmod(mm, 60)
soon = '<b><emoji document_id=6334530007968253960>☃️</emoji> Until the <u>New Year</u>: {} d. {} h. {} m. {} s.</b>\n<b><emoji document_id=5393226077520798225>🥰</emoji> Wait for the new year together <u>Family</u></b>'.format(
d.days, hh, mm, ss)
await utils.answer(message, soon)

View File

@@ -1,62 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/phstiker.jpg
# requires: phlogo
import os
from .. import loader, utils
from phlogo import generate
@loader.tds
class PhLogo(loader.Module):
"""Make Pornhub logo sticker"""
strings = {
"name": "Phlogo",
"only_two": "Something's wrong. Try giving two words only like `Hello world`",
"none_args": "Give some text bruh, e.g.: `Hello world`"
}
strings_ru = {
"only_two": "Что-то не так. Попробуйте указать только два аргумента, например «Hello world».",
"none_args": "Дай какой-нибудь текст, например: `Hello world`."
}
strings_uz = {
"only_two": "Xatolik bor. `Hello world` kabi faqat ikkita matn keltirishga harakat qiling.",
"none_args": "Bir oz matn bering, masalan: `Salom dunyo`."
}
@loader.command()
async def phl(self, message):
"Makes PHub style logo sticker."
args = utils.get_args_raw(message).split(' ')
reply = await message.get_reply_message()
if args == " ":
await utils.answer(message, self.strings('none_args'))
return
try:
p = args[0]
h = args[1]
except:
await utils.answer(message, self.strings('only_two'))
return
result = generate(f"{p}",f"{h}")
result.save("ph.webp")
path = os.getcwd()
stc = f"{path}/ph.webp"
await message.delete()
await self._client.send_file(
message.peer_id,
stc,
caption=f"{p} {h}",
link_preview=False,
reply_to=reply.id if reply else None,
)
os.remove(stc)

View File

@@ -1,88 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/poststeal.jpg
from .. import loader, utils
@loader.tds
class PostStealer(loader.Module):
"Steal post from another channel to your channel"
strings = {
'name': 'PostStealler',
'enable': '<b>Steal mode enabled.</b>',
'disable': '<b>Steal mode disabled.</b>',
'channel': 'channel id where ub will steal messages',
'my_channel': 'channel id where ub will send messages'
}
strings_ru = {
'enable': '<b>StealMod включен.</b>',
'disable': '<b>StealMod отключен.</b>',
'channel': 'айди канала откуда юб будет пересылать сообщения',
'my_channel': 'айди канала куда юб будет пересылать сообщения'
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"my_channel",
None,
lambda: self.strings("my_channel"),
),
loader.ConfigValue(
"channel",
None,
lambda: self.strings("channel"),
),
)
async def client_ready(self, client, db):
self.client = client
self.db = db
@loader.command()
async def smode(self, message):
"""- off/on steal mode"""
status = self.db.get(
"steal_status",
"status",
)
if status == "":
self.db.set("steal_status", "status", True)
if status == False:
self.db.set("steal_status", "status", True)
await utils.answer(message, self.strings("enable"))
else:
self.db.set("steal_status", "status", False)
await utils.answer(message, self.strings("disable"))
async def watcher(self, message):
"""Лень писать описание"""
status = self.db.get("steal_status", "status")
if status == False:
return False
if status == True:
steal = self.config['channel']
chatid = int(message.chat_id)
text = message.text
if chatid == steal:
if message.photo:
await self._client.send_file(int(self.config['my_channel']), message.photo, caption=message.text if text else None, link_preview=False)
elif message.video:
await self._client.send_file(int(self.config['my_channel']), message.video, caption=message.text if text else None, link_preview=False)
elif message.document:
await self._client.send_file(int(self.config['my_channel']), message.document, caption=message.text if text else None, link_preview=False)
elif message.text:
await message.client.send_message(int(self.config['my_channel']), message.text)
else:
return False

View File

@@ -1,80 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/recognition.jpg
from .. import utils, loader
import imghdr
import io
import random
from telethon.tl.types import Message
@loader.tds
class RecognitionMod(loader.Module):
"""Recognition from photo"""
strings = {
'name': 'Recognition',
'args': "No args!"
}
async def get_media(self, message: Message):
reply = await message.get_reply_message()
m = None
if reply and reply.media:
m = reply
elif message.media:
m = message
elif not reply:
await utils.answer(message, self.strings('args'))
return False
if not m:
file = io.BytesIO(bytes(reply.raw_text, "utf-8"))
file.name = "file.txt"
else:
file = io.BytesIO(await self._client.download_media(m, bytes))
file.name = (
m.file.name
or (
"".join(
[
random.choice(
"abcdefghijklmnopqrstuvwxyz1234567890")
for _ in range(16)
]
)
)
+ m.file.ext
)
return file
async def get_image(self, message: Message):
file = await self.get_media(message)
if not file:
return False
if imghdr.what(file) not in ["gif", "png", "jpg", "jpeg", "tiff", "bmp"]:
return False
return file
@loader.command()
async def reco(self, message: Message):
"""recognize from photo <reply to photo>"""
file = await self.get_image(message)
if not file:
return False
async with self._client.conversation("@Rekognition_Bot") as conv:
await conv.send_message(file=file) # upload step
await conv.get_response() # ignore message
cp = await conv.get_response() # get message
await utils.answer(message, cp)
await self.client.delete_dialog("@Rekognition_Bot")

View File

@@ -1,34 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Searchpic.jpg
# meta developer: @amoremods
from .. import loader, utils
from telethon.tl.types import Message
@loader.tds
class SearchPic(loader.Module):
strings = {"name": "SearchPic"}
@loader.unrestricted
async def spiccmd(self, message: Message):
"""Search picture"""
text = utils.get_args_raw(message)
await self.inline.form(
message=message,
text = f"🎑 Your pic found\n✍ Input argument: {text}",
reply_markup=[
[{"text": "Pic here", "url": f"https://yandex.uz/images/touch/search/?text={text}",}],
[{"text": "Close", "action": "close"}],
],
**(
{"photo": f"https://yandex.uz/images/touch/search/?text={text}"}
),
)

View File

@@ -1,94 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# scope: ffmpeg
# requires: pydub speechrecognition python-ffmpeg
# meta developer: @hikamorumods
# meta banner: https://github.com/AmoreForever/assets/blob/master/Speech.jpg?raw=true
import os
import logging
import speech_recognition as sr
from pydub import AudioSegment
from .. import loader, utils
logger = logging.getLogger(__name__)
recognizer = sr.Recognizer()
@loader.tds
class SpeechMod(loader.Module):
"""Simple speech recognition module."""
strings = {
"name": "Speech",
"only_voice": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Reply to a voice message!</b>",
"downloading": "<emoji document_id=5213251580425414358>🔽</emoji> <b>Downloading...</b>",
"recognizing": "<emoji document_id=5472199711366584503>👂</emoji> <b>Recognizing...</b>",
"not_recognized": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Not recognized</b>",
"request_error": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Request error occured.\n{}</b>",
"recognized": "<emoji document_id=5267468588985363056>🚛</emoji> <b>Recognized:</b> <code>{}</code>",
}
strings_ru = {
"only_voice": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Ответь на голосовое сообщение!</b>",
"downloading": "<emoji document_id=5213251580425414358>🔽</emoji> <b>Загрузка...</b>",
"recognizing": "<emoji document_id=5472199711366584503>👂</emoji> <b>Распознавание...</b>",
"not_recognized": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Не распознано</b>",
"request_error": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Произошла ошибка запроса.\n{}</b>",
"recognized": "<emoji document_id=5267468588985363056>🚛</emoji> <b>Распознано:</b> <code>{}</code>",
}
strings_uz = {
"only_voice": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Ovozli xabarga javob bering!</b>",
"downloading": "<emoji document_id=5213251580425414358>🔽</emoji> <b>Yuklanmoqda...</b>",
"recognizing": "<emoji document_id=5472199711366584503>👂</emoji> <b>Eshitilmoqda...</b>",
"not_recognized": "<emoji document_id=5877477244938489129>🚫</emoji> <b>Tanilmadi</b>",
"request_error": "<emoji document_id=5877477244938489129>🚫</emoji> <b>So'rovda xatolik yuz berdi.\n{}</b>",
"recognized": "<emoji document_id=5267468588985363056>🚛</emoji> <b>Text:</b> <code>{}</code>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"language",
"ru-RU",
lambda: "Language for recognition.",
validator=loader.validators.RegExp(r"^[a-z]{2}-[A-Z]{2}$"),
),
)
@loader.command()
async def spech(self, message):
"""Recognize voice message. Usage: .spech <reply to voice message>"""
reply = await message.get_reply_message()
if not reply or not reply.voice:
await utils.answer(message, self.strings("only_voice"))
return
await utils.answer(message, self.strings("downloading"))
voice = await message.client.download_media(reply.voice)
wav_voice = voice.replace(voice.split(".")[-1], "wav")
ogg_audio = AudioSegment.from_ogg(voice)
ogg_audio.export(wav_voice, format="wav")
audio = sr.AudioFile(wav_voice)
with audio as source:
try:
audio = recognizer.record(source)
await utils.answer(message, self.strings("recognizing"))
recognized = recognizer.recognize_google(audio, language=self.config["language"])
except sr.UnknownValueError:
await utils.answer(message, self.strings("not_recognized"))
return
except sr.RequestError as e:
await utils.answer(message, self.strings("request_error").format(e))
return
await utils.answer(message, self.strings("recognized").format(recognized))
os.remove(voice)
os.remove(wav_voice)

View File

@@ -1,70 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# meta pic: https://te.legra.ph/file/5ef64ee0466032d8a4687.png
# meta banner: hhttps://raw.githubusercontent.com/AmoreForever/assets/master/Telegraphup.jpg
from .. import loader, utils
import requests
from telethon.tl.types import DocumentAttributeFilename
@loader.tds
class Telegraphup(loader.Module):
"""Upload video and photo to telegraph"""
strings = {
"name": "Telegraph",
"pls_reply": "⚠️ Reply to photo or video/gif",
}
@loader.sudo
async def thupcmd(self, message):
"""<reply photo or video>"""
if message.is_reply:
reply_message = await message.get_reply_message()
data = await check_media(reply_message)
if isinstance(data, bool):
await message.edit(self.strings("pls_reply"))
return
else:
await message.edit(self.strings("pls_reply"))
return
file = await message.client.download_media(data, bytes)
path = requests.post(
"https://te.legra.ph/upload", files={"file": ("file", file, None)}
).json()
try:
amore = "https://te.legra.ph" + path[0]["src"]
except KeyError:
amore = path["error"]
await utils.answer(message, f"😸 Your file uploaded: <code>{amore}</code>")
async def check_media(reply_message):
if reply_message and reply_message.media:
if reply_message.photo:
data = reply_message.photo
elif reply_message.document:
if (
DocumentAttributeFilename(file_name="AnimatedSticker.tgs")
in reply_message.media.document.attributes
):
return False
if reply_message.audio or reply_message.voice:
return False
data = reply_message.media.document
else:
return False
else:
return False
if not data or data is None:
return False
else:
return data

View File

@@ -1,98 +0,0 @@
# ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# ⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠟⠛⠛⠛⠛⠛
# ⣶⣦⣤⣤⣤⣤⣤⣤⣬⣭⣭⣍⣉⡙⠛⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⣋⣩⣭⣥⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶
# ⣆⠀⠀⠀⢡⠁⠀⡀⠀⢸⠟⠻⣯⠙⠛⠷⣶⣬⡙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⢉⣥⣶⡟⠻⣙⡉⠀⢰⡆⠀⠀⣡⠀⣧⠀⠀⠀⢨
# ⠻⣦⠀⠀⠈⣇⣀⣧⣴⣿⣶⣶⣿⣷⠀⢀⡇⠉⠻⢶⣌⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣡⡶⠟⠉⠀⢣⠀⣿⠷⠀⠀⠀⠀⣿⡷⢀⠇⠀⠀⢠⣿
# ⣦⡈⢧⡀⠀⠘⢮⡙⠛⠉⠀⠄⠙⢿⣀⠞⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠈⠳⣄⠉⠓⠒⠚⠋⢀⡠⠋⠀⢀⣴⣏⣿
# ⣿⣿⣿⣛⣦⣀⠀⠙⠓⠦⠤⣤⠔⠛⠁⠀⠀⠀⠀⠀⢀⣀⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣤⣤⣤⣤⣤⣀⣀⣀⣀⢙⢓⣒⡒⠚⠋⢠⣤⢶⣟⣽⣿⣿
# ⣿⣿⣿⣿⣿⣿⣷⣦⠀⠀⣴⣿⣷⣶⣶⣶⣾⡖⢰⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣶⣾⣿⣿⣿⣿⣿⣿
# ⣿⣿⣿⣿⣿⣿⣿⣿⠀⢀⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# ⣿⣿⣿⣿⣿⣿⣿⡏⠀⢸⣿⣿⣿⣿⣿⣿⣿⠁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# ⣿⣿⣿⣿⣿⣿⣿⣷⠀⢸⣿⣿⣿⣿⣿⣿⣿⠀⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
#          https://t.me/amorescam
# meta developer: @amoremods
# meta banner: https://raw.githubusercontent.com/AmoreForever/assets/master/Universaltime.jpg
# @Den4ikSuperOstryyPer4ik: I Love AmoreMods :)
from .. import loader, utils
import datetime
from ..inline.types import InlineCall
import logging
logger = logging.getLogger(__name__)
def check_time():
offsets = [3, 5, 4, 2, 1, -7, 6, 9, 5.30, 9, 8, -8, -4]
hrs = []
for x in offsets:
offset = datetime.timedelta(hours=x)
not_tz = datetime.timezone(offset)
time = datetime.datetime.now(not_tz)
format_ = time.strftime("%d.%m.%y | %H:%M")
hrs.append(format_)
return f"<emoji document_id=4920662486778119009>🌐</emoji> <b>Universal time</b>\n\n<emoji document_id=6323139226418284334>🇷🇺</emoji> Russia ➪ {hrs[0]}\n<emoji document_id=6323430017179059570>🇺🇿</emoji> Uzbekistan ➪ {hrs[1]}\n<emoji document_id=6323289850921354919>🇺🇦</emoji> Ukraine ➪ {hrs[3]}\n<emoji document_id=6323575251498174463>🇦🇿</emoji> Azerbaijan ➪ {hrs[2]}\n<emoji document_id=6320817337033295141>🇩🇪</emoji> German ➪ {hrs[3]}\n<emoji document_id=6323589145717376403>🇬🇧</emoji> UK ➪ {hrs[4]}\n<emoji document_id=6323602387101550101>🇵🇱</emoji> Poland ➪ {hrs[3]}\n<emoji document_id=6323374027985389586>🇺🇸</emoji> USA ➪ {hrs[5]}\n<emoji document_id=6323615997852910673>🇰🇬</emoji> Kyrgyzstan ➪ {hrs[6]}\n<emoji document_id=6323135275048371614>🇰🇿</emoji> Kazakhstan ➪ {hrs[6]}\n<emoji document_id=6323555846835930376>🇮🇶</emoji> Iraq ➪ {hrs[0]}\n<emoji document_id=6323356796576597627>🇯🇵</emoji> Japan ➪ {hrs[7]}\n<emoji document_id=6323152716910561397>🇰🇷</emoji> South KR ➪ {hrs[7]}\n<emoji document_id=6323181871148566277>🇮🇳</emoji> India ➪ {hrs[8]}\n<emoji document_id=6323570711717742330>🇫🇷</emoji> France ➪ {hrs[3]}\n<emoji document_id=6323453751168337485>🇨🇳</emoji> China ➪ {hrs[9]}\n<emoji document_id=6321003171678259486>🇹🇷</emoji> Turkey ➪ {hrs[0]}\n<emoji document_id=6323602322677040561>🇨🇱</emoji> Mongolia ➪ {hrs[10]}\n<emoji document_id=6323325327351219831>🇨🇦</emoji> Canada ➪ {hrs[11]}\n<emoji document_id=6323471399188957082>🇮🇹</emoji> Italia ➪ {hrs[2]}\n<emoji document_id=6323516260122363644>🇪🇬</emoji> Egypt ➪ {hrs[3]}\n<emoji document_id=6323236391463421376>🇦🇲</emoji> Armenia ➪ {hrs[12]}\n\n<emoji document_id=5188216117272780281>🍙</emoji> #whyamore"
@loader.tds
class UniversalTimeMod(loader.Module):
"""See the time of other countries"""
strings = {"name": "UnivTime"}
@loader.command(ru_docs="Смотреть мировое время")
async def atimecmd(self, message):
"""See time"""
kk = check_time()
await utils.answer(message, kk)
@loader.command(ru_docs="Смотреть мировое время в инлайн режиме")
async def atimeicmd(self, message):
"""See time on inline mode"""
kk = check_time()
await self.inline.form(
text=kk,
message=message,
gif="https://te.legra.ph/file/2ab9b131ceceb9b020583.mp4",
reply_markup=[
[
{
"text": "🍃 Refresh",
"callback": self.refresh,
}
],
[
{
"text": "🔻 Close",
"action": "close",
}
]
]
)
async def refresh(self, call: InlineCall): # thanks @Den4ikSuperOstryyPer4ik
kk = check_time()
await call.edit(
text=kk,
reply_markup=[
[
{
"text": "🍃 Refresh",
"callback": self.refresh,
}
],
[
{
"text": "🔻 Close",
"action": "close",
}
]
]
)
await call.answer("Refreshed ✨")

View File

@@ -1,214 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# meta developer: @hikamorumods
# pusechka @saint_players thanks for the idea
import logging
import requests
from telethon.tl.types import Message
from telethon.tl.functions.channels import CreateChannelRequest, UpdateUsernameRequest
from telethon.tl.types import InputPeerChannel
from .. import loader, utils
from ..inline.types import InlineCall
logger = logging.getLogger(__name__)
@loader.tds
class UserStealer(loader.Module):
"""
Username tracking module.
"""
strings = {
"name": "UsernameChecker",
"create_channel": "📂 Create channel",
"user_free": "⚡️ Username @{} is free, quickly take it.",
"user_busy": "😥 Username @{} is busy. Do you want to enable tracking?",
"yes": "🛟 Yes",
"nope": "🧰 No",
"free_now": "🌟 Username {} is free, do you want to create a channel?",
"not_args": "🚫 Enter the user for check",
"status": "🆔 Username @{} is being tracked\n🟢 Status: free",
"status_busy": "🆔 Username @{} is being tracked\n🟡 Status: busy",
"none": "❌ No username is being tracked.",
"done": "🦉 Done, wait till username will be free",
"created": "🌟 Channel created, go to username @{}",
"tg_bug": "🐞 Telegram has bugs, if the channel is not created, try to create it manually",
}
strings_ru = {
"create_channel": "📂 Создать канал",
"user_free": "⚡️ Юзернейм @{} свободен, быстро забирай его.",
"user_busy": "😥 Юзернейм @{} занят. Хотите включить отслеживание?",
"yes": "🛟 Да",
"nope": "🧰 Нет",
"free_now": "🌟 Юзернейм {} свободен, хотите создать канал?",
"not_args": "🚫 Укажи юзернейм для проверки",
"status": "🆔 Юзернейм @{} отслеживается\n🟢 Статус: свободен",
"status_busy": "🆔 Юзернейм @{} отслеживается\n🟡 Статус: занят",
"none": "❌ Ни один юзернейм не отслеживается.",
"done": "🦉 Готово, жди пока юзернейм освободится",
"created": "🌟 Канал создан, переходи по юзернейму @{}",
"tg_bug": "🐞 У телеграма баги, если канал не создан, попробуй создать его вручную",
}
@loader.loop(interval=30, autostart=True)
async def _steal(self):
user = self.db.get("usernames_to_check", "username")
if user == "none":
return
else:
r = requests.get(url=f"https://t.me/{user}")
if (
r.text.find(
'If you have <strong>Telegram</strong>, you can contact <a class="tgme_username_link"'
)
>= 0
):
self._markup = lambda: self.inline.generate_markup(
[
{"text": self.strings("create_channel"), "callback": self.create_inline, "args": (user,),},
{"text": "Отмена", "callback": self.delete, },
]
)
await self.inline.bot.send_message(
self._client.tg_id,
self.strings("user_free").format(user),
disable_web_page_preview=True,
reply_markup=self._markup()
)
self.db.set("usernames_to_check", "username", "none")
else:
pass
@loader.command()
async def ucheckcmd(self, message: Message):
"""> Enter the user for check (without @)"""
args = utils.get_args_raw(message)
r = requests.get(url=f"https://t.me/{args}")
if args.startswith('@'):
args = args[1:]
if not args:
return await utils.answer(message, self.strings("not_args"))
if (
r.text.find(
'If you have <strong>Telegram</strong>, you can contact <a class="tgme_username_link"'
)
>= 0
):
await self.inline.form(
text=self.strings("free_now").format(args),
reply_markup=[
[
{
"text": self.strings("yes"),
"callback": self.create,
"args": (args,),
},
{"text": self.strings("nope"), "action": "close"},
],
], **{'video': 'https://te.legra.ph/file/a14a9ff4071d079272171.mp4'},
message=message,
)
else:
await self.inline.form(
text=self.strings("user_busy").format(args),
reply_markup=[
[
{
"text": self.strings("yes"),
"callback": self.owo,
"args": (args,),
},
{"text": self.strings("nope"), "action": "close"},
],
], **{"video": "https://te.legra.ph/file/90fbbd0deabfc5e740eb3.mp4"},
message=message,
)
@loader.command()
async def myus(self, message: Message):
"""> Check status of the user being tracked"""
proc = self.db.get("usernames_to_check", "username")
if proc == 'none':
await utils.answer(message, self.strings("none"))
else:
r = requests.get(url=f"https://t.me/{proc}")
if (
r.text.find(
'If you have <strong>Telegram</strong>, you can contact <a class="tgme_username_link"'
)
>= 0
):
await utils.answer(message, self.strings("status").format(proc))
else:
await utils.answer(message, self.strings("status_busy").format(proc))
async def owo(self, call: InlineCall, text: str):
self.db.set("usernames_to_check", "username", text)
await call.edit(self.strings("done"))
async def create(self, call: InlineCall, text: str):
channel = await self.client(
CreateChannelRequest(f"{text}", f"{text}", megagroup=False)
)
channel_id = channel.__dict__["chats"][0].__dict__["id"]
channel_hash = channel.__dict__["chats"][0].__dict__["access_hash"]
try:
await self.client(
UpdateUsernameRequest(
InputPeerChannel(channel_id=channel_id,
access_hash=channel_hash),
text,
)
)
await call.edit(self.strings("created").format(text))
except:
await call.edit(
self.strings("tg_bug"),
)
await self.client.delete_dialog(channel_id)
async def create_inline(self, call: InlineCall, text: str):
channel = await self.client(
CreateChannelRequest(f"{text}", f"{text}", megagroup=False)
)
channel_id = channel.__dict__["chats"][0].__dict__["id"]
channel_hash = channel.__dict__["chats"][0].__dict__["access_hash"]
try:
await self.client(
UpdateUsernameRequest(
InputPeerChannel(channel_id=channel_id,
access_hash=channel_hash),
text,
)
)
await call.answer(self.strings("created").format(text))
await call.delete()
except:
await call.answer(
self.strings("tg_bug"),
show_alert=True
)
await self.client.delete_dialog(channel_id)
await call.delete()
async def delete(self, call: InlineCall):
await call.delete()

View File

@@ -1,134 +0,0 @@
# █ █ █ █▄▀ ▄▀█ █▀▄▀█ █▀█ █▀█ █ █
# █▀█ █ █ █ █▀█ █ ▀ █ █▄█ █▀▄ █▄█
# 🔒 Licensed under the GNU GPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# 👤 https://t.me/hikamoru
# required: aiohttp
# meta banner: https://github.com/AmoreForever/shizuassets/blob/master/wakatime.jpg?raw=true
# meta developer: @hikamorumods
import asyncio
import aiohttp
from .. import utils, loader
@loader.tds
class Wakatime(loader.Module):
"""Show your Wakatime stats"""
strings = {
"name": "Wakatime",
"set_waka": "Set your Wakatime token",
"no_token": "🚫 <b>You don't have a token</b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
"WAKATIME_TOKEN",
None,
lambda: self.strings["set_waka"],
)
@loader.command()
async def waka(self, message):
"""See your stat"""
token = self.config["WAKATIME_TOKEN"]
if token is None:
return await utils.answer(message, self.strings("no_token"))
async with aiohttp.ClientSession() as session:
endpoints = [
"status_bar/today",
"stats/all_time",
"stats/all_time",
"all_time_since_today",
]
tasks = [
session.get(
f"https://wakatime.com/api/v1/users/current/{endpoint}?api_key={token}"
)
for endpoint in endpoints
]
responses = await asyncio.gather(*tasks)
results = await asyncio.gather(*[response.json() for response in responses])
result_t, result, result_s, result_w = results
all_time = result_w["data"]["text"]
username = result["data"]["username"]
languages = result["data"]["languages"]
today = result_t["data"]["categories"]
os = result["data"]["operating_systems"]
editor = result["data"]["editors"]
OS = ", ".join(f"<code>{stat['name']}</code>" for stat in os if stat["text"] != "0 secs")
EDITOR = ", ".join(f"<code>{stat['name']}</code>" for stat in editor if stat["text"] != "0 secs")
LANG = "\n".join(f"▫️ <b>{stat['name']}</b>: <i>{stat['text']}</i>" for stat in languages if stat["text"] != "0 secs")
TODAY = "\n".join(stat["text"] for stat in today if stat["text"] != "0 secs")
await utils.answer(
message,
f"👤 <b>Username:</b> <code>{username}</code>\n🖥 <b>OS:</b> {OS}\n🌀 <b>Editor:</b> {EDITOR}\n⏳ <b>All time</b>: <code>{all_time}</code>\n💼 <b>Today</b>: <code>{TODAY}</code>\n\n🧬 LANGUAGES\n\n{LANG}\n",
reply_markup=[
[
{
"text": "🔄 Update",
"callback": self.update_waka,
}
]
],
)
async def update_waka(self, call):
await call.edit("🔄 <b>Updating...</b>")
token = self.config["WAKATIME_TOKEN"]
if token is None:
return await call.edit(self.strings("no_token"))
async with aiohttp.ClientSession() as session:
endpoints = [
"status_bar/today",
"stats/all_time",
"stats/all_time",
"all_time_since_today",
]
tasks = [
session.get(
f"https://wakatime.com/api/v1/users/current/{endpoint}?api_key={token}"
)
for endpoint in endpoints
]
responses = await asyncio.gather(*tasks)
results = await asyncio.gather(*[response.json() for response in responses])
result_t, result, result_s, result_w = results
all_time = result_w["data"]["text"]
username = result["data"]["username"]
languages = result["data"]["languages"]
today = result_t["data"]["categories"]
os = result["data"]["operating_systems"]
editor = result["data"]["editors"]
OS = ", ".join(f"<code>{stat['name']}</code>" for stat in os if stat["text"] != "0 secs")
EDITOR = ", ".join(f"<code>{stat['name']}</code>" for stat in editor if stat["text"] != "0 secs")
LANG = "\n".join(f"▫️ <b>{stat['name']}</b>: <i>{stat['text']}</i>" for stat in languages if stat["text"] != "0 secs")
TODAY = "\n".join(stat["text"] for stat in today if stat["text"] != "0 secs")
await call.edit(
f"👤 <b>Username:</b> <code>{username}</code>\n🖥 <b>OS:</b> {OS}\n🌀 <b>Editor:</b> {EDITOR}\n⏳ <b>All time</b>: <code>{all_time}</code>\n💼 <b>Today</b>: <code>{TODAY}</code>\n\n🧬 LANGUAGES\n\n{LANG}\n",
reply_markup=[
[
{
"text": "🔄 Update",
"callback": self.update_waka,
}
]
],
)

View File

@@ -7,7 +7,7 @@ __version__ = (1, 0, 0)
# 🔑 http://www.apache.org/licenses/LICENSE-2.0 # 🔑 http://www.apache.org/licenses/LICENSE-2.0
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png # meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png
# meta developer: @FModules # meta developer: @NFModules
# meta fhsdesc: brawlstars, game, funny # meta fhsdesc: brawlstars, game, funny
from .. import loader, utils from .. import loader, utils
@@ -42,6 +42,7 @@ async def to_code(n: int) -> str:
n_shifted //= 31 n_shifted //= 31
return "X" + "".join(reversed(res)) return "X" + "".join(reversed(res))
@loader.tds @loader.tds
class BSR(loader.Module): class BSR(loader.Module):
'''Module for finding nearby game rooms in BrawlStars.''' '''Module for finding nearby game rooms in BrawlStars.'''
@@ -139,7 +140,7 @@ class BSR(loader.Module):
'''(room code/link) (previous) (next) - find rooms.''' '''(room code/link) (previous) (next) - find rooms.'''
args = utils.get_args_raw(message).split() args = utils.get_args_raw(message).split()
if not args: if not args:
return await utils.answer(message, self.strings("invalid_args").format(prefix=self.get_prefix())) return await utils.answer(message, self.strings["invalid_args"].format(prefix=self.get_prefix()))
raw_input = args[0] raw_input = args[0]
before = 0 before = 0
@@ -161,13 +162,13 @@ class BSR(loader.Module):
nxt = max(0, min(nxt, 5000)) nxt = max(0, min(nxt, 5000))
if before == 0 and nxt == 0: if before == 0 and nxt == 0:
return await utils.answer(message, self.strings("at_least_one")) return await utils.answer(message, self.strings["at_least_one"])
clean_tag = await extract_code(raw_input) clean_tag = await extract_code(raw_input)
base_id = await to_id(clean_tag) base_id = await to_id(clean_tag)
if base_id == 0: if base_id == 0:
return await utils.answer(message, self.strings("invalid_code")) return await utils.answer(message, self.strings["invalid_code"])
text, page, total_pages = await self.get_page_content(base_id, before, nxt, 0) text, page, total_pages = await self.get_page_content(base_id, before, nxt, 0)
kb = self.build_keyboard(base_id, before, nxt, page, total_pages, clean_tag) kb = self.build_keyboard(base_id, before, nxt, page, total_pages, clean_tag)
@@ -205,10 +206,10 @@ class BSR(loader.Module):
blocks = [] blocks = []
if prev_list: if prev_list:
blocks.append(self.strings("prev_block").format(prev_list="\n".join(prev_list))) blocks.append(self.strings["prev_block"].format(prev_list="\n".join(prev_list)))
if next_list: if next_list:
blocks.append(self.strings("next_block").format(next_list="\n".join(next_list))) blocks.append(self.strings["next_block"].format(next_list="\n".join(next_list)))
res = "\n\n".join(blocks) res = "\n\n".join(blocks)
if not res.strip(): if not res.strip():
@@ -220,7 +221,7 @@ class BSR(loader.Module):
kb = [ kb = [
[ [
{ {
"text": self.strings("btn_target"), "text": self.strings["btn_target"],
"copy": clean_tag "copy": clean_tag
} }
] ]

View File

@@ -1,6 +1,6 @@
__version__ = (9, 3, 9) __version__ = (9, 3, 9)
# meta developer: @FModules # meta developer: @NFModules
# meta pic: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/logo.png # meta pic: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/logo.png
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/logo.png # meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/logo.png
# scope: hikka_min 2.0.0 # scope: hikka_min 2.0.0
@@ -19,15 +19,19 @@ import re
import sys import sys
import uuid import uuid
import importlib import importlib
from contextlib import suppress
from typing import Optional, Dict, List, Union, Tuple, Any from typing import Optional, Dict, List, Union, Tuple, Any
from urllib.parse import unquote from urllib.parse import unquote
from importlib.machinery import ModuleSpec from importlib.machinery import ModuleSpec
import telethon
from .. import loader, utils from .. import loader, utils
from ..types import CoreOverwriteError from ..types import CoreOverwriteError
from herokutl.tl.functions.contacts import UnblockRequest from herokutl.tl.functions.contacts import UnblockRequest
from aiogram.types import InlineQueryResultArticle, InputTextMessageContent, LinkPreviewOptions, ChosenInlineResult, CallbackQuery, Message
try:
from aiogram.types import InlineQueryResultArticle, InputTextMessageContent, LinkPreviewOptions
except ImportError:
InlineQueryResultArticle = InputTextMessageContent = LinkPreviewOptions = Any
class FHetaAPI: class FHetaAPI:
@@ -77,7 +81,7 @@ class MInstaller:
try: try:
code = await plugin._storage.fetch(url, auth=plugin.config.get("basic_auth")) code = await plugin._storage.fetch(url, auth=plugin.config.get("basic_auth"))
except Exception: except Exception:
return "error", [] return "error",[]
for step in range(5): for step in range(5):
state = await self.load(plugin, code, url, step) state = await self.load(plugin, code, url, step)
@@ -85,10 +89,10 @@ class MInstaller:
if state == "success": if state == "success":
if plugin.fully_loaded: if plugin.fully_loaded:
plugin.update_modules_in_db() plugin.update_modules_in_db()
return "success", [] return "success",[]
if state == "overwrite": if state == "overwrite":
return "overwrite", [] return "overwrite",[]
if isinstance(state, list): if isinstance(state, list):
return "dependency", state return "dependency", state
@@ -98,35 +102,37 @@ class MInstaller:
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
return "dependency", [] return "dependency",[]
async def load(self, plugin: 'loader.Module', code: str, origin: str, step: int) -> Union[str, List[str]]: async def load(self, plugin: 'loader.Module', code: str, origin: str, step: int) -> Union[str, List[str]]:
if step == 0: if step == 0:
try: try:
dependencies = list(filter( raw_pip = loader.VALID_PIP_PACKAGES.search(code)
lambda requirement: not requirement.startswith(("-", "_", ".")), if raw_pip:
map(lambda raw: raw.strip().rstrip(','), loader.VALID_PIP_PACKAGES.search(code)[1].split()) dependencies = [
)) dep.strip() for dep in raw_pip[1].replace(',', ' ').split()
if dep.strip() and not dep.strip().startswith(("-", "_", "."))
]
if dependencies: if dependencies:
if not await plugin.install_requirements(dependencies): await plugin.install_requirements(dependencies)
return dependencies importlib.invalidate_caches()
importlib.invalidate_caches() return "retry"
return "retry"
except Exception: except Exception:
pass pass
try: try:
packages = list(filter( raw_apt = loader.VALID_APT_PACKAGES.search(code)
lambda requirement: not requirement.startswith(("-", "_", ".")), if raw_apt:
map(lambda raw: raw.strip().rstrip(','), loader.VALID_APT_PACKAGES.search(code)[1].split()) packages = [
)) pkg.strip() for pkg in raw_apt[1].replace(',', ' ').split()
if pkg.strip() and not pkg.strip().startswith(("-", "_", "."))
]
if packages: if packages:
if not await plugin.install_packages(packages): await plugin.install_packages(packages)
return packages importlib.invalidate_caches()
importlib.invalidate_caches() return "retry"
return "retry"
except Exception: except Exception:
pass pass
@@ -173,8 +179,8 @@ class MInstaller:
finally: finally:
if instance and sys.exc_info()[0] is not None: if instance and sys.exc_info()[0] is not None:
with suppress(Exception): await plugin.allmodules.unload_module(instance.__class__.__name__)
await plugin.allmodules.unload_module(instance.__class__.__name__) if instance in plugin.allmodules.modules:
plugin.allmodules.modules.remove(instance) plugin.allmodules.modules.remove(instance)
@@ -226,11 +232,17 @@ class FHetaUI:
description = utils.escape_html(description).split('\n')[0] if description else "" description = utils.escape_html(description).split('\n')[0] if description else ""
name = utils.escape_html(item.get("name", "")) name = utils.escape_html(item.get("name", ""))
if kind == "cmd": if item.get('inline'):
character = '@' + self.main.inline.bot_username + ' ' if item.get('inline') else self.main.get_prefix() character = '@' + self.main.inline.bot_username + ' '
row = f"<code>{character}{name}</code> {description}".strip() display_name = name
elif kind == "ph":
character = ""
display_name = f"{{{name}}}"
else: else:
row = f"<code>{{{name}}}</code> {description}".strip() character = self.main.get_prefix()
display_name = name
row = f"<code>{character}{display_name}</code> {description}".strip()
extra = f"<i>{self.main.strings[more].format(remaining=len(items) - index)}</i>" extra = f"<i>{self.main.strings[more].format(remaining=len(items) - index)}</i>"
test = "\n".join(lines + [row, extra]) test = "\n".join(lines + [row, extra])
@@ -302,7 +314,7 @@ class FHetaUI:
@loader.tds @loader.tds
class FHeta(loader.Module): class FHeta(loader.Module):
'''Module for searching modules! Watch all FHeta news in @FHeta_Updates!''' '''Module for searching modules! Watch all FHeta news in @NFHeta_Updates!'''
strings = { strings = {
"name": "FHeta", "name": "FHeta",
@@ -333,11 +345,12 @@ class FHeta(loader.Module):
"overwrite": "✘ Error, module tried to overwrite built-in module!", "overwrite": "✘ Error, module tried to overwrite built-in module!",
"dependency": "✘ Dependencies installation error! {deps}", "dependency": "✘ Dependencies installation error! {deps}",
"docdevs": "Use only modules from official Heroku developers when searching?", "docdevs": "Use only modules from official Heroku developers when searching?",
"doctheme": "Theme for emojis." "doctheme": "Theme for emojis.",
"channel": "This is the channel with all updates in FHeta!"
} }
strings_ru = { strings_ru = {
"_cls_doc": "Модуль для поиска модулей! Следите за всеми новостями FHeta в @FHeta_Updates!", "_cls_doc": "Модуль для поиска модулей! Следите за всеми новостями FHeta в @NFHeta_Updates!",
"lang": "ru", "lang": "ru",
"author": "от", "author": "от",
"description": "Описание", "description": "Описание",
@@ -361,15 +374,16 @@ class FHeta(loader.Module):
"counter": "{idx}/{total}", "counter": "{idx}/{total}",
"code": "Код", "code": "Код",
"success": "✔ Модуль успешно установлен!", "success": "✔ Модуль успешно установлен!",
"error": "✘ Ошибка, возможно, модуль поломан!", "error": "✘ Ошибка, возможно, модуль сломан!",
"overwrite": "✘ Ошибка, модуль пытался перезаписать встроенный модуль!", "overwrite": "✘ Ошибка, модуль пытался перезаписать встроенный модуль!",
"dependency": "✘ Ошибка установки зависимостей! {deps}", "dependency": "✘ Ошибка установки зависимостей! {deps}",
"docdevs": "Использовать только модули от официальных разработчиков Heroku при поиске?", "docdevs": "Использовать только модули от официальных разработчиков Heroku при поиске?",
"doctheme": "Тема для эмодзи." "doctheme": "Тема для эмодзи.",
"channel": "Это канал со всеми обновлениями в FHeta!"
} }
strings_ua = { strings_ua = {
"_cls_doc": "Модуль для пошуку модулів! Слідкуйте за всіма новинами FHeta в @FHeta_Updates!", "_cls_doc": "Модуль для пошуку модулів! Слідкуйте за всіма новинами FHeta в @NFHeta_Updates!",
"lang": "ua", "lang": "ua",
"author": "від", "author": "від",
"description": "Опис", "description": "Опис",
@@ -397,11 +411,12 @@ class FHeta(loader.Module):
"overwrite": "✘ Помилка, модуль намагався перезаписати вбудований модуль!", "overwrite": "✘ Помилка, модуль намагався перезаписати вбудований модуль!",
"dependency": "✘ Помилка встановлення залежностей! {deps}", "dependency": "✘ Помилка встановлення залежностей! {deps}",
"docdevs": "Використовувати тільки модулі від офіційних розробників Heroku при пошуку?", "docdevs": "Використовувати тільки модулі від офіційних розробників Heroku при пошуку?",
"doctheme": "Тема для емодзі." "doctheme": "Тема для емодзі.",
"channel": "Це канал з усіма оновленнями в FHeta!"
} }
strings_kz = { strings_kz = {
"_cls_doc": "Модульдерді іздеу модулі! FHeta барлық жаңалықтарын @FHeta_Updates арнасында қадағалаңыз!", "_cls_doc": "Модульдерді іздеу модулі! FHeta барлық жаңалықтарын @NFHeta_Updates арнасында қадағалаңыз!",
"lang": "kz", "lang": "kz",
"author": "авторы", "author": "авторы",
"description": "Сипаттама", "description": "Сипаттама",
@@ -413,7 +428,7 @@ class FHeta(loader.Module):
"search": "{query} сұрауы бойынша іздеу...", "search": "{query} сұрауы бойынша іздеу...",
"noquery": "Сіз іздеу сұрауын енгізбедіңіз, мысал: {prefix}fheta сіздің сұрауыңыз", "noquery": "Сіз іздеу сұрауын енгізбедіңіз, мысал: {prefix}fheta сіздің сұрауыңыз",
"notfound": "{query} сұрауы бойынша ештеңе табылмады.", "notfound": "{query} сұрауы бойынша ештеңе табылмады.",
"toolong": "Сіздің сұрауыңыз тым үлкен, оны 168 таңбаға дейін қысқартыңыз.", "toolong": "Сіздің сұрауыңыз тым үлкен, оны 168 таңбаға до қысқартыңыз.",
"added": "✔ Бағалау қосылды!", "added": "✔ Бағалау қосылды!",
"changed": "✔ Бағалау өзгертілді!", "changed": "✔ Бағалау өзгертілді!",
"deleted": "✔ Бағалау жойылды!", "deleted": "✔ Бағалау жойылды!",
@@ -429,11 +444,12 @@ class FHeta(loader.Module):
"overwrite": "✘ Қате, модуль кіріктірілген модульді қайта жазуға тырысты!", "overwrite": "✘ Қате, модуль кіріктірілген модульді қайта жазуға тырысты!",
"dependency": "✘ Тәуелділіктерді орнату қатесі! {deps}", "dependency": "✘ Тәуелділіктерді орнату қатесі! {deps}",
"docdevs": "Іздеу кезінде тек ресми Heroku әзірлеушілерінің модульдерін пайдалану керек пе?", "docdevs": "Іздеу кезінде тек ресми Heroku әзірлеушілерінің модульдерін пайдалану керек пе?",
"doctheme": "Эмодзилер үшін тақырып." "doctheme": "Эмодзилер үшін тақырып.",
"channel": "Бұл FHeta-дағы барлық жаңартулары бар арна!"
} }
strings_uz = { strings_uz = {
"_cls_doc": "Modullarni qidirish moduli! FHeta barcha yangilanishlarini @FHeta_Updates kanalida kuzatib boring!", "_cls_doc": "Modullarni qidirish moduli! FHeta barcha yangilanishlarini @NFHeta_Updates kanalida kuzatib boring!",
"lang": "uz", "lang": "uz",
"author": "muallif", "author": "muallif",
"description": "Tavsif", "description": "Tavsif",
@@ -449,7 +465,7 @@ class FHeta(loader.Module):
"added": "✔ Reyting qo'shildi!", "added": "✔ Reyting qo'shildi!",
"changed": "✔ Reyting o'zgartirildi!", "changed": "✔ Reyting o'zgartirildi!",
"deleted": "✔ Reyting o'chirildi!", "deleted": "✔ Reyting o'chirildi!",
"prompt": "Qidirish uchun so'rov kiriting.", "prompt": "Qidirish o'rniga so'rov kiritish.",
"hint": "Nomi, buyruq, tavsif, muallif.", "hint": "Nomi, buyruq, tavsif, muallif.",
"retry": "Boshqa so'rovni sinab ko'ring.", "retry": "Boshqa so'rovni sinab ko'ring.",
"query": "So'rov", "query": "So'rov",
@@ -461,11 +477,12 @@ class FHeta(loader.Module):
"overwrite": "✘ Xatolik, modul o'rnatilgan modulni qayta yozishga harakat qildi!", "overwrite": "✘ Xatolik, modul o'rnatilgan modulni qayta yozishga harakat qildi!",
"dependency": "✘ Bog'liqliklarni o'rnatish xatosi! {deps}", "dependency": "✘ Bog'liqliklarni o'rnatish xatosi! {deps}",
"docdevs": "Qidiruv paytida faqat rasmiy Heroku ishlab chiquvchilarining modullaridan foydalanish kerakmi?", "docdevs": "Qidiruv paytida faqat rasmiy Heroku ishlab chiquvchilarining modullaridan foydalanish kerakmi?",
"doctheme": "Emojilar uchun mavzu." "doctheme": "Emojilar uchun mavзу.",
"channel": "Bu FHeta-dagi barcha yangilanishlari bo'lgan kanal!"
} }
strings_fr = { strings_fr = {
"_cls_doc": "Module de recherche de modules! Suivez toutes les actualités FHeta sur @FHeta_Updates!", "_cls_doc": "Module de recherche de modules! Suivez toutes les actualités FHeta sur @NFHeta_Updates!",
"lang": "fr", "lang": "fr",
"author": "par", "author": "par",
"description": "Description", "description": "Description",
@@ -493,11 +510,12 @@ class FHeta(loader.Module):
"overwrite": "✘ Erreur, le module a tenté d'écraser le module intégré!", "overwrite": "✘ Erreur, le module a tenté d'écraser le module intégré!",
"dependency": "✘ Erreur d'installation des dépendances! {deps}", "dependency": "✘ Erreur d'installation des dépendances! {deps}",
"docdevs": "Utiliser uniquement les modules des développeurs Heroku officiels lors de la recherche?", "docdevs": "Utiliser uniquement les modules des développeurs Heroku officiels lors de la recherche?",
"doctheme": "Thème pour les emojis." "doctheme": "Thème pour les emojis.",
"channel": "Voici le canal avec toutes les mises à jour dans FHeta!"
} }
strings_de = { strings_de = {
"_cls_doc": "Modul zur Suche nach Modulen! Verfolgen Sie alle FHeta-Neuigkeiten auf @FHeta_Updates!", "_cls_doc": "Modul zur Suche nach Modulen! Verfolgen Sie alle FHeta-Neuigkeiten auf @NFHeta_Updates!",
"lang": "de", "lang": "de",
"author": "von", "author": "von",
"description": "Beschreibung", "description": "Beschreibung",
@@ -524,12 +542,13 @@ class FHeta(loader.Module):
"error": "✘ Fehler, vielleicht ist das Modul kaputt!", "error": "✘ Fehler, vielleicht ist das Modul kaputt!",
"overwrite": "✘ Fehler, Modul hat versucht, das integrierte Modul zu überschreiben!", "overwrite": "✘ Fehler, Modul hat versucht, das integrierte Modul zu überschreiben!",
"dependency": "✘ Fehler bei der Installation von Abhängigkeiten! {deps}", "dependency": "✘ Fehler bei der Installation von Abhängigkeiten! {deps}",
"docdevs": "Nur Module von offiziellen Heroku-Entwicklern bei der Suche verwenden?", "docdevs": "Nur Module von offiziellen Heroku-Entwicklern bei की खोज में उपयोग करें?",
"doctheme": "Thema für Emojis." "doctheme": "Theма для эмодзи.",
"channel": "Dies ist der Kanal with all updates in FHeta!"
} }
strings_jp = { strings_jp = {
"_cls_doc": "モジュール検索用モジュール!@FHeta_UpdatesでFHetaのすべてのニュースをフォローしてください", "_cls_doc": "モジュール検索用モジュール!@NFHeta_UpdatesでFHetaのすべてのニュースをフォローしてください",
"lang": "jp", "lang": "jp",
"author": "作成者", "author": "作成者",
"description": "説明", "description": "説明",
@@ -553,11 +572,12 @@ class FHeta(loader.Module):
"counter": "{idx}/{total}", "counter": "{idx}/{total}",
"code": "コード", "code": "コード",
"success": "✔ モジュールが正常にインストールされました!", "success": "✔ モジュールが正常にインストールされました!",
"error": "✘ エラーモジュールが壊れている可能性があります!", "error": "✘ エラー, モジュールが壊れている可能性があります!",
"overwrite": "✘ エラーモジュールが組み込みモジュールを上書きしようとしました!", "overwrite": "✘ エラー, モジュールが組み込みモジュールを上書きしようとしました!",
"dependency": "✘ 依存関係のインストールエラー! {deps}", "dependency": "✘ 依存関係のインストールエラー! {deps}",
"docdevs": "検索時に公式Heroku開発者のモジュールのみを使用しますか", "docdevs": "検索時に公式Heroku開発者のモジュールのみを使用しますか",
"doctheme": "絵文字のテーマ。" "doctheme": "絵文字のテーマ。",
"channel": "これはFHetaのすべての更新を含むチャンネルです"
} }
THEMES = { THEMES = {
@@ -569,7 +589,7 @@ class FHeta(loader.Module):
"command": '<tg-emoji emoji-id="5341715473882955310">⚙️</tg-emoji>', "command": '<tg-emoji emoji-id="5341715473882955310">⚙️</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5359785904535774578">🗒️</tg-emoji>', "placeholder": '<tg-emoji emoji-id="5359785904535774578">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5454112830989025752">📦</tg-emoji>', "module": '<tg-emoji emoji-id="5454112830989025752">📦</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>', "channel": '📢',
"modules_list": '<tg-emoji emoji-id="5197269100878907942">📋</tg-emoji>' "modules_list": '<tg-emoji emoji-id="5197269100878907942">📋</tg-emoji>'
}, },
"winter": { "winter": {
@@ -580,7 +600,7 @@ class FHeta(loader.Module):
"command": '<tg-emoji emoji-id="5199503707938505333">🎅</tg-emoji>', "command": '<tg-emoji emoji-id="5199503707938505333">🎅</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5204046675236109418">🗒️</tg-emoji>', "placeholder": '<tg-emoji emoji-id="5204046675236109418">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5197708768091061888">🎁</tg-emoji>', "module": '<tg-emoji emoji-id="5197708768091061888">🎁</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>', "channel": '📢',
"modules_list": '<tg-emoji emoji-id="5345935030143196497">🎄</tg-emoji>' "modules_list": '<tg-emoji emoji-id="5345935030143196497">🎄</tg-emoji>'
}, },
"summer": { "summer": {
@@ -591,7 +611,7 @@ class FHeta(loader.Module):
"command": '<tg-emoji emoji-id="5442644589703866634">🏄</tg-emoji>', "command": '<tg-emoji emoji-id="5442644589703866634">🏄</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5434121252874756456">🗒️</tg-emoji>', "placeholder": '<tg-emoji emoji-id="5434121252874756456">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5433645645376264953">🏖️</tg-emoji>', "module": '<tg-emoji emoji-id="5433645645376264953">🏖️</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>', "channel": '📢',
"modules_list": '<tg-emoji emoji-id="5472178859300363509">🏖️</tg-emoji>' "modules_list": '<tg-emoji emoji-id="5472178859300363509">🏖️</tg-emoji>'
}, },
"spring": { "spring": {
@@ -602,7 +622,7 @@ class FHeta(loader.Module):
"command": '<tg-emoji emoji-id="5449850741667668411">🦋</tg-emoji>', "command": '<tg-emoji emoji-id="5449850741667668411">🦋</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5434121252874756456">🗒️</tg-emoji>', "placeholder": '<tg-emoji emoji-id="5434121252874756456">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5440911110838425969">🌿</tg-emoji>', "module": '<tg-emoji emoji-id="5440911110838425969">🌿</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>', "channel": '📢',
"modules_list": '<tg-emoji emoji-id="5440748683765227563">🌺</tg-emoji>' "modules_list": '<tg-emoji emoji-id="5440748683765227563">🌺</tg-emoji>'
}, },
"autumn": { "autumn": {
@@ -613,7 +633,7 @@ class FHeta(loader.Module):
"command": '<tg-emoji emoji-id="5212963577098417551">🍂</tg-emoji>', "command": '<tg-emoji emoji-id="5212963577098417551">🍂</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5363965354391388799">🗒️</tg-emoji>', "placeholder": '<tg-emoji emoji-id="5363965354391388799">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5249157915041865558">🍄</tg-emoji>', "module": '<tg-emoji emoji-id="5249157915041865558">🍄</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>', "channel": '📢',
"modules_list": '<tg-emoji emoji-id="5305495722618010655">🍂</tg-emoji>' "modules_list": '<tg-emoji emoji-id="5305495722618010655">🍂</tg-emoji>'
} }
} }
@@ -623,13 +643,13 @@ class FHeta(loader.Module):
loader.ConfigValue( loader.ConfigValue(
"only_official_developers", "only_official_developers",
False, False,
lambda: self.strings("docdevs"), lambda: self.strings["docdevs"],
validator=loader.validators.Boolean() validator=loader.validators.Boolean()
), ),
loader.ConfigValue( loader.ConfigValue(
"theme", "theme",
"default", "default",
lambda: self.strings("doctheme"), lambda: self.strings["doctheme"],
validator=loader.validators.Choice(["default", "winter", "summer", "spring", "autumn"]) validator=loader.validators.Choice(["default", "winter", "summer", "spring", "autumn"])
) )
) )
@@ -638,12 +658,30 @@ class FHeta(loader.Module):
if hasattr(self, "api") and self.api.session and not self.api.session.closed: if hasattr(self, "api") and self.api.session and not self.api.session.closed:
await self.api.session.close() await self.api.session.close()
@property
def _inline_mgr(self):
if hasattr(self, "_raw_inline_cache") and self._raw_inline_cache:
return self._raw_inline_cache
am_attr = "seludomlla"[::-1]
allmodules = getattr(self, am_attr, None)
if allmodules:
for cmd in getattr(allmodules, "commands", {}).values():
mod = getattr(cmd, "__self__", None)
if mod and getattr(mod, "__origin__", "").startswith("<core"):
real_allmodules = getattr(mod, am_attr, None)
if real_allmodules:
self._raw_inline_cache = getattr(real_allmodules, "inline", None)
if self._raw_inline_cache:
return self._raw_inline_cache
return self._raw_inline_cache
async def client_ready(self, client: 'telethon.TelegramClient', database: 'loader.Database') -> None: async def client_ready(self, client: 'telethon.TelegramClient', database: 'loader.Database') -> None:
try: await client(UnblockRequest("@FHeta_robot"))
await client(UnblockRequest("@FHeta_robot")) await utils.dnd(client, "@FHeta_robot", archive=True)
await utils.dnd(client, "@FHeta_robot", archive=True)
except Exception:
pass
self.identifier = (await client.get_me()).id self.identifier = (await client.get_me()).id
self.token = database.get("FHeta", "token") self.token = database.get("FHeta", "token")
@@ -652,83 +690,101 @@ class FHeta(loader.Module):
self.installer = MInstaller() self.installer = MInstaller()
self.ui = FHetaUI(self) self.ui = FHetaUI(self)
await self.request_join(
"NFHeta_Updates",
f"{self.ui.emoji('channel')} {self.strings['channel']}"
)
self.api.token = self.token self.api.token = self.token
self._is_telethon = hasattr(self._inline_mgr, "_bot_client")
router = None if self._is_telethon:
try: if hasattr(self._inline_mgr, "register_bot_update_handler"):
frame = sys._getframe() async def telethon_chosen_handler(event: Any) -> None:
while frame: if isinstance(event, telethon.tl.types.UpdateBotInlineSend):
if 'self' in frame.f_locals and type(frame.f_locals['self']).__name__ == "Modules": if event.id.startswith("fh_"):
router = getattr(frame.f_locals['self'], "inline", None) class MockCallback:
if router: result_id = event.id
break inline_message_id = event.msg_id
frame = frame.f_back await self.click(MockCallback())
except Exception:
pass
router = router or self.inline self._inline_mgr.register_bot_update_handler("fheta_chosen", "chosen_inline_result", telethon_chosen_handler)
dispatcher = getattr(router, "_dp", getattr(router, "dp", getattr(router, "router", None))) else:
self.bot = getattr(router, "_bot", getattr(router, "bot", getattr(self.inline, "bot", None))) bot_client = self._inline_mgr._bot_client
if not hasattr(bot_client, "_fpatched"):
if dispatcher: @bot_client.on(telethon.events.Raw)
if not getattr(dispatcher, "_fpatched", False): async def telethon_raw_handler(event: Any) -> None:
if isinstance(event, telethon.tl.types.UpdateBotInlineSend):
if event.id.startswith("fh_"):
class MockCallback:
result_id = event.id
inline_message_id = event.msg_id
await self.lookup("FHeta").click(MockCallback())
bot_client._fpatched = True
elif hasattr(self._inline_mgr, "_dp"):
dispatcher = self._inline_mgr._dp
if not hasattr(dispatcher, "_fpatched"):
async def fmiddleware(handler: Any, event: Any, data: Any) -> Any: async def fmiddleware(handler: Any, event: Any, data: Any) -> Any:
try: module = self.lookup("FHeta")
module = self.lookup("FHeta") if module and event.result_id.startswith("fh_"):
await module.click(event)
if module and getattr(event, "result_id", "").startswith("fh_"): return None
await module.click(event)
return None
except Exception:
pass
return await handler(event, data) return await handler(event, data)
try: dispatcher.chosen_inline_result.middleware(fmiddleware)
dispatcher.chosen_inline_result.middleware(fmiddleware) dispatcher._fpatched = True
dispatcher._fpatched = True
except Exception:
pass
if self.token and not await self.api.fetch("validatetkn", user_id=str(self.identifier)): if self.token and not await self.api.fetch("validatetkn", user_id=str(self.identifier)):
self.token = None self.token = None
self.api.token = None self.api.token = None
if not self.token: if not self.token:
try: async with client.conversation("@FHeta_robot") as conversation:
async with client.conversation("@FHeta_robot") as conversation: await conversation.send_message('/token')
await conversation.send_message('/token') self.token = (await conversation.get_response(timeout=5)).text.strip()
self.token = (await conversation.get_response(timeout=5)).text.strip() database.set("FHeta", "token", self.token)
database.set("FHeta", "token", self.token) self.api.token = self.token
self.api.token = self.token
except Exception: asyncio.create_task(self.sync())
pass
@loader.loop(interval=1, autostart=True)
async def sync(self): async def sync(self):
now = self.strings["lang"] ll = None
if now != getattr(self, "past_lang", None): while True:
await self.api.send("dataset", params={"user_id": getattr(self, "identifier", 0), "lang": now}) cl = self.strings["lang"]
self.past_lang = now if cl != ll:
await self.api.send("dataset", user_id=self.identifier, lang=cl)
ll = cl
await asyncio.sleep(1)
async def answer(self, callback: Union[CallbackQuery, ChosenInlineResult], text: Optional[str] = None, alert: bool = False) -> None: async def answer(self, callback: Any, text: Optional[str] = None, alert: bool = False) -> None:
try: if not hasattr(callback, "answer"):
if text: return
await callback.answer(text, show_alert=alert) await callback.answer(text=text or "", show_alert=alert)
else:
await callback.answer()
except Exception:
pass
async def edit(self, target: Union[str, ChosenInlineResult, CallbackQuery, Message, 'telethon.types.Message'], text: str, buttons: List[List[Dict[str, Any]]], banner: Optional[str] = None) -> None: async def edit(self, target: Any, text: str, buttons: List[List[Dict[str, Any]]], banner: Optional[str] = None) -> None:
try: markup = self._inline_mgr.generate_markup(buttons)
if self._is_telethon:
if banner and banner not in text:
text = f'<a href="{banner}">&#8204;</a>' + text
bot_client = self._inline_mgr._bot_client
inline_msg_id = target.inline_message_id if hasattr(target, "inline_message_id") else None
await bot_client.edit_message(
inline_msg_id or target.chat_id,
None if inline_msg_id else target.message_id,
text,
parse_mode="HTML",
buttons=markup,
link_preview=banner is not None,
invert_media=True
)
elif InlineQueryResultArticle is not Any:
options = LinkPreviewOptions(url=banner, show_above_text=True, prefer_large_media=True) if banner else LinkPreviewOptions(is_disabled=True) options = LinkPreviewOptions(url=banner, show_above_text=True, prefer_large_media=True) if banner else LinkPreviewOptions(is_disabled=True)
markup = self.inline.generate_markup(buttons)
if not self.bot:
return
arguments = { arguments = {
"text": text, "text": text,
"reply_markup": markup, "reply_markup": markup,
@@ -736,64 +792,53 @@ class FHeta(loader.Module):
"parse_mode": "HTML" "parse_mode": "HTML"
} }
inline = target if isinstance(target, str) else getattr(target, "inline_message_id", None) if hasattr(target, "inline_message_id") and target.inline_message_id:
arguments["inline_message_id"] = target.inline_message_id
if inline:
arguments["inline_message_id"] = inline
else: else:
message = getattr(target, "message", target) arguments["chat_id"] = target.message.chat.id
chat = getattr(getattr(message, "chat", message), "id", getattr(message, "chat_id", None)) arguments["message_id"] = target.message.message_id
identifier = getattr(message, "message_id", getattr(message, "id", None))
if chat and identifier: await self._inline_mgr.bot.edit_message_text(**arguments)
arguments["chat_id"] = chat
arguments["message_id"] = identifier
else:
return
await self.bot.edit_message_text(**arguments) async def click(self, callback: Any) -> None:
except Exception: result_id = callback.result_id
pass if not result_id.startswith("fh_"):
return
async def click(self, callback: ChosenInlineResult) -> None: parts = result_id.split("_")
try: if len(parts) != 3:
if not getattr(callback, "result_id", "").startswith("fh_"): return
return
parts = callback.result_id.split("_") queryid = parts[1]
if len(parts) != 3: index = int(parts[2])
return
queryid = parts[1] if not hasattr(self._inline_mgr, "fheta_cache"):
index = int(parts[2]) return
cache = getattr(self.inline, "fheta_cache", {}) saved = self._inline_mgr.fheta_cache.get(queryid, {})
saved = cache.get(queryid, {}) query = saved.get("query", "")
query = saved.get("query", "") modules = saved.get("mods", [])
modules = saved.get("mods", [])
if not modules or index >= len(modules): if not modules or index >= len(modules):
return return
data = modules[index] data = modules[index]
text = self.ui.format(data, query, index+1, len(modules), True) text = self.ui.format(data, query, index+1, len(modules), True)
buttons = self.ui.buttons(data.get("install", ""), data, index, None, query) buttons = self.ui.buttons(data.get("install", ""), data, index, None, query)
await self.edit(callback, text, buttons, data.get("banner")) await self.edit(callback, text, buttons, data.get("banner"))
except Exception:
pass
async def show(self, callback: Union[CallbackQuery, ChosenInlineResult], index: int, modules: List[Dict[str, Any]], query: str) -> None: async def show(self, callback: Any, index: int, modules: List[Dict[str, Any]], query: str) -> None:
await self.answer(callback) await self.answer(callback)
text = f"{self.ui.emoji('modules_list')} <b>{self.strings['list']}</b>" text = f"{self.ui.emoji('modules_list')} <b>{self.strings['list']}</b>"
await self.edit(callback, text, self.ui.pagination(modules, query, 0, index)) await self.edit(callback, text, self.ui.pagination(modules, query, 0, index))
async def page(self, callback: Union[CallbackQuery, ChosenInlineResult], current: int, modules: List[Dict[str, Any]], query: str, index: int) -> None: async def page(self, callback: Any, current: int, modules: List[Dict[str, Any]], query: str, index: int) -> None:
await self.answer(callback) await self.answer(callback)
text = f"{self.ui.emoji('modules_list')} <b>{self.strings['list']}</b>" text = f"{self.ui.emoji('modules_list')} <b>{self.strings['list']}</b>"
await self.edit(callback, text, self.ui.pagination(modules, query, current, index)) await self.edit(callback, text, self.ui.pagination(modules, query, current, index))
async def navigate(self, callback: Union[CallbackQuery, ChosenInlineResult], index: int, modules: List[Dict[str, Any]], query: str = "") -> None: async def navigate(self, callback: Any, index: int, modules: List[Dict[str, Any]], query: str = "") -> None:
await self.answer(callback) await self.answer(callback)
if 0 <= index < len(modules): if 0 <= index < len(modules):
data = modules[index] data = modules[index]
@@ -801,7 +846,7 @@ class FHeta(loader.Module):
buttons = self.ui.buttons(data.get('install', ''), data, index, modules, query) buttons = self.ui.buttons(data.get('install', ''), data, index, modules, query)
await self.edit(callback, text, buttons, data.get("banner")) await self.edit(callback, text, buttons, data.get("banner"))
async def rate(self, callback: Union[CallbackQuery, ChosenInlineResult, Message, 'telethon.types.Message'], link: str, action: str, index: int, modules: Optional[List[Dict[str, Any]]], query: str = "") -> None: async def rate(self, callback: Any, link: str, action: str, index: int, modules: Optional[List[Dict[str, Any]]], query: str = "") -> None:
response = await self.api.send(f"rate/{self.identifier}/{link}/{action}") response = await self.api.send(f"rate/{self.identifier}/{link}/{action}")
request = await self.api.send("get", payload=[unquote(link)]) request = await self.api.send("get", payload=[unquote(link)])
@@ -810,10 +855,7 @@ class FHeta(loader.Module):
if modules and index < len(modules): if modules and index < len(modules):
modules[index].update(stats) modules[index].update(stats)
try: await self.edit(callback, self.ui.format(modules[index], query, index + 1, len(modules)), self.ui.buttons(link, stats, index, modules, query), modules[index].get("banner"))
await callback.edit(reply_markup=self.ui.buttons(link, stats, index, modules, query))
except Exception:
pass
if response and response.get("status"): if response and response.get("status"):
status = response.get("status") status = response.get("status")
@@ -827,21 +869,18 @@ class FHeta(loader.Module):
text = "" text = ""
await self.answer(callback, text, True) await self.answer(callback, text, True)
async def install(self, callback: Union[CallbackQuery, ChosenInlineResult], link: str, index: int, modules: Optional[List[Dict[str, Any]]], query: str = "") -> None: async def install(self, callback: Any, link: str, index: int, modules: Optional[List[Dict[str, Any]]], query: str = "") -> None:
state, dependencies = await self.installer.execute(self.lookup("loader"), link) state, dependencies = await self.installer.execute(self.lookup("loader"), link)
try: if state == "success":
if state == "success": await self.answer(callback, self.strings["success"], True)
await self.answer(callback, self.strings["success"], True) elif state == "dependency":
elif state == "dependency": formatted = f"({','.join(dependencies[:5])})" if dependencies else ""
formatted = f"({','.join(dependencies[:5])})" if dependencies else "" await self.answer(callback, self.strings["dependency"].format(deps=formatted), True)
await self.answer(callback, self.strings["dependency"].format(deps=formatted), True) elif state == "overwrite":
elif state == "overwrite": await self.answer(callback, self.strings["overwrite"], True)
await self.answer(callback, self.strings["overwrite"], True) else:
else: await self.answer(callback, self.strings["error"], True)
await self.answer(callback, self.strings["error"], True)
except Exception:
pass
@loader.inline_handler( @loader.inline_handler(
ru_doc="(запрос) - поиск модулей.", ru_doc="(запрос) - поиск модулей.",
@@ -860,7 +899,7 @@ class FHeta(loader.Module):
return { return {
"title": self.strings["prompt"], "title": self.strings["prompt"],
"description": self.strings["hint"], "description": self.strings["hint"],
"message": f"{self.ui.emoji('error')} <b>{self.strings['prompt']}</b>", "message": f"{self.ui.emoji('error')} <b>{self.strings['noquery'].format(prefix=f'<code>@{self._inline_mgr.bot_username} ')}</code></b>",
"thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/magnifying_glass.png" "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/magnifying_glass.png"
} }
@@ -878,18 +917,18 @@ class FHeta(loader.Module):
return { return {
"title": self.strings["retry"], "title": self.strings["retry"],
"description": self.strings["hint"], "description": self.strings["hint"],
"message": f"{self.ui.emoji('error')} <b>{self.strings['notfound'].format(query=utils.escape_html(query))}</b>", "message": f"{self.ui.emoji('error')} <b>{self.strings['notfound'].format(query=f'<code>{utils.escape_html(query)}</code>')}</b>",
"thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/try_other_query.png" "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/try_other_query.png"
} }
queryid = str(uuid.uuid4())[:8] queryid = str(uuid.uuid4())[:8]
if not hasattr(self.inline, "fheta_cache"): if not hasattr(self._inline_mgr, "fheta_cache"):
self.inline.fheta_cache = {} self._inline_mgr.fheta_cache = {}
if len(self.inline.fheta_cache) >= 50: if len(self._inline_mgr.fheta_cache) >= 50:
self.inline.fheta_cache.pop(next(iter(self.inline.fheta_cache))) self._inline_mgr.fheta_cache.pop(next(iter(self._inline_mgr.fheta_cache)))
self.inline.fheta_cache[queryid] = {"query": query, "mods": modules} self._inline_mgr.fheta_cache[queryid] = {"query": query, "mods": modules}
results = [] results = []
@@ -898,22 +937,38 @@ class FHeta(loader.Module):
if isinstance(description, dict): if isinstance(description, dict):
description = description.get(self.strings["lang"]) or description.get("doc") or next(iter(description.values()), "") description = description.get(self.strings["lang"]) or description.get("doc") or next(iter(description.values()), "")
markup = None markup = self._inline_mgr.generate_markup(self.ui.buttons(data.get("install", ""), data, index, None, query))
try:
markup = self.inline.generate_markup(self.ui.buttons(data.get("install", ""), data, index, None, query))
except Exception:
pass
results.append(InlineQueryResultArticle( if self._is_telethon:
id=f"fh_{queryid}_{index}", thumb_url = data.get("pic") or "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/empty_pic.png"
title=utils.escape_html(data.get("name", "")), thumb = self._inline_mgr._web_document(thumb_url)
description=utils.escape_html(str(description)[:250] + ("..." if len(str(description)) > 250 else "")),
thumbnail_url=data.get("pic") or "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/empty_pic.png",
input_message_content=InputTextMessageContent(message_text="", parse_mode="HTML"),
reply_markup=markup
))
await event.inline_query.answer(results, cache_time=0) results.append(
await event.builder.article(
id=f"fh_{queryid}_{index}",
title=utils.escape_html(data.get("name", "")),
description=utils.escape_html(str(description)[:250] + ("..." if len(str(description)) > 250 else "")),
thumb=thumb,
text="",
parse_mode="HTML",
buttons=markup,
link_preview=False
)
)
elif InlineQueryResultArticle is not Any:
results.append(InlineQueryResultArticle(
id=f"fh_{queryid}_{index}",
title=utils.escape_html(data.get("name", "")),
description=utils.escape_html(str(description)[:250] + ("..." if len(str(description)) > 250 else "")),
thumbnail_url=data.get("pic") or "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/empty_pic.png",
input_message_content=InputTextMessageContent(message_text="", parse_mode="HTML"),
reply_markup=markup
))
if self._is_telethon:
await event.answer(results, cache_time=0)
elif InlineQueryResultArticle is not Any:
await event.inline_query.answer(results, cache_time=0)
@loader.command( @loader.command(
ru_doc="(запрос) - поиск модулей.", ru_doc="(запрос) - поиск модулей.",
@@ -929,21 +984,21 @@ class FHeta(loader.Module):
query = utils.get_args_raw(message) query = utils.get_args_raw(message)
if not query: if not query:
return await utils.answer(message, f"{self.ui.emoji('error')} <b>{self.strings['noquery'].format(prefix=self.get_prefix())}</b>") return await utils.answer(message, f"{self.ui.emoji('error')} <b>{self.strings['noquery'].format(prefix=f'<code>{self.get_prefix()}')}</code></b>")
if len(query) > 168: if len(query) > 168:
return await utils.answer(message, f"{self.ui.emoji('warn')} <b>{self.strings['toolong']}</b>") return await utils.answer(message, f"{self.ui.emoji('warn')} <b>{self.strings['toolong']}</b>")
message = await utils.answer(message, f"{self.ui.emoji('search')} <b>{self.strings['search'].format(query=utils.escape_html(query))}</b>") message = await utils.answer(message, f"{self.ui.emoji('search')} <b>{self.strings['search'].format(query=f'<code>{utils.escape_html(query)}</code>')}</b>")
modules = await self.api.fetch("search", query=query, inline="false", token=self.token, user_id=self.identifier, ood=str(self.config["only_official_developers"]).lower()) modules = await self.api.fetch("search", query=query, inline="false", token=self.token, user_id=self.identifier, ood=str(self.config["only_official_developers"]).lower())
if not modules or not isinstance(modules, list): if not modules or not isinstance(modules, list):
return await utils.answer(message, f"{self.ui.emoji('error')} <b>{self.strings['notfound'].format(query=utils.escape_html(query))}</b>") return await utils.answer(message, f"{self.ui.emoji('error')} <b>{self.strings['notfound'].format(query=f'<code>{utils.escape_html(query)}</code>')}</b>")
data = modules[0] data = modules[0]
buttons = self.ui.buttons(data.get("install", ""), data, 0, modules, query) buttons = self.ui.buttons(data.get("install", ""), data, 0, modules, query)
form = await self.inline.form("", message, reply_markup=buttons, silent=True) form = await self._inline_mgr.form("", message, reply_markup=buttons, silent=True)
text = self.ui.format(data, query, 1, len(modules)) text = self.ui.format(data, query, 1, len(modules))
await self.edit(form, text, buttons, data.get("banner")) await self.edit(form, text, buttons, data.get("banner"))
@@ -955,20 +1010,17 @@ class FHeta(loader.Module):
if not url.startswith("https://api.fixyres.com/module/"): if not url.startswith("https://api.fixyres.com/module/"):
return return
try: state, dependencies = await self.installer.execute(self.lookup("loader"), url)
state, dependencies = await self.installer.execute(self.lookup("loader"), url)
if state == "success": if state == "success":
reply = await message.respond("") reply = await message.respond("")
elif state == "dependency": elif state == "dependency":
reply = await message.respond(f"📋{','.join(dependencies[:5])}" if dependencies else "📋") reply = await message.respond(f"📋{','.join(dependencies[:5])}" if dependencies else "📋")
elif state == "overwrite": elif state == "overwrite":
reply = await message.respond("😨") reply = await message.respond("😨")
else: else:
reply = await message.respond("") reply = await message.respond("")
await asyncio.sleep(1) await asyncio.sleep(1)
await reply.delete() await reply.delete()
await message.delete() await message.delete()
except Exception:
pass

View File

@@ -1,15 +1,6 @@
__version__ = (1, 0, 0) __version__ = (1, 0, 0)
# meta developer: @FModules # meta developer: @NFModules
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/banner.png
# scope: hikka_min 2.0.0
# ©️ Fixyres, 2024-2030
# 🌐 https://github.com/Fixyres/FModules
# 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
import asyncio import asyncio
import aiohttp import aiohttp
@@ -17,6 +8,9 @@ import html
import sys import sys
import uuid import uuid
import copy import copy
import hashlib
import json
import re
from contextlib import suppress from contextlib import suppress
from .. import loader, utils from .. import loader, utils
@@ -27,109 +21,375 @@ class FSecurity(loader.Module):
strings = { strings = {
"name": "FSecurity", "name": "FSecurity",
"lang": "en", "lang": "English",
"unavailable": "AI module check is unavailable.", "unavailable": "AI module{} check is unavailable.",
"suspicious": "AI interrupted installation of a suspicious module, reason:", "suspicious": "AI interrupted installation of a suspicious module{}, reason:",
"blocked": "AI blocked module installation, reason:", "blocked": "AI blocked module installation{}, reason:",
"continue": "Continue installation?" "continue": "Continue installation?",
"strict_mode_doc": "Block loading modules by any method (lm/dlm allowed) if the AI API is unavailable or the module is suspicious. On restart, this also applies to already installed modules.",
"nvidia_api_key_doc": "API key from build.nvidia.com, used for AI checks. If not specified, a public key from GitHub will be used."
} }
strings_ru = { strings_ru = {
"lang": "ru", "lang": "Russian",
"_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.", "_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.",
"unavailable": "Проверка модуля через ИИ недоступна.", "unavailable": "Проверка модуля{} через ИИ недоступна.",
"suspicious": "ИИ прервал установку подозрительного модуля, причина:", "suspicious": "ИИ прервал установку подозрительного модуля{}, причина:",
"blocked": "ИИ заблокировал установку модуля, причина:", "blocked": "ИИ заблокировал установку модуля{}, причина:",
"continue": "Продолжить установку?" "continue": "Продолжить установку?",
"strict_mode_doc": "Не позволять загружать модули любым методом (lm/dlm разрешено), если API ИИ недоступен или модуль подозрителен. При перезагрузке работает даже на уже установленные модули.",
"nvidia_api_key_doc": "API ключ от build.nvidia.com, используется для проверки через ИИ. Если вы его не укажете, будет использоваться общий ключ с GitHub."
} }
strings_ua = { strings_ua = {
"lang": "ua", "lang": "Ukraine",
"_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.", "_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.",
"unavailable": "Перевірка модуля через ШІ недоступна.", "unavailable": "Перевірка модуля{} через ШІ недоступна.",
"suspicious": "ШІ перервав встановлення підозрілого модуля, причина:", "suspicious": "ШІ перервав встановлення підозрілого модуля{}, причина:",
"blocked": "ШІ заблокував встановлення модуля, причина:", "blocked": "ШІ заблокував встановлення модуля{}, причина:",
"continue": "Продовжити встановлення?" "continue": "Продовжити встановлення?",
"strict_mode_doc": "Не дозволяти завантажувати модулі будь-яким методом (lm/dlm дозволено), якщо API ШІ недоступний або модуль підозрілий. При перезавантаженні працює навіть на вже встановлені модулі.",
"nvidia_api_key_doc": "API ключ від build.nvidia.com, використовується для перевірки через ШІ. Якщо ви його не вкажете, буде використовуватися загальний ключ з GitHub."
} }
strings_de = { strings_de = {
"lang": "de", "lang": "Germany",
"_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI.", "_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI.",
"unavailable": "Die KI-Modulprüfung ist nicht verfügbar.", "unavailable": "Die KI-Modulprüfung{} ist nicht verfügbar.",
"suspicious": "Die KI hat die Installation eines verdächtigen Moduls unterbrochen, Grund:", "suspicious": "Die KI hat die Installation eines verdächtigen Moduls unterbrochen{}, Grund:",
"blocked": "Die KI hat die Modulinstallation blockiert, Grund:", "blocked": "Die KI hat die Modulinstallation blockiert{}, Grund:",
"continue": "Installation fortsetzen?" "continue": "Installation fortsetzen?",
"strict_mode_doc": "Das Laden von Modulen mit jeder Methode blockieren (lm/dlm erlaubt), wenn die KI-API nicht verfügbar ist oder das Modul verdächtig ist. Beim Neustart gilt dies auch für bereits installierte Module.",
"nvidia_api_key_doc": "API-Schlüssel von build.nvidia.com, der für KI-Prüfungen verwendet wird. Wenn nicht angegeben, wird ein öffentlicher Schlüssel von GitHub verwendet."
} }
strings_jp = { strings_jp = {
"lang": "jp", "lang": "Japanese",
"_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。", "_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。",
"unavailable": "AIモジュールのチェックが利用できません。", "unavailable": "AIモジュール{}のチェックが利用できません。",
"suspicious": "AIが疑わしいモジュールのインストールを中断しました、理由", "suspicious": "AIが疑わしいモジュールのインストールを中断しました{}、理由:",
"blocked": "AIがモジュールのインストールをブロックしました、理由", "blocked": "AIがモジュールのインストールをブロックしました{}、理由:",
"continue": "インストールを続行しますか?" "continue": "インストールを続行しますか?",
"strict_mode_doc": "AI APIが利用できない場合や疑わしいモジュールの場合、すべての方法でモジュールの読み込みをブロックしますlm/dlmは許可。再起動時にはインストール済みモジュールにも適用されます。",
"nvidia_api_key_doc": "build.nvidia.com のAPIキー。AIチェックに使用されます。指定しない場合は、GitHubのパブリックキーが使用されます。"
} }
strings_tr = { strings_tr = {
"lang": "tr", "lang": "Turkish",
"_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül.", "_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül.",
"unavailable": "Yapay zeka modül kontrolü kullanılamıyor.", "unavailable": "Yapay zeka modül{} kontrolü kullanılamıyor.",
"suspicious": "Yapay zeka şüpheli bir modülün kurulumunu durdurdu, sebep:", "suspicious": "Yapay zeka şüpheli bir modülün kurulumunu durdurdu{}, sebep:",
"blocked": "Yapay zeka modül kurulumunu engelledi, sebep:", "blocked": "Yapay zeka modül kurulumunu engelledi{}, sebep:",
"continue": "Kuruluma devam edilsin mi?" "continue": "Kuruluma devam edilsin mi?",
"strict_mode_doc": "AI API kullanılamıyorsa veya modül şüpheliyse, tüm yöntemlerle modül yüklenmesini engelle (lm/dlm izinli). Yeniden başlatmada zaten kurulu modüller için de geçerlidir.",
"nvidia_api_key_doc": "Yapay zeka kontrolleri için kullanılan build.nvidia.com API anahtarı. Belirtilmezse GitHub'daki genel anahtar kullanılacaktır."
} }
strings_uz = { strings_uz = {
"lang": "uz", "lang": "Uzbekistan",
"_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul.", "_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul.",
"unavailable": "AI modul tekshiruvi mavjud emas.", "unavailable": "AI modul{} tekshiruvi mavjud emas.",
"suspicious": "AI shubhali modul o'rnatilishini to'xtatdi, sabab:", "suspicious": "AI shubhali modul o'rnatilishini to'xtatdi{}, sabab:",
"blocked": "AI modul o'rnatilishini blokladi, sabab:", "blocked": "AI modul o'rnatilishini blokladi{}, sabab:",
"continue": "O'rnatishni davom ettirasizmi?" "continue": "O'rnatishni davom ettirasizmi?",
"strict_mode_doc": "AI API mavjud bo'lmasa yoki modul shubhali bo'lsa, barcha usullar bilan modul yuklashni bloklash (lm/dlm ruxsat etilgan). Qayta ishga tushirishda allaqachon o'rnatilgan modullarga ham ta'sir qiladi.",
"nvidia_api_key_doc": "build.nvidia.com API kaliti, AI orqali tekshirish uchun ishlatiladi. Agar ko'rsatmasangiz, GitHub-dan umumiy kalit ishlatiladi."
} }
strings_kz = { strings_kz = {
"lang": "kz", "lang": "Kazakhstan",
"_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль.", "_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль.",
"unavailable": "AI модульін тексеру қолжетімсіз.", "unavailable": "AI модуль{} тексеру қолжетімсіз.",
"suspicious": "AI күдікті модульді орнатуды тоқтатты, себебі:", "suspicious": "AI күдікті модульді орнатуды тоқтатты{}, себебі:",
"blocked": "AI модульді орнатуды бұғаттады, себебі:", "blocked": "AI модульді орнатуды бұғаттады{}, себебі:",
"continue": "Орнатуды жалғастырасыз ба?" "continue": "Орнатуды жалғастырасыз ба?",
"strict_mode_doc": "AI API қолжетімсіз болса немесе модуль күдікті болса, барлық әдістермен модуль жүктеуді бұғаттау (lm/dlm рұқсат етілген). Қайта іске қосқанда орнатылған модульдерге де қолданылады.",
"nvidia_api_key_doc": "build.nvidia.com API кілті, ЖИ арқылы тексеру үшін қолданылады. Егер оны көрсетпесеңіз, GitHub-тан ортақ кілт пайдаланылады."
} }
def __init__(self): def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"strict_mode",
False,
lambda: self.strings["strict_mode_doc"],
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"nvidia_api_key",
"",
lambda: self.strings["nvidia_api_key_doc"],
validator=loader.validators.Hidden(),
)
)
self.tasks = {} self.tasks = {}
self.oreg = None self.oreg = None
self.oload = None self.oload = None
async def client_ready(self, client, db): async def client_ready(self, client, db):
self.__origin__ = "<fsecurity>"
self.core = self.lookup("loader") self.core = self.lookup("loader")
self.modules = self.core.allmodules self.modules = self.core.allmodules
self.restore_hooks()
self.patch() self.patch()
async def on_unload(self): async def on_unload(self):
self.unpatch() self.unpatch()
def _render_prompt(self, prompt, **values):
rendered = prompt
for key, value in values.items():
rendered = rendered.replace("{" + key + "}", str(value))
return rendered
def _split_code(self, code):
chunk_size = 180000
if len(code) <= chunk_size:
return [code]
chunks = []
current =[]
current_len = 0
for line in code.splitlines(keepends=True):
if current and current_len + len(line) > chunk_size:
chunks.append("".join(current))
current =[]
current_len = 0
if len(line) > chunk_size:
if current:
chunks.append("".join(current))
current =[]
current_len = 0
for i in range(0, len(line), chunk_size):
chunks.append(line[i:i + chunk_size])
continue
current.append(line)
current_len += len(line)
if current:
chunks.append("".join(current))
return chunks or [code]
def _parse_ai_json(self, raw_text):
raw_text = (raw_text or "").strip()
if not raw_text:
return None
try:
parsed = json.loads(raw_text)
if isinstance(parsed, dict):
return parsed
except Exception:
pass
match = re.search(r"\{[\s\S]*\}", raw_text)
if not match:
return None
try:
parsed = json.loads(match.group())
except Exception:
return None
return parsed if isinstance(parsed, dict) else None
async def _fetch_prompt(self, session, url):
async with session.get(url, timeout=10) as resp:
if resp.status != 200:
return None
prompt = (await resp.text()).strip()
return prompt or None
async def _get_prompts(self, session):
main_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/main.txt")
chunk_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/chank.txt")
final_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/final.txt")
if not main_prompt or not chunk_prompt or not final_prompt:
return None
return {
"main": main_prompt,
"chunk": chunk_prompt,
"final": final_prompt,
}
async def _nvidia_request(self, session, api_key, system_prompt, user_prompt):
async with session.post(
"https://integrate.api.nvidia.com/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": "qwen/qwen3-coder-480b-a35b-instruct",
"messages":[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"temperature": 0.4,
"max_tokens": 1000,
},
timeout=180,
) as resp:
if resp.status != 200:
return None
data = await resp.json()
choices = data.get("choices") or[]
if not choices:
return None
return self._parse_ai_json(choices[0].get("message", {}).get("content", ""))
async def _local_ai_check(self, session, code, lang, api_key):
prompts = await self._get_prompts(session)
if not prompts:
return None
chunks = self._split_code(code)
if len(chunks) == 1:
prompt = self._render_prompt(prompts["main"], lang=lang)
return await self._nvidia_request(
session,
api_key,
prompt,
f"Analyze this module:\n\n```python\n{code}\n```",
)
total = len(chunks)
findings =[]
for index, chunk in enumerate(chunks, start=1):
previous_context = "; ".join(
f"Part {i}: {finding}"
for i, finding in enumerate(findings, start=1)
if finding
) or "Previous parts: no issues found so far."
chunk_prompt = self._render_prompt(
prompts["chunk"],
total=total,
current=index,
previous_context=previous_context,
lang=lang,
)
chunk_result = await self._nvidia_request(
session,
api_key,
chunk_prompt,
f"Part {index}/{total}:\n\n```python\n{chunk}\n```",
)
if not chunk_result:
return None
chunk_verdict = str(chunk_result.get("chunk_verdict", "CLEAN")).lower()
chunk_finding = str(chunk_result.get("findings", "") or "")
if chunk_verdict == "blocked":
findings_text = "\n".join(
f"- Part {i}: {finding}"
for i, finding in enumerate(findings, start=1)
if finding
)
if chunk_finding:
findings_text = f"{findings_text}\n- Part {index}: {chunk_finding}".strip()
final_prompt = self._render_prompt(
prompts["final"],
total=total,
findings=findings_text or "No prior findings.",
lang=lang,
)
return await self._nvidia_request(
session,
api_key,
final_prompt,
"Give the final verdict based on all findings.",
)
findings.append(chunk_finding if chunk_verdict != "clean" else "")
findings_text = "\n".join(
f"- Part {i}: {finding}"
for i, finding in enumerate(findings, start=1)
if finding
) or "All parts: no issues found."
final_prompt = self._render_prompt(
prompts["final"],
total=total,
findings=findings_text,
lang=lang,
)
return await self._nvidia_request(
session,
api_key,
final_prompt,
"Give the final verdict based on all findings.",
)
async def check(self, code): async def check(self, code):
try: try:
form = aiohttp.FormData() lang = self.strings("lang") or "en"
form.add_field('file', code.encode('utf-8'), filename='module.py', content_type='text/x-python') module_hash = hashlib.sha256(code.encode("utf-8")).hexdigest()
form.add_field('lang', self.strings("lang") or "en")
db_cache = self.get("cache", {})
if module_hash in db_cache:
cached = db_cache[module_hash]
if cached.get("level") == "safe":
return True
return cached
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post("https://api.fixyres.com/check", data=form, timeout=30) as resp: api_keys = await self._get_api_keys(session)
if resp.status != 200: for api_key in api_keys:
return False parsed = await self._local_ai_check(session, code, lang, api_key)
return await resp.json() if not isinstance(parsed, dict):
continue
verdict = str(parsed.get("verdict", "BLOCKED")).lower()
if verdict not in {"safe", "suspicious", "blocked"}:
verdict = "blocked"
summary = str(parsed.get("summary", "") or "")
result = {"level": verdict if verdict != "safe" else "safe"}
if verdict != "safe":
result["reason"] = summary
db_cache[module_hash] = result
self.set("cache", db_cache)
if result["level"] == "safe":
return True
return result
return False
except Exception: except Exception:
return False return False
def format(self, state, reason=""): async def _get_api_keys(self, session):
configured_key = self.config["nvidia_api_key"].strip()
if configured_key:
return [configured_key]
try:
async with session.get(
"https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/api_keys.txt",
timeout=10,
) as resp:
if resp.status != 200:
return[]
raw_keys = (await resp.text()).strip()
except Exception:
return []
return[key.strip() for key in raw_keys.split(",") if key.strip()]
def format(self, state, reason="", link=""):
link_part = f' (<code>{utils.escape_html(link)}</code>)' if link else ""
if state == "unavailable": if state == "unavailable":
return f'<b>{self.strings("unavailable")}</b>\n<b>{self.strings("continue")}</b>' return f'<b>{self.strings["unavailable"].format(link_part)}</b>\n<b>{self.strings["continue"]}</b>'
if state == "suspicious": if state == "suspicious":
return f'<b>{self.strings("suspicious")}</b>\n<blockquote expandable>{utils.escape_html(reason)}</blockquote>\n<b>{self.strings("continue")}</b>' return f'<b>{self.strings["suspicious"].format(link_part)}</b>\n<blockquote expandable><b>{reason}</b></blockquote>\n<b>{self.strings["continue"]}</b>'
return f'<b>{self.strings("blocked")}</b>\n<blockquote expandable>{utils.escape_html(reason)}</blockquote>' return f'<b>{self.strings["blocked"].format(link_part)}</b>\n<blockquote expandable><b>{reason}</b></blockquote>'
def buttons(self, task): def buttons(self, task):
return [[ return [[
@@ -137,6 +397,51 @@ class FSecurity(loader.Module):
{"text": "", "callback": self.confirm, "args": (task, "no")} {"text": "", "callback": self.confirm, "args": (task, "no")}
]] ]]
def closure_var(self, func, name):
raw = getattr(func, "__func__", func)
code = getattr(raw, "__code__", None)
closure = getattr(raw, "__closure__", None)
if not code or not closure or name not in code.co_freevars:
return None
with suppress(Exception):
return closure[code.co_freevars.index(name)].cell_contents
return None
def restore_hooks(self):
with suppress(Exception):
inst_reg = getattr(self.modules, "register_module")
owner = getattr(inst_reg, "__self__", None)
if (
owner
and owner is not self
and owner.__class__.__name__ == self.__class__.__name__
):
original = getattr(owner, "oreg", None)
if original:
if getattr(original, "__self__", None) is None:
self.modules.register_module = original.__get__(
self.modules,
self.modules.__class__,
)
else:
self.modules.register_module = original
with suppress(Exception):
inst_load = getattr(self.core, "load_module")
raw = getattr(inst_load, "__func__", inst_load)
if "FSecurity.patch.<locals>.load" in getattr(raw, "__qualname__", ""):
original = self.closure_var(raw, "original")
if original:
if getattr(original, "__self__", None) is None:
self.core.load_module = original.__get__(
self.core,
self.core.__class__,
)
else:
self.core.load_module = original
def patch(self): def patch(self):
if not self.oreg: if not self.oreg:
self.oreg = getattr(self.modules, "register_module") self.oreg = getattr(self.modules, "register_module")
@@ -161,10 +466,23 @@ class FSecurity(loader.Module):
decoded = html.unescape(body[3:-4]) decoded = html.unescape(body[3:-4])
response = response.split("😖</tg-emoji>", 1)[0] + f'😖</tg-emoji> {decoded}' if decoded else response.split("😖</tg-emoji>", 1)[0] + '😖</tg-emoji>' response = response.split("😖</tg-emoji>", 1)[0] + f'😖</tg-emoji> {decoded}' if decoded else response.split("😖</tg-emoji>", 1)[0] + '😖</tg-emoji>'
return await base(message, response, *a, **k) try:
return await base(message, response, *a, **k)
except Exception:
with suppress(Exception):
return await self._client.send_message(
utils.get_chat_id(message),
response,
reply_to=getattr(message, "reply_to_msg_id", None),
buttons=k.get("reply_markup"),
)
return message
utils.answer = answer utils.answer = answer
try: try:
if getattr(original, "__self__", None) is None:
return await original(_, *args, **kwargs)
return await original(*args, **kwargs) return await original(*args, **kwargs)
finally: finally:
if utils.answer is answer: if utils.answer is answer:
@@ -183,7 +501,7 @@ class FSecurity(loader.Module):
frame = sys._getframe() frame = sys._getframe()
msg = None msg = None
fmsg = None fmsg = None
autoload = False is_dlm_lm = False
while frame: while frame:
locals = frame.f_locals locals = frame.f_locals
@@ -193,31 +511,44 @@ class FSecurity(loader.Module):
and 'message' in locals and 'message' in locals
and hasattr(locals['message'], 'edit') and hasattr(locals['message'], 'edit')
): ):
msg = locals['message'] if not msg:
fmsg = locals.get('msg') msg = locals['message']
break fmsg = locals.get('msg')
if (
frame.f_code.co_name in {"_register_modules", "register_all"} if frame.f_code.co_name in {"dlmod", "loadmod"}:
and locals.get("self") is self.modules is_dlm_lm = True
): if not msg and 'message' in locals and hasattr(locals['message'], 'edit'):
autoload = True msg = locals['message']
if frame.f_code.co_name == "download_and_install":
if not msg and 'message' in locals and hasattr(locals['message'], 'edit'):
msg = locals['message']
frame = frame.f_back frame = frame.f_back
return msg, fmsg, autoload return msg, fmsg, is_dlm_lm
def target_chat(self, msg=None, fmsg=None): def target_chat(self, msg=None, fmsg=None):
if msg: if not msg:
with suppress(Exception): return None
target = copy.copy(msg)
if fmsg: if not fmsg:
target.reply_to_msg_id = fmsg.id return msg
elif not getattr(target, 'reply_to_msg_id', None):
target.reply_to_msg_id = target.id with suppress(Exception):
return target target = copy.copy(msg)
target.reply_to_msg_id = fmsg.id
return target
return None return None
async def call_oreg(self, spec, name, origin="<core>", save_fs=False):
if getattr(self.oreg, "__self__", None) is None:
return await self.oreg(self.modules, spec, name, origin, save_fs=save_fs)
return await self.oreg(spec, name, origin, save_fs=save_fs)
async def register(self, spec, name, origin="<core>", save_fs=False): async def register(self, spec, name, origin="<core>", save_fs=False):
if origin != "<core>" and name != self.__module__: if origin != "<core>":
code = "" code = ""
if hasattr(spec.loader, "data") and spec.loader.data: if hasattr(spec.loader, "data") and spec.loader.data:
@@ -233,7 +564,7 @@ class FSecurity(loader.Module):
check = await self.check(code) check = await self.check(code)
if check is not True: if check is not True:
msg, fmsg, autoload = self.context() msg, fmsg, is_dlm_lm = self.context()
target = self.target_chat(msg, fmsg) target = self.target_chat(msg, fmsg)
if isinstance(check, dict): if isinstance(check, dict):
@@ -243,52 +574,51 @@ class FSecurity(loader.Module):
status = "unavailable" status = "unavailable"
reason = "" reason = ""
if autoload: link = origin if origin.startswith("http") else ""
return await self.oreg(spec, name, origin, save_fs=save_fs)
if not msg or not target:
raise loader.LoadError("")
if msg:
with suppress(Exception):
msg.out = False
if status == "blocked": if status == "blocked":
text = self.format("blocked", reason) if msg and target:
raise loader.LoadError(text) raise loader.LoadError(self.format("blocked", reason, link))
raise loader.LoadError("")
task = str(uuid.uuid4()) should_block = is_dlm_lm or self.config["strict_mode"]
event = asyncio.Event()
self.tasks[task] = {"event": event, "decision": False}
try: if should_block and not (msg and target):
form = await self.inline.form( raise loader.LoadError("")
text=self.format(status, reason),
message=target,
reply_markup=self.buttons(task)
)
if not form: if should_block and msg and target:
raise loader.LoadError(reason) task = str(uuid.uuid4())
event = asyncio.Event()
self.tasks[task] = {"event": event, "decision": False}
await asyncio.wait_for(event.wait(), timeout=60.0) try:
form = await self.inline.form(
text=self.format(status, reason, link),
message=target,
reply_markup=self.buttons(task)
)
if not self.tasks.pop(task)["decision"]: if not form:
raise loader.LoadError(reason)
await asyncio.wait_for(event.wait(), timeout=180.0)
if not self.tasks.pop(task)["decision"]:
with suppress(Exception):
await form.delete()
raise loader.LoadError("")
except asyncio.TimeoutError:
self.tasks.pop(task, None)
with suppress(Exception): with suppress(Exception):
await form.delete() await form.delete()
raise loader.LoadError("") raise loader.LoadError("")
except loader.LoadError:
raise
except Exception:
raise loader.LoadError("")
except asyncio.TimeoutError: return await self.call_oreg(spec, name, origin, save_fs=save_fs)
self.tasks.pop(task, None)
with suppress(Exception):
await form.delete()
raise loader.LoadError("")
except loader.LoadError:
raise
except Exception:
raise loader.LoadError("")
return await self.oreg(spec, name, origin, save_fs=save_fs)
async def confirm(self, call, task, action): async def confirm(self, call, task, action):
if task in self.tasks: if task in self.tasks:

View File

@@ -0,0 +1,87 @@
__version__ = (1, 0, 0)
# meta developer: @NFModules
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/banner.png
# meta fhsdesc: security, guard, antiscam, antivirus
# scope: hikka_min 2.0.0
# ©️ Fixyres, 2024-2030
# 🌐 https://github.com/Fixyres/FModules
# 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
import aiohttp
import os
from .. import loader
@loader.tds
class FSecurity(loader.Module):
"""Module for automatic AI-based security checks of installed modules."""
strings = {
"name": "FSecurity"
}
strings_ru = {
"_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ."
}
strings_ua = {
"_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ."
}
strings_de = {
"_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI."
}
strings_jp = {
"_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。"
}
strings_tr = {
"_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül."
}
strings_uz = {
"_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul."
}
strings_kz = {
"_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль."
}
async def client_ready(self, client, db):
core = self.lookup("loader")
try:
async with aiohttp.ClientSession() as session:
async with session.get(
"https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/FSecurity.py",
timeout=aiohttp.ClientTimeout(total=15),
) as resp:
if resp.status != 200:
return
source = await resp.text()
except Exception:
return
target = os.path.join(
os.path.dirname(loader.__file__),
"modules",
"FSecurity.py",
)
try:
with open(target, "w", encoding="utf-8") as f:
f.write(source)
except Exception:
return
await core.unload_module("FSecurity")
try:
await core.load_module(source, None, "FSecurity", target, save_fs=False)
except Exception:
pass

View File

@@ -7,7 +7,8 @@ __version__ = (1, 0, 0)
# 🔑 http://www.apache.org/licenses/LICENSE-2.0 # 🔑 http://www.apache.org/licenses/LICENSE-2.0
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png # meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png
# meta developer: @FModules # meta developer: @NFModules
# meta fhsdesc: SoundCloud, Music, Music downloader, Downloader
# requires: curl_cffi # requires: curl_cffi
@@ -105,15 +106,15 @@ class SCD(loader.Module):
'''(link) - download a song from SoundCloud.''' '''(link) - download a song from SoundCloud.'''
args = utils.get_args_raw(message) args = utils.get_args_raw(message)
if not args: if not args:
await utils.answer(message, self.strings("no_args").format(prefix=self.get_prefix())) await utils.answer(message, self.strings["no_args"].format(prefix=self.get_prefix()))
return return
m = re.search(r"(https?://(?:[a-zA-Z0-9-]+\.)?soundcloud\.com/[^\s]+)", args) m = re.search(r"(https?://(?:[a-zA-Z0-9-]+\.)?soundcloud\.com/[^\s]+)", args)
if not m: if not m:
await utils.answer(message, self.strings("not_found")) await utils.answer(message, self.strings["not_found"])
return return
msg = await utils.answer(message, self.strings("downloading")) msg = await utils.answer(message, self.strings["downloading"])
try: try:
async with requests.AsyncSession(impersonate="chrome120") as ses: async with requests.AsyncSession(impersonate="chrome120") as ses:
@@ -194,4 +195,4 @@ class SCD(loader.Module):
await msg.delete() await msg.delete()
except: except:
await utils.answer(msg, self.strings("not_found")) await utils.answer(msg, self.strings["not_found"])

View File

@@ -7,7 +7,7 @@ __version__ = (1, 1, 0)
# 🔑 http://www.apache.org/licenses/LICENSE-2.0 # 🔑 http://www.apache.org/licenses/LICENSE-2.0
# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png # meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png
# meta developer: @FModules # meta developer: @NFModules
# meta fhsdesc: game, funny, guess, question game # meta fhsdesc: game, funny, guess, question game
# requires: curl_cffi # requires: curl_cffi
@@ -308,7 +308,7 @@ class Akinator(loader.Module):
loader.ConfigValue( loader.ConfigValue(
"child_mode", "child_mode",
False, False,
lambda: self.strings("child_mode"), lambda: self.strings["child_mode"],
validator=loader.validators.Boolean() validator=loader.validators.Boolean()
) )
) )
@@ -339,17 +339,17 @@ class Akinator(loader.Module):
async def akinator(self, message): async def akinator(self, message):
'''- start the game.''' '''- start the game.'''
try: try:
aki = AsyncAki(self.strings("lang"), self.config["child_mode"]) aki = AsyncAki(self.strings["lang"], self.config["child_mode"])
await aki.start() await aki.start()
self.games.setdefault(message.chat_id, {})[message.id] = aki self.games.setdefault(message.chat_id, {})[message.id] = aki
await self.inline.form( await self.inline.form(
text=self.strings("text"), text=self.strings["text"],
message=message, message=message,
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png", photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png",
reply_markup={ reply_markup={
"text": self.strings("start"), "text": self.strings["start"],
"callback": self._cb, "callback": self._cb,
"args": (message,) "args": (message,)
} }
@@ -369,12 +369,12 @@ class Akinator(loader.Module):
question = aki.q question = aki.q
markup = [[ markup = [[
{"text": self.strings("yes"), "callback": self._ans, "args": (0, message)}, {"text": self.strings["yes"], "callback": self._ans, "args": (0, message)},
{"text": self.strings("no"), "callback": self._ans, "args": (1, message)}, {"text": self.strings["no"], "callback": self._ans, "args": (1, message)},
{"text": self.strings("idk"), "callback": self._ans, "args": (2, message)} {"text": self.strings["idk"], "callback": self._ans, "args": (2, message)}
],[ ],[
{"text": self.strings("probably"), "callback": self._ans, "args": (3, message)}, {"text": self.strings["probably"], "callback": self._ans, "args": (3, message)},
{"text": self.strings("probably_not"), "callback": self._ans, "args": (4, message)} {"text": self.strings["probably_not"], "callback": self._ans, "args": (4, message)}
] ]
] ]
@@ -393,13 +393,13 @@ class Akinator(loader.Module):
desc = aki.desc desc = aki.desc
if desc: if desc:
text = self.strings("this_is").format(name=name, description=desc) text = self.strings["this_is"].format(name=name, description=desc)
else: else:
text = self.strings("this_is_no_desc").format(name=name) text = self.strings["this_is_no_desc"].format(name=name)
markup = [[ markup = [[
{"text": self.strings("yes"), "callback": self._fin, "args": (True, message, text, aki.photo)}, {"text": self.strings["yes"], "callback": self._fin, "args": (True, message, text, aki.photo)},
{"text": self.strings("not_right"), "callback": self._rej, "args": (message,)} {"text": self.strings["not_right"], "callback": self._rej, "args": (message,)}
] ]
] ]
@@ -450,7 +450,7 @@ class Akinator(loader.Module):
await call.edit(text, photo=photo, reply_markup=[]) await call.edit(text, photo=photo, reply_markup=[])
else: else:
await call.edit( await call.edit(
self.strings("failed"), self.strings["failed"],
photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/idk.png", photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/idk.png",
reply_markup=[] reply_markup=[]
) )

View File

@@ -0,0 +1 @@
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

View File

@@ -0,0 +1,25 @@
You must strictly follow these rules with no exceptions.
Analyze only part {current}/{total} of a Hikka userbot Python module. Do NOT give a final verdict for the whole module.
Previous context: {previous_context}
BLOCKED: encrypted/obfuscated code (base64, marshal, zlib, rot13, encoded exec, or any technique hiding real logic), account deletion, mass scam/spam/ads to all chats on load, session/auth_key/2FA exfiltration, bulk message/dialog dump to external destination, string "FSecurity" (if found → findings must be ONLY: "Attempted interaction with FSecurity." translated to {lang}, nothing else).
SUSPICIOUS: watcher/scheduler/client_ready auto-installing modules without owner confirmation, download + exec of remote Python code without confirmation, runtime pip install or library download, third-party OAuth redirect.
CLEAN: no security issue in this chunk.
Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs CLEAN → CLEAN.
@loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default = not a threat.
Owner-triggered exec/eval/shell = not a threat.
A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always CLEAN, never SUSPICIOUS. This is a standard userbot feature.
Respond ONLY with valid JSON:
{"chunk_verdict":"CLEAN|SUSPICIOUS|BLOCKED","findings":"..."}
Findings rules (when not CLEAN):
- Write in {lang}. Max 1000 chars.
- Technical analysis for reading, not a reply. No "I found", no "you should". Third person only.
- Do NOT mention which rule was triggered. Just describe what the code does.
- Only the key threats in this chunk. Reference approximate line numbers within the chunk.
- Use <code>text</code> for code references: function names, variables, URLs, string literals.
- For obfuscation chains, wrap the whole chain in one <code> block: <code>base64.b64decode → zlib.decompress → exec</code>.
- If CLEAN → findings must be empty string "".

View File

@@ -0,0 +1,29 @@
You must strictly follow these rules with no exceptions.
A Hikka userbot module was split into {total} parts. Chunk findings:
{findings}
Give the final verdict for the entire module based on all findings above.
BLOCKED: encrypted/obfuscated code, account deletion, mass scam/spam on load, session/auth_key theft, bulk message dump, string "FSecurity" (if found → summary must be ONLY: "Attempted interaction with FSecurity." translated to {lang}, nothing else).
SUSPICIOUS: auto-install modules without confirmation, remote code download + exec without confirmation, runtime pip/library install, third-party OAuth redirect.
SAFE: no real security issue across all parts.
Auto-install = SUSPICIOUS, never BLOCKED.
Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs SAFE → SAFE.
@loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default = not a threat.
Owner-triggered exec/eval/shell = not a threat.
A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always SAFE, never SUSPICIOUS. This is a standard userbot feature.
Respond ONLY with valid JSON:
{"verdict":"SAFE|SUSPICIOUS|BLOCKED","summary":"..."}
Summary rules (when not SAFE):
- Write in {lang}. Max 1000 chars.
- Combine the most important findings into one coherent technical analysis.
- This is a report for reading, NOT a reply to a person. No "I found", no "you should". Third person only.
- Do NOT mention which rule was triggered or explain criteria. Just describe what the code does.
- Only the key threats. Reference line numbers from findings where available.
- Use <code>text</code> for all code references: function names, variables, URLs, string literals.
- For obfuscation, show the full chain in one <code> block: <code>base64.b64decode → zlib.decompress → exec</code>.
- If SAFE → summary must be empty string "".

View File

@@ -0,0 +1,37 @@
You must strictly follow these classification rules with no exceptions.
Classify a Hikka userbot Python module as BLOCKED, SUSPICIOUS, or SAFE.
BLOCKED (any single match):
- Code is encrypted or obfuscated (base64, marshal, zlib, rot13, compile+exec of encoded data, or any technique that hides real logic).
- Attempts to delete Telegram account (DeleteAccountRequest, client.delete_account, or equivalent).
- On load (client_ready, __init__) automatically sends scam, spam, or ads to all chats/dialogs/contacts without owner action.
- Steals and sends session string, auth_key, or 2FA password anywhere outside the device.
- Collects and forwards all messages or dialogs to any external destination.
- Contains the string "FSecurity" → summary must be ONLY: "Attempted interaction with FSecurity." translated to {lang}. Nothing else, no extra text.
SUSPICIOUS (any single match, only if BLOCKED did not trigger):
- Watcher, scheduler, or client_ready auto-installs modules from any URL without per-action owner confirmation.
- Downloads and executes remote Python code (exec/eval on fetched content) without owner confirmation.
- Installs pip packages or downloads Python libraries at runtime from the internet.
- OAuth or auth flow redirected through a non-official third-party domain.
SAFE: everything that does not match any rule above.
- Owner-triggered exec/eval/shell = always SAFE.
- A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always SAFE, never SUSPICIOUS. This is a standard feature of userbots and poses no threat.
- @loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default (no public access without explicit permission) = SAFE.
Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs SAFE → SAFE.
Respond ONLY with valid JSON:
{"verdict":"SAFE|SUSPICIOUS|BLOCKED","summary":"..."}
Summary rules (when not SAFE):
- Write in {lang}. Max 1000 chars.
- This is a technical analysis meant to be read, NOT a reply to a person. Never write "I found", "you should", "I recommend". Write in third person.
- Do NOT mention which rule was triggered or explain the classification criteria. Just describe what the code does.
- Point out ONLY the key threats. Do NOT describe what the module does overall or list safe parts.
- Reference the approximate line number where dangerous code appears: "line NN —".
- Use <code>text</code> for every code reference: function names, variables, URLs, string literals.
- For obfuscation, show the full decoding chain inside one <code> block: <code>base64.b64decode → zlib.decompress → marshal.loads → exec</code>.
- If SAFE → summary must be empty string "".

View File

@@ -2,3 +2,4 @@ akinator
FHeta FHeta
BSR BSR
SCD SCD
LFSecurity

599
Limoka.py
View File

@@ -1,5 +1,5 @@
# meta developer: @limokanews # meta developer: @limokanews
# requires: whoosh cryptography # requires: whoosh cryptography filetype
from collections import Counter, defaultdict from collections import Counter, defaultdict
@@ -24,16 +24,37 @@ from telethon.errors.rpcerrorlist import WebpageMediaEmptyError
from telethon import TelegramClient from telethon import TelegramClient
from telethon.errors.rpcerrorlist import YouBlockedUserError from telethon.errors.rpcerrorlist import YouBlockedUserError
from telethon import functions from telethon import functions
import filetype
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
import ast
from aiogram.exceptions import TelegramBadRequest as BadRequest
try:
from aiogram.utils.exceptions import BadRequest
except ImportError:
from aiogram.exceptions import TelegramBadRequest as BadRequest
from .. import utils, loader from .. import utils, loader
from ..types import InlineCall from ..types import BotInlineCall, InlineCall
logger = logging.getLogger("Limoka") logger = logging.getLogger("Limoka")
__version__ = (1, 4, 5) __version__ = (1, 5, 6)
def _parse_version_from_source(source: str):
try:
tree = ast.parse(source)
except SyntaxError:
return None
for node in tree.body:
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id == "__version__":
try:
return ast.literal_eval(node.value)
except (ValueError, SyntaxError):
return None
return None
WEIGHTS = { WEIGHTS = {
"inline.token_obtainment": 15, "inline.token_obtainment": 15,
@@ -236,9 +257,11 @@ class ModuleContentBuilder:
module_info: Dict[str, Any], module_info: Dict[str, Any],
query: str, query: str,
filters: Dict[str, List[str]], filters: Dict[str, List[str]],
url: str,
include_categories: bool = True, include_categories: bool = True,
module_path: Optional[str] = None, module_path: Optional[str] = None,
lang: str = "en", lang: str = "en",
) -> tuple: ) -> tuple:
""" """
Build complete formatted module content. Build complete formatted module content.
@@ -260,39 +283,48 @@ class ModuleContentBuilder:
categories_text = self._build_categories_text(filters) categories_text = self._build_categories_text(filters)
commands = self.formatter.format_commands(module_info, lang) commands = self.formatter.format_commands(module_info, lang)
header = self._build_header(query, name, description, dev_username, module_path) header = self._build_header(query, name, description, dev_username, module_path, url)
footer = self._build_footer(module_path) footer = self._build_footer(module_path)
body_pages = self._paginate_commands(commands) body_pages = self._paginate_commands(commands)
return header, body_pages, footer, categories_text return header, body_pages, footer, categories_text
def _build_header(self, query: str, name: str, description: str, dev_username: str, module_path: Optional[str]) -> str: def _build_header(self, query: str, name: str, description: str, dev_username: str, module_path: Optional[str], url: str) -> str:
"""Build message header with module info and tags.""" """Build message header with module info and tags."""
tags_list = self.repository.get_tags_for_module(module_path) if module_path else [] tags_list = self.repository.get_tags_for_module(module_path) if module_path else []
tags_text = ", ".join(self.strings["tags"].get(tag, tag) for tag in tags_list) tags_text = ", ".join(self.strings["tags"].get(tag, tag) for tag in tags_list)
header_template = self.strings["found_header"] header_template = self.strings["found_header"]
if not tags_text: if not tags_text:
# Replace the tags line but keep blockquote closure at the end
header_template = header_template.replace( header_template = header_template.replace(
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Tags:</b> {tags}</blockquote>\n\n", "\n\n<b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Tags:</b> {tags}</blockquote>\n\n",
"" "</blockquote>\n\n"
) )
header_template = header_template.replace( header_template = header_template.replace(
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Теги:</b> {tags}</blockquote>\n\n", "\n\n<b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Теги:</b> {tags}</blockquote>\n\n",
"" "</blockquote>\n\n"
) )
return header_template.format( header = header_template.format(
query=html.escape(query), query=html.escape(query),
name=name, name=name,
description=description, description=description,
username=dev_username, username=dev_username,
tags=tags_text, tags=tags_text,
module_path=module_path,
url=url
) )
# Remove extra newlines
header = re.sub(r'\n+', '\n', header)
return header
def _build_footer(self, module_path: Optional[str]) -> str: def _build_footer(self, module_path: Optional[str]) -> str:
"""Build message footer with download command.""" """Build message footer with download command."""
clean_path = (module_path or "").replace("\\", "/") clean_path = (module_path or "").replace("\\", "/")
return "" # unused for now, but may be used in the future for additional info
return self.strings["found_footer"].format( return self.strings["found_footer"].format(
url=html.escape(self.formatter.strings.get("limokaurl", "")), url=html.escape(self.formatter.strings.get("limokaurl", "")),
module_path=html.escape(clean_path), module_path=html.escape(clean_path),
@@ -351,22 +383,20 @@ class Limoka(loader.Module):
"found_header": ( "found_header": (
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Found module <b>{name}</b> " "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Found module <b>{name}</b> "
"by query: <b>{query}</b>\n\n" "by query: <b>{query}</b>\n\n"
"<b><tg-emoji emoji-id=5413350219800661019>🌐</tg-emoji> <a href='{url}{module_path}'>Source</a></b>\n"
"<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Description:</b> {description}\n" "<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Description:</b> {description}\n"
"<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Developer:</b> {username}\n\n" "<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Developer:</b> {username}\n\n"
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Tags:</b> {tags}</blockquote>\n\n" "<b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Tags:</b> {tags}</blockquote>\n\n"
), ),
"found_body": ("{commands}"), "found_body": ("{commands}"),
"found_footer": (
"<blockquote>\n<tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>"
),
"caption_short": ( "caption_short": (
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>{safe_name}</b>\n" "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>{safe_name}</b>\n"
"<b><tg-emoji emoji-id=5413350219800661019>🌐</tg-emoji> <a href='{url}{module_path}'>Source</a></b>\n"
"<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Description:</b> {safe_desc}\n" "<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Description:</b> {safe_desc}\n"
"<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Dev:</b> {dev_username}\n" "<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Dev:</b> {dev_username}</blockquote>\n"
"<tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {module_path}</code></blockquote>"
), ),
"command_template": "{emoji} <code>{prefix}{command}</code> — {description}\n", "command_template": "<blockquote>{emoji} <code>{prefix}{command}</code> — {description}</blockquote>\n",
"inline_handler_template": "{inline_bot} {command}{description}\n", "inline_handler_template": "<blockquote>{inline_bot} {command}{description}</blockquote>\n",
"emojis": { "emojis": {
1: "<tg-emoji emoji-id=5416037945909987712>1⃣</tg-emoji>", 1: "<tg-emoji emoji-id=5416037945909987712>1⃣</tg-emoji>",
2: "<tg-emoji emoji-id=5413855071731470617>2⃣</tg-emoji>", 2: "<tg-emoji emoji-id=5413855071731470617>2⃣</tg-emoji>",
@@ -381,7 +411,7 @@ class Limoka(loader.Module):
"404": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Not found by query: <i>{query}</i></b></blockquote>", "404": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Not found by query: <i>{query}</i></b></blockquote>",
"noargs": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>No args</b></blockquote>", "noargs": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>No args</b></blockquote>",
"?": "<blockquote><tg-emoji emoji-id=5951895176908640647>🔎</tg-emoji> Request too short / not found</blockquote>", "?": "<blockquote><tg-emoji emoji-id=5951895176908640647>🔎</tg-emoji> Request too short / not found</blockquote>",
"no_info": "<blockquote>No information</blockquote>", "no_info": "No information",
"facts": [ "facts": [
"<blockquote><tg-emoji emoji-id=5472193350520021357>🛡</tg-emoji> The limoka catalog is carefully moderated!</blockquote>", "<blockquote><tg-emoji emoji-id=5472193350520021357>🛡</tg-emoji> The limoka catalog is carefully moderated!</blockquote>",
"<blockquote><tg-emoji emoji-id=5940434198413184876>🚀</tg-emoji> Limoka performance allows you to search for modules quickly!</blockquote>", "<blockquote><tg-emoji emoji-id=5940434198413184876>🚀</tg-emoji> Limoka performance allows you to search for modules quickly!</blockquote>",
@@ -414,6 +444,7 @@ class Limoka(loader.Module):
"global_button": "🌍 Results", "global_button": "🌍 Results",
"filtered_button": "🏷️ Filtered search", "filtered_button": "🏷️ Filtered search",
"inline_search": "🔍 Search in Limoka", "inline_search": "🔍 Search in Limoka",
"install_button": "🪄 Install",
"inline_no_results": "<blockquote>❌ No modules found</blockquote>", "inline_no_results": "<blockquote>❌ No modules found</blockquote>",
"inline_error": "<blockquote>❌ Search error occurred</blockquote>", "inline_error": "<blockquote>❌ Search error occurred</blockquote>",
"inline_short_query": "<blockquote>❌ Query too short (min 2 chars)</blockquote>", "inline_short_query": "<blockquote>❌ Query too short (min 2 chars)</blockquote>",
@@ -450,7 +481,19 @@ class Limoka(loader.Module):
"If error persists again, report to developers</blockquote>" "If error persists again, report to developers</blockquote>"
), ),
"body_page": "Commands", "body_page": "Commands",
"limokaurl": "https://raw.githubusercontent.com/MuRuLOSE/limoka/refs/heads/main/" "install_failed": "Installation failed. Check logs for details.",
"install_succeeded": "Module installed successfully!",
"update_available": (
"🔔 <b>New update available!</b>\n\n"
"New Limoka Version {version} already available. Please update for better performance, bug fixes, and new features.\n"
"Press the button below to update the module."
),
"no_updates_available": "<blockquote>❌ No updates available. You are using the latest version of Limoka.</blockquote>",
"module_update_available": "<blockquote>🔔 Notification about module update has been sent, check @{bot}.</blockquote>",
"index_update_started": "<blockquote>🔄 Limoka module index update has started. This may take a few minutes. Please wait...</blockquote>",
"index_update_failed": "<blockquote>❌ Failed to update Limoka module index. Please try again later. If the error persists, report to developers</blockquote>",
"index_update_success": "<blockquote>✅ Limoka module index updated successfully!</blockquote>",
"update_check_started": "<blockquote>🔍 Checking for Limoka updates...</blockquote>",
} }
strings_ru = { strings_ru = {
"name": "Limoka", "name": "Limoka",
@@ -461,23 +504,21 @@ class Limoka(loader.Module):
), ),
"found_header": ( "found_header": (
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Найден модуль <b>{name}</b> " "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Найден модуль <b>{name}</b> "
"по запросу: <b>{query}</b></blockquote>\n\n" "по запросу: <b>{query}</b>\n\n"
"<blockquote><b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Описание:</b> {description}</blockquote>\n" "<b><tg-emoji emoji-id=5413350219800661019>🌐</tg-emoji> <a href='{url}{module_path}'>Исходный код</a></b>\n"
"<blockquote><b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Разработчик:</b> {username}</blockquote>\n\n" "<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Описание:</b> {description}\n"
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Теги:</b> {tags}</blockquote>\n\n" "<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Разработчик:</b> {username}\n\n"
"<b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Теги:</b> {tags}</blockquote>\n\n"
), ),
"found_body": ("{commands}"), "found_body": ("{commands}"),
"found_footer": (
"\n<blockquote><tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>"
),
"caption_short": ( "caption_short": (
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>{safe_name}</b>\n" "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>{safe_name}</b>\n"
"<b><tg-emoji emoji-id=5413350219800661019>🌐</tg-emoji> <a href='{url}{module_path}'>Исходный код</a></b>\n"
"<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Описание:</b> {safe_desc}\n" "<b><tg-emoji emoji-id=5418376169055602355></tg-emoji> Описание:</b> {safe_desc}\n"
"<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Разработчик:</b> {dev_username}\n" "<b><tg-emoji emoji-id=5418299289141004396>🧑‍💻</tg-emoji> Разработчик:</b> {dev_username}</blockquote>\n"
"<tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {module_path}</code></blockquote>"
), ),
"command_template": "<blockquote>{emoji} <code>{prefix}{command}</code> — {description}</blockquote>\n", "command_template": "<blockquote>{emoji} <code>{prefix}{command}</code> — {description}</blockquote>\n",
"inline_handler_template": "{inline_bot} {command}{description}\n", "inline_handler_template": "<blockquote>{inline_bot} {command}{description}</blockquote>\n",
"emojis": { "emojis": {
1: "<tg-emoji emoji-id=5416037945909987712>1⃣</tg-emoji>", 1: "<tg-emoji emoji-id=5416037945909987712>1⃣</tg-emoji>",
2: "<tg-emoji emoji-id=5413855071731470617>2⃣</tg-emoji>", 2: "<tg-emoji emoji-id=5413855071731470617>2⃣</tg-emoji>",
@@ -493,7 +534,7 @@ class Limoka(loader.Module):
"404": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Не найдено по запросу: <i>{query}</i></b></blockquote>", "404": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Не найдено по запросу: <i>{query}</i></b></blockquote>",
"noargs": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Нет аргументов</b></blockquote>", "noargs": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Нет аргументов</b></blockquote>",
"?": "<blockquote><tg-emoji emoji-id=5951895176908640647>🔎</tg-emoji> Запрос слишком короткий / не найден</blockquote>", "?": "<blockquote><tg-emoji emoji-id=5951895176908640647>🔎</tg-emoji> Запрос слишком короткий / не найден</blockquote>",
"no_info": "<blockquote>Нет информации</blockquote>", "no_info": "Нет информации",
"facts": [ "facts": [
"<blockquote><tg-emoji emoji-id=5472193350520021357>🛡</tg-emoji> Каталог Limoka тщательно модерируется!</blockquote>", "<blockquote><tg-emoji emoji-id=5472193350520021357>🛡</tg-emoji> Каталог Limoka тщательно модерируется!</blockquote>",
"<blockquote><tg-emoji emoji-id=5940434198413184876>🚀</tg-emoji> Limoka позволяет искать модули с невероятной скоростью!</blockquote>", "<blockquote><tg-emoji emoji-id=5940434198413184876>🚀</tg-emoji> Limoka позволяет искать модули с невероятной скоростью!</blockquote>",
@@ -530,6 +571,7 @@ class Limoka(loader.Module):
"global_button": "🌍 Результаты", "global_button": "🌍 Результаты",
"filtered_button": "🏷️ Поиск с фильтрами", "filtered_button": "🏷️ Поиск с фильтрами",
"inline_search": "🔍 Поиск в Limoka", "inline_search": "🔍 Поиск в Limoka",
"install_button": "🪄 Установить",
"inline_no_results": "<blockquote>❌ Модули не найдены</blockquote>", "inline_no_results": "<blockquote>❌ Модули не найдены</blockquote>",
"inline_error": "<blockquote>❌ Ошибка поиска</blockquote>", "inline_error": "<blockquote>❌ Ошибка поиска</blockquote>",
"inline_short_query": "<blockquote>❌ Запрос слишком короткий (мин. 2 символа)</blockquote>", "inline_short_query": "<blockquote>❌ Запрос слишком короткий (мин. 2 символа)</blockquote>",
@@ -566,6 +608,19 @@ class Limoka(loader.Module):
"Если ошибка сохраняется снова, сообщите разработчикам</blockquote>" "Если ошибка сохраняется снова, сообщите разработчикам</blockquote>"
), ),
"body_page": "Команды", "body_page": "Команды",
"install_failed": "Установка не удалась. Проверьте логи для деталей.",
"install_succeeded": "Модуль успешно установлен!",
"update_available": (
"🔔 <b>Доступно новое обновление!</b>\n\n"
"Новая версия Limoka {version} уже доступна. Пожалуйста, обновитесь для лучшей производительности, исправления багов и новых функций.\n"
"Нажмите кнопку ниже, чтобы обновить модуль."
),
"no_updates_available": "<blockquote>❌ Нет доступных обновлений. У вас установлена последняя версия Limoka.</blockquote>",
"module_update_available": "<blockquote>🔔 Уведомление об обновлении модуля было отправлено, проверьте @{bot}.</blockquote>",
"index_update_started": "<blockquote>🔄 Обновление индекса модулей Limoka началось. Это может занять несколько минут. Пожалуйста, подождите...</blockquote>",
"index_update_failed": "<blockquote>❌ Не удалось обновить индекс модулей Limoka. Пожалуйста, попробуйте снова позже. Если ошибка сохраняется, сообщите разработчикам</blockquote>",
"index_update_success": "<blockquote>✅ Индекс модулей Limoka успешно обновлен!</blockquote>",
"update_check_started": "<blockquote>🔍 Проверка обновлений Limoka...</blockquote>",
"_cls_doc": "Модули теперь в одном месте с простым и удобным поиском!", "_cls_doc": "Модули теперь в одном месте с простым и удобным поиском!",
} }
@@ -590,11 +645,18 @@ class Limoka(loader.Module):
lambda: "If enabled, modules from developers with newbies tag will be not shown.", lambda: "If enabled, modules from developers with newbies tag will be not shown.",
validator=loader.validators.Boolean(), validator=loader.validators.Boolean(),
), ),
loader.ConfigValue(
"auto_update_check",
True,
lambda: "If enabled, Limoka will periodically check for updates and notify you when a new version is available.",
validator=loader.validators.Boolean(),
)
) )
self.name = self.strings["name"] self.name = self.strings["name"]
self._invalid_banners = set() self._invalid_banners = set()
self._bot_username = "limoka_bbot" self._bot_username = "limoka_bbot"
self._base_url = self.config["limokaurl"] self._base_url = self.config["limokaurl"]
self._self_bot_username = None
self.SEARCH_STATES = { self.SEARCH_STATES = {
"no_banner": "no_banner", "no_banner": "no_banner",
@@ -614,6 +676,50 @@ class Limoka(loader.Module):
"""[DEPRECATED] Use ModuleRepository.apply_newbie_filter instead.""" """[DEPRECATED] Use ModuleRepository.apply_newbie_filter instead."""
return self.repository.apply_newbie_filter(self.config.get("filter_newbies_modules", False)) return self.repository.apply_newbie_filter(self.config.get("filter_newbies_modules", False))
@loader.loop(interval=3600*24)
async def periodic_update_check(self):
"""Periodically check for module updates if auto_update_check is enabled."""
if self.config["auto_update_check"]:
await self.check_for_module_update()
async def check_for_module_update(self):
async with aiohttp.ClientSession() as session:
try:
async with session.get(self._base_url + "Limoka.py", timeout=10) as response:
if response.status == 200:
version = _parse_version_from_source(await response.text())
if version is not None and version > __version__:
markup = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=self.strings.get("install_button", "Install"),
callback_data="limoka:update_module"
)
]
]
)
await self.inline.bot.send_message(
self._tg_id,
self.strings["update_available"].format(version='.'.join(str(v) for v in version)),
reply_markup=markup
)
return True
return False
except Exception as e:
logger.error(f"Error checking for module update: {e}")
@loader.callback_handler()
async def callback_handler(self, call: BotInlineCall):
if call.data == "limoka:update_module":
result = await self._install_module_limoka()
call.as_(self.inline.bot)
if result:
await call.answer(f"{self.strings['install_succeeded']}")
else:
await call.answer(f"{self.strings['install_failed']}")
def _create_search_session( def _create_search_session(
self, self,
state: str, state: str,
@@ -668,8 +774,8 @@ class Limoka(loader.Module):
self.repository = ModuleRepository(raw_modules, repositories) self.repository = ModuleRepository(raw_modules, repositories)
self.modules = self.repository.apply_newbie_filter(self.config["filter_newbies_modules"]) self.modules = self.repository.apply_newbie_filter(self.config["filter_newbies_modules"])
self._userbot_bot_username = (await self.inline.bot.get_me()).username self._self_bot_username = (await self.inline.bot.get_me()).username
self.formatter = CommandFormatter(self.strings, self._userbot_bot_username, self.get_prefix()) self.formatter = CommandFormatter(self.strings, self._self_bot_username, self.get_prefix())
self.content_builder = ModuleContentBuilder(self.strings, self.formatter, self.repository) self.content_builder = ModuleContentBuilder(self.strings, self.formatter, self.repository)
self._service_bot_id = (await self.client.get_entity(self._bot_username)).id self._service_bot_id = (await self.client.get_entity(self._bot_username)).id
@@ -699,7 +805,7 @@ class Limoka(loader.Module):
@loader.loop(interval=3600) @loader.loop(interval=3600)
async def _update_modules_loop(self): async def _update_modules_loop(self):
"""Periodically update modules list and rebuild index.""" """Periodically update modules list and rebuild index."""
raw_modules = await self.api.fetch_json(self._base_url, "modules.json") await self.api.fetch_json(self._base_url, "modules.json")
self.modules = self.repository.apply_newbie_filter( self.modules = self.repository.apply_newbie_filter(
self.config.get("filter_newbies_modules", False) self.config.get("filter_newbies_modules", False)
) )
@@ -740,21 +846,85 @@ class Limoka(loader.Module):
logger.error(f"Skipping unsafe rmtree for {folder}") logger.error(f"Skipping unsafe rmtree for {folder}")
async def _validate_url(self, url: str) -> Optional[str]: async def _validate_url(self, url: str) -> Optional[str]:
if not url or url in self._invalid_banners: logger.debug(f"_validate_url called with: {url}")
if not url:
logger.warning("_validate_url: URL is empty, returning None")
return None return None
if url in self._invalid_banners:
logger.debug(f"_validate_url: URL already in invalid_banners: {url}, returning None")
return None
# Headers to mimic a browser request
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
try: try:
logger.debug(f"_validate_url: Starting validation for {url}")
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.head( ct = None
url, timeout=5, allow_redirects=True response_status = None
) as response:
if response.status != 200: # Try HEAD first (more efficient)
self._invalid_banners.add(url) try:
return None logger.debug(f"_validate_url: Attempting HEAD request for {url}")
ct = response.headers.get("Content-Type", "").lower() async with session.head(
if not ct.startswith("image/"): url, timeout=5, allow_redirects=True, headers=headers
self._invalid_banners.add(url) ) as response:
return None response_status = response.status
logger.debug(f"_validate_url: HEAD request returned status {response.status} for {url}")
if response.status == 200:
ct = response.headers.get("Content-Type", "").lower()
logger.debug(f"_validate_url: Content-Type from HEAD: '{ct}' for {url}")
except (aiohttp.ClientError, asyncio.TimeoutError) as head_error:
logger.debug(f"_validate_url: HEAD failed ({type(head_error).__name__}), will try GET for {url}")
def _is_supported_media(content_type: str) -> bool:
return content_type.startswith("image/") or content_type.startswith("video/mp4")
# If HEAD didn't work or returned non-200, try GET
if ct is None:
max_retries = 2
for attempt in range(max_retries):
try:
async with session.get(
url, timeout=10, headers=headers, allow_redirects=True
) as response:
if response.status != 200:
self._invalid_banners.add(url)
return None
ct = response.headers.get("Content-Type", "").lower()
# Try to get MIME if Content-Type is missing
if not ct:
try:
data = await response.content.read(2048)
mime = filetype.guess_mime(data, mime=True)
if mime and _is_supported_media(mime):
return url
else:
self._invalid_banners.add(url)
return None
except Exception as mime_error:
logger.error(f"_validate_url: Error reading content for MIME detection: {mime_error}")
break # Success, exit retry loop
except (aiohttp.ClientError, asyncio.TimeoutError) as get_error:
if attempt < max_retries - 1:
await asyncio.sleep(1) # Wait before retry
else:
self._invalid_banners.add(url)
return None
# Check Content-Type from successful request
if ct and _is_supported_media(ct):
return url return url
elif ct:
self._invalid_banners.add(url)
return None
else:
self._invalid_banners.add(url)
return None
except Exception as e: except Exception as e:
if url: if url:
self._invalid_banners.add(url) self._invalid_banners.add(url)
@@ -805,20 +975,20 @@ class Limoka(loader.Module):
module_info: Dict[str, Any], module_info: Dict[str, Any],
query: str, query: str,
filters: Dict[str, List[str]], filters: Dict[str, List[str]],
url: str,
include_categories: bool = True, include_categories: bool = True,
module_path: Optional[str] = None, module_path: Optional[str] = None,
lang: str = "en", lang: str = "en"
) -> tuple: ) -> tuple:
"""[DEPRECATED] Use ModuleContentBuilder.build_content instead.""" """[DEPRECATED] Use ModuleContentBuilder.build_content instead."""
return self.content_builder.build_content( return self.content_builder.build_content(
module_info, query, filters, include_categories, module_path, lang module_info, query, filters, url, include_categories, module_path, lang
) )
def _build_navigation_markup(self, session: Dict[str, Any]) -> list: def _build_navigation_markup(self, session: Dict[str, Any]) -> list:
result = session["results"] result = session["results"]
index = session["current_index"] index = session["current_index"]
query = session["query"]
filters = session["filters"]
page = index + 1 page = index + 1
markup = [ markup = [
@@ -872,8 +1042,6 @@ class Limoka(loader.Module):
) -> list: ) -> list:
result = session["results"] result = session["results"]
index = session["current_index"] index = session["current_index"]
query = session["query"]
filters = session["filters"]
markup = [] markup = []
if len(body_pages) > 1: if len(body_pages) > 1:
@@ -951,6 +1119,15 @@ class Limoka(loader.Module):
}, },
] ]
) )
markup.append(
[
{
"text": self.strings["install_button"],
"callback": self._install_module,
"args": (session,),
},
]
)
markup.append( markup.append(
[{"text": self.strings.get("close", "❌ Close"), "action": "close", "style": "danger"}] [{"text": self.strings.get("close", "❌ Close"), "action": "close", "style": "danger"}]
) )
@@ -1024,6 +1201,7 @@ class Limoka(loader.Module):
include_categories=True, include_categories=True,
module_path=module_path, module_path=module_path,
lang=lang, lang=lang,
url=self._base_url
) )
current_body = body_pages[min(page_body, len(body_pages) - 1)] current_body = body_pages[min(page_body, len(body_pages) - 1)]
full_message = header + current_body + footer + categories_text full_message = header + current_body + footer + categories_text
@@ -1070,12 +1248,36 @@ class Limoka(loader.Module):
include_categories=True, include_categories=True,
module_path=module_path, module_path=module_path,
lang=self.user_lang, lang=self.user_lang,
url=self.config["limokaurl"]
) )
new_page_body = min(page_body + 1, len(body_pages) - 1) new_page_body = min(page_body + 1, len(body_pages) - 1)
await self._display_module( await self._display_module(
call, module_info, module_path, session, page_body=new_page_body call, module_info, module_path, session, page_body=new_page_body
) )
async def _install_module(self, call: InlineCall, session: Dict[str, Any]):
try:
loader = self.lookup("Loader")
await loader.download_and_install(f"{self.config['limokaurl']}{session['results'][session['current_index']]}")
if getattr(loader, "fully_loaded", False):
loader.update_modules_in_db()
except Exception:
await call.answer(f"{self.strings['install_failed']}", alert=True)
else:
await call.answer(f"{self.strings['install_succeeded']}", alert=True)
async def _install_module_limoka(self):
try:
loader = self.lookup("Loader")
await loader.download_and_install(f"{self.config['limokaurl']}Limoka.py")
if getattr(loader, "fully_loaded", False):
loader.update_modules_in_db()
return True
except Exception as e:
logger.exception(f"Error updating Limoka module: {e}")
return False
async def _display_filter_menu(self, call: InlineCall, session: Dict[str, Any]): async def _display_filter_menu(self, call: InlineCall, session: Dict[str, Any]):
query = session["query"] query = session["query"]
current_filters = session["filters"] current_filters = session["filters"]
@@ -1217,6 +1419,7 @@ class Limoka(loader.Module):
result = searcher.search() result = searcher.search()
except Exception: except Exception:
await call.edit(self.strings["?"], reply_markup=[]) await call.edit(self.strings["?"], reply_markup=[])
return return
if not result: if not result:
markup = ( markup = (
@@ -1570,7 +1773,8 @@ class Limoka(loader.Module):
) )
try: try:
result = SearchIndex(args.lower(), self.ix).search() result = SearchIndex(args.lower(), self.ix).search()
except Exception: except Exception as e:
logger.exception(f"Error occurred while searching: {e}")
return await utils.answer(message, self.strings["?"]) return await utils.answer(message, self.strings["?"])
if not result: if not result:
return await utils.answer(message, self.strings["404"].format(query=args)) return await utils.answer(message, self.strings["404"].format(query=args))
@@ -1589,6 +1793,29 @@ class Limoka(loader.Module):
message, module_info, module_path, display_session, 0 message, module_info, module_path, display_session, 0
) )
@loader.command(ru_doc="— Обновить индекс ")
async def updateindex(self, message: Message):
"""— Update search index"""
await utils.answer(message, self.strings["index_update_started"])
try:
await self._update_index()
except Exception as e:
logger.exception(f"Error updating index: {e}")
await utils.answer(message, self.strings["index_update_failed"])
else:
await utils.answer(message, self.strings["index_update_success"])
@loader.command(ru_doc="— Проверить наличие обновлений модуля")
async def limokaupdatecmd(self, message: Message):
"""— Check for module updates"""
await utils.answer(message, self.strings["checking_for_updates"])
is_update_available = await self.check_for_module_update()
if is_update_available:
await utils.answer(message, self.strings["module_update_available"].format(bot=self._self_bot_username))
else:
await utils.answer(message, self.strings["no_updates_available"])
async def _show_global_form(self, call: InlineCall, message: Message): async def _show_global_form(self, call: InlineCall, message: Message):
markup = [ markup = [
[ [
@@ -1742,135 +1969,135 @@ class Limoka(loader.Module):
self.strings["history"].format(history="\n".join(formatted_history)), self.strings["history"].format(history="\n".join(formatted_history)),
) )
@loader.watcher(from_dl=False) # @loader.watcher(from_dl=False)
async def secure_install_watcher(self, message: Message): # async def secure_install_watcher(self, message: Message):
if not message.text: # if not message.text:
return # return
if not hasattr(message, "from_id") or not message.from_id: # if not hasattr(message, "from_id") or not message.from_id:
return # return
sender_id = None # sender_id = None
if hasattr(message.from_id, "user_id"): # if hasattr(message.from_id, "user_id"):
sender_id = message.from_id.user_id # sender_id = message.from_id.user_id
elif hasattr(message.from_id, "channel_id"): # elif hasattr(message.from_id, "channel_id"):
sender_id = message.from_id.channel_id # sender_id = message.from_id.channel_id
if sender_id != self._service_bot_id: # if sender_id != self._service_bot_id:
# logger.debug("Message not from official bot, ignoring") # # logger.debug("Message not from official bot, ignoring")
return # return
if not self.config["external_install_allowed"]: # if not self.config["external_install_allowed"]:
return # return
try: # try:
clean_text = ( # clean_text = (
getattr(message, "raw_text", None) # getattr(message, "raw_text", None)
or getattr(message, "message", None) # or getattr(message, "message", None)
or message.text # or message.text
or "" # or ""
) # )
if message.entities: # if message.entities:
from html import unescape # from html import unescape
clean_text = unescape(clean_text) # clean_text = unescape(clean_text)
clean_text = re.sub(r"<[^>]+>", "", clean_text) # clean_text = re.sub(r"<[^>]+>", "", clean_text)
match = re.search(r"#limoka:([^\s\"'<>]+)", clean_text) # match = re.search(r"#limoka:([^\s\"'<>]+)", clean_text)
if not match: # if not match:
logger.debug( # logger.debug(
"No #limoka tag found in cleaned text; leaving original message intact" # "No #limoka tag found in cleaned text; leaving original message intact"
) # )
return # return
tag_content = match.group(1) # tag_content = match.group(1)
parts = tag_content.split(":", 1) # parts = tag_content.split(":", 1)
if len(parts) != 2: # if len(parts) != 2:
logger.error("Invalid tag format after cleaning") # logger.error("Invalid tag format after cleaning")
await utils.answer(message, self.strings["watcher_invalid_format"]) # await utils.answer(message, self.strings["watcher_invalid_format"])
return # return
module_path, signature_hex = parts # module_path, signature_hex = parts
module_path = re.sub(r"[<>\"']", "", module_path).strip() # module_path = re.sub(r"[<>\"']", "", module_path).strip()
if module_path.startswith("href="): # if module_path.startswith("href="):
module_path = module_path[5:].strip('"').strip("'") # module_path = module_path[5:].strip('"').strip("'")
if module_path not in self.modules: # if module_path not in self.modules:
found = False # found = False
for db_path in self.modules.keys(): # for db_path in self.modules.keys():
if module_path in db_path or db_path in module_path: # if module_path in db_path or db_path in module_path:
module_path = db_path # module_path = db_path
found = True # found = True
break # break
if not found: # if not found:
logger.warning(f"Module not found after cleanup: {module_path}") # logger.warning(f"Module not found after cleanup: {module_path}")
await utils.answer( # await utils.answer(
message, # message,
self.strings["watcher_module_not_found"].format( # self.strings["watcher_module_not_found"].format(
path=html.escape(module_path) # path=html.escape(module_path)
), # ),
) # )
return # return
try: # try:
import base64 # import base64
from cryptography.hazmat.primitives.asymmetric import ed25519 # from cryptography.hazmat.primitives.asymmetric import ed25519
PUB_KEY_B64 = ( # PUB_KEY_B64 = (
"MCowBQYDK2VwAyEA1ltSnqtf3pGBuctuAYqHivCXsaRtKOVxavai7yin7ZE=" # "MCowBQYDK2VwAyEA1ltSnqtf3pGBuctuAYqHivCXsaRtKOVxavai7yin7ZE="
) # )
der_bytes = base64.b64decode(PUB_KEY_B64) # der_bytes = base64.b64decode(PUB_KEY_B64)
raw_pubkey = der_bytes[-32:] # raw_pubkey = der_bytes[-32:]
module_url = self.config["limokaurl"] + module_path # module_url = self.config["limokaurl"] + module_path
async with aiohttp.ClientSession() as session: # async with aiohttp.ClientSession() as session:
async with session.get(module_url, timeout=10) as resp: # async with session.get(module_url, timeout=10) as resp:
if resp.status != 200: # if resp.status != 200:
logger.error( # logger.error(
f"Failed to fetch module for verification: {module_url} (HTTP {resp.status})" # f"Failed to fetch module for verification: {module_url} (HTTP {resp.status})"
) # )
await utils.answer( # await utils.answer(
message, self.strings["watcher_loader_missing"] # message, self.strings["watcher_loader_missing"]
) # )
return # return
module_bytes = await resp.read() # module_bytes = await resp.read()
sha256 = hashlib.sha256(module_bytes).hexdigest() # sha256 = hashlib.sha256(module_bytes).hexdigest()
public_key = ed25519.Ed25519PublicKey.from_public_bytes( # public_key = ed25519.Ed25519PublicKey.from_public_bytes(
raw_pubkey # raw_pubkey
) # )
signature = bytes.fromhex(signature_hex) # signature = bytes.fromhex(signature_hex)
signed_payload = f"{module_path}|{sha256}".encode() # signed_payload = f"{module_path}|{sha256}".encode()
public_key.verify(signature, signed_payload) # public_key.verify(signature, signed_payload)
except Exception as e: # except Exception as e:
logger.error(f"Signature verification failed for {module_path}: {e}") # logger.error(f"Signature verification failed for {module_path}: {e}")
await utils.answer(message, self.strings["watcher_signature_invalid"]) # await utils.answer(message, self.strings["watcher_signature_invalid"])
return # return
loader_mod = self.lookup("loader") # loader_mod = self.lookup("loader")
if not loader_mod: # if not loader_mod:
logger.error("Loader module not found") # logger.error("Loader module not found")
await utils.answer(message, self.strings["watcher_loader_missing"]) # await utils.answer(message, self.strings["watcher_loader_missing"])
return # return
module_url = self.config["limokaurl"] + module_path # module_url = self.config["limokaurl"] + module_path
status = await loader_mod.download_and_install(module_url, None) # status = await loader_mod.download_and_install(module_url, None)
if getattr(loader_mod, "fully_loaded", False): # if getattr(loader_mod, "fully_loaded", False):
loader_mod.update_modules_in_db() # loader_mod.update_modules_in_db()
try: # try:
await message.delete() # await message.delete()
except Exception as e: # except Exception as e:
logger.error(f"Failed to delete message: {e}") # logger.error(f"Failed to delete message: {e}")
if status: # if status:
try: # try:
bot_peer = await self.client.get_entity(self._service_bot_id) # bot_peer = await self.client.get_entity(self._service_bot_id)
await self.client.send_message( # await self.client.send_message(
bot_peer, f"#limoka:sucsess:{message.id}" # bot_peer, f"#limoka:sucsess:{message.id}"
) # )
except Exception as e: # except Exception as e:
logger.error(f"Failed to send success confirmation: {e}") # logger.error(f"Failed to send success confirmation: {e}")
else: # else:
logger.error(f"Installation failed with status: {status}") # logger.error(f"Installation failed with status: {status}")
try: # try:
bot_peer = await self.client.get_entity(self._service_bot_id) # bot_peer = await self.client.get_entity(self._service_bot_id)
await self.client.send_message( # await self.client.send_message(
bot_peer, f"#limoka:failed:{message.id}" # bot_peer, f"#limoka:failed:{message.id}"
) # )
except Exception as e: # except Exception as e:
logger.error(f"Failed to send failure notification: {e}") # logger.error(f"Failed to send failure notification: {e}")
except Exception as e: # except Exception as e:
logger.exception(f"CRITICAL ERROR in secure_install_watcher: {e}") # logger.exception(f"CRITICAL ERROR in secure_install_watcher: {e}")
try: # try:
await utils.answer( # await utils.answer(
message, self.strings["watcher_critical"].format(error=str(e)[:100]) # message, self.strings["watcher_critical"].format(error=str(e)[:100])
) # )
await asyncio.sleep(5) # await asyncio.sleep(5)
await message.delete() # await message.delete()
except Exception: # except Exception:
pass # pass

View File

@@ -523,6 +523,10 @@ class Limoka(loader.Module):
async def _validate_url(self, url: str) -> Optional[str]: async def _validate_url(self, url: str) -> Optional[str]:
if not url or url in self._invalid_banners: if not url or url in self._invalid_banners:
return None return None
def _is_supported_media(content_type: str) -> bool:
return content_type.startswith("image/") or content_type.startswith("video/mp4")
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.head( async with session.head(
@@ -532,7 +536,7 @@ class Limoka(loader.Module):
self._invalid_banners.add(url) self._invalid_banners.add(url)
return None return None
ct = response.headers.get("Content-Type", "").lower() ct = response.headers.get("Content-Type", "").lower()
if not ct.startswith("image/"): if not _is_supported_media(ct):
self._invalid_banners.add(url) self._invalid_banners.add(url)
return None return None
return url return url
@@ -760,7 +764,7 @@ class Limoka(loader.Module):
), ),
}, },
{ {
"text": f"{self.strings["body_page"]} {page_body + 1}/{len(body_pages)}", "text": f"{self.strings['body_page']} {page_body + 1}/{len(body_pages)}",
"callback": self._inline_void, "callback": self._inline_void,
}, },
{ {

View File

@@ -0,0 +1,157 @@
# Midga3
# I AM NOT AFFICIATED WITH WORDLE
# meta developer: @midga3_modules
import requests
import random
import logging
from .. import loader, utils
from herokutl.tl.types import Message
__verison__ = (0, 1, 1)
logger = logging.getLogger(__name__)
@loader.tds
class wordle(loader.Module):
"""Wordle!"""
strings = {
"name": "Wordle",
"loading": "Loading...",
"language": "Language of the wordle",
"have_a_good_game": "Have a good game! Try to guess the 5 letter word in {}",
"attempts_left": "WRONG! Attempts left: {}",
"gg": "GG! YOU DIDN'T GUESS THE WORD {}",
"win": "GG! YOU WON! THE WORD WAS {}",
"already_playing": "ALREADY PLAYING! type .stopwordle to stop the current game",
"length": "Must be 5 letters",
"no_game": "No game is currently running",
"ok": "Game stopped",
"ad": "I tried to Guess word {}. Check out my result:\n{}",
"real_word": "This word is not in the word list"
}
strings_ru ={
"name": "Wordle",
"loading": "Загрузка...",
"language": "Язык вордла",
"have_a_good_game": "Хорошей игры! Попытайтесь угадать слово из 5 букв на {}",
"attempts_left": "НВЕВЕРНО! Осталось попыток: {}",
"gg": "ГГ! ВЫ НЕ УГАДАЛИ СЛОВО {}",
"win": "ГГ! ВЫ ВЫИГРАЛИ! СЛОВО БЫЛО {}",
"already_playing": "УЖЕ ИГРАЕТЕ! напишите .stopwordle чтобы остановить текущую игру",
"length": "Должно содержать 5 букв",
"no_game": "Сейчас нет активной игры",
"ok": "Игра остановлена",
"ad": "Я попытался угадать слово {}. Чекайте мой результат:\n{}",
"real_word": "Такого слова нет в списке слов"
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"language",
"en",
self.strings["language"],
validator=loader.validators.Choice(["en", "ru"])
),
)
async def handler(self, call, data):
guess = data.upper()
word = self._db.get("wordle", "word", "")
attempts = self._db.get("wordle", "attempts", 0)
buttons = self._db.get("wordle", "buttons", [])
markup = buttons + [[{"text":"Введите слово","input":self.strings("length"),"handler": self.handler}]]
if len(guess) != 5:
await call.edit(self.strings("length"), reply_markup=markup)
return
if guess not in self._db.get("wordle", "words", []):
await call.edit(self.strings("real_word"), reply_markup=markup)
return
buttons2 = []
for i in range(5):
if guess[i] == word[i]:
buttons2.append({"text": guess[i], "data": "custom/data", "style": "success"})
elif guess[i] in word:
buttons2.append({"text": guess[i], "data": "custom/data", "style": "primary"})
else:
buttons2.append({"text": guess[i], "data": "custom/data", "style": "danger"})
buttons.append(buttons2)
if guess == word:
self._db.set("wordle", "buttons", buttons)
self._db.set("wordle", "now_playing", False)
result = ""
for btn in buttons:
for b in btn:
if b["style"] == "success":
result += "🟩"
elif b["style"] == "primary":
result += "🟨"
else:
result += ""
result += "\n"
buttons.append([{"text":"Поделится резултатом","copy":self.strings("ad").format(word, result)}])
await call.edit(f"{self.strings('win').format(word)}", reply_markup=buttons)
return
self._db.set("wordle", "buttons", buttons)
attempts -= 1
self._db.set("wordle", "attempts", attempts)
if attempts == 0:
result = ""
for btn in buttons:
for b in btn:
if b["style"] == "success":
result += "🟩"
elif b["style"] == "primary":
result += "🟨"
else:
result += ""
result += "\n"
buttons.append([{"text":"Поделится резултатом","copy":self.strings("ad").format(word, result)}])
await call.edit(f"{self.strings('gg').format(word)}", reply_markup=buttons)
self._db.set("wordle", "now_playing", False)
else:
await call.edit(f"{self.strings('attempts_left').format(attempts)}", reply_markup=markup)
@loader.command()
async def wordle(self, message: Message):
"""Play wordle!"""
await utils.answer(message, self.strings("loading"))
if self._db.get("wordle", "now_playing", False):
await utils.answer(message, self.strings("already_playing"))
return
args = utils.get_args(message)
if args and args[0].lower():
if args[0].lower() == "--no-sec":
self._db.set("wordle", "nosec", True)
return
else:
self._db.set("wordle", "nosec", False)
try:
response = requests.get(f"https://raw.githubusercontent.com/mimimishka449/Worlde/refs/heads/main/words_{self.config['language']}.txt")
if response.status_code == 200:
words = response.text.splitlines()
word = random.choice(words).upper()
self._db.set("wordle", "now_playing", True)
self._db.set("wordle", "attempts", 6)
self._db.set("wordle", "word", word)
self._db.set("wordle", "words", words)
self._db.set("wordle", "buttons", [])
await self.inline.form(self.strings("have_a_good_game").format("английском" if self.config['language'] == "en" else "русском"), message, reply_markup=[[{"text":"Введите слово","input":self.strings("length"),"handler": self.handler}]])
else:
await utils.answer(message, "Error fetching wordle data.")
except Exception as e:
logger.exception(f"Error: {e}")
await utils.answer(message, "An error occurred while fetching wordle data.")
@loader.command()
async def stopwordle(self, message: Message):
"""Stop the wordle game."""
if not self._db.get("wordle", "now_playing", False):
await utils.answer(message, self.strings("no_game"))
return
self._db.set("wordle", "now_playing", False)
await utils.answer(message, self.strings("ok"))

View File

@@ -1,150 +1,122 @@
#meta developer: @matubuntu # meta developer: @matubuntu
import requests, bs4
from datetime import datetime
from .. import loader, utils
import lxml
# requires: lxml requests bs4 import time
from datetime import datetime
import aiohttp
from .. import loader, utils
_FLAGS = { _FLAGS = {
"AUD": "🇦🇺", "AUD": "🇦🇺", "AZN": "🇦🇿", "GBP": "🇬🇧", "AMD": "🇦🇲",
"AZN": "🇦🇿", "BYN": "🇧🇾", "BGN": "🇧🇬", "BRL": "🇧🇷", "HUF": "🇭🇺",
"GBP": "🇬🇧", "VND": "🇻🇳", "HKD": "🇭🇰", "GEL": "🇬🇪", "DKK": "🇩🇰",
"AMD": "🇦🇲", "AED": "🇦🇪", "USD": "🇺🇸", "EUR": "🇪🇺", "EGP": "🇪🇬",
"BYN": "🇧🇾", "INR": "🇮🇳", "IDR": "🇮🇩", "KZT": "🇰🇿", "CAD": "🇨🇦",
"BGN": "🇧🇬", "QAR": "🇶🇦", "KGS": "🇰🇬", "CNY": "🇨🇳", "MDL": "🇲🇩",
"BRL": "🇧🇷", "NZD": "🇳🇿", "NOK": "🇳🇴", "PLN": "🇵🇱", "RON": "🇷🇴",
"HUF": "🇭🇺", "SGD": "🇸🇬", "TJS": "🇹🇯", "THB": "🇹🇭", "TRY": "🇹🇷",
"VND": "🇻🇳", "TMT": "🇹🇲", "UZS": "🇺🇿", "UAH": "🇺🇦", "CZK": "🇨🇿",
"HKD": "🇭🇰", "SEK": "🇸🇪", "CHF": "🇨🇭", "RSD": "🇷🇸", "ZAR": "🇿🇦",
"GEL": "🇬🇪", "KRW": "🇰🇷", "JPY": "🇯🇵",
"DKK": "🇩🇰",
"AED": "🇦🇪",
"USD": "🇺🇸",
"EUR": "🇪🇺",
"EGP": "🇪🇬",
"INR": "🇮🇳",
"IDR": "🇮🇩",
"KZT": "🇰🇿",
"CAD": "🇨🇦",
"QAR": "🇶🇦",
"KGS": "🇰🇬",
"CNY": "🇨🇳",
"MDL": "🇲🇩",
"NZD": "🇳🇿",
"NOK": "🇳🇴",
"PLN": "🇵🇱",
"RON": "🇷🇴",
"SGD": "🇸🇬",
"TJS": "🇹🇯",
"THB": "🇹🇭",
"TRY": "🇹🇷",
"TMT": "🇹🇲",
"UZS": "🇺🇿",
"UAH": "🇺🇦",
"CZK": "🇨🇿",
"SEK": "🇸🇪",
"CHF": "🇨🇭",
"RSD": "🇷🇸",
"ZAR": "🇿🇦",
"KRW": "🇰🇷",
"JPY": "🇯🇵",
} }
_CRYPTO_EMOJIS = { _CRYPTO_EMOJIS = {
"BTC": "<emoji document_id=5289519973285257969>💰</emoji>", "BTC": "<emoji document_id=5289519973285257969>💰</emoji>",
"ETH": "<emoji document_id=5287735049301550386>💰</emoji>", "ETH": "<emoji document_id=5287735049301550386>💰</emoji>",
"SOL": "<emoji document_id=5251712673258697260>💰</emoji>", "SOL": "<emoji document_id=5251712673258697260>💰</emoji>",
"TON": "<emoji document_id=5289648693455119919>💰</emoji>", "TON": "<emoji document_id=5289648693455119919>💰</emoji>",
"USDT": "<emoji document_id=5289904548951911168>💰</emoji>", "USDT": "<emoji document_id=5289904548951911168>💰</emoji>",
"XRP": "<emoji document_id=5373312921214401986>💰</emoji>", "XRP": "<emoji document_id=5373312921214401986>💰</emoji>",
"USDC": "<emoji document_id=5372958453268497353>💰</emoji>", "USDC": "<emoji document_id=5372958453268497353>💰</emoji>",
"ADA": "<emoji document_id=5373076801092338046>💰</emoji>", "ADA": "<emoji document_id=5373076801092338046>💰</emoji>",
"DOGE": "<emoji document_id=5375192042420842380>💰</emoji>", "DOGE": "<emoji document_id=5375192042420842380>💰</emoji>",
"TRX": "<emoji document_id=5375187081733616165>💰</emoji>", "TRX": "<emoji document_id=5375187081733616165>💰</emoji>",
"AVAX": "<emoji document_id=5375311275007947936>💰</emoji>", "AVAX": "<emoji document_id=5375311275007947936>💰</emoji>",
"LTC": "<emoji document_id=5373035462032113888>💰</emoji>", "LTC": "<emoji document_id=5373035462032113888>💰</emoji>",
"BCH": "<emoji document_id=5375596920397903962>💰</emoji>", "BCH": "<emoji document_id=5375596920397903962>💰</emoji>",
"ATOM": "<emoji document_id=5375468745688889977>💰</emoji>", "ATOM": "<emoji document_id=5375468745688889977>💰</emoji>",
"XLM": "<emoji document_id=5372823290647690288>💰</emoji>", "XLM": "<emoji document_id=5372823290647690288>💰</emoji>",
"SHIB": "<emoji document_id=5375231036428924778>💰</emoji>", "SHIB": "<emoji document_id=5375231036428924778>💰</emoji>",
"UNI": "<emoji document_id=5372953110329180525>💰</emoji>", "UNI": "<emoji document_id=5372953110329180525>💰</emoji>",
"XMR": "<emoji document_id=5375507073977038661>💰</emoji>", "XMR": "<emoji document_id=5375507073977038661>💰</emoji>",
"LINK": "<emoji document_id=5375149651093633217>💰</emoji>", "LINK": "<emoji document_id=5375149651093633217>💰</emoji>",
"ETC": "<emoji document_id=5375543306321146693>💰</emoji>", "ETC": "<emoji document_id=5375543306321146693>💰</emoji>",
"SUI": "<emoji document_id=5391002164929772708>💰</emoji>", "SUI": "<emoji document_id=5391002164929772708>💰</emoji>",
"NEAR": "<emoji document_id=5391181990915487346>💰</emoji>", "NEAR": "<emoji document_id=5391181990915487346>💰</emoji>",
"VET": "<emoji document_id=5391091302681033446>💰</emoji>", "VET": "<emoji document_id=5391091302681033446>💰</emoji>",
"FIL": "<emoji document_id=5373117173784919811>💰</emoji>", "FIL": "<emoji document_id=5373117173784919811>💰</emoji>",
"XTZ": "<emoji document_id=5390985478981829698>💰</emoji>", "XTZ": "<emoji document_id=5390985478981829698>💰</emoji>",
"ALGO": "<emoji document_id=5391337713544738420>💰</emoji>", "ALGO": "<emoji document_id=5391337713544738420>💰</emoji>",
"THETA": "<emoji document_id=5391256014676833736>💰</emoji>", "THETA": "<emoji document_id=5391256014676833736>💰</emoji>",
"FTM": "<emoji document_id=5393179395521263785>💰</emoji>", "FTM": "<emoji document_id=5393179395521263785>💰</emoji>",
"XDAI": "<emoji document_id=5391325992578988886>💰</emoji>", "XDAI": "<emoji document_id=5391325992578988886>💰</emoji>",
"RUNE": "<emoji document_id=5391347570494684983>💰</emoji>", "RUNE": "<emoji document_id=5391347570494684983>💰</emoji>",
"DOT": "<emoji document_id=5375224568208177973>💰</emoji>", "DOT": "<emoji document_id=5375224568208177973>💰</emoji>",
} }
_CRYPTO_LIST = { _CRYPTO_NAMES = {
"BTC": "Bitcoin", "BTC": "Bitcoin", "ETH": "Ethereum", "XMR": "Monero",
"ETH": "Ethereum", "LTC": "Litecoin", "XRP": "XRP", "ADA": "Cardano",
"XMR": "Monero", "DOGE": "Dogecoin", "SOL": "Solana", "DOT": "Polkadot",
"LTC": "Litecoin", "USDT": "Tether", "TON": "Toncoin", "USDC": "USD Coin",
"XRP": "XRP", "TRX": "TRON", "AVAX": "Avalanche", "BCH": "Bitcoin Cash",
"ADA": "Cardano", "ATOM": "Cosmos", "XLM": "Stellar", "SHIB": "Shiba Inu",
"DOGE": "Dogecoin", "UNI": "Uniswap", "LINK": "Chainlink", "ETC": "Ethereum Classic",
"SOL": "Solana", "SUI": "Sui", "NEAR": "NEAR Protocol", "VET": "VeChain",
"DOT": "Polkadot", "FIL": "Filecoin", "XTZ": "Tezos", "ALGO": "Algorand",
"USDT": "Tether", "THETA": "Theta Network", "FTM": "Fantom", "XDAI": "xDai",
"TON": "Toncoin",
"USDC": "USD Coin",
"TRX": "TRON",
"AVAX": "Avalanche",
"BCH": "Bitcoin Cash",
"ATOM": "Cosmos",
"XLM": "Stellar",
"SHIB": "Shiba Inu",
"UNI": "Uniswap",
"LINK": "Chainlink",
"ETC": "Ethereum Classic",
"SUI": "Sui",
"NEAR": "NEAR Protocol",
"VET": "VeChain",
"FIL": "Filecoin",
"XTZ": "Tezos",
"ALGO": "Algorand",
"THETA": "Theta Network",
"FTM": "Fantom",
"XDAI": "xDai",
"RUNE": "THORChain", "RUNE": "THORChain",
} }
def _fmt_num(v, d=3): _CBR_URL = "https://www.cbr.ru/scripts/XML_daily.asp"
p = f"{v:,.{d}f}".replace(",", " ").split(".") _CRYPTO_URL = "https://api.coinlore.net/api/tickers/?limit=100"
i = p[0]
d = p[1].rstrip("0") if len(p) > 1 else "" CACHE_TTL = 300 # seconds
return f"{i},{d}" if d else i
def _fmt_num(value: float, decimals: int = 3) -> str:
if decimals == 0:
return f"{int(value):,}".replace(",", " ")
rounded = round(value, decimals)
int_part = int(rounded)
dec_part = str(rounded - int_part)[2:2 + decimals].rstrip("0")
int_str = f"{int_part:,}".replace(",", " ")
return f"{int_str},{dec_part}" if dec_part else int_str
def _parse_cbr_xml(xml_bytes: bytes) -> tuple[str | None, dict]:
"""Parse CBR XML without bs4/lxml — pure stdlib ElementTree."""
import xml.etree.ElementTree as ET
root = ET.fromstring(xml_bytes)
date_str = root.attrib.get("Date", "")
try:
date = datetime.strptime(date_str, "%d.%m.%Y").strftime("%d.%m.%Y")
except ValueError:
date = date_str
rates: dict[str, dict] = {}
for valute in root.findall("Valute"):
code = valute.findtext("CharCode", "").strip()
if not code or code == "XDR":
continue
try:
nominal = float(valute.findtext("Nominal", "1").replace(",", "."))
value = float(valute.findtext("Value", "0").replace(",", "."))
except ValueError:
continue
rates[code] = {
"name": valute.findtext("Name", code).strip(),
"nominal": nominal,
"rub": value / nominal,
}
return date, rates
@loader.tds @loader.tds
class FinanceMod(loader.Module): class FinanceMod(loader.Module):
strings = { """Курсы валют (ЦБ РФ) и криптовалют (CoinLore)"""
"name": "FinanceMod",
"valute_description": "<кол-во> <код> - курс валюты\n<кол-во> - список", strings = {"name": "FinanceMod"}
"valute_no_args": (
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
"<b>Актуально на</b> <i>{}</i>\n\n<blockquote expandable>{}</blockquote>"
),
"valute_specific": (
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
"<b>Актуально на</b> <i>{}</i>\n\n{}"
),
"valute_not_found": "🚫 Валюта {} не найдена",
"crypto_description": "<кол-во> <код> - курс крипты\n<кол-во> - список",
"crypto_no_args": "💎 <b>Курсы криптовалют</b>\n\n<blockquote expandable>{}</blockquote>",
"crypto_specific": "💎 <b>Курс криптовалюты</b>\n\n{}",
"crypto_not_found": "🚫 Криптовалюта {} не найдена",
"error": "🚫 Ошибка получения данных",
}
def __init__(self): def __init__(self):
self.config = loader.ModuleConfig( self.config = loader.ModuleConfig(
@@ -152,149 +124,194 @@ class FinanceMod(loader.Module):
"crypto_currency", "crypto_currency",
"USD", "USD",
lambda: "Валюта для отображения крипты (USD, RUB, EUR)", lambda: "Валюта для отображения крипты (USD, RUB, EUR)",
validator=loader.validators.Choice(["USD", "RUB", "EUR"]) validator=loader.validators.Choice(["USD", "RUB", "EUR"]),
) )
) )
# Simple in-process cache
self._cbr_cache: tuple[float, str, dict] | None = None # (ts, date, rates)
self._crypto_cache: tuple[float, list] | None = None # (ts, data)
async def _get_curr_data(self): # ──────────────────────────── HTTP helpers ────────────────────────────
async def _fetch(self, url: str, *, as_json: bool = False):
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
resp.raise_for_status()
return await resp.json() if as_json else await resp.read()
# ──────────────────────────── CBR data ────────────────────────────────
async def _cbr_data(self) -> tuple[str | None, dict]:
now = time.monotonic()
if self._cbr_cache and now - self._cbr_cache[0] < CACHE_TTL:
return self._cbr_cache[1], self._cbr_cache[2]
try: try:
r = requests.get("https://www.cbr.ru/scripts/XML_daily.asp") raw = await self._fetch(_CBR_URL)
s = bs4.BeautifulSoup(r.content, 'xml') date, rates = _parse_cbr_xml(raw)
d = datetime.strptime(s.ValCurs['Date'], "%d.%m.%Y").strftime("%d.%m.%Y") self._cbr_cache = (now, date, rates)
return d, s.find_all('Valute') return date, rates
except: except Exception:
return None, None if self._cbr_cache:
return self._cbr_cache[1], self._cbr_cache[2]
return None, {}
async def _get_rates(self): # ──────────────────────────── Crypto data ─────────────────────────────
async def _crypto_data(self) -> list:
now = time.monotonic()
if self._crypto_cache and now - self._crypto_cache[0] < CACHE_TTL:
return self._crypto_cache[1]
try: try:
r = requests.get("https://www.cbr.ru/scripts/XML_daily.asp") js = await self._fetch(_CRYPTO_URL, as_json=True)
s = bs4.BeautifulSoup(r.content, 'xml') data = js.get("data", [])
rt = {'USD': None, 'EUR': None} self._crypto_cache = (now, data)
for v in s.find_all('Valute'): return data
if v.CharCode.text in ['USD', 'EUR']: except Exception:
n = float(v.Nominal.text.replace(',', '.')) return self._crypto_cache[1] if self._crypto_cache else []
vl = float(v.Value.text.replace(',', '.'))
rt[v.CharCode.text] = vl / n
if rt['USD'] and rt['EUR']:
rt['EUR_USD'] = rt['USD'] / rt['EUR']
else:
rt['EUR_USD'] = None
return rt
except:
return None
async def _fmt_curr(self, v, a=1): # ──────────────────────────── Formatters ──────────────────────────────
if v.CharCode.text == "XDR":
return None
c = v.CharCode.text
n = v.Name.text
v = float(v.Value.text.replace(',', '.')) / float(v.Nominal.text.replace(',', '.'))
t = v * a
ts = _fmt_num(t, 3)
return f"{_FLAGS.get(c, '🏳')} [{a}] {n} ({c}) - {ts} руб."
async def _get_crypto(self): def _fmt_valute(self, code: str, info: dict, amount: float = 1.0) -> str:
total = info["rub"] * amount
flag = _FLAGS.get(code, "🏳")
return f"{flag} [{_fmt_num(amount, 0)}] {info['name']} ({code}) — {_fmt_num(total, 3)}"
def _fmt_crypto(self, coin: dict, rates: dict, amount: float = 1.0) -> str:
symbol = coin["symbol"].upper()
try: try:
return requests.get("https://api.coinlore.net/api/tickers/").json().get('data', []) price_usd = float(coin["price_usd"])
except: except (KeyError, ValueError, TypeError):
return None return ""
async def _fmt_crypto(self, c, a=1): currency = self.config["crypto_currency"]
r = await self._get_rates() if currency == "RUB":
if not r: usd_rate = rates.get("USD", {}).get("rub")
return "🚫 Ошибка получения курсов валют" if not usd_rate:
cr = self.config["crypto_currency"] return ""
try: price = price_usd * usd_rate
p = float(c['price_usd']) sign = ""
except: elif currency == "EUR":
return "🚫 Ошибка данных криптовалюты" usd_rate = rates.get("USD", {}).get("rub")
if cr == "RUB": eur_rate = rates.get("EUR", {}).get("rub")
if not r['USD']: if not usd_rate or not eur_rate:
return "🚫 Курс USD не найден" return ""
p *= r['USD'] price = price_usd * (usd_rate / eur_rate)
elif cr == "EUR": sign = ""
if not r['EUR_USD']: else:
return "🚫 Курс EUR/USD не рассчитан" price = price_usd
p *= r['EUR_USD'] sign = "$"
t = p * a
ts = _fmt_num(t)
s = c['symbol'].upper()
e = _CRYPTO_EMOJIS.get(s, "💠")
n = _CRYPTO_LIST.get(s, c['name'])
cs = {"USD": "$", "RUB": "", "EUR": ""}.get(cr, "$")
return f"{e} [{a}] {n} ({s}) - {ts}{cs}"
@loader.command() total = price * amount
async def valutecmd(self, m): emoji = _CRYPTO_EMOJIS.get(symbol, "💠")
"""[count] [usd, eur, ...]""" name = _CRYPTO_NAMES.get(symbol, coin.get("name", symbol))
a = utils.get_args(m) return f"{emoji} [{_fmt_num(amount, 0)}] {name} ({symbol}) — {_fmt_num(total, 3)}{sign}"
d, v = await self._get_curr_data()
if not d or not v: # ──────────────────────────── Commands ────────────────────────────────
return await utils.answer(m, self.strings["error"])
if len(a) == 0: @loader.command(ru_doc="[кол-во] [код] — курс валюты по ЦБ РФ")
l = [] async def valutecmd(self, message):
for x in v: """[amount] [code] — exchange rates from CBR"""
if (n := await self._fmt_curr(x)): args = utils.get_args(message)
l.append(n) date, rates = await self._cbr_data()
await utils.answer(m, self.strings["valute_no_args"].format(d, "\n".join(l)))
elif len(a) == 1: if not rates:
return await utils.answer(message, "🚫 Не удалось получить данные ЦБ РФ")
header = (
f"💵 <b>Курс валюты</b> · <a href='https://www.cbr.ru/'>ЦБ РФ</a>\n"
f"<b>Актуально на</b> <i>{date}</i>\n\n"
)
# .valute — список всех, кол-во = 1
if not args:
lines = [self._fmt_valute(c, i) for c, i in rates.items()]
return await utils.answer(
message,
header + f"<blockquote expandable>{chr(10).join(lines)}</blockquote>",
)
# Первый аргумент: число или код валюты?
amount = 1.0
code = None
arg0 = args[0].upper()
if len(args) >= 2:
# .valute 100 USD
try: try:
am = float(a[0]) amount = float(args[0].replace(",", "."))
l = [] except ValueError:
for x in v: return await utils.answer(message, "🚫 Некорректное число")
if (n := await self._fmt_curr(x, am)): code = args[1].upper()
l.append(n) else:
await utils.answer(m, self.strings["valute_no_args"].format(d, "\n".join(l))) # .valute USD или .valute 100
except:
await utils.answer(m, "🚫 Некорректное число")
elif len(a) == 2:
try: try:
am = float(a[0]) amount = float(arg0.replace(",", "."))
c = a[1].upper() # число без кода — список с умножением
for x in v: except ValueError:
if x.CharCode.text == c: code = arg0
if (n := await self._fmt_curr(x, am)):
return await utils.answer(m, self.strings["valute_specific"].format(d, n))
await utils.answer(m, self.strings["valute_not_found"].format(c))
except:
await utils.answer(m, "🚫 Некорректное число")
@loader.command() if code:
async def cryptocmd(self, m): if code not in rates:
"""[count] [ton, btc, ...]""" return await utils.answer(message, f"🚫 Валюта <b>{code}</b> не найдена")
a = utils.get_args(m) line = self._fmt_valute(code, rates[code], amount)
c = await self._get_crypto() return await utils.answer(message, header + line)
if not c:
return await utils.answer(m, self.strings["error"]) # список с кол-вом
try: lines = [self._fmt_valute(c, i, amount) for c, i in rates.items()]
if len(a) == 0: await utils.answer(
f = [x for x in c if x['symbol'].upper() in _CRYPTO_LIST] message,
l = [] header + f"<blockquote expandable>{chr(10).join(lines)}</blockquote>",
for x in f: )
if (n := await self._fmt_crypto(x)):
l.append(n) @loader.command(ru_doc="[кол-во] [код] — курс крипты")
await utils.answer(m, self.strings["crypto_no_args"].format("\n".join(l))) async def cryptocmd(self, message):
elif len(a) == 1: """[amount] [symbol] — crypto rates from CoinLore"""
am = float(a[0]) args = utils.get_args(message)
f = [x for x in c if x['symbol'].upper() in _CRYPTO_LIST] coins = await self._crypto_data()
l = [] _, rates = await self._cbr_data()
for x in f:
if (n := await self._fmt_crypto(x, am)): if not coins:
l.append(n) return await utils.answer(message, "🚫 Не удалось получить данные крипты")
await utils.answer(m, self.strings["crypto_no_args"].format("\n".join(l)))
elif len(a) == 2: header = f"💎 <b>Курсы криптовалют</b> · <i>{self.config['crypto_currency']}</i>\n\n"
am = float(a[0])
t = a[1].upper() amount = 1.0
f = False symbol = None
for x in c:
if x['symbol'].upper() == t: if not args:
if (n := await self._fmt_crypto(x, am)): pass # список, amount=1
f = True elif len(args) == 1:
await utils.answer(m, self.strings["crypto_specific"].format(n)) try:
break amount = float(args[0].replace(",", "."))
if not f: except ValueError:
await utils.answer(m, self.strings["crypto_not_found"].format(t)) symbol = args[0].upper()
except ValueError: else:
await utils.answer(m, "🚫 Некорректное число") try:
except Exception as e: amount = float(args[0].replace(",", "."))
await utils.answer(m, f"🚫 Ошибка: {str(e)}") except ValueError:
return await utils.answer(message, "🚫 Некорректное число")
symbol = args[1].upper()
if symbol:
coin = next((c for c in coins if c["symbol"].upper() == symbol), None)
if not coin:
return await utils.answer(message, f"🚫 Крипта <b>{symbol}</b> не найдена")
line = self._fmt_crypto(coin, rates, amount)
if not line:
return await utils.answer(message, "🚫 Ошибка форматирования")
return await utils.answer(message, header + line)
# список только известных монет
known = {c["symbol"].upper(): c for c in coins if c["symbol"].upper() in _CRYPTO_NAMES}
# сортируем по порядку _CRYPTO_NAMES
lines = []
for sym in _CRYPTO_NAMES:
if sym in known:
line = self._fmt_crypto(known[sym], rates, amount)
if line:
lines.append(line)
await utils.answer(
message,
header + f"<blockquote expandable>{chr(10).join(lines)}</blockquote>",
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
ChatCopy.py
Gemini.py
GiftFinder.py
MaillingChatGT99.py
NekoEditorMod.py

View File

@@ -0,0 +1,97 @@
# requires: Pillow numpy
# meta developer: @SunnexGB
# meta banner: https://i.pinimg.com/control1/1200x/24/8d/40/248d40b6afa5bd3c3764556b50635691.jpg
__version__ = (1, 0, 0)
import io
import logging
from herokutl.types import Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class ASCII(loader.Module):
"""Convert images to braille ASCII"""
strings = {
"name": "ASCII",
"no_lib": "<tg-emoji emoji-id=5447385112612208213>🚫</tg-emoji> | <b>Library not loaded</b>",
"no_image": "<tg-emoji emoji-id=5447381715293074599>⚠️</tg-emoji> | <b>Reply to image</b>",
"processing": "<tg-emoji emoji-id=5445373981290952548>®️</tg-emoji> | <b>Processing...</b>",
"empty": "<tg-emoji emoji-id=5287613115180006030>🤬</tg-emoji> | <b>Empty result</b>",
"result": "<pre>{art}</pre>",
"Failed_to_load_library": "Failed to load library",
"Conversion_error": "Conversion error",
}
strings_ru = {
"_cls_doc": "Конвертирует картинку в braille ASCII",
"no_lib": "<tg-emoji emoji-id=5447385112612208213>🚫</tg-emoji> | <b>Библиотека не была загружена</b>",
"no_image": "<tg-emoji emoji-id=5447381715293074599>⚠️</tg-emoji> | <b>Ответьте на картинку</b>",
"processing": "<tg-emoji emoji-id=5445373981290952548>®️</tg-emoji> | <b>Обработка...</b>",
"empty": "<tg-emoji emoji-id=5287613115180006030>🤬</tg-emoji> | <b>Пустой результат</b>",
"result": "<pre>{art}</pre>",
"Failed_to_load_library": "Не удалось загрузить библиотеку",
"Conversion_error": "Ошибка конвертации",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue("width", 50),
loader.ConfigValue("threshold", 0.65),
loader.ConfigValue("contrast", 2.0),
loader.ConfigValue("chars", 464),
loader.ConfigValue("invert", False),
)
self.lib = None
async def client_ready(self):
try:
self.lib = await self.import_lib("https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/Assets/ASCII/ascii-lib.py", suspend_on_error=True)
except Exception:
logger.exception(self.strings["Failed_to_load_library"])
self.lib = None
@loader.command(ru_doc="- Отрисовать ASCII-ART (аргумент -f, отправляет файлом)")
async def dotcmd(self, message: Message):
"""- Draw ASCII-ART (argument -f, sends as a file)"""
if not self.lib:
return await utils.answer(message, self.strings["no_lib"])
args = utils.get_args_raw(message)
force_file = "-f" in args.lower()
reply = await message.get_reply_message() or message
if not reply or not (
reply.photo
or (
reply.document
and str(getattr(reply.document, "mime_type", "")).startswith("image/")
)
):
return await utils.answer(message, self.strings["no_image"])
msg = await utils.answer(message, self.strings["processing"])
try:
image_bytes = await reply.download_media(bytes)
art = self.lib.convert(
image_bytes,
width=self.config["width"],
threshold=self.config["threshold"],
contrast_boost=self.config["contrast"],
invert=self.config["invert"],
target_chars=self.config["chars"],
)
except Exception as e:
logger.exception(self.strings["Conversion_error"])
return await utils.answer(msg, f"<pre>{e}</pre>")
if not art or not art.strip():
return await utils.answer(msg, self.strings["empty"])
formatted_art = self.strings("result").format(art=art)
if force_file or len(formatted_art) > 4096:
file = io.BytesIO(art.encode("utf-8"))
file.name = "ascii.txt"
await message.client.send_file(message.peer_id, file)
await msg.delete()
else:
await utils.answer(msg, formatted_art)

View File

@@ -0,0 +1,110 @@
# requires: Pillow numpy
# Дикие оправдания по поводу именно этого ассета а точнее кода в нем,честно я не знаю что сказать была попытка переписать JS на Py и как бы особых проблем не было,
# до момента пост-обработки на помощь я позвал Claude и он не решил мою проблему от слова совсем,так как в целом я своего рода призираю пилоу,а модуль мне хотелось
# написать я примерно вайб-кодил около 50 минут и я уверен из за этого будет возможно много проблем,в итоге благодаря немного копанию в коде,я нашел проблему и уже
# начал ее решать,НО я опять же вообще не понимал как сделать то что мне нужно,в интернете были сюрсы но будто бы тот или иной мне не подходили? Я не знаю почему я
# дропнул эту идею. Потом я стал искать в JS-е что там вообще можно сделать,в итоге я там импортировал модель какую то блядскую не нужную и опять впустую время
# потратил,думал что тут определено есть решение и снова пошел к ии,вывод опятьь 0 помощи,я не знаю почему я так вцепился лишь в 1 идею.Как бы я мог упростить все,
# даже наверное просто попросив какую то флагмен ии написать модуль и переписать его,но я уже на тот момент по моему мнению сделал много и не хотел ни каким образом
# оставлять это,поэтому через время я нашел сайты которые в целом давали возможность настраивать фильтр,была переделана логика(в целом ее переделал на 60 процентов
# клод,я просто убирал мусор который он испражнял.И вот дальше точно бред я убил более дня на решение проблем которые были решены мной,но результат мне не нравился
# И ОПЯТЬ я пошел просить помощи у гугла,потом понял что возможно даже будет легко(по факту легко,но я ленивый) пока искал,мне перехотелось и я уже потом пытался
# сделать режимы в модуле,что оказалось ужасом ведь они работали,но при возможности гармонично вписать их в код были конфликты И Я В ОЧЕРЕДНОЙ РАЗ ПОШЕЛ К ИИ,спойлер
# он не смог написать лучше чем я,в итоге я отбросил эту идею и думаю в целом никак больше не апдейтать модуль по крупному.
# Да это были оправдания,но зато какие!
import io
import numpy as np
from PIL import Image, ImageFilter, ImageEnhance, ImageOps
from .. import loader
BASE = 0x2800
INVERT_MAP = {chr(BASE + c): chr(BASE + (c ^ 0xFF)) for c in range(256)}
class AsciiLib(loader.Library):
developer = "@SunnexGB"
def resize(self, img):
if img.width > 768:
img = img.resize((768, int(img.height * 768 / img.width)), Image.LANCZOS)
w = img.width - img.width % 4
h = img.height - img.height % 4
if w != img.width or h != img.height:
img = img.resize((w, h), Image.LANCZOS)
return img
def mode(self, img, threshold, contrast):
gray = img.convert("L")
edges = ImageOps.invert(gray.filter(ImageFilter.FIND_EDGES))
contrast_img = ImageEnhance.Contrast(img).enhance(contrast).convert("L")
e = np.array(edges, dtype=np.float32) / 255.0
c = np.array(contrast_img, dtype=np.float32) / 255.0
blended = Image.fromarray((e * c * 255).astype(np.uint8), "L")
t = int(threshold * 255)
processed = blended.point(lambda p: 255 if p > t else 0, "L")
return processed, t
def braille(self, img, threshold, width):
cw = width * 2
o = -(-round(cw * img.height / img.width) // 4)
ch = 4 * o
px = np.array(img.resize((cw, ch), Image.LANCZOS).convert("L"))
order = [(0,0),(1,0),(2,0),(0,1),(1,1),(2,1),(3,0),(3,1)]
rows = []
for rs in range(0, ch, 4):
line = []
for cs in range(0, cw, 2):
grays = [
int(px[rs+dy, cs+dx]) if (rs+dy < ch and cs+dx < cw) else 255
for dy, dx in order
]
bits = list(reversed([1 if g < threshold else 0 for g in grays]))
code = int("".join(str(b) for b in bits), 2)
line.append(chr(BASE + code))
rows.append("".join(line))
return rows
def trim(self, lines):
blank = "\u2800"
while lines and all(c == blank for c in lines[0]):
lines = lines[1:]
while lines and all(c == blank for c in lines[-1]):
lines = lines[:-1]
if not lines:
return lines
left = min(next((i for i,c in enumerate(r) if c!=blank), len(r)) for r in lines)
right = min(next((i for i,c in enumerate(reversed(r)) if c!=blank), len(r)) for r in lines)
return [r[left: len(r)-right if right else len(r)] for r in lines]
def invert(self, lines):
return ["".join(INVERT_MAP.get(c,c) for c in l) for l in lines]
def fit(self, img, threshold, chars, width):
lo, hi = 5, 200
best = ""
for _ in range(14):
mid = (lo + hi)//2
lines = self.trim(self.braille(img, threshold, mid))
art = "\n".join(lines)
if len(art) <= chars:
best = art
lo = mid + 1
else:
hi = mid - 1
return best
def convert(self, data, width=50, threshold=0.65, contrast_boost=2.0, invert=False, target_chars=0):
buf = io.BytesIO(data)
img = Image.open(buf)
img.load()
buf.close()
img = img.convert("RGB")
img = self.resize(img)
processed, t = self.mode(img, threshold, contrast_boost)
if target_chars > 0:
art = self.fit(processed, t, target_chars, width)
else:
art = "\n".join(self.trim(self.braille(processed, t, width)))
if invert and art:
art = "\n".join(self.invert(art.split("\n")))
return art

View File

@@ -0,0 +1,832 @@
{
"prologue": [
{
"type": "label",
"name": "prologue"
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_1",
"action": "load_asset",
"location": "anim/prolog_1",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_1.jpg?raw=true"
},
{
"type": "narration",
"text": "Мне опять снился сон."
},
{
"type": "narration",
"text": "<i>Этот</i> сон..."
},
{
"type": "narration",
"text": "Каждую ночь одно и то же."
},
{
"type": "narration",
"text": "Но наутро, как обычно, всё забудется."
},
{
"type": "narration",
"text": "Может быть, оно и к лучшему..."
},
{
"type": "narration",
"text": "Останутся только туманные воспоминания о приоткрытых, словно приглашающих куда-то воротах, рядом с которыми в камне застыли два пионера."
},
{
"type": "narration",
"text": "А ещё странная девочка...{w} которая постоянно спрашивает:"
},
{
"type": "scene",
"kind": "bg",
"name": "anim_prolog1_off",
"action": "load_asset",
"location": "anim/anim_prolog1_off",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/anim_prolog1_off.gif?raw=true"
},
{
"type": "dialogue",
"char_id": "dreamgirl",
"character": "...",
"text": "Ты пойдёшь со мной?"
},
{
"type": "narration",
"text": "Пойду?.."
},
{
"type": "narration",
"text": "Но куда?"
},
{
"type": "narration",
"text": "И зачем?.."
},
{
"type": "narration",
"text": "Да и где я вообще нахожусь?"
},
{
"type": "narration",
"text": "Конечно, случись всё на самом деле, наяву, стоило бы непременно испугаться."
},
{
"type": "narration",
"text": "Как же иначе!"
},
{
"type": "narration",
"text": "Но это всего лишь сон.{w} Тот самый, который я вижу каждую ночь."
},
{
"type": "narration",
"text": "А ведь всё это неспроста!"
},
{
"type": "narration",
"text": "Необязательно знать <i>где</i> и <i>почему</i>, чтобы понять что-то происходит."
},
{
"type": "narration",
"text": "Нечто, отчаянно требующее моего внимания."
},
{
"type": "narration",
"text": "Ведь всё окружающее меня здесь реально!"
},
{
"type": "narration",
"text": "Реально настолько, насколько реальны вещи в моей квартире; я бы мог открыть ворота, услышать скрип петель, смахнуть рукой осыпающуюся ржавчину, потянуть носом свежий прохладный воздух и поёжиться от холода."
},
{
"type": "narration",
"text": "Мог бы, но для этого надо сдвинуться с места, сделать шаг, пошевелить рукой..."
},
{
"type": "narration",
"text": "А ведь это сон я понимаю, но что дальше, что изменит моё <i>понимание</i>?"
},
{
"type": "narration",
"text": "Ведь здесь словно по ту сторону потрескавшегося экрана старого телевизора, который из последних сил борется с помехами и силится показать зрителям всё, не упустив ни малейшей детали."
},
{
"type": "narration",
"text": "Но вот картинка теряет чёткость...{w} Наверное, скоро просыпаться."
},
{
"type": "narration",
"text": "..."
},
{
"type": "narration",
"text": "Может быть, спросить у неё что-то?{w} У девочки."
},
{
"type": "narration",
"text": "Как же её зовут..."
},
{
"type": "narration",
"text": "Например про звёзды..."
},
{
"type": "narration",
"text": "Хотя почему про звёзды?"
},
{
"type": "narration",
"text": "Можно же спросить про ворота!{w} Да, про ворота!"
},
{
"type": "narration",
"text": "Вот она удивится."
},
{
"type": "narration",
"text": "Или лучше про букву <i>ё</i>."
},
{
"type": "narration",
"text": "Хорошая была буква..."
},
{
"type": "narration",
"text": "Как будто её больше нет!"
},
{
"type": "narration",
"text": "И какое отношение буквы, ворота и звёзды имеют к этому месту?"
},
{
"type": "narration",
"text": "Ведь если мне каждую ночь снится <i>этот</i> сон, который потом всё равно забудется, надо искать разгадку здесь и сейчас!"
},
{
"type": "narration",
"text": "А вот, если присмотреться, можно увидеть Магелланово Облако..."
},
{
"type": "narration",
"text": "Словно попал в южное полушарие!"
},
{
"type": "narration",
"text": "..."
},
{
"type": "narration",
"text": "Во сне всегда больше волнуют мелочи: неестественный цвет травы, невозможная кривизна прямых или своё перекошенное отражение а реальная опасность, готовая оборвать всё здесь и сейчас, кажется пустяком."
},
{
"type": "narration",
"text": "Естественно, ведь <i>здесь</i> нельзя умереть."
},
{
"type": "narration",
"text": "Я точно знаю я делал это сотни раз."
},
{
"type": "narration",
"text": "Но если нельзя умереть, нет смысла жить?"
},
{
"type": "narration",
"text": "Надо будет спросить у девочки: она местная должна знать!"
},
{
"type": "narration",
"text": "Да, именно!{w} Спросить, например, про сову."
},
{
"type": "narration",
"text": "Больно уж птица странная..."
},
{
"type": "narration",
"text": "А впрочем, неважно..."
},
{
"type": "narration",
"text": "..."
},
{
"type": "dialogue",
"char_id": "dreamgirl",
"character": "...",
"text": "Ты пойдёшь со мной?"
},
{
"type": "narration",
"text": "И каждый раз надо отвечать."
},
{
"type": "narration",
"text": "Иначе никак, иначе сон не закончится, а я не проснусь."
},
{
"type": "route",
"id": "prologue_choice_1"
},
{
"type": "narration",
"text": "Каждый раз так сложно решить, что же ответить."
},
{
"type": "narration",
"text": "Где я, что я здесь делаю, кто она такая?"
},
{
"type": "narration",
"text": "И почему от ответа на этот вопрос зависит так много в моей жизни?"
},
{
"type": "narration",
"text": "Или не зависит?.."
},
{
"type": "narration",
"text": "Ведь это просто сон..."
},
{
"type": "narration",
"text": "Просто сон..."
},
{
"type": "scene",
"kind": "bg",
"name": "black",
"action": "load_asset",
"location": "bg/black",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/bg/black.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "1_prologue",
"action": "load_asset",
"location": "cg/p_kb_1",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/cg/p_kb_1.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "2_prologue",
"action": "load_asset",
"location": "cg/p_kb_2",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/cg/p_kb_2.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "3_prologue",
"action": "load_asset",
"location": "cg/p_kb_3",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/cg/p_kb_3.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "4_prologue",
"action": "load_asset",
"location": "cg/p_kb_4",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/cg/p_kb_4.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "5_prologue",
"action": "load_asset",
"location": "cg/p_kb_5",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/cg/p_kb_5.png?raw=true"
},
{
"type": "narration",
"text": "Экран монитора смотрел на меня словно живой."
},
{
"type": "narration",
"text": "Иногда мне правда казалось, что он обладает сознанием, своими мыслями и желаниями, стремлениями; умеет чувствовать, любить и страдать."
},
{
"type": "narration",
"text": "Словно в наших отношениях инструмент не он неодушевлённый кусок пластика и текстолита, а я."
},
{
"type": "narration",
"text": "Наверное, в этом есть доля правды, ведь компьютер на 90% обеспечивает моё общение с внешним миром."
},
{
"type": "narration",
"text": "Анонимные имиджборды, иногда какие-то чаты, редко аська или джаббер, ещё реже форумы."
},
{
"type": "narration",
"text": "А людей, сидящих по ту сторону сетевого кабеля, попросту не существует!"
},
{
"type": "narration",
"text": "Все они всего лишь плод его больной фантазии, ошибка в программном коде или баг ядра, зажившего собственной жизнью."
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_15",
"action": "load_asset",
"location": "anim/prolog_15",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_15.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_3",
"action": "load_asset",
"location": "anim/prolog_3",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_3.png?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_4",
"action": "load_asset",
"location": "anim/prolog_4",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_4.png?raw=true"
},
{
"type": "narration",
"text": "Если посмотреть со стороны на моё существование, то такие мысли покажутся не столь уж бредовыми, а какой-нибудь психолог наверняка поставит мне кучу заумных диагнозов и, возможно, выпишет направление в жёлтый дом."
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_5",
"action": "load_asset",
"location": "anim/prolog_5",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_5.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_14",
"action": "load_asset",
"location": "anim/prolog_14",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_14.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_11",
"action": "load_asset",
"location": "anim/prolog_11",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_11.jpg?raw=true"
},
{
"type": "narration",
"text": "Маленькая квартирка без следов какого бы то ни было ремонта или даже подобия порядка, и вечно одинаковый вид из окна на серый, день и ночь куда-то бегущий мегаполис, вот условия моей жизни."
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_2",
"action": "load_asset",
"location": "anim/prolog_2",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_2.jpg?raw=true"
},
{
"type": "narration",
"text": "Конечно, всё начиналось не так..."
},
{
"type": "narration",
"text": "Я родился, пошёл в школу, закончил её всё как у людей."
},
{
"type": "narration",
"text": "Поступил в институт, где кое-как промучился полтора курса."
},
{
"type": "narration",
"text": "Работал на паре-тройке разных работ.{w} Иногда даже и неплохо, иногда даже получая за это достойные деньги."
},
{
"type": "narration",
"text": "Однако всё это казалось чужим, словно списанным с биографии другого человека."
},
{
"type": "narration",
"text": "Я не ощущал полноту жизни она словно зациклилась и продолжала идти по кругу.{w} Как в фильме «День сурка»."
},
{
"type": "narration",
"text": "Только у меня не было выбора, как именно провести этот день, и каждый раз всё повторялось по одной и той же схеме.{w} Схеме пустоты, уныния и отчаяния."
},
{
"type": "narration",
"text": "Последние несколько лет я просто целыми днями сидел за компьютером."
},
{
"type": "narration",
"text": "Иногда подворачивались какие-то халтурки, иногда помогали родители."
},
{
"type": "narration",
"text": "В общем, на жизнь хватало."
},
{
"type": "narration",
"text": "Это и немудрено, ведь потребности у меня небольшие."
},
{
"type": "narration",
"text": "На улицу я практически не выхожу, а всё моё общение с людьми сводится к интернет-переписке с <i>анонимами</i>, у которых нет ни реального имени, ни пола, ни возраста."
},
{
"type": "narration",
"text": "Короче говоря, достаточно типичная жизнь достаточно типичного асоциального человека своего времени.{w} Этакий Обломов XXI века."
},
{
"type": "narration",
"text": "Может быть, маститый писатель напишет обо мне роман, который станет классикой современной литературы.{w} Или напишу я сам…"
},
{
"type": "narration",
"text": "Впрочем нет, что себя обманывать уже не раз пытался, но меня не хватало даже на короткий рассказ."
},
{
"type": "narration",
"text": "Изучал я и множество других вещей."
},
{
"type": "narration",
"text": "Рисовать не дано от природы.{w} Программирование надоело.{w} Иностранные языки долго и скучно…"
},
{
"type": "narration",
"text": "Любил я разве что читать, но даже при этом никогда бы не назвал себя эрудированным человеком."
},
{
"type": "narration",
"text": "Возможно, я был асом в просмотре аниме и гроссмейстером неумелых шуточек в интернете."
},
{
"type": "narration",
"text": "Плати мне за это деньги, я бы обрадовался (да и заработал неплохо), но вряд ли так просто можно заполнить пустоту в душе."
},
{
"type": "scene",
"kind": "bg",
"name": "semen_room_window",
"action": "load_asset",
"location": "bg/semen_room_window",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/bg/semen_room_window.jpg?raw=true"
},
{
"type": "narration",
"text": "Сегодня очередной типичный день моей типичной жизни типичного неудачника."
},
{
"type": "narration",
"text": "И именно сегодня мне нужно ехать на встречу институтских товарищей."
},
{
"type": "narration",
"text": "По правде говоря, совершенно не хотелось."
},
{
"type": "narration",
"text": "Да и какой смысл, если вместе с ними я отучился всего ничего?"
},
{
"type": "narration",
"text": "Однако меня всё же уговорил друг, бывший одногруппник, один из немногих, с кем я поддерживал контакт не только в интернете."
},
{
"type": "scene",
"kind": "anim",
"name": "intro_1",
"action": "load_asset",
"location": "anim/intro_1",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_1.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_2",
"action": "load_asset",
"location": "anim/intro_2",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_2.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_3",
"action": "load_asset",
"location": "anim/intro_3",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_3.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_4",
"action": "load_asset",
"location": "anim/intro_4",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_4.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_5",
"action": "load_asset",
"location": "anim/intro_5",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_5.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_6",
"action": "load_asset",
"location": "anim/intro_6",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_6.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_8",
"action": "load_asset",
"location": "anim/intro_8",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_8.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_7",
"action": "load_asset",
"location": "anim/intro_7",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_7.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "bg",
"name": "bus_stop",
"action": "load_asset",
"location": "bg/bus_stop",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/bg/bus_stop.jpg?raw=true"
},
{
"type": "narration",
"text": "Вечер. Мороз.{w} Остановка и ожидание автобуса."
},
{
"type": "narration",
"text": "Я никогда не любил зиму.{w} Впрочем, и жаркое лето тоже не моя стихия."
},
{
"type": "narration",
"text": "Просто не вижу смысла выделять какое-то одно время года не столь важно, какая погода на улице, если ты целыми днями сидишь дома."
},
{
"type": "narration",
"text": "Автобус сегодня задерживался так сильно, что я уже был готов плюнуть на всё и потратить последнюю пару сотен на такси (совсем не ехать мне почему-то в голову не пришло)."
},
{
"type": "narration",
"text": "В мозгу, как всегда, роились миллионы мыслей, из которых совершенно невозможно выудить хотя бы одну стоящую."
},
{
"type": "narration",
"text": "Такую, которую можно закончить, привести в порядок, облечь в форму идеи и претворить в жизнь."
},
{
"type": "narration",
"text": "Может быть, заняться бизнесом?{w} Но откуда я возьму деньги?"
},
{
"type": "narration",
"text": "Или пойти опять работать в офис?{w} Нет уж!"
},
{
"type": "narration",
"text": "Может, стоит попробовать фриланс?{w} Да что я умею, и кому я нужен…"
},
{
"type": "scene",
"kind": "anim",
"name": "prolog_2",
"action": "load_asset",
"location": "anim/prolog_2",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/prolog_2.jpg?raw=true"
},
{
"type": "narration",
"text": "Вдруг мне вспомнилось детство…{w} Или скорее юношество 15-17 лет."
},
{
"type": "narration",
"text": "Почему именно это время?{w} Не знаю."
},
{
"type": "narration",
"text": "Наверное, потому что тогда всё было проще."
},
{
"type": "narration",
"text": "Было проще принимать такие сложные сейчас и такие простые тогда решения."
},
{
"type": "narration",
"text": "Проснувшись с утра, я чётко знал, как пройдёт мой день, а выходных ждал с нетерпением смогу отдохнуть, заняться любимыми делами: компьютер, футбол, встречи с друзьями."
},
{
"type": "narration",
"text": "А потом, когда наступит новая неделя, вновь примусь за учёбу."
},
{
"type": "narration",
"text": "Ведь раньше не возникало этих мучительных вопросов «зачем», «кому это надо», «что изменится, если я это сделаю» или «что не изменится»."
},
{
"type": "narration",
"text": "Простой поток жизни, такой привычный для любого нормального человека и такой чуждый для меня теперешнего."
},
{
"type": "narration",
"text": "Время беззаботного детства…{w} Тогда же я и встретил свою первую любовь."
},
{
"type": "narration",
"text": "Стёрлись из памяти её внешность, характер."
},
{
"type": "narration",
"text": "Как строчка из профиля в социальной сети осталось лишь имя, да те чувства, которые захлёстывали меня, когда я был с ней.{w} Теплота, нежность, желание заботиться, защитить…"
},
{
"type": "narration",
"text": "Жаль, что это продолжалось так недолго."
},
{
"type": "narration",
"text": "Сейчас я уже с трудом могу себе представить что-то подобное."
},
{
"type": "narration",
"text": "Наверное, и хочется познакомиться с девушкой, только не знаю, как начать диалог, о чём вообще с ней говорить, чем её заинтересовать."
},
{
"type": "narration",
"text": "Да и подходящих девушек я давно не встречал.{w} Хотя где мне их встретить…"
},
{
"type": "scene",
"kind": "anim",
"name": "intro_9",
"action": "load_asset",
"location": "anim/intro_9",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_9.jpg"
},
{
"type": "narration",
"text": "Звук работающего двигателя вернул меня к реальности."
},
{
"type": "narration",
"text": "Подъехал автобус."
},
{
"type": "narration",
"text": "«Какой-то он не такой» мелькнула мысль."
},
{
"type": "narration",
"text": "Впрочем, какая разница по этому маршруту ходит только 410-ый."
},
{
"type": "scene",
"kind": "anim",
"name": "intro_10",
"action": "load_asset",
"location": "anim/intro_10",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_10.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_11",
"action": "load_asset",
"location": "anim/intro_11",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_11.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "anim",
"name": "intro_13",
"action": "load_asset",
"location": "anim/intro_13",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/anim/intro_13.jpg?raw=true",
"duration": null
},
{
"type": "scene",
"kind": "bg",
"name": "intro_xx",
"action": "load_asset",
"location": "bg/intro_xx",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/bg/intro_xx.jpg?raw=true"
},
{
"type": "narration",
"text": "Огни пролетают мимо, их холодный свет словно зажигает внутри давно погасшие чувства."
},
{
"type": "narration",
"text": "Или не зажигает, а просто пробуждает…"
},
{
"type": "narration",
"text": "Ведь «они» уже давно живут во мне, то затихая, то просыпаясь вновь."
},
{
"type": "narration",
"text": "Какая-то очень известная мелодия играла в радиоприёмнике у водителя.{w} Но я её не слушал."
},
{
"type": "narration",
"text": "Я смотрел в запотевшее окно автобуса на проезжающие мимо машины."
},
{
"type": "narration",
"text": "Ведь люди куда-то спешат, ведь им что-то нужно, и, погружённые в свои дела, они не задумываются о вопросах, мучающих меня."
},
{
"type": "narration",
"text": "Наверное, у них тоже есть свои серьёзные проблемы, а может, им живётся куда легче."
},
{
"type": "narration",
"text": "Знать наверняка нельзя, так как все люди разные.{w} Или не разные?"
},
{
"type": "narration",
"text": "Бывает, поступки человека легко предсказуемы, но, пытаясь заглянуть к нему в душу, видишь лишь непроглядную тьму."
},
{
"type": "narration",
"text": "..."
},
{
"type": "narration",
"text": "Автобус приближался к центру, и мои мысли прервал яркий свет огней большого города."
},
{
"type": "narration",
"text": "Сотни рекламных вывесок, тысячи машин, миллионы людей."
},
{
"type": "narration",
"text": "Я смотрел на это светопреставление, и мне почему-то безумно захотелось спать."
},
{
"type": "narration",
"text": "Глаза закрылись всего на полсекунды и…"
},
{
"type": "scene",
"kind": "bg",
"name": "black",
"action": "load_asset",
"location": "bg/black",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/images/1920/bg/int_bus_black.jpg?raw=true"
},
{
"type": "opening",
"kind": "opening",
"name": "opening",
"action": "load_asset",
"location": "opening/opening",
"raw_url": "https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/main/Assets/Everlasting_Summer/opening/opening.mp4?raw=true"
}
]
}

View File

@@ -0,0 +1,62 @@
{
"prologue_choice_1": {
"question": "Иначе никак, иначе сон не закончится, а я не проснусь. — что выбрать?",
"chapter": "prologue",
"options": {
"Да, я пойду с тобой": {
"effects": {}
},
"Нет, я останусь здесь": {
"effects": {}
}
}
},
"endings": {
"labels": [
"main_good_ending",
"main_bad_ending",
"sl_good_ending",
"sl_bad_ending",
"dv_good_ending",
"dv_bad_ending",
"un_good_ending",
"un_bad_ending",
"us_good_ending",
"us_bad_ending",
"mi_ending",
"uv_ending",
"harem_ending"
],
"routes": {
"sl": {
"good": "sl_good_ending",
"bad": "sl_bad_ending",
"point_key": "sl_points"
},
"dv": {
"good": "dv_good_ending",
"bad": "dv_bad_ending",
"point_key": "dv_points"
},
"un": {
"good": "un_good_ending",
"bad": "un_bad_ending",
"point_key": "un_points"
},
"us": {
"good": "us_good_ending",
"bad": "us_bad_ending",
"point_key": "us_points"
},
"mi": {
"single": "mi_ending",
"point_key": "mi_points"
},
"uv": {
"single": "uv_ending",
"point_key": "uv_points"
}
},
"fallback": "main_bad_ending"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Some files were not shown because too many files have changed in this diff Show More