Added and updated repositories 2025-11-30 01:13:31

This commit is contained in:
github-actions[bot]
2025-11-30 01:13:31 +00:00
parent 80f140550c
commit eccbd74a5e
62 changed files with 768 additions and 16985 deletions

View File

@@ -1,51 +0,0 @@
name: Generate Index Page
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Generate index.html
run: |
cat <<EOF > index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>H:Mods</title>
<link type=text/css href="https://github.com/C0dwiz/H.Modules/raw/assets/style.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<h1>H:Mods modules</h1>
<ul class="module-list">
$(for file in *.py; do echo " <li class="module-item"><a href=\"$file\" class="module-link">$file</a></li>"; done)
</ul>
</div>
</body>
</html>
EOF
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: main
folder: .

View File

@@ -1,4 +0,0 @@
full.py
# Ruff Format
.ruff_cache/

View File

@@ -1,133 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: ASCIIArt
# Description: Converting images to ASCII art
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: ASCIIArt
# scope: ASCIIArt 0.0.1
# requires: pillow
# ---------------------------------------------------------------------------------
import os
import tempfile
from PIL import Image
from .. import loader, utils
@loader.tds
class ASCIIArtMod(loader.Module):
"""Converting images to ASCII art"""
strings = {
"name": "ASCIIArt",
"no_media_reply": "<b>Please reply to the image!</b>",
"loading": "<emoji document_id=5116240346656801621>❓</emoji> <b>Converting an image to ASCII...</b>",
"error": "<emoji document_id=5121063440311386962>👎</emoji> <b>Error when converting an image.</b>",
"done": "<emoji document_id=5123163417326126159>✅</emoji> <b>Here is your ASCII art:</b>",
}
strings_ru = {
"no_media_reply": "<b>Пожалуйста, ответьте на изображение!</b>",
"loading": "<emoji document_id=5116240346656801621>❓</emoji> <b>Конвертирую изображение в ASCII...</b>",
"error": "<emoji document_id=5121063440311386962>👎</emoji> <b>Ошибка при конвертации изображения.</b>",
"done": "<emoji document_id=5123163417326126159>✅</emoji> <b>Вот ваш ASCII-арт:</b>",
}
@loader.command(
ru_doc="<реплай на изображение> сделать ascii art",
en_doc="<replay on image> make ascii art",
)
async def cascii(self, message):
reply = await message.get_reply_message()
if not self._is_image(reply):
await utils.answer(message, self.strings("no_media_reply"))
return
await utils.answer(message, self.strings("loading"))
ascii_art = await self._generate_ascii_art(reply)
if ascii_art:
await self._send_ascii_file(message, ascii_art)
await message.delete()
else:
await utils.answer(message, self.strings("error"))
def _is_image(self, reply):
"""Проверка, является ли ответ изображением"""
return reply and (
reply.photo
or (reply.document and reply.file.mime_type.startswith("image/"))
)
async def _generate_ascii_art(self, reply):
"""Генерирует ASCII-арт из изображения"""
try:
image_path = await reply.download_media(tempfile.gettempdir())
if not image_path:
return None
with Image.open(image_path) as img:
img = img.convert("L")
img = img.resize(self._get_new_dimensions(img), Image.NEAREST)
chars = "@#S%?*+;:,. "
pixels = img.getdata()
ascii_str = "".join(chars[pixel // 25] for pixel in pixels)
return "\n".join(
ascii_str[i : i + img.width]
for i in range(0, len(ascii_str), img.width)
)
except Exception as e:
print(f"Error generating ASCII art: {e}")
return None
finally:
if image_path and os.path.exists(image_path):
os.remove(image_path)
def _get_new_dimensions(self, img):
"""Получаем новые размеры для изображения"""
new_width = 100
aspect_ratio = img.height / img.width
new_height = int(aspect_ratio * new_width * 0.55)
return new_width, new_height
async def _send_ascii_file(self, message, ascii_art):
"""Сохраняет ASCII-арт во временный файл и отправляет его"""
try:
with tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", suffix=".txt", delete=False
) as tmp_file:
tmp_file_path = tmp_file.name
tmp_file.write(ascii_art)
await message.client.send_file(
message.chat_id,
tmp_file_path,
caption=self.strings("done"),
force_document=True,
reply_to=getattr(message, "reply_to_msg_id", None),
)
finally:
if tmp_file_path and os.path.exists(tmp_file_path):
os.remove(tmp_file_path)

View File

@@ -1,65 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AccountData
# Description: Find out the approximate date of registration of the telegram account
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api AccountData
# scope: Api AccountData 0.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
@loader.tds
class AccountData(loader.Module):
"""Find out the approximate date of registration of the telegram account"""
strings = {
"name": "AccountData",
"date_text": "<emoji document_id=5983150113483134607>⏰️</emoji> Date of registration of this account: {data}",
"date_text_ps": "<emoji document_id=6028435952299413210></emoji> The registration date is approximate, as it is almost impossible to know for sure",
"no_reply": "<emoji document_id=6030512294109122096>💬</emoji> You did not reply to the user's message",
}
strings_ru = {
"date_text": "<emoji document_id=5983150113483134607>⏰️</emoji> Дата регистрации этого аккаунта: {data}",
"date_text_ps": "<emoji document_id=6028435952299413210></emoji> Дата регистрации примерная, так как точно узнать практически невозможно",
"no_reply": "<emoji document_id=6030512294109122096>💬</emoji> Вы не ответили на сообщение пользователя",
}
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib(
"https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py"
)
@loader.command(
ru_doc="Узнать примерную дату регистрации аккаунта телеграмм",
en_doc="Find out the approximate date of registration of the telegram account",
)
async def accdata(self, message):
if reply := await message.get_reply_message():
data = await self.hmodslib.get_creation_date(reply.from_id)
await utils.answer(
message,
f"{self.strings('date_text').format(data=data)}\n\n{self.strings('date_text_ps')}",
)
else:
await utils.answer(message, self.strings("no_reply"))

View File

@@ -1,188 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AniLibria
# Description: Searches and gives random agtme on the AniLibria database.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: AniLibria
# scope: AniLibria 0.0.1
# requires: git+https://github.com/C0dwiz/anilibria.py.git
# ---------------------------------------------------------------------------------
from ..inline.types import InlineQuery
from aiogram.types import InlineQueryResultPhoto, CallbackQuery
from anilibria import AniLibriaClient
from .. import loader
ani_client = AniLibriaClient()
@loader.tds
class AniLibriaMod(loader.Module):
"""Searches and gives random agtme on the AniLibria database."""
strings = {
"name": "AniLibria",
"announce": "<b>The announcement</b>:",
"status": "<b>Status</b>:",
"type": "<b>Type</b>:",
"genres": "<b>Genres</b>:",
"favorite": "<b>Favourites &lt;3</b>:", # &lt; == <
"season": "<b>Season</b>:",
}
strings_ru = {
"announce": "<b>Анонс</b>:",
"status": "<b>Статус</b>:",
"type": "<b>Тип</b>:",
"genres": "<b>Жанры</b>:",
"favorite": "<b>Избранное &lt;3</b>:", # &lt; == <
"season": "<b>Сезон</b>:",
}
link = "https://anilibria.tv"
@loader.command(
ru_doc="Возвращает случайный тайтл из базы",
en_doc="Returns a random title from the database",
)
async def arandom(self, message) -> None:
anime_title = await ani_client.get_random_title()
text = f"{anime_title.names.ru} \n"
text += f"{self.strings['status']} {anime_title.status.string}\n\n"
text += f"{self.strings['type']} {anime_title.type.full_string}\n"
text += f"{self.strings['season']} {anime_title.season.string}\n"
text += f"{self.strings['genres']} {' '.join(anime_title.genres)}\n\n"
text += f"<code>{anime_title.description}</code>\n\n"
text += f"{self.strings['favorite']} {anime_title.in_favorites}"
kb = [
[
{
"text": "Ссылка",
"url": f"https://anilibria.tv/release/{anime_title.code}.html",
}
]
]
kb.extend(
[
{
"text": f"{torrent.quality.string}",
"url": f"https://anilibria.tv/{torrent.url}",
}
]
for torrent in anime_title.torrents.list
)
kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}])
kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}])
await self.inline.form(
text=text,
photo=self.link + anime_title.posters.original.url,
message=message,
reply_markup=kb,
silent=True,
)
@loader.inline_handler(
ru_doc="Возвращает список найденных по названию тайтлов",
en_doc="Returns a list of titles found by name",
)
async def asearch_inline_handler(self, query: InlineQuery) -> None:
text = query.args
if not text:
return
anime_titles = await ani_client.search_titles(search=text)
inline_query = []
for anime_title in anime_titles:
title_text = (
f"{anime_title.names.ru} | {anime_title.names.en}\n"
f"{self.strings['status']} {anime_title.status.string}\n\n"
f"{self.strings['type']} {anime_title.type.full_string}\n"
f"{self.strings['season']} {anime_title.season.string} {anime_title.season.year}\n"
f"{self.strings['genres']} {' '.join(anime_title.genres)}\n\n"
f"<code>{anime_title.description}</code>\n\n"
f"{self.strings['favorite']} {anime_title.in_favorites}"
)
inline_query.append(
InlineQueryResultPhoto(
id=str(anime_title.code),
title=anime_title.names.ru,
description=anime_title.type.full_string,
caption=title_text,
thumb_url=self.link + anime_title.posters.small.url,
photo_url=self.link + anime_title.posters.original.url,
parse_mode="html",
)
)
await query.answer(inline_query, cache_time=0)
async def inline__close(self, call: CallbackQuery) -> None:
await call.delete()
async def inline__update(self, call: CallbackQuery) -> None:
anime_title = await ani_client.get_random_title()
text = (
f"{anime_title.names.ru} \n"
f"{self.strings['status']} {anime_title.status.string}\n\n"
f"{self.strings['type']} {anime_title.type.full_string}\n"
f"{self.strings['season']} {anime_title.season.string}\n"
f"{self.strings['genres']} {' '.join(anime_title.genres)}\n\n"
f"<code>{anime_title.description}</code>\n\n"
f"{self.strings['favorite']} {anime_title.in_favorites}"
)
kb = [
[
{
"text": "Ссылка",
"url": f"https://anilibria.tv/release/{anime_title.code}.html",
}
]
]
kb.extend(
[
{
"text": f"{torrent.quality.string}",
"url": f"https://anilibria.tv/{torrent.url}",
}
]
for torrent in anime_title.torrents.list
)
kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}])
kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}])
await call.edit(
text=text,
photo=self.link + anime_title.posters.original.url,
reply_markup=kb,
)

View File

@@ -1,81 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AnimeQuotes
# Description: A module for sending random quotes from anime
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: AnimeQuotes
# scope: AnimeQuotes 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import aiohttp
from .. import loader, utils
@loader.tds
class AnimeQuotesMod(loader.Module):
"""A module for sending random quotes from anime"""
strings = {
"name": "AnimeQuotes",
"quote_template": (
'<b>Quote:</b> "{quote}"\n\n'
"<b>Character:</b> {character}\n"
"<b>Anime:</b> {anime}"
),
"error": "<b>Couldn't get a quote. Try again later!</b>",
}
strings_ru = {
"quote_template": (
'<b>Цитата:</b> "{quote}"\n\n'
"<b>Персонаж:</b> {character}\n"
"<b>Аниме:</b> {anime}"
),
"error": "<b>Не удалось получить цитату. Попробуйте позже!</b>",
}
@loader.command(
ru_doc="Получить случайную цитату из аниме",
en_doc="Get a random quote from the anime",
)
async def quote(self, message):
url = "https://api.animechan.io/v1/quotes/random"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
response.raise_for_status()
data = await response.json()
quote_content = data["data"]["content"]
character_name = data["data"]["character"]["name"]
anime_name = data["data"]["anime"]["name"]
quote = self.strings["quote_template"].format(
quote=quote_content, character=character_name, anime=anime_name
)
await utils.answer(message, quote)
except aiohttp.ClientError:
await utils.answer(message, self.strings["error"])

View File

@@ -1,73 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Article
# Description: Displays your article Criminal Code of the Russian Federation
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Article
# scope: Article 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import requests
import json
import random
from typing import Dict
from .. import loader, utils
@loader.tds
class ArticleMod(loader.Module):
"""Displays your article Criminal Code of the Russian Federation"""
strings = {
"name": "Article",
"article": "<emoji document_id=5226512880362332956>📖</emoji> <b>Your article of the Criminal Code of the Russian Federation</b>:\n\n<blockquote>Number {}\n\n{}</blockquote>",
}
strings_ru = {
"article": "<emoji document_id=5226512880362332956>📖</emoji> <b>Твоя статья УК РФ</b>:\n\n<blockquote>Номер {}\n\n{}</blockquote>",
}
@loader.command(
ru_doc="Отображается ваша статья Уголовного кодекса Российской Федерации",
en_doc="Displays your article Criminal Code of the Russian Federation",
)
async def arccmd(self, message):
if values := self._load_values():
random_key = random.choice(list(values.keys()))
random_value = values[random_key]
await utils.answer(
message, self.strings("article").format(random_key, random_value)
)
def _load_values(self) -> Dict[str, str]:
url = "https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/zakon.json"
try:
response = requests.get(url)
if response.ok:
data = json.loads(response.text)
return data
except (requests.RequestException, json.JSONDecodeError):
pass
return {}

View File

@@ -1,204 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: AutofarmCookies
# Description: Autofarm in the bot @cookies_game_bot
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: AutofarmCookies
# scope: AutofarmCookies 0.0.1
# ---------------------------------------------------------------------------------
import random
from datetime import timedelta
from telethon import functions
from .. import loader, utils
__version__ = (1, 0, 0)
@loader.tds
class AutofarmCookiesMod(loader.Module):
"""Autofarm in the bot @cookies_game_bot"""
strings = {
"name": "AutofarmCookies",
"farmon": (
"<i>The deferred task has been created, autofarming has been started, everything will start in 10 minutes"
" seconds...</i>"
),
"farmon_already": "<i>It has already been launched :)</i>",
"farmoff": "<i>The autopharm is stopped\nSelected:</i> <b>%coins% Cookies</b>",
"farm": "<i>I typed:</i> <b>%coins% Cookies</b>",
}
strings_ru = {
"farmon": (
"<i>Отложенная задача создана, автофарминг запущен, всё начнётся через 10"
" секунд...</i>"
),
"farmon_already": "<i>Уже запущено :)</i>",
"farmoff": "<i>Автофарм остановлен.\nНвброно:</i> <b>%coins% Cookies</b>",
"farm": "<i>Я набрал:</i> <b>%coins% Cookies</b>",
}
def __init__(self):
self.name = self.strings["name"]
async def client_ready(self, client, db):
self.client = client
self.db = db
self.myid = (await client.get_me()).id
self.cookies = "@cookies_game_bot"
@loader.command(
ru_doc="Запустить автофарминг",
en_doc="Launch auto-farming",
)
async def cookon(self, message):
status = self.db.get(self.name, "status", False)
if status:
return await message.edit(self.strings["farmon_already"])
self.db.set(self.name, "status", True)
await self.client.send_message(
self.cookies, "/cookie", schedule=timedelta(seconds=10)
)
await message.edit(self.strings["farmon"])
@loader.command(
ru_doc="Остановить автофарминг",
en_doc="Stop auto-farming",
)
async def cookoff(self, message):
self.db.set(self.name, "status", False)
coins = self.db.get(self.name, "coins", 0)
if coins:
self.db.set(self.name, "coins", 0)
await message.edit(self.strings["farmoff"].replace("%coins%", str(coins)))
@loader.command(
ru_doc="Вывод кол-ва коинов, добытых этим модулем",
en_doc="Output of the number of coins mined by this module",
)
async def cookies(self, message):
coins = self.db.get(self.name, "coins", 0)
await message.edit(self.strings["farm"].replace("%coins%", str(coins)))
async def watcher(self, event):
if not isinstance(event, Message): # noqa: F821
return
chat = utils.get_chat_id(event)
if chat != self.cookies:
return
status = self.db.get(self.name, "status", False)
if not status:
return
if event.raw_text == "/cookie":
return await self.client.send_message(
self.cookies, "/cookie", schedule=timedelta(hours=2)
)
if event.sender_id != self.cookies:
return
if "🙅‍♂️!" in event.raw_text:
args = [int(x) for x in event.raw_text.split() if x.isnumeric()]
randelta = random.randint(20, 60)
if len(args) == 4:
delta = timedelta(
hours=args[1], minutes=args[2], seconds=args[3] + randelta
)
elif len(args) == 3:
delta = timedelta(minutes=args[1], seconds=args[2] + randelta)
elif len(args) == 2:
delta = timedelta(seconds=args[1] + randelta)
else:
return
sch = (
await self.client(
functions.messages.GetScheduledHistoryRequest(self.cookies, 1488)
)
).messages
await self.client(
functions.messages.DeleteScheduledMessagesRequest(
self.cookies, id=[x.id for x in sch]
)
)
return await self.client.send_message(
self.cookies, "/cookie", schedule=delta
)
if "" in event.raw_text:
args = event.raw_text.split()
for x in args:
if x[0] == "+":
return self.db.set(
self.name,
"coins",
self.db.get(self.name, "coins", 0) + int(x[1:]),
)
async def message_q(
self,
text: str,
user_id: int,
mark_read: bool = False,
delete: bool = False,
):
async with self.client.conversation(user_id) as conv:
msg = await conv.send_message(text)
response = await conv.get_response()
if mark_read:
await conv.mark_read()
if delete:
await msg.delete()
await response.delete()
return response
@loader.command(
ru_doc="Показывает ваш мешок",
en_doc="Shows your bag",
)
async def me(self, message):
bot = "@cookies_game_bot"
bags = await self.message_q(
"/me",
bot,
delete=True,
)
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, bags.text)
@loader.command(
ru_doc="Помощь по модулю AutofarmCookies",
en_doc="Help with the AutofarmCookies module",
)
async def ckies(self, message):
chelp = """
🍀| <b>Помощь по командам:</b>
.cookon - Включает авто фарм.
.cookoff - Выключает авто фарм.
.farm - Показывает сколько вы нафармили.
.me - Показывает ваш ммешок"""
await utils.answer(message, chelp)

View File

@@ -1,242 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: BirthdayTime
# Description: Counting down to your birthday
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: BirthdayTime
# scope: Api BirthdayTime 0.0.1
# ---------------------------------------------------------------------------------
import random
import asyncio
import calendar
from datetime import datetime
from telethon.tl.functions.users import GetFullUserRequest
from telethon.tl.functions.account import UpdateProfileRequest
from telethon.errors.rpcerrorlist import UserPrivacyRestrictedError
from .. import loader, utils
D_MSG = [
"Ждешь его?",
"Осталось немного)",
"Дни пролетят, даже не заметишь",
"Уже знаешь что хочешь получить в подарок?)",
"Сколько исполняется?",
"Жду не дождусь уже",
]
@loader.tds
class DaysToMyBirthday(loader.Module):
"""Counting down to your birthday"""
strings = {
"name": "BirthdayTime",
"date_error": "<emoji document_id=5422840512681877946>❗️</emoji> <b>Your birthdate is not specified in the config, please correct this :)</b>",
"msg": (
"<emoji document_id=5377476217698001788>🎉</emoji> <b>"
"There are {} days, {} hours, {} minutes, and {} seconds left until your birthday. \n<emoji document_id=5377442914521588226>"
"💙</emoji> {}</b>"
),
"conf": "<i>Open config...</i>",
"name_changed": "<b>Name updated!</b>",
"name_not_changed": "<b>Name was not updated.</b>",
"name_privacy_error": "<b>Unable to change name due to privacy settings.</b>",
"error": "<b>An error occurred. Please check the logs.</b>",
}
strings_ru = {
"date_error": "<emoji document_id=5422840512681877946>❗️</emoji> <b>В конфиге не указан день вашего рождения, пожалуйста, исправь это :)</b>",
"msg": (
"<emoji document_id=5377476217698001788>🎉</emoji> <b>"
"До вашего дня рождения осталось {} дней, {} часов, {} "
"минут, {} секунд. \n<emoji document_id=5377442914521588226>"
"💙</emoji> {}</b>"
),
"conf": "<i>Открываю конфиг...</i>",
"btname_yes": (
"<b><emoji document_id=6327560044845991305>😶</emoji> Хорошо, теперь я "
"буду изменять ваше имя в зависимости от количества дней до дня рождения</b>"
),
"btname_no": "<emoji document_id=6325696222313055607>😶</emoji>Хорошо, я больше не буду изменять ваше имя",
"name_changed": "<b>Имя обновлено!</b>",
"name_not_changed": "<b>Имя не было обновлено.</b>",
"name_privacy_error": "<b>Не удалось изменить имя из-за настроек приватности.</b>",
"error": "<b>Произошла ошибка. Пожалуйста, проверьте логи.</b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"birthday_date",
None,
lambda: "Дата вашего рождения. Указывать только день",
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"birthday_month",
None,
"Месяц вашего рождения",
validator=loader.validators.Choice(
[
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]
),
),
)
self._task = None
async def client_ready(self):
if self._task:
self._task.cancel()
self._task = asyncio.create_task(self.checker())
async def checker(self):
while True:
if not self.db.get(__name__, "change_name", False):
await asyncio.sleep(60)
continue
try:
now = datetime.now()
day = self.config["birthday_date"]
monthy = self.config["birthday_month"]
month = list(calendar.month_name).index(monthy)
birthday = datetime(now.year, month, day)
if now.month > month or (now.month == month and now.day > day):
birthday = datetime(now.year + 1, month, day)
time_to_birthday = abs(birthday - now)
days = time_to_birthday.days
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
if not user or not user.users:
await asyncio.sleep(60)
continue
name = user.users[0].last_name or ""
ln = f'{self.db.get(__name__, "last_name", "")}{days} d.'
if name == ln:
await asyncio.sleep(60)
continue
else:
await self.client(UpdateProfileRequest(last_name=ln))
self.db.set(__name__, "last_name", name)
except UserPrivacyRestrictedError:
self.db.set(__name__, "change_name", False)
print("Error: Can't change name due to privacy settings.")
except Exception as e:
print(f"Error in checker: {e}")
finally:
await asyncio.sleep(60)
@loader.command(
ru_doc="Выставить таймер дней в ник (нестабильно)",
en_doc="Set the timer of days in the nickname (unstable)",
)
async def btname(self, message):
try:
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
name = user.users[0].last_name or ""
except Exception as e:
print(f"Error getting user info: {e}")
await utils.answer(message, self.strings("error"))
return
self.db.set(__name__, "last_name", name)
change_name = self.db.get(__name__, "change_name", False)
if change_name:
self.db.set(__name__, "change_name", False)
await utils.answer(message, self.strings("btname_no"))
try:
await self.client(
UpdateProfileRequest(last_name=self.db.get(__name__, "last_name"))
)
await utils.answer(message, self.strings("name_not_changed"))
except UserPrivacyRestrictedError:
await utils.answer(message, self.strings("name_privacy_error"))
except Exception as e:
print(f"Error removing name: {e}")
await utils.answer(message, self.strings("error"))
else:
self.db.set(__name__, "change_name", True)
await utils.answer(message, self.strings("btname_yes"))
@loader.command(
ru_doc="Вывести таймер",
en_doc="Display the timer",
)
async def bt(self, message):
if (
self.config["birthday_date"] is None
or self.config["birthday_month"] is None
):
await utils.answer(message, self.strings("date_error"))
msg = await self.client.send_message(message.chat_id, self.strings("conf"))
await self.allmodules.commands["config"](
await utils.answer(msg, f"{self.get_prefix()}config BirthdayTime")
)
return
try:
now = datetime.now()
day = self.config["birthday_date"]
monthy = self.config["birthday_month"]
month = list(calendar.month_name).index(monthy)
birthday = datetime(now.year, month, day)
if now.month > month or (now.month == month and now.day > day):
birthday = datetime(now.year + 1, month, day)
time_to_birthday = abs(birthday - now)
await utils.answer(
message,
self.strings("msg").format(
time_to_birthday.days,
(time_to_birthday.seconds // 3600),
(time_to_birthday.seconds // 60 % 60),
(time_to_birthday.seconds % 60),
random.choice(D_MSG),
),
)
except Exception as e:
print(f"Error in bt command: {e}")
await utils.answer(message, self.strings("error"))

View File

@@ -1 +0,0 @@
mods.codwiz.life

View File

@@ -1,53 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: CheckSpamBan
# Description: Check spam ban for your account.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: CheckSpamBan
# scope: CheckSpamBan 0.0.1
# ---------------------------------------------------------------------------------
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class SpamBanCheckMod(loader.Module):
"""Checks spam ban for your account."""
strings = {
"name": "CheckSpamBan",
}
@loader.command(
ru_doc="Проверяет вашу учетную запись на спам-бан с помощью бота @SpamBot",
en_doc="Checks your account for spam ban via @SpamBot bot",
)
async def spambot(self, message):
async with self.client.conversation(178220800) as conv:
user_message = await conv.send_message('/start')
await user_message.delete()
spam_message = await conv.get_response()
await utils.answer(message, spam_message.text)
await spam_message.delete()

View File

@@ -1,107 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: CryptoCurrency
# Description: Module for displaying current cryptocurrency exchange rates.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api CryptoCurrency
# scope: Api CryptoCurrency 0.0.1
# ---------------------------------------------------------------------------------
import aiohttp
from .. import loader, utils
@loader.tds
class CryptoCurrencyMod(loader.Module):
"""Module for displaying current cryptocurrency exchange rates."""
strings = {
"name": "CryptoCurrency",
"query_missing": "Please specify a cryptocurrency ticker or name.",
"coin_not_found": "Cryptocurrency '{query}' not found.",
}
strings_ru = {
"query_missing": "Пожалуйста, укажите тикер или название криптовалюты.",
"coin_not_found": "Криптовалюта '{query}' не найдена.",
}
async def fetch_json(self, url):
"""Fetch JSON data from a given URL."""
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
response.raise_for_status()
return await response.json()
async def get_exchange_rates(self):
"""Get exchange rates for RUB and EUR based on USD."""
data = await self.fetch_json("https://open.er-api.com/v6/latest/USD")
return data["rates"]["RUB"], data["rates"]["EUR"]
async def find_coin(self, query):
"""Find a cryptocurrency by its name or symbol."""
data = await self.fetch_json(
"https://api.coinlore.net/api/tickers/?start=0&limit=100"
)
return next(
(
item
for item in data["data"]
if query.lower() in item["name"].lower()
or query.lower() in item["symbol"].lower()
),
None,
)
@loader.command(
ru_doc="Отображает текущий курс криптовалюты в рублях, долларах США и евро",
en_doc="Displays the current cryptocurrency rate in RUB, USD, and EUR",
)
async def crypto(self, message):
query = utils.get_args_raw(message)
if not query:
return await utils.answer(message, self.strings("query_missing"))
coin = await self.find_coin(query)
if not coin:
return await utils.answer(
message, self.strings("coin_not_found").format(query=query)
)
price_usd = float(coin["price_usd"])
usd_rub_rate, usd_eur_rate = await self.get_exchange_rates()
price_rub = price_usd * usd_rub_rate
price_eur = price_usd * usd_eur_rate
response = self.format_response(coin, price_usd, price_rub, price_eur)
await utils.answer(message, response)
def format_response(self, coin, price_usd, price_rub, price_eur):
"""Format the response message with cryptocurrency information."""
return (
f"💰 {coin['name']} ({coin['symbol']})\n"
f"USD: ${price_usd:.2f}\n"
f"RUB: ₽{price_rub:.2f}\n"
f"EUR: €{price_eur:.2f}\n"
)

View File

@@ -1,81 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: EnvsSH
# Description: Module for reuploading files to envs.sh
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api EnvsSH
# scope: Api EnvsSH 0.0.1
# requires: aiohttp
# ---------------------------------------------------------------------------------
import aiohttp
from .. import loader, utils # pylint: disable=relative-beyond-top-level
@loader.tds
class EnvsMod(loader.Module):
"""Module for reuploading files to envs.sh"""
strings = {
"name": "EnvsSH",
"connection_error": "🚫 Host is unreachable for now, try again later.",
"no_reply": "⚠️ <b>You must reply to a message with media</b>",
"success": "✅ URL for <code>{}</code>:\n\n<code>{}</code>",
"error": "❌ An error occurred:\n<code>{}</code>",
"uploading": "⏳ <b>Uploading {} ({}{})...</b>",
}
strings_ru = {
"connection_error": "🚫 Хост в настоящее время недоступен, попробуйте позже.",
"no_reply": "⚠️ <b>Вы должны ответить на сообщение с медиа</b>",
"success": "✅ URL для <code>{}</code>:\n\n<code>{}</code>",
"error": "❌ Произошла ошибка:\n<code>{}</code>",
"uploading": "⏳ <b>Загрузка {} ({}{})...</b>",
}
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib('https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py')
async def envcmd(self, message):
"""Reupload to envs.sh."""
reply = await message.get_reply_message()
if not reply or not reply.media:
return await utils.answer(message, self.strings["no_reply"])
size_len, size_unit = self.hmodslib.convert_size(reply.file.size)
await utils.answer(
message,
self.strings["uploading"].format(reply.file.name, size_len, size_unit),
)
path = await self.client.download_media(reply)
try:
uploaded_url = await self.hmodslib.upload_to_envs(path)
except aiohttp.ClientConnectionError:
await utils.answer(message, self.strings["connection_error"])
except aiohttp.ClientResponseError as e:
await utils.answer(message, self.strings["error"].format(str(e)))
else:
await utils.answer(
message, self.strings["success"].format(path, uploaded_url)
)

View File

@@ -1,88 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: FakeActions
# Description: Module for simulating various actions in chat
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api FakeActions
# scope: Api FakeActions 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
from .. import loader, utils
@loader.tds
class FakeActionsMod(loader.Module):
"""Module for simulating various actions in chat"""
strings = {"name": "FakeActions"}
def __init__(self):
self.config = loader.ModuleConfig(
"DEFAULT_DURATION", 5, "Default duration for actions in seconds"
)
async def ftcmd(self, message):
"""<seconds> - Simulates typing in chat for the specified number of seconds."""
await self._simulate_action_command(message, "typing")
async def ffcmd(self, message):
"""<seconds> - Simulates sending a file."""
await self._simulate_action_command(message, "document")
async def fgcmd(self, message):
"""<seconds> - Simulates recording a voice message."""
await self._simulate_action_command(message, "record-audio")
async def fvgcmd(self, message):
"""<seconds> - Simulates recording a video message."""
await self._simulate_action_command(message, "record-round")
async def fpgcmd(self, message):
"""<seconds> - Simulates playing a game."""
await self._simulate_action_command(message, "game")
async def _simulate_action_command(self, message, action):
"""General function for handling action simulation commands."""
duration = self._parse_duration(message)
if duration is None:
await utils.answer(
message,
f"Usage: {self.get_prefix()}{message.raw_text.split()[0][1:]} <seconds>",
)
return
await message.delete()
await self._simulate_action(message, action, duration)
def _parse_duration(self, message):
"""Parse the duration from the message."""
args = message.raw_text.split()
if len(args) == 2 and args[1].isdigit():
return int(args[1])
return self.config["DEFAULT_DURATION"]
async def _simulate_action(self, message, action, duration):
"""Simulate the specified action in chat."""
async with message.client.action(message.chat_id, action):
await asyncio.sleep(duration)

View File

@@ -1,176 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: FakeWallet
# Description: Fun joke - fake crypto wallet. You can change cryptocurrency values using .cfg FakeWallet.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: hikka_only
# scope: hikka_min 1.4.2
# -----------------------------------------------------------------------------------
from .. import loader, utils
@loader.tds
class FakeWallet(loader.Module):
"""Fun joke - fake crypto wallet. You can change cryptocurrency values using .cfg FakeWallet."""
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"Toncoin",
0,
lambda: self.strings("ton"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Tether",
0,
lambda: self.strings("tether"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Bitcoin",
0,
lambda: self.strings("btc"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Etherium",
0,
lambda: self.strings("ether"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Binance",
0,
lambda: self.strings("binc"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Tron",
0,
lambda: self.strings("tron"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"USDT",
0,
lambda: self.strings("usdt"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Gram",
0,
lambda: self.strings("gram"),
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"Litecoin",
0,
lambda: self.strings("lite"),
validator=loader.validators.Integer(),
),
)
strings = {
"name": "FakeWallet",
"crypto": "Enter a value for your cryptovalute",
"wallet": "<emoji document_id=5438626338560810621>👛</emoji> <b>Wallet</b>\n\n"
"<emoji document_id=5215276644620586569>☺️</emoji> <a href='https://ton.org'>Toncoin</a>: {} TON\n\n"
"<emoji document_id=5215699136258524363>☺️</emoji> <a href='https://tether.to'>Tether</a>: {} USDT\n\n"
"<emoji document_id=5215590800003451651>☺️</emoji> <a href='https://bitcoin.org'>Bitcoin</a>: {} BTC\n\n"
"<emoji document_id=5217867240044512715>☺️</emoji> <a href='https://etherium.org'>Etherium</a>: {} ETH\n\n"
"<emoji document_id=5215595550237279768>☺️</emoji> <a href='https://binance.org'>Binance coin</a>: {} BNB\n\n"
"<emoji document_id=5215437796088499410>☺️</emoji> <a href='https://tron.network'>TRON</a>: {} TRX\n\n"
"<emoji document_id=5215440441788351459>☺️</emoji> <a href='https://www.centre.io/usdc'>USD Coin</a>: {} USDC\n\n"
"<emoji document_id=5215267041073711005>☺️</emoji> <a href='https://gramcoin.org'>Gram</a>: {} GRAM\n\n"
"<emoji document_id=5217877586620729050>☺️</emoji> <a href='https://litecoin.org'>Litecoin</a>: {} LTC",
"ton": "Enter a value for Toncoin",
"teth": "Enter a value for Tethcoin",
"btc": "Enter a value for Bitcoin",
"ether": "Enter a value for Etherium",
"binc": "Enter a value for Binance coin",
"tron": "Enter a value for Tron",
"usdt": "Enter a value for USDT coin",
"gram": "Enter a value for Gramcoin",
"lite": "Enter a value for Litecoin",
"info": "<b><emoji document_id=5305467350064047192>🫥</emoji><i>Attention!</b>\n\n"
"<i><emoji document_id=5915991028430542030>☝️</emoji>This module is strictly prohibited from being used for the purposes of <b>scam, fraud and advertising</b>.\n\n"
"<emoji document_id=5787190061644647815>🗣</emoji>The module is provided solely for entertainment purposes, and any violation of the <b>Rules for using the module</b>, if detected, will be subject <b>to appropriate punishment</i>",
}
strings_ru = {
"wallet": "<emoji document_id=5438626338560810621>👛</emoji> <b>Кошелёк</b>\n\n"
"<emoji document_id=5215276644620586569>☺️</emoji> <a href='https://ton.org'>Toncoin</a>: {} TON\n\n"
"<emoji document_id=5215699136258524363>☺️</emoji> <a href='https://tether.to'>Tether</a>: {} USDT\n\n"
"<emoji document_id=5215590800003451651>☺️</emoji> <a href='https://bitcoin.org'>Bitcoin</a>: {} BTC\n\n"
"<emoji document_id=5217867240044512715>☺️</emoji> <a href='https://etherium.org'>Etherium</a>: {} ETH\n\n"
"<emoji document_id=5215595550237279768>☺️</emoji> <a href='https://binance.org'>Binance coin</a>: {} BNB\n\n"
"<emoji document_id=5215437796088499410>☺️</emoji> <a href='https://tron.network'>TRON</a>: {} TRX\n\n"
"<emoji document_id=5215440441788351459>☺️</emoji> <a href='https://www.centre.io/usdc'>USD Coin</a>: {} USDC\n\n"
"<emoji document_id=5215267041073711005>☺️</emoji> <a href='https://gramcoin.org'>Gram</a>: {} GRAM\n\n"
"<emoji document_id=5217877586620729050>☺️</emoji> <a href='https://litecoin.org'>Litecoin</a>: {} LTC",
"ton": "Введите количество валюты для Toncoin",
"teth": "Введите количество валюты для Tethcoin",
"btc": "Введите количество валюты для Bitcoin",
"ether": "Введите количество валюты для Etherium",
"binc": "Введите количество валюты для Binance coin",
"tron": "Введите количество валюты для Tron",
"usdt": "Введите количество валюты для USDT coin",
"gram": "Введите количество валюты для Gramcoin",
"lite": "Введите количество валюты для Litecoin",
"info": "<b><emoji document_id=5305467350064047192>🫥</emoji><i> Внимание!</b>\n\n"
"<i><emoji document_id=5915991028430542030>☝️</emoji> Использование этого модуля в целях <b>скама, обмана и рекламы</b> строго запрещено.\n\n"
"<emoji document_id=5787190061644647815>🗣</emoji> Модуль предоставлен исключительно в развлекательных целях, и любое нарушение <b>Правил использования модуля</b>, если его обнаружат, будет подлежать соответствующему наказанию.</i>",
}
@loader.command(
ru_doc="Чтобы заполучить поддельный кошелек",
en_doc="To get a fake wallet",
)
@loader.command()
async def fwalletcmd(self, message):
ton = self.config["Toncoin"]
teth = self.config["Tether"]
btc = self.config["Bitcoin"]
ether = self.config["Etherium"]
binc = self.config["Binance"]
tron = self.config["Tron"]
usdt = self.config["USDT"]
gram = self.config["Gram"]
lite = self.config["Litecoin"]
await utils.answer(
message,
self.strings("wallet").format(
ton, teth, btc, ether, binc, tron, usdt, gram, lite
),
)
@loader.command(
ru_doc="Информация о FakeModule",
en_doc="Info about FakeModule",
)
@loader.command()
async def fwinfocmd(self, message):
await utils.answer(message, self.strings("info"))

View File

@@ -1,121 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: GigaChat
# Description: Module for using GigaChat
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api GigaChat
# scope: Api GigaChat 0.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
@loader.tds
class GigaChatMod(loader.Module):
"""Module for using GigaChat"""
strings = {
"name": "GigaChat",
"api_key_missing": "Please set the API key in the module configuration.",
"query_missing": "Please enter a query after the command.",
"response_error": "Failed to get a response from GigaChat.",
"error_occurred": "An error occurred: {}",
"formatted_response": (
"<emoji document_id=6030848053177486888>❓</emoji> Query: {}\n"
"<emoji document_id=6030400221232501136>🤖</emoji> GigaChat: {}"
),
"giga_model": "List of GigaChat models:\n{}",
}
strings_ru = {
"api_key_missing": "Пожалуйста, установите API ключ в конфигурации модуля.",
"query_missing": "Пожалуйста, введите запрос после команды.",
"response_error": "Не удалось получить ответ от GigaChat.",
"error_occurred": "Произошла ошибка: {}",
"formatted_response": (
"<emoji document_id=6030848053177486888>❓</emoji> Запрос: {}\n"
"<emoji document_id=6030400221232501136>🤖</emoji> GigaChat: {}"
),
"giga_model": "Список моделей GigaChat:\n{}",
}
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib(
"https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py"
)
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"GIGACHAT_API_KEY",
None,
"Введите ваш API ключ для GigaChat, Чтобы получить ключ API, перейдите сюда: https://developers.sber.ru/studio/workspaces",
validator=loader.validators.Hidden(),
),
loader.ConfigValue(
"GIGACHAT_MODEL",
"GigaChat",
"Введите модель, ее можно получить при команде .gigamodel",
),
)
@loader.command(
ru_doc="Получите исчерпывающий ответ на свой вопрос",
en_doc="Get GigaResponse to your question",
)
async def giga(self, message):
api_key = self.config["GIGACHAT_API_KEY"]
if not api_key:
return await utils.answer(message, self.strings("api_key_missing"))
query = utils.get_args_raw(message)
if not query:
return await utils.answer(message, self.strings("query_missing"))
try:
response = await self.hmodslib.get_giga_response(api_key, query)
if response:
await utils.answer(
message, self.strings("formatted_response").format(query, response)
)
else:
await utils.answer(message, self.strings("response_error"))
except Exception as e:
await utils.answer(message, self.strings("error_occurred").format(str(e)))
@loader.command(
ru_doc="Получить список моделей",
en_doc="Get a list of models",
)
async def gigamodel(self, message):
api_key = self.config["GIGACHAT_API_KEY"]
if not api_key:
return await utils.answer(message, self.strings("api_key_missing"))
try:
response = await self.hmodslib.get_giga_models(api_key)
if response:
await utils.answer(message, self.strings("giga_model").format(response))
else:
await utils.answer(message, self.strings("response_error"))
except Exception as e:
await utils.answer(message, self.strings("error_occurred").format(str(e)))

View File

@@ -1,44 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: H
# Description: H.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: H
# scope: H 0.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
@loader.tds
class H(loader.Module):
"""H"""
strings = {"name": "H", "h": "H"}
strings_ru = {"h": "H"}
@loader.command(
ru_doc="H",
)
async def h(self, message):
"""H"""
await utils.answer(message, self.strings("h"))

View File

@@ -1,307 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: HAFK
# Description: Your personal assistant while you are in AFK mode
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: HAFK
# scope: HAFK 0.0.1
# ---------------------------------------------------------------------------------
import datetime
import logging
import time
import asyncio
from telethon import types
from telethon.utils import get_peer_id
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class HAFK(loader.Module):
strings = {
"name": "HAFK",
"afk_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on!</b>",
"afk_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on!</b>\n\n<b>Reason:</b> <i>{}</i>",
"afk_here_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on in this chat!</b>",
"afk_here_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK mode is on in this chat!</b>\n\n<b>Reason:</b> <i>{}</i>",
"afk_off": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK mode is off!</b>",
"afk_off_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK mode is off!</b>\n\n<b>You were AFK for:</b> {}",
"afk_off_here_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK mode is off in this chat!</b>\n\n<b>You were AFK for:</b> {}",
"already_afk": "<emoji document_id=5465665476971471368>❌</emoji> <b>You are already in AFK mode!</b>",
"already_afk_here": "<emoji document_id=5465665476971471368>❌</emoji> <b>You are already in AFK mode in this chat!</b>",
"not_afk": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK mode is already off.</b>",
"not_afk_here": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK mode is already off in this chat.</b>",
"afk_message": "<emoji document_id=5330130448142049118>🫤</emoji> <b>I'm currently not accepting messages!</b>\n<b>Reason:</b> <i>{}</i>\n\n<i>Inactive mode has been on for:</i> {}",
"afk_message_reason": "<emoji document_id=5330130448142049118>🫤</emoji> <b>I'm currently not accepting messages!</b>\n<b>Reason:</b> <i>{}</i>\n\n<i>Inactive mode has been on for:</i> {}",
"afk_set_failed": "<b>Failed to set AFK status. Check logs.</b>",
"added_excluded_chat": "<b>Chat {} added to excluded chats.</b>",
"removed_excluded_chat": "<b>Chat {} removed from excluded chats.</b>",
"excluded_chats_list": "<b>Excluded chats:</b>\n{}",
"no_excluded_chats": "<b>No chats are excluded.</b>",
"invalid_chat_id": "<b>Invalid chat ID.</b>",
"already_excluded": "<b>Chat {} is already excluded.</b>",
"not_excluded": "<b>Chat {} is not excluded.</b>",
}
strings_ru = {
"afk_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен!</b>",
"afk_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен!</b>\n\n<b>Причина:</b> <i>{}</i>",
"afk_here_on": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен в этом чате!</b>",
"afk_here_on_reason": "<emoji document_id=5357107687484038897>🫶</emoji> <b>AFK-режим включен в этом чате!</b>\n\n<b>Причина:</b> <i>{}</i>",
"afk_off": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK-режим отключен!</b>",
"afk_off_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK-режим отключен!</b>\n\n<b>Вы были AFK:</b> {}",
"afk_off_here_time": "<emoji document_id=5472234792659458002>🤌</emoji> <b>AFK-режим отключен в этом чате!</b>\n\n<b>Вы были AFK:</b> {}",
"already_afk": "<emoji document_id=5465665476971471368>❌</emoji> <b>Вы уже находитесь в AFK-режиме!</b>",
"already_afk_here": "<emoji document_id=5465665476971471368>❌</emoji> <b>Вы уже находитесь в AFK-режиме в этом чате!</b>",
"not_afk": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK-режим уже отключён.</b>",
"not_afk_here": "<emoji document_id=5330365133745038003>😐</emoji> <b>AFK-режим уже отключен в этом чате.</b>",
"afk_message": "<emoji document_id=5330130448142049118>🫤</emoji> <b>На данный момент я не принимаю сообщения!</b>\n<b>Причина:</b> <i>{}</i>\n\n<i>С момента включения режима неактивности:</i> {}",
"afk_message_reason": "<emoji document_id=5330130448142049118>🫤</emoji> <b>На данный момент я не принимаю сообщения!</b>\n<b>Причина:</b> <i>{}</i>\n\n<i>С момента включения режима неактивности:</i> {}",
"afk_set_failed": "<b>Не удалось установить AFK-статус. Проверьте логи.</b>",
"added_excluded_chat": "<b>Чат {} добавлен в список исключений.</b>",
"removed_excluded_chat": "<b>Чат {} удален из списка исключений.</b>",
"excluded_chats_list": "<b>Список исключенных чатов:</b>\n{}",
"no_excluded_chats": "<b>Нет исключенных чатов.</b>",
"invalid_chat_id": "<b>Неверный ID чата.</b>",
"already_excluded": "<b>Чат {} уже исключен.</b>",
"not_excluded": "<b>Чат {} не исключен.</b>",
}
DEFAULT_AFK_TIMEOUT = 60
DEFAULT_DELETE_TIMEOUT = 5
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"excluded_chats",
[],
lambda: "List of chat IDs where AFK mode will not be activated",
validator=loader.validators.Series(
validator=loader.validators.Integer()
),
)
)
async def client_ready(self, client, db):
self.client = client
self.db = db
self._me = await client.get_me()
self._ratelimit_cache = {}
self.global_afk = self.db.get(__name__, "afk", False)
self.global_afk_reason = self.db.get(__name__, "afk_reason", None)
self.global_gone_time = self.db.get(__name__, "gone_afk", None)
logger.debug(
f"Initial global AFK state: afk={self.global_afk}, reason={self.global_afk_reason}, gone_time={self.global_gone_time}"
)
@loader.command(
ru_doc="[reason / none] Установить режим AFK",
en_doc="[reason / none] Set AFK mode globally",
)
async def afk(self, message):
await self._afk_toggle(message, global_afk=True)
@loader.command(
ru_doc="[reason / none] Установить режим AFK только в этом чате.",
en_doc="[reason / none] Set AFK mode in current chat only.",
)
async def afkhere(self, message):
await self._afk_toggle(message, global_afk=False)
async def _afk_toggle(self, message, global_afk: bool):
chat_id = utils.get_chat_id(message)
db_key = "afk" if global_afk else f"afk_here_{chat_id}"
already_afk_string = "already_afk" if global_afk else "already_afk_here"
afk_on_string = "afk_on" if global_afk else "afk_here_on"
afk_on_reason_string = "afk_on_reason" if global_afk else "afk_here_on_reason"
if self._is_afk_enabled(chat_id, global_afk):
await utils.answer(message, self.strings(already_afk_string, message))
return
reason = utils.get_args_raw(message) or None
success = self._set_afk(
True, reason=reason, chat_id=chat_id if not global_afk else None
)
if not success:
await utils.answer(message, self.strings("afk_set_failed", message))
return
await utils.answer(
message,
self.strings(afk_on_reason_string, message).format(reason)
if reason
else self.strings(afk_on_string, message),
)
@loader.command(
ru_doc="Выйти из режима AFK",
en_doc="Exit AFK mode",
)
async def unafk(self, message):
await self._unafk_toggle(message, global_afk=True)
@loader.command(
ru_doc="Выйти из режима AFK в этом чате",
en_doc="Exit AFK mode in this chat",
)
async def unafkhere(self, message):
await self._unafk_toggle(message, global_afk=False)
async def _unafk_toggle(self, message, global_afk: bool):
chat_id = utils.get_chat_id(message)
not_afk_string = "not_afk" if global_afk else "not_afk_here"
afk_off_time_string = "afk_off_time" if global_afk else "afk_off_here_time"
if not self._is_afk_enabled(chat_id, global_afk):
await utils.answer(message, self.strings(not_afk_string, message))
return
total_gone_time = self._calculate_total_afk_time(
datetime.datetime.now().replace(microsecond=0),
chat_id=chat_id if not global_afk else None,
)
self._set_afk(False, chat_id=chat_id if not global_afk else None)
await self.allmodules.log("unafk" if global_afk else "unafkhere")
await utils.answer(
message, self.strings(afk_off_time_string, message).format(total_gone_time)
)
async def watcher(self, message):
if not isinstance(message, types.Message):
return
chat_id = get_peer_id(message.peer_id)
user_id = getattr(message.to_id, "user_id", None)
is_mentioned = message.mentioned or user_id == self._me.id
is_private = isinstance(message.to_id, types.PeerUser)
if not (is_mentioned or is_private) or chat_id in self.config["excluded_chats"]:
return
reason = None
gone_time = None
if self._is_afk_enabled(chat_id, False):
reason = self.db.get(__name__, f"afk_here_{chat_id}_reason", None)
gone_time = self.db.get(__name__, f"gone_afk_here_{chat_id}", None)
elif self.global_afk:
reason = self.global_afk_reason
gone_time = self.global_gone_time
else:
return
if gone_time is None:
logger.warning(f"No 'gone' time found for chat {chat_id}. Cannot send AFK.")
return
afk_message = await self._send_afk_message(message, reason, gone_time)
if afk_message:
await self._delete_message(afk_message, self.DEFAULT_DELETE_TIMEOUT)
def _set_afk(self, value: bool, reason: str = None, chat_id: int = None) -> bool:
try:
key_prefix = f"afk_here_{chat_id}" if chat_id else "afk"
self.db.set(__name__, key_prefix, value)
self.db.set(__name__, f"{key_prefix}_reason", reason)
gone_key = f"gone_{key_prefix}"
gone_time = time.time() if value else None
self.db.set(__name__, gone_key, gone_time)
logger.debug(f"AFK status updated. {gone_key} set to {gone_time}")
if chat_id is None:
self.global_afk = value
self.global_afk_reason = reason
self.global_gone_time = gone_time
logger.debug("Updated global AFK vars")
return True
except Exception as e:
logger.exception(f"Error setting AFK status: {e}")
return False
def _calculate_total_afk_time(
self, now: datetime.datetime, chat_id: int = None
) -> datetime.timedelta:
key_prefix = f"afk_here_{chat_id}" if chat_id else "afk"
gone_time = self.db.get(__name__, f"gone_{key_prefix}")
if gone_time is None:
return datetime.timedelta(seconds=0)
try:
gone = datetime.datetime.fromtimestamp(gone_time).replace(microsecond=0)
return now - gone
except Exception as e:
logger.error(f"Error calculating AFK time: {e}")
return datetime.timedelta(seconds=0)
async def _send_afk_message(self, message, reason, gone_time):
user = await utils.get_user(message)
if user.is_self or user.bot or user.verified:
logger.debug("User is self, bot, or verified. Not sending AFK message.")
return None
now = datetime.datetime.now().replace(microsecond=0)
try:
gone = datetime.datetime.fromtimestamp(gone_time).replace(microsecond=0)
except (TypeError, ValueError) as e:
logger.error(f"Error converting timestamp: {e}")
return None
time_string = str(now - gone)
ret = (
self.strings("afk_message_reason", message).format(reason, time_string)
if reason
else self.strings("afk_message", message).format(time_string)
)
try:
reply = await utils.answer(message, ret, reply_to=message)
return reply
except Exception as e:
logger.exception(f"Error sending AFK message: {e}")
return None
def _is_afk_enabled(self, chat_id: int = None, global_afk: bool = False) -> bool:
return (
self.global_afk
if global_afk
else self.db.get(__name__, f"afk_here_{chat_id}", False)
)
async def _delete_message(self, message, delay):
await asyncio.sleep(delay)
try:
await self.client.delete_messages(message.chat_id, message.id)
except Exception as e:
logger.exception(f"Error deleting message: {e}")

View File

@@ -1,335 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: HModsLibrary
# Description: Library required for most H:Mods modules.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: HModsLibrary
# scope: HModsLibrary 0.0.1
# ---------------------------------------------------------------------------------
import logging
import re
import aiohttp
import random
import asyncio
import os
from bs4 import BeautifulSoup
from gigachat import GigaChat
from typing import Optional, Dict, Any
from .. import loader, utils
logger = logging.getLogger(__name__)
__version__ = (0, 0, 2)
class HModsLib(loader.Library):
"""Library required for most H:Mods modules."""
developer = "@hikka_mods"
version = __version__
async def parse_time(self, time_str):
time_units = {"d": 86400, "h": 3600, "m": 60, "s": 1}
if not re.fullmatch(r"(\d+[dhms])+", time_str):
return None
seconds = 0
matches = re.findall(r"(\d+)([dhms])", time_str)
for amount, unit in matches:
seconds += int(amount) * time_units[unit]
return seconds if seconds > 0 else None
@staticmethod
def convert_size(size):
"""Convert file size to human-readable format."""
power = 2**10
n = 0
units = {0: "B", 1: "KB", 2: "MB", 3: "GB", 4: "TB"}
while size > power:
size /= power
n += 1
return round(size, 2), units[n]
async def upload_to_envs(self, path):
"""Upload file to envs.sh and return the URL."""
url = "https://envs.sh"
async with aiohttp.ClientSession() as session:
async with session.post(url, data={"file": open(path, "rb")}) as response:
if response.status != 200:
os.remove(path)
raise aiohttp.ClientResponseError(
request_info=response.request_info,
history=response.history,
status=response.status,
message=await response.text(),
headers=response.headers,
)
result = await response.text()
os.remove(path)
return result
async def get_creation_date(tg_id: int) -> str:
url = "https://restore-access.indream.app/regdate"
headers = {
"accept": "*/*",
"content-type": "application/x-www-form-urlencoded",
"user-agent": "Nicegram/92 CFNetwork/1390 Darwin/22.0.0",
"x-api-key": "e758fb28-79be-4d1c-af6b-066633ded128",
"accept-language": "en-US,en;q=0.9",
}
data = {"telegramId": tg_id}
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=data) as response:
if response.status == 200:
json_response = await response.json()
return json_response["data"]["date"]
else:
return "Ошибка получения данных"
async def get_giga_response(self, api_key, query):
"""Gets a response from GigaChat with the specified query."""
async with GigaChat(
credentials=api_key,
scope="GIGACHAT_API_PERS",
model=self.config["GIGACHAT_MODEL"],
verify_ssl_certs=False,
) as giga:
response = giga.chat(query)
if response.choices:
return response.choices[0].message.content
return None
async def get_giga_models(self, api_key):
"""Gets a response from GigaChat with the specified query."""
async with GigaChat(
credentials=api_key, scope="GIGACHAT_API_PERS", verify_ssl_certs=False
) as giga:
response = giga.get_models()
if response:
return (
[model.id_ for model in response.data]
if hasattr(response, "data")
else []
)
return None
async def get_random_image():
random_site = random.randint(1, 3389)
url = f"https://www.memify.ru/memes/{random_site}"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
content = await response.text()
soup = BeautifulSoup(content, "html.parser")
items = soup.find_all("div", {"class": "infinite-item card"})
random_item = random.choice(items)
second_a = random_item.find_all("a")[1]
img = second_a.get("href")
return img
async def virustotal_request(
self,
session: aiohttp.ClientSession,
url: str,
headers: Dict[str, str],
method: str = "GET",
data: Optional[Dict[str, Any]] = None,
files: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
"""
Generic function to make requests to the VirusTotal API.
"""
try:
if files:
form = aiohttp.FormData()
for k, v in files.items():
form.add_field(k, v)
async with session.request(
method, url, headers=headers, data=form
) as response:
logger.debug(f"Response status: {response.status}")
logger.debug(f"Response body: {await response.text()}")
if response.status == 200:
return await response.json()
else:
logger.error(
f"VirusTotal API request failed with status: {response.status}, reason: {response.reason}"
)
return None
else:
async with session.request(
method, url, headers=headers, json=data
) as response:
logger.debug(f"Response status: {response.status}")
logger.debug(f"Response body: {await response.text()}")
if response.status == 200:
return await response.json()
else:
logger.error(
f"VirusTotal API request failed with status: {response.status}, reason: {response.reason}"
)
return None
except aiohttp.ClientError as e:
logger.exception(f"AIOHTTP Client error: {e}")
return None
except Exception as e:
logger.exception(f"An unexpected error occurred: {e}")
return None
async def get_upload_url(self, api_key: str) -> Optional[str]:
"""
Retrieves a special upload URL for large files.
"""
headers = {"x-apikey": api_key, "accept": "application/json"}
url = "https://www.virustotal.com/api/v3/files/upload_url"
async with aiohttp.ClientSession() as session:
response = await self.virustotal_request(session, url, headers)
if response and "data" in response and isinstance(response["data"], str):
return response["data"]
else:
logger.error(f"Failed to retrieve upload URL: {response}")
return None
async def scan_file_virustotal(
self, file_path: str, api_key: str, is_large_file: bool = False
) -> Optional[Dict[str, Any]]:
"""
Uploads a file to VirusTotal and retrieves the analysis results. Handles files larger than 32MB.
"""
headers = {"x-apikey": api_key}
url = "https://www.virustotal.com/api/v3/files"
async with aiohttp.ClientSession() as session:
try:
with open(file_path, "rb") as file:
files = {"file": file}
if is_large_file:
upload_url = await self.get_upload_url(api_key)
if not upload_url:
logger.error("Failed to get upload URL for large file.")
return None
url = upload_url
upload_response = await self.virustotal_request(
session, url, headers, method="POST", files=files
)
if (
upload_response
and "data" in upload_response
and "id" in upload_response["data"]
):
analysis_id = upload_response["data"]["id"]
analysis_url = (
f"https://www.virustotal.com/api/v3/analyses/{analysis_id}"
)
for attempt in range(20):
analysis_response = await self.virustotal_request(
session, analysis_url, headers
)
logger.debug(
f"Analysis response (attempt {attempt + 1}): {analysis_response}"
)
if (
analysis_response
and "data" in analysis_response
and "attributes" in analysis_response["data"]
and analysis_response["data"]["attributes"].get(
"status"
)
== "completed"
):
return analysis_response
await asyncio.sleep(10)
logger.warning(
f"Analysis not completed after multiple retries for ID: {analysis_id}"
)
return None
else:
logger.error(
f"File upload or analysis request failed: {upload_response}"
)
return None
except FileNotFoundError:
logger.error(f"File not found: {file_path}")
return None
except Exception as e:
logger.exception(f"An error occurred during file scanning: {e}")
return None
def format_analysis_results(self, analysis_results: Dict[str, Any]) -> str:
"""
Formats the analysis results into a user-friendly message, including specific detections.
"""
if (
not analysis_results
or "data" not in analysis_results
or "attributes" not in analysis_results["data"]
or "stats" not in analysis_results["data"]["attributes"]
or "results" not in analysis_results["data"]["attributes"]
):
logger.warning(
f"Unexpected structure in analysis_results: {analysis_results}"
)
return self.strings("error")
stats = analysis_results["data"]["attributes"]["stats"]
harmless = stats.get("harmless", 0)
malicious = stats.get("malicious", 0)
suspicious = stats.get("suspicious", 0)
undetected = stats.get("undetected", 0)
total_scans = harmless + malicious + suspicious + undetected
analysis_id = analysis_results["data"]["id"]
url = f"https://www.virustotal.com/gui/file-analysis/{analysis_id}"
text = (
f"<b>📊 VirusTotal Scan Results</b>\n\n"
f"🦠 <b>Detections:</b> {malicious} / {total_scans}\n"
f"🟢 <b>Harmless:</b> {harmless}\n"
f"⚠️ <b>Suspicious:</b> {suspicious}\n"
f"❓ <b>Undetected:</b> {undetected}\n\n"
)
if malicious > 0:
text += "<b>⚠️ Detections by engine:</b>\n"
results = analysis_results["data"]["attributes"]["results"]
for engine, result in results.items():
if result["category"] == "malicious":
engine_name = engine.replace("_", " ").title()
text += f" • <b>{engine_name}:</b> {result['result']}\n"
text += "\n"
return {"text": text, "url": url}

View File

@@ -1,80 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: InlineButton
# Description: Create inline button
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: InlineButton
# scope: InlineButton 0.0.1
# ---------------------------------------------------------------------------------
from ..inline.types import InlineQuery
from .. import loader, utils
@loader.tds
class InlineButtonMod(loader.Module):
"""Create inline button"""
strings = {
"name": "InlineButton",
"titles": "Create a message with the Inline Button",
"error_title": "Error",
"error_description": "Invalid input format. Please provide exactly three comma-separated values.",
"error_message": "Make sure your input is formatted as: message, name, url.",
}
strings_ru = {
"titles": "Создай сообщение с Inline Кнопкой",
"error_title": "Ошибка",
"error_description": "Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми.",
"error_message": "Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, url.",
}
@loader.command(
ru_doc="Создать inline кнопку\nНапример: @username_bot crinl Текст сообщения, Текст кнопки, Ссылка в кнопке",
en_doc="Create an inline button\nexample: @username_bot crinl Message text, Button text, Link in the button",
)
async def crinl_inline_handler(self, query: InlineQuery):
args = utils.get_args_raw(query.query)
if args:
args_list = [arg.strip() for arg in args.split(",")]
if len(args_list) == 3:
message, name, url = args_list
return {
"title": self.strings("titles"),
"description": f"{message}, {name}, {url}",
"message": message,
"reply_markup": [{
"text": name,
"url": url
}]
}
return {
"title": self.strings("error_title"),
"description": self.strings("error_description"),
"message": self.strings("error_message"),
}

View File

@@ -1,74 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: InlineCoin
# Description: Mini game heads or tails.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: InlineCoin
# scope: InlineCoin 0.0.1
# ---------------------------------------------------------------------------------
import random
from ..inline.types import InlineQuery
from .. import loader
@loader.tds
class CoinSexMod(loader.Module):
"""Mini game heads or tails"""
strings = {
"name": "InlineCoin",
"titles": "Heads or tails?",
"description": "Let's find out!",
"heads": "🌚 An eagle fell out!",
"tails": "🌝 Tails fell out!",
"edge": "🙀 Miraculously, the coin remained on the edge!",
}
strings_ru = {
"titles": "Орёл или решка?",
"description": "Давай узнаем!",
"heads": "🌚 Выпал орёл!",
"tails": "🌝 Выпала решка!",
"edge": "🙀 Чудо, монетка осталась на ребре!",
}
def get_coin_flip_result(self) -> dict:
results = [self.strings("heads"), self.strings("tails")]
if random.random() < 0.1:
return self.strings("edge")
else:
return random.choice(results)
@loader.command(
ru_doc="Подбросит монетку ",
en_doc="Flip a coin",
)
async def coin_inline_handler(self, query: InlineQuery):
result = self.get_coin_flip_result()
return {
"title": self.strings("titles"),
"description": self.strings("description"),
"message": f"<b>{result}</b>",
"thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png",
}

View File

@@ -1,213 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: InlineHelper
# Description: Basic management of the UB in case only the inline works
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: InlineHelper
# scope: InlineHelper 0.0.1
# ---------------------------------------------------------------------------------
import sys
import os
import asyncio
import logging
from ..inline.types import InlineQuery
from .. import loader, utils, main
@loader.tds
class InlineHelperMod(loader.Module):
"""Basic management of the UB in case only the inline works"""
strings = {
"name": "InlineHelper",
"call_restart": "Restarting...",
"call_update": "Updating...",
"res_prefix": "Successfully reset prefix to default",
"restart_inline_handler_title": "Restart Userbot",
"restart_inline_handler_description": "Restart your userbot via inline",
"restart_inline_handler_message": "Press the button below to restart your userbot",
"restart_inline_handler_reply_text": "Restart",
"update_inline_handler_title": "Update Userbot",
"update_inline_handler_description": "Update your userbot via inline",
"update_inline_handler_message": "Press the button below to update your userbot",
"update_inline_handler_reply_text": "Update",
"terminal_inline_handler_title": "Command Executed!",
"terminal_inline_handler_description": "Command executed successfully",
"terminal_inline_handler_message": "Command {text} executed successfully in terminal",
"modules_inline_handler_title": "Modules",
"modules_inline_handler_description": "List all installed modules",
"modules_inline_handler_result": "☘️ Installed modules:\n",
"resetprefix_inline_handler_title": "Reset Prefix",
"resetprefix_inline_handler_description": "Reset your prefix back to default",
"resetprefix_inline_handler_message": "Are you sure you want to reset your prefix to default dot?",
"resetprefix_inline_handler_reply_text_yes": "Yes",
"resetprefix_inline_handler_reply_text_no": "No",
}
strings_ru = {
"call_restart": "Перезагружаю...",
"call_update": "Обновляю...",
"res_prefix": "Префикс успешно сброшен по умолчанию",
"restart_inline_handler_title": "Перезагрузить юзербота",
"restart_inline_handler_description": "Перезагрузить юзербота через инлайн",
"restart_inline_handler_message": "<b>Нажмите на кнопку ниже для рестарта юзербота</b>",
"restart_inline_handler_reply_text": "Перезапуск",
"update_inline_handler_title": "Обновить юзербота",
"update_inline_handler_description": "Обновить юзербота через инлайн",
"update_inline_handler_message": "<b>Нажмите на кнопку ниже для обновления юзербота</b>",
"update_inline_handler_reply_text": "Обновить",
"terminal_inline_handler_title": "Команда выполнена!",
"terminal_inline_handler_description": "Команда завершена.",
"terminal_inline_handler_message": "Команда <code>{text}</code> была успешно выполнена в терминале",
"modules_inline_handler_title": "Модули",
"modules_inline_handler_description": "Вывести список установленных моудей",
"modules_inline_handler_result": "☘️ Все установленные модули:\n",
"resetprefix_inline_handler_title": "Сбросить префикс",
"resetprefix_inline_handler_description": "Сбросить префикс по умолчанию",
"resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?",
"resetprefix_inline_handler_reply_text_yes": "Да",
"resetprefix_inline_handler_reply_text_no": "Нет",
}
async def client_ready(self, client, db):
self.client = client
self.db = db
async def restart(self, call):
"""Restart callback"""
logging.error("InlineHelper: restarting userbot...")
await call.edit(self.strings("call_restart"))
await sys.exit(0)
async def update(self, call):
"""Update callback"""
logging.error("InlineHelper: updating userbot...")
os.system(f"cd {utils.get_base_dir()} && cd .. && git reset --hard HEAD")
os.system("git pull")
await call.edit(self.strings("call_update"))
await sys.exit(0)
async def reset_prefix(self, call):
"""Reset prefix"""
self.db.set(main.__name__, "command_prefix", ".")
await call.edit(self.strings("res_prefix"))
@loader.inline_handler(
ru_doc="Перезагрузить юзербота",
en_doc="Reboot the userbot",
)
async def restart_inline_handler(self, _: InlineQuery):
return {
"title": self.strings("restart_inline_handler_title"),
"description": self.strings("restart_inline_handler_description"),
"message": self.strings("restart_inline_handler_message"),
"reply_markup": [
{
"text": self.strings("restart_inline_handler_reply_text"),
"callback": self.restart,
}
],
}
@loader.inline_handler(
ru_doc="Обновить юзербота",
en_doc="Update the userbot",
)
async def update_inline_handler(self, _: InlineQuery):
return {
"title": self.strings("update_inline_handler_title"),
"description": self.strings("update_inline_handler_description"),
"message": self.strings("update_inline_handler_message"),
"reply_markup": [
{
"text": self.strings("update_inline_handler_reply_text"),
"callback": self.update,
}
],
}
@loader.inline_handler(
ru_doc="Выполнить команду в терминале (лучше сразу подготовить команду и просто вставить)",
en_doc="Execute the command in the terminal (it is better to prepare the command immediately and just paste it)",
)
async def terminal_inline_handler(self, _: InlineQuery):
text = _.args
await asyncio.create_subprocess_shell(
f"{text}",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=utils.get_base_dir(),
)
return {
"title": self.strings("terminal_inline_handler_title"),
"description": self.strings("terminal_inline_handler_description"),
"message": self.strings("terminal_inline_handler_message").format(
text=text
),
}
@loader.inline_handler(
ru_doc="Вывести список установленных модулей через инлайн",
en_doc="Display a list of installed modules via the inline",
)
async def modules_inline_handler(self, _: InlineQuery):
result = self.strings("modules_inline_handler_result")
for mod in self.allmodules.modules:
try:
name = mod.strings["name"]
except KeyError:
name = mod.__clas__.__name__
result += f"{name}\n"
return {
"title": self.strings("modules_inline_handler_title"),
"description": self.strings("modules_inline_handler_description"),
"message": result,
}
@loader.inline_handler(
ru_doc="Сбросить префикс (осторожнее, сбрасывает ваш префикс на . )",
en_doc="Reset the prefix (be careful, resets your prefix to . )",
)
async def resetprefix_inline_handler(self, _: InlineQuery):
return {
"title": self.strings("resetprefix_inline_handler_title"),
"description": self.strings("resetprefix_inline_handler_description"),
"message": self.strings("resetprefix_inline_handler_message"),
"reply_markup": [
{
"text": self.strings("resetprefix_inline_handler_reply_text_yes"),
"callback": self.reset_prefix,
},
{
"text": self.strings("resetprefix_inline_handler_reply_text_no"),
"action": "close",
},
],
}

View File

@@ -1,88 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: IrisSimpleMod
# Description: Module for basic interaction with Iris.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: IrisSimpleMod
# scope: IrisSimpleMod 1.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
__version__ = (1, 0, 1)
@loader.tds
class IrisSimpleMod(loader.Module):
"""Модуль для базового взаимодействия с Ирисом"""
strings = {"name": "IrisSimpleMod"}
@loader.command(
ru_doc="Проверить мешок"
)
async def bag(self, message):
"""Check bag"""
async with self.client.conversation(5443619563) as conv:
usermessage = await conv.send_message('мешок')
await usermessage.delete()
bagmessage = await conv.get_response()
await utils.answer(message, 'Ваш мешок:\n' + bagmessage.text)
await bagmessage.delete()
@loader.command(
ru_doc="Зафармить ирис-коины"
)
async def farm(self, message):
"""Farm iris-coins"""
async with self.client.conversation(5443619563) as conv:
usermessage = await conv.send_message('ферма')
await usermessage.delete()
farmmessage = await conv.get_response()
await utils.answer(message, farmmessage.text)
await farmmessage.delete()
@loader.command(
ru_doc="Вывести анкету"
)
async def irisstats(self, message):
"""Display user stats"""
async with self.client.conversation(5443619563) as conv:
usermessage = await conv.send_message('анкета')
await usermessage.delete()
statsmessage = await conv.get_response()
await utils.answer(message, statsmessage.text)
await statsmessage.delete()
@loader.command(
ru_doc="Вывести статистику ботов"
)
async def irisping(self, message):
"""Display bot stats"""
async with self.client.conversation(5443619563) as conv:
usermessage = await conv.send_message('🌺 Семейство ирисовых')
await usermessage.delete()
pingmessage = await conv.get_response()
await utils.answer(message, pingmessage.text)
await pingmessage.delete()

View File

@@ -1,100 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: KBSwapper
# Description: KBSwapper is a module for changing the keyboard layout
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: KBSwapper
# scope: KBSwapper 0.0.1
# ---------------------------------------------------------------------------------
import string
from .. import loader, utils
EN_TO_RU = str.maketrans(
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
"йцукенгшщзхъфывапролджэячсмитьбю.ё" + "ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё",
)
RU_TO_EN = str.maketrans(
"йцукенгшщзхъфывапролджэячсмитьбю.ё" + "ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё",
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
)
@loader.tds
class KBSwapperMod(loader.Module):
"""KBSwapper is a module for changing the keyboard layout"""
strings = {
"name": "KBSwapper",
"no_reply": "<emoji document_id=5774077015388852135>❌</emoji> <b>Please reply to a message.</b>",
"no_text": "<emoji document_id=5774077015388852135>❌</emoji> <b>The replied message does not contain text.</b>",
"original_message": "<emoji document_id=5260450573768990626>➡️</emoji> <b>Original message:</b>\n<code>{original}</code>",
"fixed_message": "<emoji document_id=5774022692642492953>✅</emoji> <b>Fixed message:</b>\n<code>{fixed}</code>",
"error": "<emoji document_id=5774077015388852135>❌</emoji> <b>An error occurred while processing the message.</b>",
}
strings_ru = {
"no_reply": "<emoji document_id=5774077015388852135>❌</emoji> <b>Пожалуйста, ответьте на сообщение.</b>",
"no_text": "<emoji document_id=5774077015388852135>❌</emoji> <b>Отвеченное сообщение не содержит текста.</b>",
"original_message": "<emoji document_id=5260450573768990626>➡️</emoji> <b>Оригинальное сообщение:</b>\n<code>{original}</code>",
"fixed_message": "<emoji document_id=5774022692642492953>✅</emoji> <b>Исправленное сообщение:</b>\n<code>{fixed}</code>",
"error": "<emoji document_id=5774077015388852135>❌</emoji> <b>Произошла ошибка при обработке сообщения.</b>",
}
@loader.command(
ru_doc="При ответе на своё сообщение меняет раскладку путем редактирования, на чужое — в отдельном сообщении.",
en_doc="Change keyboard layout for the replied message.",
)
async def swap(self, message):
reply = await message.get_reply_message()
if not reply:
await utils.answer(message, self.strings("no_reply"))
return
original_text = reply.text
if not original_text:
await utils.answer(message, self.strings("no_text"))
return
try:
first_char = original_text[0].lower()
if first_char in string.ascii_lowercase:
fixed_text = original_text.translate(EN_TO_RU)
elif first_char in "йцукенгшщзхъфывапролджэячсмитьбю.ё":
fixed_text = original_text.translate(RU_TO_EN)
else:
fixed_text = original_text
if message.sender_id == reply.sender_id:
await reply.edit(fixed_text)
else:
await utils.answer(
message,
f"{self.strings('original_message').format(original=original_text)}\n"
f"{self.strings('fixed_message').format(fixed=fixed_text)}",
)
except Exception as e:
print(f"Error during swap: {e}")
await utils.answer(message, self.strings("error"))

View File

@@ -1,20 +0,0 @@
Proprietary License Agreement
Copyright (c) 2024-29 CodWiz
Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
5. By using the Software, you agree to be bound by the terms and conditions of this license.
For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
Sergei
08.10.2024

View File

@@ -1,107 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Meme
# Description: Random memes
# Author: @hikka_mods
# Commands:
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Meme
# scope: Meme 0.0.1
# ---------------------------------------------------------------------------------
from bs4 import BeautifulSoup
import aiohttp
import random
from .. import loader
@loader.tds
class MemesMod(loader.Module):
"""Random memes"""
strings = {
"name": "Memes",
"done": "☄️ Catch the meme",
"still": "🔄 Update",
"dell": "❌ Close",
}
strings_ru = {
"done": "☄️ Лови мем",
"still": "🔄 Обновить",
"dell": "❌ Закрыть",
}
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib('https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py')
@loader.command(
ru_doc="",
en_doc="",
)
async def memescmd(self, message):
img = await self.hmodslib.get_random_image()
await self.inline.form(
text=self.strings("done"),
photo=img,
message=message,
reply_markup=[
[
{
"text": self.strings("still"),
"callback": self.ladno,
}
],
[
{
"text": self.strings("dell"),
"callback": self.dell,
}
],
],
silent=True,
)
async def ladno(self, call):
img = await self.hmodslib.get_random_image()
await call.edit(
text=self.strings("done"),
photo=img,
reply_markup=[
[
{
"text": self.strings("still"),
"callback": self.ladno,
}
],
[
{
"text": self.strings("dell"),
"callback": self.dell,
}
],
],
)
async def dell(self, call):
"""Callback button"""
await call.delete()

File diff suppressed because it is too large Load Diff

View File

@@ -1,202 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Music
# Description: Searches for music using Telegram music bots.
# Author: @hikka_mods
# meta developer: @hikka_mods
# scope: Music
# scope: Music 0.0.2
# ---------------------------------------------------------------------------------
# Thanks to @murpizz for the search code yandex
import logging
from telethon.errors.rpcerrorlist import (
BotMethodInvalidError,
FloodWaitError,
MessageNotModifiedError,
)
from telethon.tl.types import Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class MusicMod(loader.Module):
strings = {
"name": "Music",
"no_query": "<emoji document_id=5337117114392127164>🤷‍♂</emoji> <b>Provide a search query.</b>",
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Searching...</b>",
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Possible match:</b>",
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Track not found: <code>{}</code>.</b>",
"invalid_service": "<emoji document_id=5462295343642956603>🚫</emoji> <b>Invalid service. (yandex, vk)</b>",
"usage": "<b>Usage:</b> <code>.music [yandex|vk] [track name]</code>",
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Error:</b> <code>{}</code>",
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>No results: <code>{}</code>.</b>",
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Wait {}s (Telegram limits).</b>",
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Bot error: <code>{}</code></b>",
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>No audio.</b>",
"generic_result": "<emoji document_id=5336965905773504919></emoji> <b>Non-media result. Check the bot's chat.</b>",
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Searching Yandex.Music...</b>",
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Track not found on Yandex.Music.</b>",
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Error (Yandex): {}</b>",
}
strings_ru = {
"name": "Music",
"no_query": "<emoji document_id=5337117114392127164>🤷‍♂</emoji> <b>Укажите запрос.</b>",
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Поиск...</b>",
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Возможно, это оно:</b>",
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Трек не найден: <code>{}</code>.</b>",
"invalid_service": "<emoji document_id=5462295343642956603>🚫</emoji> <b>Неверный сервис. (yandex, vk)</b>",
"usage": "<b>Использование:</b> <code>.music [yandex|vk] [название трека]</code>",
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Ошибка:</b> <code>{}</code>",
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>Нет результатов: <code>{}</code>.</b>",
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Подождите {}с (лимиты Telegram).</b>",
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Ошибка бота: <code>{}</code></b>",
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>Нет аудио.</b>",
"generic_result": "<emoji document_id=5336965905773504919></emoji> <b>Немедийный результат. Проверьте чат с ботом.</b>",
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Поиск в Яндекс.Музыке...</b>",
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Трек не найден в Яндекс.Музыке.</b>",
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Ошибка (Яндекс): {}</b>",
}
def __init__(self):
self.murglar_bot = "@murglar_bot"
self.vk_bot = "@vkmusic_bot"
@loader.command(
ru_doc="Найти трек в Yandex Music или VK: `.music yandex {название}` или `.music vk {название}`",
en_doc="Find a track in Yandex Music or VK: `.music yandex {name}` or `.music vk {name}`",
)
async def music(self, message):
args = utils.get_args(message)
if not args:
if reply := await message.get_reply_message():
await self._yafind(message, reply.raw_text.strip())
else:
await utils.answer(message, self.strings("usage", message))
return
service, query = args[0].lower(), " ".join(args[1:])
if service == "yandex":
await self._yafind(message, query)
elif service == "vk":
await self._vkfind(message, query)
else:
await utils.answer(message, self.strings("invalid_service", message))
async def _yafind(self, message: Message, query: str):
if not query:
return await utils.answer(message, self.strings("no_query", message))
await utils.answer(message, self.strings("yafind_searching", message))
try:
results = await message.client.inline_query(
self.murglar_bot, f"s:ynd {query}"
)
if not results:
return await utils.answer(
message, self.strings("yafind_not_found", message)
)
await results[0].click(
entity=message.chat_id,
hide_via=True,
reply_to=message.reply_to_msg_id if message.reply_to_msg_id else None,
)
await message.delete()
except Exception as e:
logger.exception("Yandex search error:")
await utils.answer(message, self.strings("yafind_error", message).format(e))
async def _vkfind(self, message, query: str):
if not query:
return await utils.answer(message, self.strings("no_query", message))
await utils.answer(message, self.strings("searching", message))
try:
music = await message.client.inline_query(self.vk_bot, query)
if not music or len(music) <= 1:
return await utils.answer(
message, self.strings("not_found", message).format(query)
)
for i in range(1, len(music), 2):
try:
result = music[i].result
if hasattr(result, "audio") and result.audio:
await message.client.send_file(
message.to_id,
result.audio,
caption=self.strings("found", message),
reply_to=utils.get_topic(message)
if message.reply_to_msg_id
else None,
)
await message.delete()
return
if hasattr(result, "document") and result.document:
await message.client.send_file(
message.to_id,
result.document,
caption=self.strings("found", message),
reply_to=utils.get_topic(message)
if message.reply_to_msg_id
else None,
)
await message.delete()
return
logger.warning(f"No audio/document in result {i}")
await utils.answer(message, self.strings("no_audio", message))
await message.delete()
return
except MessageNotModifiedError:
logger.warning("MessageNotModifiedError, skipping.")
except Exception as e:
logger.error(f"Send error: {e}")
await utils.answer(
message, self.strings("not_found", message).format(query)
)
except BotMethodInvalidError as e:
logger.error(f"VK bot error: {e}")
await utils.answer(message, self.strings("bot_error", message).format(e))
except FloodWaitError as e:
logger.warning(f"Flood wait: {e.seconds}s")
await utils.answer(
message, self.strings("flood_wait", message).format(e.seconds)
)
except Exception as e:
logger.exception("VK search error:")
await utils.answer(message, self.strings("error", message).format(e))

View File

@@ -1,94 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: PastebinAPI
# Description: fills in the code on pastebin
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: PastebinAPI
# scope: PastebinAPI 0.0.1
# requires: aiohttp
# ---------------------------------------------------------------------------------
import aiohttp
from .. import loader, utils
@loader.tds
class PastebinAPIMod(loader.Module):
"""PastebinAPI"""
strings = {
"name": "PastebinAPI",
"no_reply": (
"<emoji document_id=5462882007451185227>🚫</emoji> You didn't specify the text"
),
"no_key": "<emoji document_id=5843952899184398024>🚫</emoji> The key was not found",
"done": "Your link with the code\n<emoji document_id=5985571061993837069>➡️</emoji> <code>{response_text}</code>",
}
strings_ru = {
"no_reply": (
"<emoji document_id=5462882007451185227>🚫</emoji> Вы не указали текст"
),
"no_key": "<emoji document_id=5843952899184398024>🚫</emoji> Ключ не найден",
"done": "Ваша ссылка с кодом\n<emoji document_id=5985571061993837069>➡️</emoji> <code>{response_text}</code>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"pastebin",
None,
lambda: "link to get api https://pastebin.com/doc_api#1",
validator=loader.validators.Hidden(),
)
)
@loader.command(
ru_doc="Заливает код в Pastebin",
en_doc="Uploads the code to Pastebin",
)
async def past(self, message):
text = utils.get_args(message)
if self.config["pastebin"] is None:
await utils.answer(message, self.strings("no_key"))
return
if not text:
await utils.answer(message, self.strings("no_reply"))
return
async with aiohttp.ClientSession() as Session:
async with Session.post(
url="https://pastebin.com/api/api_post.php",
data={
"api_dev_key": self.config["pastebin"],
"api_paste_code": text,
"api_option": "paste",
},
) as response:
response_text = await response.text()
await utils.answer(
message, self.strings("done").format(response_text=response_text)
)

View File

@@ -1,91 +0,0 @@
<div align="center">
<img src="https://github.com/Codwizer/ReModules/blob/main/assets/Vector.png" alt="hikka_mods" width="600">
</div>
<h1 align="center">H:Mods - Hikka Modules</h1>
<p align="center">
Enhance your <a href="https://github.com/hikariatama/Hikka">Hikka</a> experience with a curated collection of community modules.
</p>
<p align="center">
<a href="https://t.me/hikka_mods">
<img src="https://img.shields.io/badge/Join%20the-Telegram%20Channel-blue?style=flat-square&logo=telegram" alt="Telegram Channel">
</a>
</p>
---
## ✨ Installation Guide
### 1. Direct Installation
To install a module directly from the repository, use the following command:
```bash
.dlm https://raw.githubusercontent.com/C0dwiz/H.Modules/main/<module_name>.py
```
**Example:** To install example_module.py, use:
```bash
.dlm https://raw.githubusercontent.com/C0dwiz/H.Modules/main/example_module.py
```
### 2. Repository Installation (Recommended)
For easier module management and updates, install the entire repository.
**Step 1: Add the Repository**
```bash
.addrepo https://github.com/C0dwiz/H.Modules/raw/main
```
**Step 2: Install Modules from the Repository**
```bash
.dlm <module_name>
```
**Example:** After adding the repository, to install example_module, use:
```bash
.dlm example_module
```
---
## 📜 License
This project is licensed under a **Proprietary License**. By using this software, you agree to the terms and conditions outlined below.
> **You are granted permission to use the Software for personal and non-commercial purposes, subject to the following conditions:**
1. Modification or alteration of the Software is strictly prohibited without explicit written permission from the author.
2. Redistribution of the Software, in original or modified form, is strictly prohibited without explicit written permission from the author.
3. The Software is provided "as is", without any warranty, express or implied.
4. The copyright notice and this permission notice must be included in all copies or substantial portions of the Software.
5. By using the Software, you agree to be bound by the terms and conditions of this license.
**Contact:** For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
<details>
<summary>Show Full License Details</summary>
> <i>All files of this repository are under <b>Proprietary License.</b></i><br>
> <b>Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:</b>
1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
3. The Software is provided "as is", without warranty of any kind, express or implied.
4. The copyright notice and this permission notice must be included in all copies or substantial portions of the Software.
5. By using the Software, you agree to be bound by the terms and conditions of this license.
For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
</details>
---
<p align="center">
<img src="https://raw.githubusercontent.com/trinib/trinib/82213791fa9ff58d3ca768ddd6de2489ec23ffca/images/footer.svg" width="100%">
</p>

View File

@@ -1,78 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: VowelReplacer
# Description: Replaces vowel letters with ё
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: VowelReplacer
# scope: VowelReplacer 0.0.1
# ---------------------------------------------------------------------------------
from telethon.tl.types import Message
from .. import loader, utils
@loader.tds
class VowelReplacer(loader.Module):
"""Replaces vowel letters with ё"""
strings = {
"name": "Vowel Replacer",
"on": "✅ Vowel substitution for ё has been successfully enabled.",
"off": "🚫 Vowel substitution for ё is disabled.",
}
strings_ru = {
"on": "✅ Замена гласных на ё успешно включена.",
"off": "🚫 Замена гласных на ё отключена.",
}
async def client_ready(self, client, db):
self.db = db
self._client = client
self.enabled = self.db.get("vowel_replacer", "enabled", False)
@loader.command(
ru_doc="Включить или отключить замену гласных на ё.",
en_doc="Enable or disable vowel substitution for ё.",
)
async def vowelreplace(self, message):
self.enabled = not self.enabled
self.db.set("vowel_replacer", "enabled", self.enabled)
if self.enabled:
response = self.strings("on")
else:
response = self.strings("off")
await utils.answer(message, response)
async def watcher(self, message: Message):
"""Автоматическая замена гласных на ё при получении собственного сообщения."""
if self.enabled and message.out:
vowels = "аеёиоуыэюяАЕЁИОУЫЭЮЯ"
message_text = message.text
replaced_text = "".join(
"ё" if char in vowels else char for char in message_text
)
await message.edit(replaced_text)

View File

@@ -1,149 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: SMArchiver
# Description: unloads all messages from Favorites
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: SMArchiver
# scope: SMArchiver 0.0.1
# requires: zipfile
# ---------------------------------------------------------------------------------
import zipfile
import os
from datetime import datetime
from .. import loader, utils
@loader.tds
class SMArchiver(loader.Module):
"""unloads all messages from Favorites"""
strings = {
"name": "SMArchiver",
"archive_created": "🎉 Archive with messages has been successfully created: {filename}",
"no_messages": "⚠️ There are no messages in Saved Messages.",
"error": "❌ An error occurred: {error}",
"processing": "🛠️ Processing messages... Please wait.\n\nP.S: Be careful, if you have a lot of messages, you may get flooding, and if you have a lot of heavy files, the download will be slower than usual.",
}
strings_ru = {
"archive_created": "🎉 Архив с сообщениями успешно создан: {filename}",
"no_messages": "⚠️ В Сохраненных сообщениях нет сообщений.",
"error": "❌ Произошла ошибка: {error}",
"processing": "🛠️ Обработка сообщений... Пожалуйста, подождите.\n\nP.S: Будьте осторожны, если у вас много сообщений, вы можете получить флуд, а если у вас много тяжелых файлов, загрузка будет медленнее обычного.",
}
@loader.command(
ru_doc="выгружает все сообщения из Избранного / Saved Messages и собирает их в одном архиве.",
en_doc="downloads all messages from Favorites / Saved Messages and collects them in one archive.",
)
async def smdump(self, message):
await utils.answer(message, self.strings["processing"])
saved_messages = await message.client.get_messages("me", limit=None)
if not saved_messages:
await utils.answer(message, self.strings["no_messages"])
return
archive_path = self.create_archive(saved_messages)
try:
await message.client.send_file(
message.chat_id,
archive_path,
caption=self.strings["archive_created"].format(
filename=os.path.basename(archive_path)
),
)
except Exception as e:
await utils.answer(message, self.strings["error"].format(error=str(e)))
finally:
self.cleanup(archive_path)
def create_archive(self, saved_messages):
current_month = datetime.now().strftime("%B %Y")
archive_path = "saved_messages.zip"
with zipfile.ZipFile(archive_path, "w") as archive:
self.initialize_archive_structure(archive, current_month)
for msg in saved_messages:
await self.add_message_to_archive(msg, archive, current_month)
return archive_path
def initialize_archive_structure(self, archive, current_month):
month_folder = f"{current_month}/"
archive.writestr(month_folder, "")
message_folders = {
"Text Messages": f"{month_folder}Text Messages/",
"Voice Messages": f"{month_folder}Voice Messages/",
"Video Messages": f"{month_folder}Video Messages/",
"Videos": f"{month_folder}Videos/",
"Audios": f"{month_folder}Audios/",
"GIFs": f"{month_folder}GIFs/",
"Files": f"{month_folder}Files/",
}
for folder in message_folders.values():
archive.writestr(folder, "")
async def add_message_to_archive(self, msg, archive, current_month):
"""Обрабатывает отдельное сообщение и добавляет его в архив."""
if msg.message:
await self.add_text_message_to_archive(msg, archive, current_month)
if msg.media:
await self.add_media_to_archive(msg, archive, current_month)
async def add_text_message_to_archive(self, msg, archive, current_month):
timestamp = datetime.fromtimestamp(msg.date.timestamp()).strftime(
"%Y%m%d_%H%M%S"
)
safe_name = f"message_{timestamp}.txt"
archive.writestr(
os.path.join(f"{current_month}/Text Messages/", safe_name), msg.message
)
async def add_media_to_archive(self, msg, archive, current_month):
media_file = await msg.client.download_media(msg.media)
if media_file:
mime_type = (
msg.media.document.mime_type if hasattr(msg.media, "document") else None
)
folder = self.get_media_folder(mime_type, current_month)
archive.write(
media_file, os.path.join(folder, os.path.basename(media_file))
)
def get_media_folder(self, mime_type, current_month):
if mime_type:
if mime_type.startswith("audio/"):
return f"{current_month}/Audios/"
elif mime_type.startswith("video/"):
return f"{current_month}/Videos/"
elif mime_type.startswith("image/gif"):
return f"{current_month}/GIFs/"
return f"{current_month}/Files/"
def cleanup(self, archive_path):
if os.path.exists(archive_path):
os.remove(archive_path)

View File

@@ -1,103 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: SafetyMod
# Description: generate random password
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api SafetyMod
# scope: Api SafetyMod 0.0.1
# ---------------------------------------------------------------------------------
import random
import string
from .. import loader, utils
def generate_password(
length: int, letters: bool = True, numbers: bool = True, symbols: bool = True
) -> str:
"""Generates a random password with customizable options.
Args:
length: The desired length of the password.
letters: Include lowercase and uppercase letters (default: True).
numbers: Include digits (default: True).
symbols: Include common symbols (default: True).
Returns:
A randomly generated password string.
Raises:
ValueError: If all character sets are disabled (letters, numbers, symbols).
"""
character_sets = []
if letters:
character_sets.append(string.ascii_letters)
if numbers:
character_sets.append(string.digits)
if symbols:
character_sets.append(string.punctuation)
if not character_sets:
raise ValueError("At least one of letters, numbers, or symbols must be True")
combined_characters = "".join(character_sets)
password = "".join(random.choice(combined_characters) for _ in range(length))
return password
@loader.tds
class SafetyMod(loader.Module):
"""generate random password"""
strings = {
"name": "Safety",
"pass": "<emoji document_id=5472287483318245416>*⃣</emoji> <b>Here is your secure password:</b> <code>{}</code>",
}
strings_ru = {
"pass": "<emoji document_id=5472287483318245416>*⃣</emoji> <b>Вот ваш безопасный пароль:</b> <code>{}</code>"
}
@loader.command(
ru_doc="Случайный пароль\n-n - цифры\n-s - символы \n -l - буквы",
en_doc="Random password\n-n - numbers\n-s - symbols \n -l - letters",
)
async def password(self, message):
"""random password\n-n - numbers\n-s - symbols \n -l - letters"""
text = message.text.split()
length = 10
letters = True
numbers = False
symbols = False
for i in text:
if i.startswith("password"):
length = int(i.split("password")[1])
elif i == "-n":
numbers = True
elif i == "-s":
symbols = True
elif i == "-l":
letters = True
password = generate_password(
length=length, letters=letters, numbers=numbers, symbols=symbols
)
await utils.answer(message, self.strings("pass").format(password))

View File

@@ -1,358 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TaskManager
# Description: Manages tasks with Telegram commands and inline keyboards.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: TaskManager
# scope: TaskManager 0.0.1
# ---------------------------------------------------------------------------------
import datetime
import json
import logging
import os
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from .. import loader, utils
logger = logging.getLogger(__name__)
@dataclass
class Task:
"""Represents a task."""
description: str
due_date: Optional[datetime.datetime] = None
completed: bool = False
created_at: datetime.datetime = field(default_factory=datetime.datetime.now)
class TaskManager:
"""Manages tasks, storing them in a JSON file."""
def __init__(self, data_file: str):
self.data_file = data_file
self.tasks: Dict[int, List[Task]] = {}
self.load_data()
def load_data(self):
"""Loads task data from the JSON file."""
if os.path.exists(self.data_file):
try:
with open(self.data_file, "r") as f:
data = json.load(f)
self.tasks = {
int(user_id): [
Task(
description=task["description"],
due_date=datetime.datetime.fromisoformat(
task["due_date"]
)
if task["due_date"]
else None,
completed=task["completed"],
created_at=datetime.datetime.fromisoformat(
task["created_at"]
),
)
for task in task_list
]
for user_id, task_list in data.items()
}
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.warning(f"Failed to load task data: {e}. Starting empty.")
self.tasks = {}
else:
self.tasks = {}
logger.info("Task data file not found. Starting empty.")
def save_data(self):
"""Saves task data to the JSON file."""
try:
with open(self.data_file, "w") as f:
data = {
user_id: [
{
"description": task.description,
"due_date": task.due_date.isoformat()
if task.due_date
else None,
"completed": task.completed,
"created_at": task.created_at.isoformat(),
}
for task in task_list
]
for user_id, task_list in self.tasks.items()
}
json.dump(data, f, indent=4, default=str)
except IOError as e:
logger.error(f"Failed to save task data: {e}")
def add_task(self, user_id: int, task: Task):
self.tasks.setdefault(user_id, []).append(task)
self.save_data()
def remove_task(self, user_id: int, index: int):
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
del self.tasks[user_id][index]
self.save_data()
else:
logger.warning(f"Invalid index for removal: {index}, user: {user_id}")
def complete_task(self, user_id: int, index: int):
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
self.tasks[user_id][index].completed = True
self.save_data()
else:
logger.warning(f"Invalid index for completion: {index}, user: {user_id}")
def get_tasks(self, user_id: int) -> List[Task]:
return self.tasks.get(user_id, [])
def clear_tasks(self, user_id: int):
if user_id in self.tasks:
self.tasks[user_id] = []
self.save_data()
else:
logger.info(f"No tasks to clear for user: {user_id}")
def get_task(self, user_id: int, index: int) -> Optional[Task]:
return (
self.tasks[user_id][index]
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id])
else None
)
@loader.tds
class TaskManagerModule(loader.Module):
"""Manages tasks with Telegram commands and inline keyboards."""
strings = {
"name": "TaskManager",
"task_added": "<emoji document_id=5776375003280838798>✅</emoji> Task added.",
"task_removed": "<emoji document_id=5776375003280838798>✅</emoji> Task removed.",
"task_completed": "<emoji document_id=5776375003280838798>✅</emoji> Task completed.",
"task_not_found": "<emoji document_id=5778527486270770928>❌</emoji> Task not found.",
"no_tasks": "<emoji document_id=5956561916573782596>📄</emoji> No active tasks.",
"task_list": "<emoji document_id=5956561916573782596>📄</emoji> Your tasks:\n{}",
"invalid_index": "<emoji document_id=5778527486270770928>❌</emoji> Invalid index. Provide valid integer.",
"description_required": "<emoji document_id=5879813604068298387>❗️</emoji> Provide task description.",
"clear_confirmation": "⚠️ Delete all tasks?",
"tasks_cleared": "✅ All tasks deleted.",
"due_date_format": "<emoji document_id=5778527486270770928>❌</emoji> Invalid date. Use YYYY-MM-DD HH:MM.",
"task_info": "<emoji document_id=6028435952299413210></emoji> Task: {description}\n<emoji document_id=5967412305338568701>📅</emoji> Due: {due_date}\n<emoji document_id=5825794181183836432>✔️</emoji> Completed: {completed}\n<emoji document_id=5936170807716745162>🎛</emoji> Created: {created_at}",
"confirm_clear": "Confirm",
"cancel_clear": "Cancel",
"clear_cancelled": "❌ Deletion cancelled.",
"index_required": "⚠️ Provide task index.",
"clear_confirmation_text": "Are you sure you want to clear all tasks?",
"confirm": "Confirm",
"cancel": "Cancel",
}
strings_ru = {
"task_added": "<emoji document_id=5776375003280838798>✅</emoji> Задача добавлена.",
"task_removed": "<emoji document_id=5776375003280838798>✅</emoji> Задача удалена.",
"task_completed": "<emoji document_id=5776375003280838798>✅</emoji> Задача выполнена.",
"task_not_found": "<emoji document_id=5778527486270770928>❌</emoji> Задача не найдена.",
"no_tasks": "<emoji document_id=5956561916573782596>📄</emoji> Нет активных задач.",
"task_list": "<emoji document_id=5956561916573782596>📄</emoji> Ваши задачи:\n{}",
"invalid_index": "<emoji document_id=5778527486270770928>❌</emoji> Неверный индекс. Укажите целое число.",
"description_required": "<emoji document_id=5879813604068298387>❗️</emoji> Укажите описание задачи.",
"clear_confirmation": "⚠️ Удалить все задачи?",
"tasks_cleared": "Все задачи удалены.",
"due_date_format": "<emoji document_id=5778527486270770928>❌</emoji> Неверный формат даты. Используйте ГГГГ-ММ-ДД ЧЧ:ММ.",
"task_info": "<emoji document_id=6028435952299413210></emoji> Задача: {description}\n<emoji document_id=5967412305338568701>📅</emoji> Срок: {due_date}\n<emoji document_id=5825794181183836432>✔️</emoji> Выполнена: {completed}\n<emoji document_id=5936170807716745162>🎛</emoji> Создана: {created_at}",
"confirm_clear": "Подтвердить",
"cancel_clear": "Отменить",
"clear_cancelled": "❌ Удаление отменено.",
"index_required": "⚠️ Укажите индекс задачи.",
"clear_confirmation_text": "Вы уверены, что хотите удалить все задачи?",
"confirm": "Подтвердить",
"cancel": "Отменить",
}
def __init__(self):
self.task_manager: Optional[TaskManager] = None
async def client_ready(self, client, db):
self.task_manager = TaskManager(os.path.join(os.getcwd(), "tasks.json"))
@loader.command(
ru_doc="Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>",
en_doc="Add task:\n.taskadd <description> | <date (opt)>",
)
async def taskadd(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("description_required"))
return
try:
description, due_date_str = (
args.split("|", 1) if "|" in args else (args, None)
)
description = description.strip()
due_date_str = due_date_str.strip() if due_date_str else None
due_date = (
datetime.datetime.fromisoformat(due_date_str) if due_date_str else None
)
except ValueError:
await utils.answer(message, self.strings("due_date_format"))
return
except Exception as e:
logger.error(f"Error adding task: {e}")
await utils.answer(message, f"<emoji document_id=5778527486270770928>❌</emoji> Error: {e}")
return
task = Task(description=description, due_date=due_date)
self.task_manager.add_task(message.sender_id, task)
await utils.answer(message, self.strings("task_added"))
@loader.command(
ru_doc="[index] - удалить задачу",
en_doc="[index] - remove task"
)
async def taskremove(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("index_required"))
return
try:
index = int(args) - 1
except ValueError:
await utils.answer(message, self.strings("invalid_index"))
return
if self.task_manager.get_task(message.sender_id, index) is None:
await utils.answer(message, self.strings("task_not_found"))
return
self.task_manager.remove_task(message.sender_id, index)
await utils.answer(message, self.strings("task_removed"))
@loader.command(
ru_doc="[index] - Завершите задачу",
en_doc="[index] - Complete task"
)
async def taskcomplete(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("index_required"))
return
try:
index = int(args) - 1
except ValueError:
await utils.answer(message, self.strings("invalid_index"))
return
if self.task_manager.get_task(message.sender_id, index) is None:
await utils.answer(message, self.strings("task_not_found"))
return
self.task_manager.complete_task(message.sender_id, index)
await utils.answer(message, self.strings("task_completed"))
@loader.command(
ru_doc="Список задач",
en_doc="List tasks"
)
async def tasklist(self, message):
tasks = self.task_manager.get_tasks(message.sender_id)
if not tasks:
await utils.answer(message, self.strings("no_tasks"))
return
task_list_str = "\n".join(
[
f" {i + 1}. {'<emoji document_id=5776375003280838798>✅</emoji>' if task.completed else '<emoji document_id=5778527486270770928>❌</emoji>'} {task.description} (Due: {task.due_date.strftime('%Y-%m-%d %H:%M') if task.due_date else 'None'})"
for i, task in enumerate(tasks)
]
)
await utils.answer(message, self.strings("task_list").format(task_list_str))
@loader.command(
ru_doc="[index] - Посмотреть информацию о задаче",
en_doc="[index] - Show task info",
)
async def taskinfo(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("index_required"))
return
try:
index = int(args) - 1
except ValueError:
await utils.answer(message, self.strings("invalid_index"))
return
task = self.task_manager.get_task(message.sender_id, index)
if not task:
await utils.answer(message, self.strings("task_not_found"))
return
due_date_str = (
task.due_date.strftime("%Y-%m-%d %H:%M") if task.due_date else "None"
)
created_at_str = task.created_at.strftime("%Y-%m-%d %H:%M")
await utils.answer(
message,
self.strings("task_info").format(
description=task.description,
due_date=due_date_str,
completed="Yes" if task.completed else "No",
created_at=created_at_str,
),
)
@loader.command(
ru_doc="Удалить все задачи",
en_doc="Clear all tasks"
)
async def taskclear(self, message):
await self.inline.form(
text=self.strings("clear_confirmation_text"),
message=message,
reply_markup=[
[
{"text": self.strings("confirm"), "callback": self.clear_confirm},
{"text": self.strings("cancel"), "callback": self.clear_cancel},
]
],
silent=True,
)
async def clear_confirm(self, call):
"""Callback for confirming task clearing."""
self.task_manager.clear_tasks(call.from_user.id)
await call.edit(self.strings("tasks_cleared"))
async def clear_cancel(self, call):
"""Callback for canceling task clearing."""
await call.edit(self.strings("clear_cancelled"))

View File

@@ -1,133 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TelegramStatusCodes
# Description: Dictionary of telegram status codes
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api TelegramStatusCodes
# scope: Api TelegramStatusCodes 0.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
responses = {
300: (
"⛔ SEE_OTHER",
"The request must be repeated, but directed to a different data center.",
),
400: (
"⛔ BAD_REQUEST",
"The query contains errors. In the event that a request was created using a form and contains user generated data, the user should be notified that the data must be corrected before the query is repeated.",
),
401: (
"⛔ UNAUTHORIZED",
"There was an unauthorized attempt to use functionality available only to authorized users.",
),
403: (
"⛔ FORBIDDEN",
"Privacy violation. For example, an attempt to write a message to someone who has blacklisted the current user.",
),
404: (
"⛔ NOT_FOUND",
"An attempt to invoke a non-existent object, such as a method",
),
406: (
"⛔ NOT_ACCEPTABLE",
"""
Similar to <b>400 BAD_REQUESTS</b>, but the app must display the error to the user a bit differently.
Do not display any visible error to the user when receiving the <b>rpc_error</b> constructor: instead, wait for an <a href="https://core.telegram.org/constructor/updateServiceNotification ">updateServiceNotification</a> update, and handle it as usual.
Basically, an <a href="https://core.telegram.org/constructor/updateServiceNotification"updateServiceNotification</a> <b>pop-up</b> update will be emitted independently (ie NOT as an <a href="https://core.telegram.org/type/Updates">Updates</a> constructor inside <b>rpc_result</b> but as a normal update) immediately after emission of a 406 <b>rpc_error</b>: the update will contain the actual localized error message to show to the user with a UI popup.
An exception to this is the <b>AUTH_KEY_DUPLICATED</b> error, which is only emitted if any of the non-media DC detects that an authorized session is sending requests in parallel from two separate TCP connections, from the same or different IP addresses.
Note that parallel connections are still allowed and actually recommended for media DCs.
Also note that by session we mean a logged-in session identified by an <a href="https://core.telegram.org/constructor/authorization">authorization</a> constructor, fetchable using <a href="https://core.telegram.org/method/account.getAuthorizations">account.getAuthorizations</a>, not an MTProto session.
If the client receives an <b>AUTH_KEY_DUPLICATED</b> error, the session was already invalidated by the server and the user must generate a new auth key and login again.""",
),
420: (
"⛔ FLOOD",
"The maximum allowed number of attempts to invoke the given method with the given input parameters has been exceeded. For example, in an attempt to request a large number of text messages (SMS) for the same phone number.",
),
500: (
"⛔ INTERNAL",
"""An internal server error occurred while a request was being processed; for example, there was a disruption while accessing a database or file storage.
If a client receives a 500 error, or you believe this error should not have occurred, please collect as much information as possible about the query and error and send it to the developers""",
),
}
@loader.tds
class TelegramStatusCodes(loader.Module):
"""Dictionary of telegram status codes"""
strings = {
"name": "TelegramStatusCodes",
"args_incorrect": "<b>Incorrect args</b>",
"not_found": "<b>Code not found</b>",
"syntax_error": "<b>Args are mandatory</b>",
"scode": "<b>{} {}</b>\n⚜️ Code Description: <i>{}</i>",
}
strings_ru = {
"args_incorrect": "<b>Неверные аргументы</b>",
"not_found": "<b>Код не найден</b>",
"syntax_error": "<b>Аргументы обязательны</b>",
"_cmd_doc_httpsc": "<код> - Получить информацию о Telegram error",
"_cmd_doc_httpscs": "Показать все доступные коды",
"_cls_doc": "Словарь telegram error",
}
@loader.unrestricted
@loader.command(
ru_doc="<код состояния> - Получение информации о коде состояния",
en_doc="<statuscode> - Get status code info",
)
async def tgccmd(self, message):
args = utils.get_args(message)
if not args:
await utils.answer(message, self.strings("syntax_error", message))
try:
if int(args[0]) not in responses:
await utils.answer(message, self.strings("not_found", message))
except ValueError:
await utils.answer(message, self.strings("args_incorrect", message))
await utils.answer(
message,
self.strings("scode", message).format(
responses[int(args[0])][0], args[0], responses[int(args[0])][1]
),
)
@loader.unrestricted
@loader.command(
ru_doc="Получите все коды статуса telegram",
en_doc="Get all telegram status codes",
)
async def tgcscmd(self, message):
await utils.answer(
message,
"\n".join(
[f"<b>{str(sc)}: {text}</b>" for sc, (text, _) in responses.items()]
),
)

View File

@@ -1,162 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TempChat
# Description: Creates a temporary private chat with a message forwarding restriction and adds the specified user to it.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: TempChat
# scope: TempChat 0.0.1
# ---------------------------------------------------------------------------------
import logging
import asyncio
from hikkatl import functions
from datetime import datetime as dt
from .. import loader, utils
logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)
@loader.tds
class TempChatMod(loader.Module):
"""Creates a temporary private chat with a message forwarding restriction and adds the specified user to it."""
strings = {
"name": "TempChat",
"selfchat": "You can't create a chat with yourself.",
"wrongargs": "<emoji document_id=5980953710157632545>❌</emoji> <b>Wrong arguments. Use </b><code>.tmpchat [@user/reply] [time]</code><b>",
"alreadychatting": "<emoji document_id=5980953710157632545>❌</emoji> <b>You already have an active conversation with this person.</b>",
"invalidtime": "<emoji document_id=5980953710157632545>❌</emoji> <b>Invalid time format. Use combinations like 1h30m.</b>",
"invitemsg": "<emoji document_id=5818967120213445821>🛡</emoji> You've been invited to a temporary private chat!\n\n<emoji document_id=5451646226975955576>⌛️</emoji> Auto-deletes in ",
"joinlink": "🔗 Join link: ",
"chatcreated": "<emoji document_id=5980930633298350051>✅</emoji> The temporary chat has been successfully created!"
}
strings_ru = {
"selfchat": "Ты не можешь создать чат сам с собой.",
"wrongargs": "<emoji document_id=5980953710157632545>❌</emoji> <b>Неверные аргументы. Используй </b><code>.tmpchat [@user/reply] [время]</code>",
"alreadychatting": "<emoji document_id=5980953710157632545>❌</emoji> <b>У вас уже есть открытая переписка с этим человеком.</b>",
"invalidtime": "<emoji document_id=5980953710157632545>❌</emoji> <b>Неверный формат времени. Убедитесь, что вы вводите время в формате 1h, 2h30m.</b>",
"invitemsg": "<emoji document_id=5818967120213445821>🛡</emoji> Вы были приглашены во временный приватный чат!\n\n<emoji document_id=5451646226975955576>⌛️</emoji> Авто-удаление через ",
"joinlink": "🔗 Ссылка: ",
"chatcreated": "<emoji document_id=5980930633298350051>✅</emoji> Временный чат успешно создан!",
}
def __init__(self):
self.temp_chats = {}
async def check_expired_chats(self):
while True:
now = dt.now().timestamp()
for chat_id in list(self.temp_chats.keys()):
if self.temp_chats[chat_id][1] <= now:
try:
await self.client(
functions.channels.DeleteChannelRequest(chat_id)
)
del self.temp_chats[chat_id]
self.set("temp_chats", self.temp_chats)
except Exception as e:
logger.error(f"Error deleting chat {chat_id}: {e}")
try:
self.client(
functions.channels.GetFullChannelRequest(
channel=chat_id
)
)
except Exception:
del self.temp_chats[chat_id]
self.set("temp_chats", self.temp_chats)
await asyncio.sleep(30)
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib(
"https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py"
)
self.temp_chats = self.get("temp_chats", {})
asyncio.create_task(self.check_expired_chats())
@loader.command(
ru_doc="Создает временный чат. Использование: .tmpchat [@user/reply] [time]"
)
async def tmpchat(self, message):
"""Create temporary chat. Usage: .tmpchat [@user/reply] [time]"""
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if reply:
user = await self.client.get_entity(reply.sender_id)
time_str = args.strip() if args else None
else:
parts = args.split(",", 1) if "," in args else args.rsplit(" ", 1)
if len(parts) != 2:
return await utils.answer(message, self.strings["wrongargs"])
user_str, time_str = parts[0].strip(), parts[1].strip()
try:
user = await self.client.get_entity(user_str)
except Exception:
return await utils.answer(message, self.strings["wrongargs"])
if not time_str:
return await utils.answer(message, self.strings["wrongargs"])
seconds = await self.hmodslib.parse_time(time_str)
if not seconds:
return await utils.answer(message, self.strings["invalidtime"])
if any(user.id == uid for uid, _ in self.temp_chats.values()):
return await utils.answer(message, self.strings["alreadychatting"])
try:
created = await self.client(
functions.channels.CreateChannelRequest(
title=f"TempChat #{user.id}",
about=f"Temporary private chat with {user.id} | Expires after: {time_str}",
megagroup=True,
)
)
chat_id = created.chats[0].id
expires_at = dt.now().timestamp() + seconds
await self.client(
functions.messages.ToggleNoForwardsRequest(peer=chat_id, enabled=True)
)
self.temp_chats[chat_id] = (user.id, expires_at)
self.set("temp_chats", self.temp_chats)
invite = await self.client(
functions.messages.ExportChatInviteRequest(peer=chat_id, usage_limit=1)
)
invite_message = (
self.strings["invitemsg"]
+ time_str
+ f"\n{self.strings['joinlink']} {invite.link}"
)
await self.client.send_message(user.id, invite_message)
await utils.answer(message, self.strings["chatcreated"])
except Exception as e:
logger.error(f"Error creating temp chat: {e}")
await utils.answer(message, "❌ Error! Check log-chat.")

View File

@@ -1,75 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Text2File
# Description: Module for convertation your text to file
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Text2File
# scope: Text2File 0.0.1
# ---------------------------------------------------------------------------------
import io
from .. import loader, utils
@loader.tds
class Text2File(loader.Module):
"""Module for convertation your text to file"""
strings = {
"name": "Text2File",
"no_args": "Don't have any args! Use .ttf text/code",
"cfg_name": "You can change the extension and file name",
}
strings_ru = {
"no_args": "Недостаточно аргументов! Используйте: .ttf текст/код",
"cfg_name": "Вы можете выбрать расширение и название для файла",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"name",
"file.txt",
lambda: self.strings("cfg_name"),
),
)
@loader.command(
ru_doc="Создать файл с вашим текстом или кодом",
en_doc="Create a file with your text or code",
)
async def ttfcmd(self, message):
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings("no_args"))
else:
text = args
by = io.BytesIO(text.encode("utf-8"))
by.name = self.config["name"]
await utils.answer_file(
message,
by,
reply_to=getattr(message, "reply_to_msg_id", None),
)

View File

@@ -1,108 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Text in sticker
# Description: Text in sticker
# Author: @hikka_mods
# Commands:
# .st <hex color> [text]
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Text in sticker
# scope: Text in sticker 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import io
from textwrap import wrap
import requests
from PIL import Image, ImageColor, ImageDraw
from PIL import ImageFont
from .. import loader, utils
@loader.tds
class TextinstickerMod(loader.Module):
"""Text to sticker"""
strings = {
"name": "Text in sticker",
"error": "white st <color name> [text]",
}
strings_ru = {
"error": "Укажите .st <color name> [text]",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"font",
"https://github.com/CodWize/ReModules/blob/main/assets/Samson.ttf?raw=true",
lambda: "add a link to the font you want",
)
)
@loader.command(
ru_doc="<название цвета> [текст]",
en_doc="<color name> [text]",
)
@loader.owner
async def stcmd(self, message):
await message.delete()
text = utils.get_args_raw(message)
reply = await message.get_reply_message()
if not text:
if not reply:
text = self.strings("error")
elif not reply.message:
text = self.strings("error")
else:
text = reply.raw_text
color_name = text.split(" ", 1)[0].lower()
color = None
if len(text.split(" ", 1)) > 1:
text = text.split(" ", 1)[1]
else:
if reply and reply.message:
text = reply.raw_text
try:
color = ImageColor.getrgb(color_name)
except ValueError:
color = (255, 255, 255)
txt = []
for line in text.split("\n"):
txt.append("\n".join(wrap(line, 30)))
text = "\n".join(txt)
bytes_font = requests.get(self.config["font"]).content
font = io.BytesIO(bytes_font)
font = ImageFont.truetype(font, 100)
image = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
draw = ImageDraw.Draw(image)
w, h = draw.multiline_textsize(text=text, font=font)
image = Image.new("RGBA", (w + 100, h + 100), (0, 0, 0, 0))
draw = ImageDraw.Draw(image)
draw.multiline_text((50, 50), text=text, font=font, fill=color, align="center")
output = io.BytesIO()
output.name = f"{color_name}.webp"
image.save(output, "WEBP")
output.seek(0)
await self.client.send_file(message.to_id, output, reply_to=reply)

View File

@@ -1,327 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: TikTokDownloader
# Description: A module for downloading videos and photos from TikTok without watermark
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api TikTokDownloader
# scope: Api TikTokDownloader 0.0.1
# ---------------------------------------------------------------------------------
import aiohttp
import asyncio
import re
import os
import warnings
import functools
import logging
from dataclasses import dataclass
from urllib.parse import urljoin
from typing import Union, Optional, List
from tqdm import tqdm
from .. import loader, utils
@dataclass
class data:
dir_name: str
media: Union[str, List[str]]
type: str
class TikTok:
def __init__(self, host: Optional[str] = None):
self.headers = {
"User-Agent": "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) "
"Version/4.0.4 Mobile/7B334b Safari/531.21.10"
}
self.host = host or "https://www.tikwm.com/"
self.session = aiohttp.ClientSession()
self.data_endpoint = "api"
self.search_videos_keyword_endpoint = "api/feed/search"
self.search_videos_hashtag_endpoint = "api/challenge/search"
self.link = None
self.result = None
self.logger = logging.getLogger("damirtag-TikTok")
handler = logging.StreamHandler()
formatter = logging.Formatter(
"[damirtag-TikTok:%(funcName)s]: %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
def _warn(reason: str = "This function is NOT used but may be useful"):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
f"Warning! Deprecated: {func.__name__}\nReason: {reason}",
category=DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)
return wrapper
return decorator
async def close_session(self):
await self.session.close()
async def _ensure_data(self, link: str):
try:
if self.result is None or self.link != link:
self.link = link
self.result = await self.fetch(link)
self.logger.info("Successfully ensured data from the link")
except Exception as e:
self.logger.error(f"Error occurred when trying to get data from tikwm: {e}")
raise
async def __getimages(self, download_dir: Optional[str] = None):
download_dir = download_dir or self.result["id"]
os.makedirs(download_dir, exist_ok=True)
tasks = [
self._download_file(url, os.path.join(download_dir, f"image_{i + 1}.jpg"))
for i, url in enumerate(self.result["images"])
]
await asyncio.gather(*tasks)
self.logger.info(f"Images - Downloaded and saved photos to {download_dir}")
return data(
dir_name=download_dir,
media=[
os.path.join(download_dir, f"image_{i + 1}.jpg")
for i in range(len(self.result["images"]))
],
type="images",
)
async def __getvideo(self, video_filename: Optional[str] = None, hd: bool = False):
video_url = self.result["hdplay"] if hd else self.result["play"]
video_filename = video_filename or f"{self.result['id']}.mp4"
async with self.session.get(video_url) as response:
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
with open(video_filename, "wb") as file:
with tqdm(
total=total_size, unit="B", unit_scale=True, desc=video_filename
) as pbar:
async for chunk in response.content.iter_any():
file.write(chunk)
pbar.update(len(chunk))
self.logger.info(f"Video - Downloaded and saved video as {video_filename}")
return data(
dir_name=os.path.dirname(video_filename), media=video_filename, type="video"
)
async def _makerequest(self, endpoint: str, params: dict) -> dict:
async with self.session.get(
urljoin(self.host, endpoint), params=params, headers=self.headers
) as response:
response.raise_for_status()
data = await response.json()
return data.get("data", {})
@staticmethod
def get_url(text: str) -> Optional[str]:
urls = re.findall(r"http[s]?://[^\s]+", text)
return urls[0] if urls else None
@_warn()
async def convert_share_urls(self, url: str) -> Optional[str]:
url = self.get_url(url)
if "@" in url:
return url
async with self.session.get(
url, headers=self.headers, allow_redirects=False
) as response:
if response.status == 301:
return response.headers["Location"].split("?")[0]
return None
@_warn()
async def get_tiktok_video_id(self, original_url: str) -> Optional[str]:
original_url = await self.convert_share_urls(original_url)
matches = re.findall(r"/video|v|photo/(\d+)", original_url)
return matches[0] if matches else None
async def fetch(self, link: str) -> dict:
url = self.get_url(link)
params = {"url": url, "hd": 1}
return await self._makerequest(self.data_endpoint, params=params)
async def _download_file(self, url: str, path: str):
async with self.session.get(url) as response:
response.raise_for_status()
with open(path, "wb") as file:
while chunk := await response.content.read(1024):
file.write(chunk)
async def download_sound(
self,
link: Union[str],
audio_filename: Optional[str] = None,
audio_ext: Optional[str] = ".mp3",
):
await self._ensure_data(link)
if not audio_filename:
audio_filename = f"{self.result['music_info']['title']}{audio_ext}"
else:
audio_filename += audio_ext
await self._download_file(self.result["music_info"]["play"], audio_filename)
self.logger.info(f"Sound - Downloaded and saved sound as {audio_filename}")
return audio_filename
async def download(
self, link: Union[str], video_filename: Optional[str] = None, hd: bool = True
) -> data:
"""
Asynchronously downloads a TikTok video or photo post.
Args:
video_filename (Optional[str]): The name of the file for the TikTok video or photo. If None, the file will be named based on the video or photo ID.
hd (bool): If True, downloads the video in HD format. Defaults to False.
Returns:
dir_name (str): Directory name
media (Union[str, List[str]]): Full list of downloaded media
type (str): The type of downloaded objects: Images or video
Raises:
Exception: No downloadable content found in the provided link.
"""
await self._ensure_data(link)
if "images" in self.result:
self.logger.info("Starting to download images")
return await self.__getimages(video_filename)
elif "hdplay" in self.result or "play" in self.result:
self.logger.info("Starting to download video.")
return await self.__getvideo(video_filename, hd)
else:
self.logger.error("No downloadable content found in the provided link.")
raise Exception("No downloadable content found in the provided link.")
def _get_video_link(self, unique_id: str, aweme_id: str) -> str:
return f"https://www.tiktok.com/@{unique_id}/video/{aweme_id}"
def _get_uploader_link(self, unique_id: str) -> str:
return f"https://www.tiktok.com/@{unique_id}"
@loader.tds
class TikTokDownloader(loader.Module):
"""TikTok Downloader module"""
strings = {
"name": "TikTokDownloader",
"downloading": "<emoji document_id=5436024756610546212>⚡</emoji> <b>Downloading…</b>",
"success_photo": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The photo(s) has/have been successfully downloaded!</b>!",
"success_video": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The video has been successfully downloaded!</b>",
"success_sound": "<emoji document_id=5436246187944460315>❤️</emoji> <b>The sound has been successfully downloaded!</b>",
"error": "Error occurred while downloading.\n{}",
}
strings_ru = {
"downloading": "<emoji document_id=5436024756610546212>⚡</emoji> <b>Загружаем…</b>",
"success_photo": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Фотография(-и) была(-и) успешно загружены!</b>!",
"success_video": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Видео было успешно загружено!</b>",
"success_sound": "<emoji document_id=5436246187944460315>❤️</emoji> <b>Звук был успешно загружен!</b>",
"error": "Во время загрузки произошла ошибка.\n{}",
}
@loader.command(
ru_doc="Скачать звук с TikTok",
en_doc="Download sound from TikTok",
)
async def ttsound(self, message):
args = utils.get_args(message)
if not args:
await utils.answer(message, "Please provide a TikTok URL.")
return
url = args[0]
await utils.answer(message, self.strings("downloading"))
tiktok_downloader = TikTok()
try:
download_result = await tiktok_downloader.download_sound(url)
await message.client.send_file(
message.to_id, download_result, caption=self.strings("success_sound")
)
await message.delete()
except Exception as e:
await utils.answer(
message,
f"{self.strings('error').format(e)}\n Убедитесь, что ссылка ведет именно на видео или фото с нужным звуком, прямая ссылка на звук не сработает!",
)
finally:
await tiktok_downloader.close_session()
@loader.command(
ru_doc="Скачать видео или фото с TikTok",
en_doc="Download videos or photos from TikTok",
)
async def tt(self, message):
args = utils.get_args(message)
if not args:
await utils.answer(message, "Please provide a TikTok URL.")
return
url = args[0]
await utils.answer(message, self.strings("downloading"))
tiktok_downloader = TikTok()
try:
download_result = await tiktok_downloader.download(url)
if download_result.type == "video":
await message.client.send_file(
message.to_id,
download_result.media,
caption=self.strings("success_video"),
)
await message.delete()
elif download_result.type == "images":
await message.client.send_file(
message.to_id,
download_result.media,
caption=self.strings("success_photo"),
)
await message.delete()
except Exception as e:
await utils.answer(message, self.strings("error").format(e))
finally:
await tiktok_downloader.close_session()

View File

@@ -1,972 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: UserbotAvast
# Description: A module for checking modules for security.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: UserbotAvast
# scope: UserbotAvast 0.0.1
# ---------------------------------------------------------------------------------
import logging
import ast
import astor
import requests
import base64
import zlib
import re
import urllib.parse
from .. import loader, utils
logger = logging.getLogger(__name__)
try:
import g4f
G4F_AVAILABLE = True
except ImportError:
G4F_AVAILABLE = False
logger.warning("g4f is not installed. AI analysis will be disabled.")
class SecurityAnalyzer:
"""
Продвинутый анализатор безопасности Python-кода с эвристическим анализом.
"""
SECURITY_KEYWORDS = {
"critical": [
{
"keyword": "DeleteAccountRequest",
"description": "Удаление аккаунта",
"relevance": "Высокая",
},
{
"keyword": "ResetAuthorizationRequest",
"description": "Сброс авторизации",
"relevance": "Высокая",
},
{
"keyword": "client.export_session_string",
"description": "Экспорт сессии",
"relevance": "Высокая",
},
{
"keyword": "edit_2fa",
"description": "Изменение 2FA",
"relevance": "Высокая",
},
{
"keyword": "os.system",
"description": "Системные команды",
"relevance": "Высокая",
},
{
"keyword": "subprocess.Popen",
"description": "Внешние процессы",
"relevance": "Высокая",
},
{
"keyword": "eval",
"description": "Выполнение кода (eval)",
"relevance": "Критическая",
},
{
"keyword": "exec",
"description": "Выполнение кода (exec)",
"relevance": "Критическая",
},
{
"keyword": "MessagePacker.append",
"description": "Патч MessagePacker",
"relevance": "Средняя",
},
{
"keyword": "MessagePacker.extend",
"description": "Патч MessagePacker",
"relevance": "Средняя",
},
{
"keyword": "Scrypt",
"description": "Класс Scrypt (подозрительно)",
"relevance": "Высокая",
},
{
"keyword": "socket.socket",
"description": "Создание сокета",
"relevance": "Высокая",
},
{
"keyword": "shell=True",
"description": "Использование shell=True в subprocess",
"relevance": "Критическая",
},
{
"keyword": "codecs.decode",
"description": "Декодирование с использованием codecs",
"relevance": "Средняя",
},
{
"keyword": "pickle.loads",
"description": "Десериализация (pickle)",
"relevance": "Высокая",
},
{
"keyword": "marshal.loads",
"description": "Десериализация (marshal)",
"relevance": "Высокая",
},
{
"keyword": "__import__",
"description": "Динамический импорт",
"relevance": "Средняя",
},
{
"keyword": "ctypes.CDLL",
"description": "Загрузка динамической библиотеки",
"relevance": "Высокая",
},
{
"keyword": "create_connection",
"description": "Установка соединения",
"relevance": "Высокая",
},
{
"keyword": "http.server",
"description": "Запуск веб-сервера",
"relevance": "Средняя",
},
{
"keyword": "asyncio.create_subprocess_shell",
"description": "Асинхронный запуск процесса через shell",
"relevance": "Критическая",
},
],
"warning": [
{
"keyword": "requests",
"description": "HTTP-запросы",
"relevance": "Средняя",
},
{
"keyword": "aiohttp",
"description": "Асинхронные HTTP-запросы",
"relevance": "Средняя",
},
{
"keyword": "os.remove",
"description": "Удаление файлов",
"relevance": "Средняя",
},
{
"keyword": "os.mkdir",
"description": "Создание каталогов",
"relevance": "Низкая",
},
{
"keyword": "json.loads",
"description": "Парсинг JSON",
"relevance": "Низкая",
},
{
"keyword": "open(..., 'w')",
"description": "Открытие файла на запись",
"relevance": "Средняя",
},
{
"keyword": "open(..., 'a')",
"description": "Открытие файла на добавление",
"relevance": "Средняя",
},
{
"keyword": "telnetlib.Telnet",
"description": "Telnet соединение",
"relevance": "Средняя",
},
{
"keyword": "ftplib.FTP",
"description": "FTP соединение",
"relevance": "Средняя",
},
{
"keyword": "shutil.move",
"description": "Перемещение файлов",
"relevance": "Средняя",
},
{
"keyword": "shutil.copy",
"description": "Копирование файлов",
"relevance": "Средняя",
},
{
"keyword": "threading.Thread",
"description": "Создание потока",
"relevance": "Низкая",
},
{
"keyword": "multiprocessing.Process",
"description": "Создание процесса",
"relevance": "Низкая",
},
{
"keyword": "queue.Queue",
"description": "Использование очереди",
"relevance": "Низкая",
},
{
"keyword": "subprocess.check_output",
"description": "Запуск процесса с захватом вывода",
"relevance": "Средняя",
},
{
"keyword": "subprocess.run",
"description": "Запуск процесса",
"relevance": "Средняя",
},
{
"keyword": "codecs.encode",
"description": "Кодирование с использованием codecs",
"relevance": "Средняя",
},
],
"info": [
{
"keyword": "telethon",
"description": "Использование Telethon",
"relevance": "Низкая",
},
{
"keyword": "pyrogram",
"description": "Использование Pyrogram",
"relevance": "Низкая",
},
{
"keyword": "import",
"description": "Импорт модулей",
"relevance": "Низкая",
},
{
"keyword": "print",
"description": "Вывод в консоль",
"relevance": "Низкая",
},
{
"keyword": "logging.info",
"description": "Логирование",
"relevance": "Низкая",
},
],
}
def __init__(self, ai_enabled: bool = False):
"""Инициализация анализатора."""
self.results = {"critical": [], "warning": [], "info": []}
self.reported_issues = set()
self.code_lines = []
self.is_decoded = False
self.ai_enabled = ai_enabled
def reset(self):
"""Сброс результатов анализа."""
self.results = {"critical": [], "warning": [], "info": []}
self.reported_issues = set()
self.code_lines = []
self.is_decoded = False
async def analyze(self, code: str, strings: dict) -> str:
"""
Выполняет анализ предоставленного Python-кода.
Args:
code: Python-код для анализа.
strings: Словарь строк для локализации.
Returns:
Форматированный отчет об анализе.
"""
self.reset()
original_code = code
try:
code = self._try_decode(code)
self.is_decoded = True
except Exception:
logger.warning("Не удалось расшифровать код, анализ как есть.")
self.code_lines = code.splitlines()
try:
tree = ast.parse(code)
self._visit_tree(tree)
self._heuristic_analysis(code, tree)
if self.ai_enabled and G4F_AVAILABLE:
ai_analysis_result = await self._ai_analysis(code)
if ai_analysis_result:
self.results["critical"].append(
{
"keyword": "AI Analysis",
"description": ai_analysis_result,
"relevance": "Критическая",
"line": 0,
"col": 0,
}
)
except SyntaxError as e:
logger.error(f"Ошибка синтаксиса в коде: {e}")
return strings["syntax_error"].format(error=e)
except Exception as e:
logger.exception("Unexpected error during analysis")
return strings["syntax_error"].format(error=str(e))
return self._format_report(
strings, original_code
)
async def _ai_analysis(self, code: str) -> str or None:
"""
Использует g4f для анализа кода и выявления потенциальных угроз.
"""
try:
prompt = f"Проанализируйте следующий Python-код на предмет потенциальных угроз безопасности, уязвимостей и вредоносных действий. Предоставьте подробное объяснение, если что-то будет обнаружено:\n\n{code}"
response = await utils.run_sync(
g4f.ChatCompletion.create,
model=g4f.models.default,
messages=[{"role": "user", "content": prompt}],
)
return str(response)
except Exception as e:
logger.error(f"Ошибка при анализе с помощью g4f: {e}")
return None
def _try_decode(self, code):
"""Попытка расшифровать base64 + zlib код."""
if re.search(r"__import__\('zlib'\).decompress\(", code) and re.search(
r"__import__\('base64'\).b64decode\(", code
):
try:
match = re.search(r"b'([A-Za-z0-9+/=]+)'", code)
if match:
encoded_string = match.group(1)
decoded_code = self._decode_base64_zlib(encoded_string)
logger.info("Код успешно расшифрован.")
return decoded_code
except Exception as e:
logger.error(f"Ошибка при расшифровке кода: {e}")
raise
return code
def _decode_base64_zlib(self, encoded_string):
"""Расшифровывает base64 + zlib код."""
try:
decoded_bytes = base64.b64decode(encoded_string)
decompressed_bytes = zlib.decompress(decoded_bytes)
return decompressed_bytes.decode("utf-8")
except Exception as e:
logger.error(f"Ошибка при расшифровке base64+zlib: {e}")
raise
def _get_line_from_code(self, lineno):
"""Получает строку кода по номеру строки."""
try:
return self.code_lines[lineno - 1]
except IndexError:
return ""
def _visit_tree(self, tree):
"""Рекурсивно обходит AST-дерево."""
for node in ast.walk(tree):
self._analyze_node(node)
def _analyze_node(self, node):
"""Анализирует отдельный узел AST."""
if isinstance(node, ast.Name):
self._check_keyword(node.id, node)
elif isinstance(node, ast.Call):
self._check_call(node)
elif isinstance(node, (ast.Import, ast.ImportFrom)):
self._check_import(node)
elif isinstance(node, ast.FunctionDef):
self._check_function_def(node)
elif isinstance(node, ast.ClassDef):
self._check_class_def(node)
elif isinstance(node, ast.Assign):
self._check_assign(node)
def _check_keyword(self, keyword, node):
"""Проверяет ключевые слова."""
for severity, keywords in self.SECURITY_KEYWORDS.items():
for item in keywords:
if item["keyword"] == keyword:
issue_key = (
item["keyword"],
node.lineno,
node.col_offset,
)
if issue_key not in self.reported_issues:
self.results[severity].append(
{
"keyword": item["keyword"],
"description": item["description"],
"relevance": item["relevance"],
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
def _check_call(self, node):
"""Анализирует вызовы функций."""
if isinstance(node.func, ast.Name):
self._check_keyword(node.func.id, node)
elif isinstance(node.func, ast.Attribute):
full_attr = ""
if isinstance(node.func.value, ast.Name):
full_attr = node.func.value.id + "." + node.func.attr
self._check_keyword(full_attr, node)
else:
self._check_keyword(node.func.attr, node)
elif isinstance(
node.func, ast.Subscript
):
if isinstance(node.func.value, ast.Attribute):
full_attr = ""
if isinstance(node.func.value.value, ast.Name):
full_attr = node.func.value.value.id + "." + node.func.value.attr
self._check_keyword(full_attr, node)
def _check_function_def(self, node):
self._check_keyword(node.name, node)
def _check_class_def(self, node):
self._check_keyword(node.name, node)
def _check_import(self, node):
"""Анализирует импорты."""
if isinstance(node, ast.Import):
for alias in node.names:
self._check_keyword(alias.name, node)
elif isinstance(node, ast.ImportFrom):
self._check_keyword(node.module, node)
for alias in node.names:
self._check_keyword(alias.name, node)
def _check_assign(self, node):
"""Анализирует присваивания."""
for target in node.targets:
if isinstance(target, ast.Name):
self._check_keyword(target.id, node)
def _heuristic_analysis(self, code: str, tree: ast.AST):
"""
Эвристический анализ для обнаружения подозрительного кода.
"""
self._check_obfuscation(code, tree)
self._check_dynamic_code_generation(code, tree)
self._check_url_patterns(code)
self._check_api_abuse(tree)
self._check_reverse_shell(code)
self._check_file_operations(code)
def _check_obfuscation(self, code: str, tree: ast.AST):
"""Обнаружение обфускации кода."""
if len(re.findall(r"[A-Za-z0-9+/]{30,}", code)) > 2:
issue_key = ("Base64", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Base64",
"description": "Подозрительные строки Base64",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
if "zlib.decompress" in code:
issue_key = ("zlib.decompress", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "zlib.decompress",
"description": "Использование zlib декомпрессии",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
for node in ast.walk(tree):
if isinstance(node, (ast.Call)):
if isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
if len(node.args) > 0 and isinstance(node.args[0], ast.Str):
obfuscated_string = node.args[0].s
if (
len(re.findall(r"[A-Za-z0-9+/]{30,}", obfuscated_string))
> 0
):
issue_key = (
"eval/exec+Base64",
node.lineno,
node.col_offset,
)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "eval/exec+Base64",
"description": "eval/exec с обфускацией Base64",
"relevance": "Критическая",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
elif isinstance(node.func, ast.Name) and node.func.id in (
"eval",
"exec",
):
if len(node.args) > 0 and isinstance(node.args[0], ast.Name):
issue_key = ("eval/exec+Variable", node.lineno, node.col_offset)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "eval/exec+Variable",
"description": "eval/exec с переменной",
"relevance": "Критическая",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
hash_functions = ["md5", "sha1", "sha256", "sha512"]
for hash_func in hash_functions:
if f"hashlib.{hash_func}" in code:
issue_key = (f"hashlib.{hash_func}", 1, 1)
if issue_key not in self.reported_issues:
self.results["info"].append(
{
"keyword": f"hashlib.{hash_func}",
"description": f"Использование {hash_func} хеширования",
"relevance": "Низкая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
if any(
x in code
for x in [
"hashlib.md5(password.encode()).hexdigest()",
"hashlib.sha256(password.encode()).hexdigest()",
]
):
issue_key = ("Weak Hashing", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Weak Hashing",
"description": "Использование хеширования без соли",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
def _check_dynamic_code_generation(self, code, tree: ast.AST):
"""Обнаружение динамической генерации кода."""
if "compile(" in code:
issue_key = ("compile", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "compile",
"description": "Использование compile() для генерации кода",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
for node in ast.walk(tree):
if (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "type"
):
if (
len(node.args) == 3
and isinstance(node.args[0], ast.Str)
and isinstance(node.args[1], ast.Tuple)
and isinstance(node.args[2], ast.Dict)
):
issue_key = ("type() class", node.lineno, node.col_offset)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "type() class",
"description": "Динамическое создание классов через type()",
"relevance": "Средняя",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
def _check_url_patterns(self, code: str):
"""Обнаружение подозрительных URL-паттернов."""
short_url_domains = [
"bit.ly",
"goo.gl",
"t.co",
"tinyurl.com",
"is.gd",
"ow.ly",
"github.com",
"raw.githubusercontent.com",
]
for domain in short_url_domains:
if domain in code:
issue_key = (f"Short URL ({domain})", 1, 1)
if issue_key not in self.reported_issues:
line = self._get_line_from_code(
1
)
match = re.search(r"(https?://\S+)", line)
url = (
match.group(1)
if match
else f"Не удалось извлечь URL ({domain})"
)
self.results["warning"].append(
{
"keyword": f"Short URL ({domain})",
"description": f"Обнаружен сокращенный URL: {url}",
"relevance": "Низкая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
webhook_patterns = ["discord.com/api/webhooks", "api.telegram.org/bot"]
for pattern in webhook_patterns:
if pattern in code:
issue_key = (f"Webhook ({pattern})", 1, 1)
if issue_key not in self.reported_issues:
line = self._get_line_from_code(
1
)
match = re.search(pattern, line)
url = (
match.group(0)
if match
else f"Не удалось извлечь Webhook ({pattern})"
)
self.results["critical"].append(
{
"keyword": f"Webhook ({pattern})",
"description": f"Обнаружен Webhook: {url}",
"relevance": "Критическая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
def _check_api_abuse(self, tree: ast.AST):
"""Обнаружение потенциального злоупотребления Telegram API."""
send_methods = ["send_message", "send_file", "send_photo"]
for node in ast.walk(tree):
if isinstance(node, ast.For):
for send_method in send_methods:
if send_method in astor.to_source(node):
issue_key = (
f"Mass {send_method}",
node.lineno,
node.col_offset,
)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": f"Mass {send_method}",
"description": f"Подозрение на массовую рассылку ({send_method})",
"relevance": "Средняя",
"line": node.lineno,
"col": node.col_offset,
}
)
self.reported_issues.add(issue_key)
if "time.sleep(" in astor.to_source(tree):
sleep_calls = re.findall(r"time\.sleep\((.*?)\)", astor.to_source(tree))
for sleep_time in sleep_calls:
try:
sleep_value = float(sleep_time)
if sleep_value < 1:
issue_key = ("Short Sleep Time", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Short Sleep Time",
"description": "Обнаружена короткая задержка (менее 1 секунды)",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
except ValueError:
pass
def _check_reverse_shell(self, code: str):
"""Обнаружение попыток создания обратного шелла."""
try:
reverse_shell_patterns = [
r"socket\.socket\(\s*socket\.AF_INET",
r"os\.dup2\(",
r"subprocess\.Popen\(\s*\[.+?\]\s*,\s*shell=True",
r"/bin/bash -i",
r"/bin/sh -i",
r"nc -e /bin/bash",
r"nc -e /bin/sh",
r"> /dev/tcp/",
r"python -c 'import socket,subprocess,os;s=socket.socket",
r"python3 -c 'import socket,subprocess,os;s=socket.socket",
]
for pattern in reverse_shell_patterns:
if re.search(pattern, code):
issue_key = ("Reverse Shell", 1, 1)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "Reverse Shell",
"description": "Обнаружена попытка создания обратного шелла",
"relevance": "Критическая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
except Exception as e:
logger.error(f"Error in _check_reverse_shell: {e}")
def _check_file_operations(self, code: str):
"""Обнаружение потенциально опасных операций с файлами."""
dangerous_file_paths = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/etc/sudoers",
]
for file_path in dangerous_file_paths:
if file_path in code:
issue_key = ("File Override", 1, 1)
if issue_key not in self.reported_issues:
self.results["critical"].append(
{
"keyword": "File Override",
"description": f"Попытка записи в критический файл: {file_path}",
"relevance": "Критическая",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
if "shutil.rmtree" in code:
issue_key = ("Recursive Delete", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Recursive Delete",
"description": "Обнаружено рекурсивное удаление каталога",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
executable_extensions = [".py", ".sh", ".bat", ".exe"]
for ext in executable_extensions:
if f"open(..., '{ext}'" in code or f"open(... + '{ext}'" in code:
issue_key = ("Executable File Creation", 1, 1)
if issue_key not in self.reported_issues:
self.results["warning"].append(
{
"keyword": "Executable File Creation",
"description": f"Обнаружено создание файла с расширением {ext}",
"relevance": "Средняя",
"line": 1,
"col": 1,
}
)
self.reported_issues.add(issue_key)
def _format_report(self, strings: dict, original_code: str) -> str:
"""Форматирует отчет об анализе."""
report = strings["report_header"]
if self.is_decoded:
report += "<b>⚠️ Код был расшифрован перед анализом.</b>\n\n"
else:
report += "<b>⚠️ Анализ проводился над исходным кодом, расшифровка не удалась.</b>\n\n"
total_issues = 0
for severity, issues in self.results.items():
if issues:
report += strings[f"{severity}_header"]
total_issues += len(issues)
for issue in issues:
report += strings["issue_format"].format(
keyword=issue["keyword"],
description=issue["description"],
relevance=issue["relevance"],
line=issue["line"],
col=issue["col"],
)
report += "\n"
if total_issues == 0:
report += strings["no_issues"]
else:
report += strings["report_footer"].format(count=total_issues)
return report
@loader.tds
class UserbotAvast(loader.Module):
"""A module for checking modules for security."""
strings = {
"name": "UserbotAvast",
"cfg_ai_enabled": "Включить анализ с помощью AI (g4f)",
"cfg_lingva_url": "Анализирует Python-код модуля на предмет потенциальных угроз безопасности, включая обфускацию и эвристические признаки.",
"report_header": "<b>🛡️ Отчет об анализе безопасности модуля:</b>\n\n",
"critical_header": "<b>🔴 Критические угрозы:</b>\n",
"warning_header": "<b>🟠 Предупреждения:</b>\n",
"info_header": "<b>🔵 Информация:</b>\n",
"issue_format": " - ⚠️ <code>{keyword}</code>: {description} (Важность: {relevance}, Строка: {line}, Позиция: {col})\n",
"no_issues": "Не обнаружено проблем безопасности.\n",
"report_footer": "\nВсего обнаружено {count} проблем.\n",
"syntax_error": "❌ Ошибка синтаксиса в коде: {error}\n",
"loading": "⏳ Запуск анализатора безопасности...",
"no_module": "⚠️ Не удалось получить код модуля. Убедитесь, что ссылка верна или прикрепите файл к сообщению.",
"decoding_error": "⚠️ Обнаружен зашифрованный код, но не удалось его расшифровать.",
}
strings_ru = {
"cfg_ai_enabled": "Включить анализ с помощью AI (g4f)",
"cfg_lingva_url": "Анализирует Python-код модуля на предмет потенциальных угроз безопасности, включая обфускацию и эвристические признаки.",
"report_header": "<b>🛡️ Отчет об анализе безопасности модуля:</b>\n\n",
"critical_header": "<b>🔴 Критические угрозы:</b>\n",
"warning_header": "<b>🟠 Предупреждения:</b>\n",
"info_header": "<b>🔵 Информация:</b>\n",
"issue_format": " - ⚠️ <code>{keyword}</code>: {description} (Важность: {relevance}, Строка: {line}, Позиция: {col})\n",
"no_issues": "Не обнаружено проблем безопасности.\n",
"report_footer": "\nВсего обнаружено {count} проблем.\n",
"syntax_error": "❌ Ошибка синтаксиса в коде: {error}\n",
"loading": "⏳ Запуск анализатора безопасности...",
"no_module": "⚠️ Не удалось получить код модуля. Убедитесь, что ссылка верна или прикрепите файл к сообщению.",
"decoding_error": "⚠️ Обнаружен зашифрованный код, но не удалось его расшифровать.",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"ai_enabled",
False,
lambda: self.strings["cfg_ai_enabled"],
validator=loader.validators.Boolean(),
),
)
async def client_ready(self, client, db):
"""Вызывается при готовности клиента."""
self.client = client
self.db = db
self.security_analyzer = SecurityAnalyzer(self.config["ai_enabled"])
@loader.unrestricted
@loader.ratelimit
async def checkmodcmd(self, message):
"""
[module_link] или [reply file] или [send file] - выполняет проверку модуля на безопасность.
"""
await utils.answer(message, self.strings["loading"])
args = utils.get_args_raw(message)
code = None
if args:
code = await self._get_code_from_url(args)
if not code:
code = await self._get_code_from_message(message)
if not code:
await utils.answer(message, self.strings["no_module"])
return
try:
result = await self.security_analyzer.analyze(code, self.strings)
await utils.answer(message, result)
except Exception as e:
logger.exception("Error during analysis")
await utils.answer(message, f"An error occurred during analysis: {e}")
async def _get_code_from_url(self, url: str) -> str or None:
"""Получает код модуля по URL."""
try:
response = await utils.run_sync(requests.get, url)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
logger.error(f"Ошибка при получении кода из URL: {e}")
return None
async def _get_code_from_message(self, message) -> str or None:
"""Получает код модуля из прикрепленного файла или ответа на сообщение."""
try:
if message.media:
code = (await self.client.download_file(message.media, bytes)).decode(
"utf-8"
)
return code
reply = await message.get_reply_message()
if reply and reply.media:
code = (await self.client.download_file(reply.media, bytes)).decode(
"utf-8"
)
return code
except Exception as e:
logger.error(f"Ошибка при получении кода из сообщения: {e}")
return None

View File

@@ -1,105 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Video2GIF
# Description: Converts video to GIF
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Video2GIF
# scope: Video2GIF 0.0.1
# requires: moviepy
# ---------------------------------------------------------------------------------
import os
import subprocess
from .. import loader, utils
@loader.tds
class Video2GIF(loader.Module):
"""Converts video to GIF"""
strings = {
"name": "Video2GIF",
"conversion_success": "🎉 The conversion is completed!",
"conversion_error": "❌ An error occurred when converting video to GIF.",
"not_video": "⚠️ Please reply to the message with the video or send the video in one message.",
"loading": "⏳ Conversion is underway",
}
strings_ru = {
"conversion_success": "🎉 Преобразование завершено!",
"conversion_error": "❌ Произошла ошибка при преобразовании видео в GIF.",
"not_video": "⚠️ Пожалуйста, ответьте на сообщение с видео или отправьте видео одним сообщением.",
"loading": "⏳ Идет преобразование",
}
@loader.command(
ru_doc="[reply | в одном сообщении с видео] — конвертирует видео в GIF.",
en_doc="[reply | in one message with video] — Converts video to GIF.",
)
async def gifc(self, message):
video = await self.get_video_from_message(message)
if not video:
await utils.answer(message, self.strings["not_video"])
return
await utils.answer(message, self.strings["loading"])
video_path = await self.client.download_media(video)
gif_path = f"{os.path.splitext(video_path)[0]}.gif"
try:
self.convert_video_to_gif(video_path, gif_path)
await message.client.send_file(
message.chat_id, gif_path, caption=self.strings["conversion_success"]
)
except Exception as e:
await utils.answer(message, self.strings["conversion_error"])
print(f"Error during conversion: {e}")
finally:
self.cleanup_temp_files(video_path, gif_path)
async def get_video_from_message(self, message):
"""Получает видео из сообщения."""
if reply := await message.get_reply_message():
return reply.video
return message.video
def convert_video_to_gif(self, video_path: str, gif_path: str) -> None:
"""Конвертирует видео в GIF с улучшенными параметрами."""
command = [
"ffmpeg",
"-i",
video_path,
"-vf",
"fps=30,scale=640:-1:flags=lanczos",
"-c:v",
"gif",
gif_path,
]
subprocess.run(command, check=True)
def cleanup_temp_files(self, video_path: str, gif_path: str) -> None:
"""Удаляет временные файлы."""
for temp_file in [video_path, gif_path]:
if os.path.exists(temp_file):
os.remove(temp_file)

View File

@@ -1,161 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: VirusTotal
# Description: Checks files for viruses using VirusTotal
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api VirusTotal
# scope: Api VirusTotal 0.0.1
# requires: json aiohttp tempfile
# ---------------------------------------------------------------------------------
import os
import tempfile
import logging
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class VirusTotalMod(loader.Module):
"""Checks files for viruses using VirusTotal."""
strings = {
"name": "VirusTotal",
"no_file": "<emoji document_id=5210952531676504517>🚫</emoji> <b>You haven't selected a file.</b>",
"download": "<emoji document_id=5334677912270415274>😑</emoji> <b>Downloading...</b>",
"scan": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Scanning...</b>",
"link": "🦠 VirusTotal Link",
"no_virus": "✅ File is clean.",
"error": "<emoji document_id=5463193238393274687>⚠️</emoji> Scan error.",
"no_format": "This format is not supported.",
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> You have not specified an API Key",
"config": "Need a token with www.virustotal.com/gui/my-apikey",
"scanning": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Waiting for scan results...</b>",
"getting_upload_url": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Getting upload URL...</b>",
"analysis_failed": "<emoji document_id=5463193238393274687>⚠️</emoji> Analysis failed after multiple retries.",
}
strings_ru = {
"no_file": "<emoji document_id=5210952531676504517>🚫</emoji> </b>Вы не выбрали файл.</b>",
"download": "<emoji document_id=5334677912270415274>😑</emoji> </b>Скачивание...</b>",
"scan": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Сканирую...</b>",
"link": "🦠 Ссылка на VirusTotal",
"no_virus": "✅ Файл чист.",
"error": "<emoji document_id=5463193238393274687>⚠️</emoji> Ошибка сканирования.",
"no_format": "Этот формат не поддерживается.",
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> Вы не указали Api Key",
"config": "Need a token with www.virustotal.com/gui/my-apikey",
"scanning": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Ожидание результатов сканирования...</b>",
"getting_upload_url": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Получение URL для загрузки...</b>",
"analysis_failed": "<emoji document_id=5463193238393274687>⚠️</emoji> Анализ не удался после нескольких попыток.",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"token-vt",
None,
lambda: "Need a token with www.virustotal.com/gui/my-apikey",
validator=loader.validators.Hidden(),
)
)
async def client_ready(self, client, db):
self.hmodslib = await self.import_lib(
"https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/main-fix/HModsLibrary.py"
)
@loader.command(
ru_doc="<ответ на файл> - Проверяет файлы на наличие вирусов с использованием VirusTotal",
en_doc="<file response> - Checks files for viruses using VirusTotal",
)
async def vt(self, message):
if not message.is_reply:
await utils.answer(message, self.strings("no_file"))
return
reply = await message.get_reply_message()
if not reply.document:
await utils.answer(message, self.strings("no_file"))
return
api_key = self.config.get("token-vt")
if not api_key:
await utils.answer(message, self.strings("no_apikey"))
return
file_extension = os.path.splitext(reply.file.name)[1].lower()
allowed_extensions = (".jpg", ".png", ".ico", ".mp3", ".mp4", ".gif", ".txt")
if file_extension in allowed_extensions:
await utils.answer(message, self.strings("no_format"))
return
try:
await utils.answer(message, self.strings("download"))
with tempfile.TemporaryDirectory() as temp_dir:
file_path = os.path.join(temp_dir, reply.file.name)
await reply.download_media(file_path)
file_size = os.path.getsize(file_path)
is_large_file = file_size > 32 * 1024 * 1024
if is_large_file:
await utils.answer(message, self.strings("getting_upload_url"))
await utils.answer(message, self.strings("scan"))
analysis_results = await self.hmodslib.scan_file_virustotal(
file_path, api_key, is_large_file
)
if analysis_results:
formatted_results = self.hmodslib.format_analysis_results(
analysis_results
)
try:
await self.inline.form(
text=formatted_results["text"],
message=message,
reply_markup={
"text": self.strings("link"),
"url": formatted_results["url"],
}
if formatted_results["url"]
else None,
)
except Exception as e:
logger.error(f"Error displaying inline results: {e}")
await utils.answer(
message,
self.strings("error_report").format(
formatted_results["url"]
),
)
else:
await utils.answer(message, self.strings("analysis_failed"))
except Exception as e:
logger.exception("An error occurred during the VT scan process.")
await utils.answer(
message, self.strings("error") + f"\n\n{type(e).__name__}: {str(e)}"
)

View File

@@ -1,125 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: VoiceDL
# Description: Voice Downloader module
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: VoiceDL
# scope: VoiceDL 0.0.1
# requires: tempfile
# ---------------------------------------------------------------------------------
import os
import subprocess
import tempfile
import time
from .. import loader, utils
@loader.tds
class VoiceDL(loader.Module):
"""Voice Downloader module"""
strings = {
"name": "VoiceDL",
"download_success": "Voice message downloaded in MP3 format.",
"download_error": "Error downloading voice message.",
"no_voice_message": "Please reply to a voice message.",
"conversion_error": "Error converting to MP3.",
"file_not_found": "File not found.",
"unsupported_format": "The file format is not supported.",
}
strings_ru = {
"download_success": "Голосовое сообщение загружено в формате MP3.",
"download_error": "Ошибка при загрузке голосового сообщения.",
"no_voice_message": "Пожалуйста, ответьте на голосовое сообщение.",
"conversion_error": "Ошибка при конвертации в MP3.",
"file_not_found": "Файл не найден.",
"unsupported_format": "Формат файла не поддерживается.",
}
@loader.command(
ru_doc=" [reply] — загружает выбранное голосовое сообщение в виде файла mp3 и кидает его в чат.",
en_doc=" [reply] — downloads the selected voice message as an MP3 file and sends it in the chat.",
)
async def voicedl(self, message):
reply = await message.get_reply_message()
if reply:
if reply.voice:
try:
with tempfile.NamedTemporaryFile(
delete=False, suffix=".ogg"
) as temp_voice_file:
voice_file_path = temp_voice_file.name
await message.client.download_file(reply.voice, voice_file_path)
timestamp = int(time.time())
mp3_file_path = f"voice_message_{timestamp}.mp3"
await self.convert_to_mp3(voice_file_path, mp3_file_path)
await message.client.send_file(
message.chat.id,
mp3_file_path,
caption=self.strings("download_success"),
)
os.remove(voice_file_path)
os.remove(mp3_file_path)
except FileNotFoundError:
await utils.answer(
message,
self.strings("download_error")
+ " "
+ self.strings("file_not_found"),
)
except subprocess.CalledProcessError as e:
await utils.answer(
message,
self.strings("conversion_error") + f" {e.stderr.decode()}",
)
except Exception as e:
await utils.answer(
message, self.strings("download_error") + f" {str(e)}"
)
else:
await utils.answer(message, self.strings("no_voice_message"))
else:
await utils.answer(message, self.strings("no_voice_message"))
async def convert_to_mp3(self, input_file: str, output_file: str):
"""Convert audio file to MP3 format using FFmpeg."""
command = ["ffmpeg", "-i", input_file, output_file]
process = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if process.returncode != 0:
raise subprocess.CalledProcessError(
process.returncode,
command,
output=process.stdout,
stderr=process.stderr,
)

View File

@@ -1,329 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Weather
# Description: Advanced weather module with detailed information
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: api Weather
# scope: api Weather 0.0.1
# ---------------------------------------------------------------------------------
import logging
import requests
from datetime import datetime
from typing import Union, Dict, List
from dataclasses import dataclass
from .. import loader, utils
logger = logging.getLogger(__name__)
DEFAULT_FORECAST_DAYS = 3
DEFAULT_HOURLY_INDEX = 4
WEATHER_API_URL = "https://wttr.in/{city}?format=j1&lang=en"
@dataclass
class WeatherCondition:
"""Represents a weather condition with its emoji."""
condition: str
emoji: str
@dataclass
class WindDirection:
"""Represents a wind direction with its description."""
direction: str
description: str
@dataclass
class ForecastDay:
"""Represents a single day's weather forecast."""
date: str
emoji: str
condition: str
temp_min: str
temp_max: str
wind_speed: str
wind_direction: str
WEATHER_EMOJI: List[WeatherCondition] = [
WeatherCondition("clear", "<emoji document_id=5402477260982731644>☀️</emoji>"),
WeatherCondition("sunny", "<emoji document_id=5402477260982731644>☀️</emoji>"),
WeatherCondition("partly cloudy", "<emoji document_id=5350424168615649565>⛅️</emoji>"),
WeatherCondition("cloudy", "☁️<emoji document_id=5208563370218762357>☁️</emoji>"),
WeatherCondition("overcast", "<emoji document_id=5208563370218762357>☁️</emoji>"),
WeatherCondition("mist", "<emoji document_id=5449510395574229527>😶‍🌫️</emoji>"),
WeatherCondition("fog", "<emoji document_id=5449510395574229527>😶‍🌫️</emoji>"),
WeatherCondition("light rain", "<emoji document_id=5283097055852503586>🌦</emoji>"),
WeatherCondition("rain", "<emoji document_id=5283243028905994049>🌧</emoji>"),
WeatherCondition("heavy rain", "<emoji document_id=5282939632416206153>⛈</emoji>"),
WeatherCondition("thunderstorm", "<emoji document_id=5282939632416206153>⛈</emoji>"),
WeatherCondition("snow", "<emoji document_id=5282833267551117457>🌨</emoji>"),
WeatherCondition("heavy snow", "<emoji document_id=5449449325434266744>❄️</emoji>"),
WeatherCondition("sleet", "<emoji document_id=5282833267551117457>🌨</emoji>"),
WeatherCondition("wind", "💨"),
]
WIND_DIRECTIONS: List[WindDirection] = [
WindDirection("N", "⬆️ North"),
WindDirection("NE", "↗️ Northeast"),
WindDirection("E", "➡️ East"),
WindDirection("SE", "↘️ Southeast"),
WindDirection("S", "⬇️ South"),
WindDirection("SW", "↙️ Southwest"),
WindDirection("W", "⬅️ West"),
WindDirection("NW", "↖️ Northwest"),
]
WIND_DIRECTIONS_RU: List[WindDirection] = [
WindDirection("N", "⬆️ Северный"),
WindDirection("NE", "↗️ Северо-восточный"),
WindDirection("E", "➡️ Восточный"),
WindDirection("SE", "↘️ Юго-восточный"),
WindDirection("S", "⬇️ Южный"),
WindDirection("SW", "↙️ Юго-западный"),
WindDirection("W", "⬅️ Западный"),
WindDirection("NW", "↖️ Северо-западный"),
]
@loader.tds
class Weather(loader.Module):
"""Advanced weather module with detailed information"""
strings = {
"name": "Weather",
"no_city": "🚫 <b>Please specify a city</b>",
"invalid_city": "🚫 <b>City not found</b>",
"loading": "🔄 <b>Fetching weather data for {}</b>...",
"error": "<emoji document_id=5980953710157632545>❌</emoji> <b>Error retrieving weather data</b>",
"default_city": "<emoji document_id=5980930633298350051>✅</emoji> Default city set to: <code>{city}</code>",
"weather_text": """<b>{emoji} Weather: {location}</b>
<b>📊 Current conditions:</b>
├ 🌡 Temperature: <code>{temp}°C</code>
├– <i>Feels like:</i> <code>{feels_like}°C</code>
├ 💧 Humidity: <code>{humidity}%</code>
├ 💨 Wind: <code>{wind_speed} km/h</code> {wind_direction}
├ 🌪 Pressure: <code>{pressure} mmHg</code>
├ 👁 Visibility: <code>{visibility} km</code>
└ ☁️ Cloudiness: <code>{clouds}</code>
<b>🌅 Time:</b>
├ 🌅 Sunrise: <code>{sunrise}</code>
├ 🌇 Sunset: <code>{sunset}</code>
└ ⏱ Local time: <code>{local_time}</code>
<b>📅 Forecast for {forecast_days} days:</b>
{forecast}
⏰ Updated: <code>{updated}</code>""",
"forecast_day": """<b>{date}</b> {emoji}
├ 🌡 Temperature: {temp_min}°C ... {temp_max}°C
└ 💨 Wind: {wind_speed} km/h {wind_direction}
""",
}
strings_ru = {
"no_city": "🚫 <b>Пожалуйста, укажите город</b>",
"invalid_city": "🚫 <b>Город не найден</b>",
"loading": "🔄 <b>Получаю метеоданные для {}</b>...",
"default_city": "<emoji document_id=5980930633298350051>✅</emoji> Город по умолчанию установлен: <code>{city}</code>",
"error": "<emoji document_id=5980953710157632545>❌</emoji> <b>Ошибка при получении данных о погоде</b>",
"weather_text": """<b>{emoji} Погода: {location}</b>
<b>📊 Текущие условия:</b>
├ 🌡 Температура: <code>{temp}°C</code>
├– <i>Ощущается как:</i> <code>{feels_like}°C</code>
├ 💧 Влажность: <code>{humidity}%</code>
├ 💨 Ветер: <code>{wind_speed} км/ч</code> {wind_direction}
├ 🌪 Давление: <code>{pressure} мм.рт.ст</code>
├ 👁 Видимость: <code>{visibility} км</code>
└ ☁️ Облачность: <code>{clouds}</code>
<b>🌅 Время:</b>
├ 🌅 Восход: <code>{sunrise}</code>
├ 🌇 Закат: <code>{sunset}</code>
└ ⏱ Местное время: <code>{local_time}</code>
<b>📅 Прогноз на {forecast_days} дня:</b>
{forecast}
⏰ Обновлено: <code>{updated}</code>""",
"forecast_day": """<b>{date}</b> {emoji}
├ 🌡 Температура: {temp_min}°C ... {temp_max}°C
└ 💨 Ветер: {wind_speed} км/ч {wind_direction}
""",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"default_city",
None,
lambda: "Default city for weather command",
),
loader.ConfigValue(
"language",
"ru",
lambda: "Language for weather output (en/ru)",
),
)
def get_weather_emoji(self, condition: str) -> str:
"""Get emoji for weather conditions"""
condition = condition.lower()
for item in WEATHER_EMOJI:
if item.condition in condition:
return item.emoji
return "🌡"
def get_wind_direction(self, direction: str) -> str:
"""Get wind direction description"""
lang = self.config["language"]
directions = WIND_DIRECTIONS_RU if lang == "ru" else WIND_DIRECTIONS
for item in directions:
if item.direction == direction.upper():
return item.description
return direction
async def get_weather_data(self, city: str) -> Union[Dict, None]:
"""Get weather data from wttr.in"""
lang = self.config["language"]
url = WEATHER_API_URL.format(city=city)
if lang == "ru":
url = f"https://wttr.in/{city}?format=j1&lang=ru"
try:
response = await utils.run_sync(requests.get, url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logger.error(f"Failed to fetch weather data for {city}: {e}")
return None
except Exception as e:
logger.exception(f"Error fetching weather data: {e}")
return None
def format_forecast(self, forecast_data: list) -> str:
"""Format weather forecast for multiple days."""
forecast_text = ""
for day in forecast_data:
hourly = day["hourly"][DEFAULT_HOURLY_INDEX]
forecast_day = ForecastDay(
date=day["date"],
emoji=self.get_weather_emoji(hourly["weatherDesc"][0]["value"]),
condition=hourly["weatherDesc"][0]["value"],
temp_min=day["mintempC"],
temp_max=day["maxtempC"],
wind_speed=hourly["windspeedKmph"],
wind_direction=self.get_wind_direction(hourly["winddir16Point"]),
)
forecast_text += self.strings("forecast_day").format(
date=forecast_day.date,
emoji=forecast_day.emoji,
condition=forecast_day.condition,
temp_min=forecast_day.temp_min,
temp_max=forecast_day.temp_max,
wind_speed=forecast_day.wind_speed,
wind_direction=forecast_day.wind_direction,
)
return forecast_text
async def process_weather_data(self, weather_data: Dict) -> str:
"""Process weather data and format the text."""
current = weather_data["current_condition"][0]
forecast = weather_data["weather"]
location = (
f"{weather_data['nearest_area'][0]['areaName'][0]['value']}, "
f"{weather_data['nearest_area'][0]['country'][0]['value']}"
)
forecast_text = self.format_forecast(forecast[:DEFAULT_FORECAST_DAYS])
return self.strings("weather_text").format(
location=location,
emoji=self.get_weather_emoji(current["weatherDesc"][0]["value"]),
temp=current["temp_C"],
feels_like=current["FeelsLikeC"],
humidity=current["humidity"],
wind_speed=current["windspeedKmph"],
wind_direction=self.get_wind_direction(current["winddir16Point"]),
pressure=current["pressure"],
visibility=current["visibility"],
clouds=current["weatherDesc"][0]["value"],
sunrise=forecast[0]["astronomy"][0]["sunrise"],
sunset=forecast[0]["astronomy"][0]["sunset"],
local_time=current["observation_time"],
forecast=forecast_text,
forecast_days=DEFAULT_FORECAST_DAYS,
updated=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
@loader.command(
ru_doc="Узнайте погоду для указанного города",
en_doc="Get the weather for the specified city",
)
async def weather(self, message):
city = utils.get_args_raw(message) or self.config["default_city"]
if not city:
await utils.answer(message, self.strings("no_city"))
return
await utils.answer(message, self.strings("loading").format(city))
weather_data = await self.get_weather_data(city)
if not weather_data:
await utils.answer(message, self.strings("error"))
return
try:
weather_text = await self.process_weather_data(weather_data)
await utils.answer(message, weather_text)
except Exception as e:
logger.exception(f"Error processing weather data: {e}")
await utils.answer(message, self.strings("error"))
@loader.command(
ru_doc="Установите город по умолчанию для определения погоды",
en_doc="Set the default city for weather",
)
async def weatherset(self, message):
city = utils.get_args_raw(message)
if not city:
await utils.answer(message, self.strings("no_city"))
return
weather_data = await self.get_weather_data(city)
if not weather_data:
await utils.answer(message, self.strings("invalid_city"))
return
self.config["default_city"] = city
await utils.answer(message, self.strings("default_city").format(city=city))

View File

@@ -1,136 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: WindowsKeys
# Description: Provides you Windows activation keys
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: WindowsKeys
# scope: WindowsKeys 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import logging
import json
import requests
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class WindowsKeys(loader.Module):
"""Provides you Windows activation keys"""
strings = {
"name": "WindowsKeys",
"winkey": "✅ Your key: <code>{}</code>\n\n⚠ Warning! This key is not a pirate key. It is taken from the official Microsoft site and is intended for further activation via KMS-server",
"error": "❌ An error occurred while retrieving the key. Please try again later.",
}
strings_ru = {
"winkey": "✅ Ваш ключ: <code>{}</code>\n\n⚠ Внимание! Указанный ключ не является пиратским. Он взят с официального сайта Microsoft и предназначен для дальнейшей активации посредством KMS-сервера",
"error": "❌ Произошла ошибка при получении ключа. Попробуйте позже.",
}
@loader.command(
ru_doc="Открывает выбор ключа для активации Windows",
en_doc="Opens the Windows activation key selection",
)
async def winkey(self, message):
await self.inline.form(
text="🔓 Выберите версию и издание Windows, для которой вам необходим ключ",
message=message,
reply_markup=[
[
{
"text": "Windows 10/11 Pro",
"callback": self._inline__give_key,
"args": ["win10_11pro"],
}
],
[
{
"text": "Windows 10/11 Enterprise LTSC",
"callback": self._inline__give_key,
"args": ["win10_11enterpriseLTSC"],
}
],
[
{
"text": "Windows 8.1 Pro",
"callback": self._inline__give_key,
"args": ["win8.1pro"],
}
],
[
{
"text": "Windows 8 Pro",
"callback": self._inline__give_key,
"args": ["win8pro"],
}
],
[
{
"text": "Windows 7 Pro",
"callback": self._inline__give_key,
"args": ["win7pro"],
}
],
[
{
"text": "Windows Vista Business",
"callback": self._inline__give_key,
"args": ["winvistabusiness"],
}
],
[
{
"text": "🎈 Закрыть",
"action": "close",
}
],
],
force_me=False,
silent=True,
)
async def _inline__give_key(self, call, winver):
url = "https://raw.githubusercontent.com/C0dwiz/H.Modules/refs/heads/assets/winkeys.json"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
await call.edit(self.strings["winkey"].format(data[winver]))
except requests.exceptions.RequestException as e:
logger.error("Request error: %e", e)
await call.answer(self.strings("error"), show_alert=True)
except json.JSONDecodeError as e:
logger.error("JSON decode error: %e", e)
await call.answer(self.strings("error"), show_alert=True)
except KeyError as e:
logger.error("Key error: %e", e)
await call.answer(self.strings("error"), show_alert=True)
except Exception as e:
logger.exception("An unexpected error occurred: %e", e)
await call.answer(self.strings("error"), show_alert=True)

File diff suppressed because it is too large Load Diff

View File

@@ -1,97 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: animals
# Description: Random cats and dogs
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api animals
# scope: Api animals 0.0.1
# requires: requests
# ---------------------------------------------------------------------------------
import requests
from .. import loader, utils
@loader.tds
class animals(loader.Module):
"""Random cats and dogs"""
strings = {
"name": "animals",
"loading": "<b>Generation is underway</b>",
"done": "<b>Here is your salute</b>",
}
strings_ru = {
"loading": "<b>Генерация идет полным ходом</b>",
"done": "<b>Вот ваш результат</b>",
}
# thanks https://github.com/C0dwiz/H.Modules/pull/1
async def get_photo(self, prefix: str) -> str:
response = requests.get(f"https://api.{prefix}.com/v1/images/search")
return response.json()[0]["url"]
@loader.command(
ru_doc="Файлы случайных фотографий кошек",
en_doc="Random photos of cats files",
)
async def fcatcmd(self, message):
await utils.answer(message, self.strings("loading"))
cat_url = await self.get_photo("thecat")
await utils.answer_file(
message, cat_url, self.strings("done"), force_document=True
)
@loader.command(
ru_doc="Случайные фотографии собачьих файлов",
en_doc="Random photos of dog files",
)
async def fdogcmd(self, message):
await utils.answer(message, self.strings("loading"))
dog_url = await self.get_photo("thedogapi")
await utils.answer_file(
message, dog_url, self.strings("done"), force_document=True
)
@loader.command(
ru_doc="Случайные фотографии кошек",
en_doc="Random photos of cats",
)
async def catcmd(self, message):
await utils.answer(message, self.strings("loading"))
cat_url = await self.get_photo("thecat")
await utils.answer_file(
message, cat_url, self.strings("done"), force_document=False
)
@loader.command(
ru_doc="Случайные фотографии собаки",
en_doc="Random photos of dog",
)
async def dogcmd(self, message):
await utils.answer(message, self.strings("loading"))
dog_url = await self.get_photo("thedogapi")
await utils.answer_file(
message, dog_url, self.strings("done"), force_document=False
)

View File

@@ -1,76 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: face
# Description: Random face
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api face
# scope: Api face 0.0.1
# requires: aiohttp
# ---------------------------------------------------------------------------------
import aiohttp
from .. import loader, utils
@loader.tds
class face(loader.Module):
"""random face"""
strings = {
"name": "face",
"loading": (
"<emoji document_id=5348399448017871250>🔍</emoji> I'm looking for you kaomoji"
),
"random_face": (
"<emoji document_id=5208878706717636743>🗿</emoji> Here is your random one kaomoji\n<code>{}</code>"
),
"error": "An error has occurred!",
}
strings_ru = {
"loading": (
"<emoji document_id=5348399448017871250>🔍</emoji> Ищю вам kaomoji"
),
"random_face": (
"<emoji document_id=5208878706717636743>🗿</emoji> Вот ваш рандомный kaomoji\n<code>{}</code>"
),
"error": "Произошла ошибка!",
}
@loader.command(
ru_doc="Рандом kaomoji",
en_doc="Random kaomoji",
)
async def rfacecmd(self, message):
await utils.answer(message, self.strings("loading"))
url = "https://vsecoder.dev/api/faces"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
random_face = data["data"]
await utils.answer(message, self.strings("random_face").format(random_face))
else:
await utils.answer(message, self.strings("error"))

View File

@@ -1,46 +0,0 @@
AccountData
AniLibria
animals
AnimeQuotes
Article
ASCIIArt
AutofarmCookies
BirthdayTime
CheckSpamBan
CryptoCurrency
EnvsSH
face
FakeActions
FakeWallet
full
GigaChat
globalrestrict
hikkahost
InlineButton
InlineCoin
InlineHelper
IrisSimpleMod
jacques
KBSwapper
Memes
Music
novoice
nsfwart
numbersapi
PastebinAPI
profile
ReplaceVowels
SafetyMod
search
shortener
SMAcrhiver
TelegramStatusCodes
TempChat
Text2File
Text_Sticker
TikTokDownloader
Video2GIF
VirusTotal
VoiceDL
HAFK
Weather

View File

@@ -1,681 +0,0 @@
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
# █▀█ █ █ █ █▀█ █▀▄ █
# © Copyright 2022
# https://t.me/hikariatama
#
# 🔒 Licensed under the GNU AGPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# Some functions took from Hikarichat by Hikariatama
# ---------------------------------------------------------------------------------
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: GlobalRestrict
# Description: Global mutation or ban
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api GlobalRestrict
# scope: Api GlobalRestrict 0.0.1
# ---------------------------------------------------------------------------------
import re
import time
import typing
from telethon.tl.types import (
Channel,
Chat,
Message,
User,
)
from .. import loader, utils
BANNED_RIGHTS = {
"view_messages": False,
"send_messages": False,
"send_media": False,
"send_stickers": False,
"send_gifs": False,
"send_games": False,
"send_inline": False,
"send_polls": False,
"change_info": False,
"invite_users": False,
}
MUTES_RIGHTS = {
"view_messages": True,
"send_messages": False,
"send_media": False,
"send_stickers": False,
"send_gifs": False,
"send_games": False,
"send_inline": False,
"send_polls": False,
"change_info": False,
"invite_users": False,
}
def get_full_name(user: typing.Union[User, Channel]) -> str:
return utils.escape_html(
user.title
if isinstance(user, Channel)
else (
f"{user.first_name} "
+ (user.last_name if getattr(user, "last_name", False) else "")
)
).strip()
@loader.tds
class GlobalRestrict(loader.Module):
"""Global mutation or ban"""
strings = {
"name": "GlobalRestrict",
"no_reason": "Not specified",
"args": (
"<emoji document_id=5300759756669984376>🚫</emoji> <b>Incorrect arguments</b>"
),
"glban": (
'<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>'
" has been globally banned.</b>\n<b>Reason: </b><i>{}</i>\n\n{}"
),
"glbanning": (
"<emoji document_id=5301059317753979286>🖕</emoji> <b>Globally banning <a"
' href="{}">{}</a>...</b>'
),
"gunban": (
'<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>'
" has been globally unbanned.</b>\n\n{}"
),
"gunbanning": (
"<emoji document_id=6334872157947955302>🤗</emoji> <b>Global unbanning <a"
' href="{}">{}</a>...</b>'
),
"in_n_chats": (
"<emoji document_id=5379568936218009290>👎</emoji> <b>Banned in {}"
" chat(s)</b>"
),
"unbanned_in_n_chats": (
"<emoji document_id=5461129450341014019>✋️</emoji> <b>Unbanned in {}"
" chat(s)</b>"
),
"glmute": (
'<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>'
" has been globally muted.</b>\n<b>Reason: </b><i>{}</i>\n\n{}"
),
"glmutes": (
"<emoji document_id=5301059317753979286>🖕</emoji> <b>Global mute <a"
' href="{}">{}</a>...</b>'
),
"gunmute": (
'<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>'
" has been globally unmuted.</b>\n\n{}"
),
"gunmutes": (
"<emoji document_id=6334872157947955302>🤗</emoji> <b>Global unmute <a"
' href="{}">{}</a>...</b>'
),
"in_m_chats": (
"<emoji document_id=5379568936218009290>👎</emoji> <b>Muted in {}"
" chat(s)</b>"
),
"unmute_in_n_chats": (
"<emoji document_id=5461129450341014019>✋️</emoji> <b>Unmuted in {}"
" chat(s)</b>"
),
}
strings_ru = {
"no_reason": "Не указана",
"args": (
"<emoji document_id=5300759756669984376>🚫</emoji> <b>Неверные"
" аргументы</b>"
),
"glban": (
'<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>'
" был гзабанен.</b>\n<b>Причина: </b><i>{}</i>\n\n{}"
),
"glbanning": (
"<emoji document_id=5301059317753979286>🖕</emoji> <b>Гбан <a"
' href="{}">{}</a>...</b>'
),
"gunban": (
'<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>'
" был гразбанен.</b>\n\n{}"
),
"gunbanning": (
"<emoji document_id=6334872157947955302>🤗</emoji> <b>Гразбан <a"
' href="{}">{}</a>...</b>'
),
"in_n_chats": (
"<emoji document_id=5379568936218009290>👎</emoji> <b>Забанил в {}"
" чат(-ах)</b>"
),
"unbanned_in_n_chats": (
"<emoji document_id=5461129450341014019>✋️</emoji> <b>Разбанил in {}"
" чат(-ах)</b>"
),
"glmute": (
'<emoji document_id=5301059317753979286>🖕</emoji> <b><a href="{}">{}</a>'
" был замучен.</b>\n<b>Причина: </b><i>{}</i>\n\n{}"
),
"glmutes": (
"<emoji document_id=5301059317753979286>🖕</emoji> <b>Гмут <a"
' href="{}">{}</a>...</b>'
),
"gunmute": (
'<emoji document_id=6334872157947955302>🤗</emoji> <b><a href="{}">{}</a>'
" был размучен.</b>\n\n{}"
),
"gunmutes": (
"<emoji document_id=6334872157947955302>🤗</emoji> <b>Гразмут <a"
' href="{}">{}</a>...</b>'
),
"in_m_chats": (
"<emoji document_id=5379568936218009290>👎</emoji> <b>Мут в {} чат(-ах)</b>"
),
"unmute_in_n_chats": (
"<emoji document_id=5461129450341014019>✋️</emoji> <b>Размут in {}"
" чат(-ах)</b>"
),
}
def __init__(self):
self._gban_cache = {}
self._gmute_cache = {}
@staticmethod
def convert_time(t: str) -> int:
"""
Tries to export time from text
"""
try:
if not str(t)[:-1].isdigit():
return 0
if "d" in str(t):
t = int(t[:-1]) * 60 * 60 * 24
if "h" in str(t):
t = int(t[:-1]) * 60 * 60
if "m" in str(t):
t = int(t[:-1]) * 60
if "s" in str(t):
t = int(t[:-1])
t = int(re.sub(r"[^0-9]", "", str(t)))
except ValueError:
return 0
return t
async def args_parser(
self,
message: Message,
include_force: bool = False,
include_silent: bool = False,
) -> tuple:
"""Get args from message"""
args = " " + utils.get_args_raw(message)
if include_force and " -f" in args:
force = True
args = args.replace(" -f", "")
else:
force = False
if include_silent and " -s" in args:
silent = True
args = args.replace(" -s", "")
else:
silent = False
args = args.strip()
reply = await message.get_reply_message()
if reply and not args:
return (
(await self._client.get_entity(reply.sender_id)),
0,
utils.escape_html(self.strings("no_reason")).strip(),
*((force,) if include_force else []),
*((silent,) if include_silent else []),
)
try:
a = args.split()[0]
if str(a).isdigit():
a = int(a)
user = await self._client.get_entity(a)
except Exception:
try:
user = await self._client.get_entity(reply.sender_id)
except Exception:
return False
t = ([arg for arg in args.split() if self.convert_time(arg)] or ["0"])[0]
args = args.replace(t, "").replace(" ", " ")
t = self.convert_time(t)
if not reply:
try:
args = " ".join(args.split()[1:])
except Exception:
pass
if time.time() + t >= 2208978000: # 01.01.2040 00:00:00
t = 0
return (
user,
t,
utils.escape_html(args or self.strings("no_reason")).strip(),
*((force,) if include_force else []),
*((silent,) if include_silent else []),
)
async def ban(
self,
chat: typing.Union[Chat, int],
user: typing.Union[User, Channel, int],
period: int = 0,
reason: str = None,
message: typing.Optional[Message] = None,
silent: bool = False,
):
"""Ban user in chat"""
if str(user).isdigit():
user = int(user)
if reason is None:
reason = self.strings("no_reason")
try:
await self.inline.bot.kick_chat_member(
int(f"-100{getattr(chat, 'id', chat)}"),
int(getattr(user, "id", user)),
)
except Exception:
await self._client.edit_permissions(
chat,
user,
until_date=(time.time() + period) if period else 0,
**BANNED_RIGHTS,
)
if silent:
return
async def mute(
self,
chat: typing.Union[Chat, int],
user: typing.Union[User, Channel, int],
period: int = 0,
reason: str = None,
message: typing.Optional[Message] = None,
silent: bool = False,
):
"""Mute user in chat"""
if str(user).isdigit():
user = int(user)
if reason is None:
reason = self.strings("no_reason")
try:
await self.inline.bot.restrict_chat_member(
int(f"-100{getattr(chat, 'id', chat)}"),
int(getattr(user, "id", user)),
)
except Exception:
await self._client.edit_permissions(
chat,
user,
until_date=(time.time() + period) if period else 0,
**MUTES_RIGHTS,
)
if silent:
return
@loader.command(
ru_doc="<реплай | юзер> [причина] [-s] - Забанить пользователя во всех чатах где ты админ",
en_doc="<replay | user> [reason] [-s] - Ban the user in all chats where you are the admin",
)
async def glban(self, message):
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not reply and not args:
await utils.answer(message, self.strings("args"))
return
a = await self.args_parser(message, include_silent=True)
if not a:
await utils.answer(message, self.strings("args"))
return
user, t, reason, silent = a
message = await utils.answer(
message,
self.strings("glbanning").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
),
)
if not self._gban_cache or self._gban_cache["exp"] < time.time():
self._gban_cache = {
"exp": int(time.time()) + 10 * 60,
"chats": [
chat.entity.id
async for chat in self._client.iter_dialogs()
if (
(
isinstance(chat.entity, Chat)
or (
isinstance(chat.entity, Channel)
and getattr(chat.entity, "megagroup", False)
)
)
and chat.entity.admin_rights
and chat.entity.participants_count > 5
and chat.entity.admin_rights.ban_users
)
],
}
chats = ""
counter = 0
for chat in self._gban_cache["chats"]:
try:
await self.ban(chat, user, 0, reason, silent=True)
except Exception:
pass
else:
chats += '▫️ <b><a href="{}">{}</a></b>\n'.format(
utils.get_entity_url(await self._client.get_entity(chat, exp=0)),
utils.escape_html(
get_full_name(await self._client.get_entity(chat, exp=0))
),
)
counter += 1
await utils.answer(
message,
self.strings("glban").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
reason,
self.strings("in_n_chats").format(counter) if silent else chats,
),
)
@loader.command(
ru_doc="<реплай | юзер> [причина] [-s] - Разбанить пользователя во всех где ты админ",
en_doc="<replay | user> [reason] [-s] - To unban the user in all where you are the admin",
)
async def glunban(self, message: Message):
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not reply and not args:
await utils.answer(message, self.strings("args"))
return
a = await self.args_parser(message, include_silent=True)
if not a:
await utils.answer(message, self.strings("args"))
return
user, t, reason, silent = a
message = await utils.answer(
message,
self.strings("gunbanning").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
),
)
if not self._gban_cache or self._gban_cache["exp"] < time.time():
self._gban_cache = {
"exp": int(time.time()) + 10 * 60,
"chats": [
chat.entity.id
async for chat in self._client.iter_dialogs()
if (
(
isinstance(chat.entity, Chat)
or (
isinstance(chat.entity, Channel)
and getattr(chat.entity, "megagroup", False)
)
)
and chat.entity.admin_rights
and chat.entity.participants_count > 5
and chat.entity.admin_rights.ban_users
)
],
}
chats = ""
counter = 0
for chat in self._gban_cache["chats"]:
try:
await self._client.edit_permissions(
chat,
user,
until_date=0,
**{right: True for right in BANNED_RIGHTS.keys()},
)
except Exception:
pass
else:
chats += '▫️ <b><a href="{}">{}</a></b>\n'.format(
utils.get_entity_url(await self._client.get_entity(chat, exp=0)),
utils.escape_html(
get_full_name(await self._client.get_entity(chat, exp=0))
),
)
counter += 1
await utils.answer(
message,
self.strings("gunban").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
(
self.strings("unbanned_in_n_chats").format(counter)
if silent
else chats
),
),
)
@loader.command(
ru_doc="<реплай | юзер> [причина] [-s] - Замутить пользователя во всех чатах где ты админ",
en_doc="<replay | user> [reason] [-s] - To hook up the user in all chats where you are the admin",
)
async def glmute(self, message):
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not reply and not args:
await utils.answer(message, self.strings("args"))
return
a = await self.args_parser(message, include_silent=True)
if not a:
await utils.answer(message, self.strings("args"))
return
user, t, reason, silent = a
message = await utils.answer(
message,
self.strings("glmutes").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
),
)
if not self._gmute_cache or self._gmute_cache["exp"] < time.time():
self._gmute_cache = {
"exp": int(time.time()) + 10 * 60,
"chats": [
chat.entity.id
async for chat in self._client.iter_dialogs()
if (
(
isinstance(chat.entity, Chat)
or (
isinstance(chat.entity, Channel)
and getattr(chat.entity, "megagroup", False)
)
)
and chat.entity.admin_rights
and chat.entity.participants_count > 5
and chat.entity.admin_rights.ban_users
)
],
}
chats = ""
counter = 0
for chat in self._gmute_cache["chats"]:
try:
await self.mute(chat, user, 0, reason, silent=True)
except Exception:
pass
else:
chats += '▫️ <b><a href="{}">{}</a></b>\n'.format(
utils.get_entity_url(await self._client.get_entity(chat, exp=0)),
utils.escape_html(
get_full_name(await self._client.get_entity(chat, exp=0))
),
)
counter += 1
await utils.answer(
message,
self.strings("glmute").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
reason,
self.strings("in_m_chats").format(counter) if silent else chats,
),
)
@loader.command(
ru_doc="<реплай | юзер> [причина] [-s] - Размутит пользователя во всех где ты админ",
en_doc="<replay | user> [reason] [-s] - Will confuse the user in all where you are the admin",
)
async def glunmute(self, message: Message):
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not reply and not args:
await utils.answer(message, self.strings("args"))
return
a = await self.args_parser(message, include_silent=True)
if not a:
await utils.answer(message, self.strings("args"))
return
user, t, reason, silent = a
message = await utils.answer(
message,
self.strings("gunmutes").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
),
)
if not self._gmute_cache or self._gmute_cache["exp"] < time.time():
self._gmute_cache = {
"exp": int(time.time()) + 10 * 60,
"chats": [
chat.entity.id
async for chat in self._client.iter_dialogs()
if (
(
isinstance(chat.entity, Chat)
or (
isinstance(chat.entity, Channel)
and getattr(chat.entity, "megagroup", False)
)
)
and chat.entity.admin_rights
and chat.entity.participants_count > 5
and chat.entity.admin_rights.ban_users
)
],
}
chats = ""
counter = 0
for chat in self._gmute_cache["chats"]:
try:
await self._client.edit_permissions(
chat,
user,
until_date=0,
**{right: True for right in MUTES_RIGHTS.keys()},
)
except Exception:
pass
else:
chats += '▫️ <b><a href="{}">{}</a></b>\n'.format(
utils.get_entity_url(await self._client.get_entity(chat, exp=0)),
utils.escape_html(
get_full_name(await self._client.get_entity(chat, exp=0))
),
)
counter += 1
await utils.answer(
message,
self.strings("gunmute").format(
utils.get_entity_url(user),
utils.escape_html(get_full_name(user)),
(
self.strings("unmutes_in_n_chats").format(counter)
if silent
else chats
),
),
)

View File

@@ -1,314 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: HikkaHost
# Description: Hikkahost manager.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: api HikkaHost
# scope: api HikkaHost 0.0.1
# ---------------------------------------------------------------------------------
import aiohttp
import json
from datetime import datetime, timedelta, timezone
from .. import loader, utils
class HostApi:
"""
A class for interacting with a Host API.
Args:
token (str): The API token.
"""
def __init__(self, token: str):
self.token = token
async def _request(self, path: str, method: str = "GET") -> dict:
"""
Sends a request to the API.
Args:
path (str): The API path.
method (str, optional): The HTTP method. Defaults to "GET".
Returns:
dict: The API response as a dictionary.
"""
url = "http://158.160.84.24:5000" + path
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.request(
method,
url,
headers={
"Content-Type": "application/json",
"token": self.token,
},
ssl=False,
) as response:
return await response.json()
async def stats(self, user_id: int) -> dict:
"""
Gets the host stats.
Args:
user_id (int): The user ID.
Returns:
dict: The host stats.
"""
url = f"/api/host/{user_id}/stats"
return await self._request(url)
async def host_info(self, user_id: int) -> dict:
"""
Gets the host information.
Args:
user_id (int): The user ID.
Returns:
dict: The host information.
"""
url = f"/api/host/{user_id}"
return await self._request(url)
async def status(self, user_id: int) -> dict:
"""
Gets the host status.
Args:
user_id (int): The user ID.
Returns:
dict: The host status.
"""
url = f"/api/host/{user_id}/status"
return await self._request(url)
async def logs(self, user_id: int) -> dict:
"""
Gets the host logs.
Args:
user_id (int): The user ID.
Returns:
dict: The host logs.
"""
url = f"/api/host/{user_id}/logs/all"
return await self._request(url)
async def action(self, user_id: int, action: str = "restart") -> dict:
"""
Performs an action on the host.
Args:
user_id (int): The user ID.
action (str, optional): The action to perform. Defaults to "restart".
Returns:
dict: The action result.
"""
url = f"/api/host/{user_id}?action={action}"
return await self._request(url, method="PUT")
def bytes_to_megabytes(b: int):
"""
Converts bytes to megabytes.
Args:
b (int): The number of bytes.
Returns:
float: The number of megabytes.
"""
return round(b / 1024 / 1024, 1)
@loader.tds
class HikkahostMod(loader.Module):
"""Hikkahost manager."""
MAX_RAM = 750
strings = {
"name": "HikkaHost",
"info": (
"<emoji document_id=5879770735999717115>👤</emoji> <b>Information panel</b>\n\n"
"<emoji document_id=5974526806995242353>🆔</emoji> <b>Server ID:</b> <code>{server_id}</code>\n"
"<emoji document_id=6005570495603282482>🔑</emoji> <b>ID:</b> <code>{id}</code>\n"
"<emoji document_id=5874986954180791957>📶</emoji> <b>Status:</b> <code>{status}</code>\n"
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>Subscription ends:</b> <code>{end_dates}</code> | <code>{days_end} days</code>\n\n"
"<emoji document_id=5877260593903177342>⚙️</emoji> <b>CPU:</b> <code>{cpu_percent} %</code>\n"
"<emoji document_id=5379652232813750191>💾</emoji> <b>RAM:</b> <code>{memory} / {max_ram} MB</code> <b>{ram_percent} %</b>"
),
"logs": (
"<emoji document_id=5188377234380954537>🌘</emoji> <b>Here are your logs</b>"
),
"restart": (
"<emoji document_id=5789886476472815477>✅</emoji> <b>Restart request sent</b>\n"
"This message remains unchanged after the restart"
),
"loading_info": "<emoji document_id=5451646226975955576>⌛️</emoji> Loading...",
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> You have not specified an API Key\nTo get a token.\n\n1. Go to the @hikkahost_bot\n2. Write /token\n3. Paste it into the config",
"condition": "works",
}
strings_ru = {
"info": (
"<emoji document_id=5879770735999717115>👤</emoji> <b>Панель информации</b>\n\n"
"<emoji document_id=5974526806995242353>🆔</emoji> <b>Server ID:</b> <code>{server_id}</code>\n"
"<emoji document_id=6005570495603282482>🔑</emoji> <b>ID:</b> <code>{id}</code>\n"
"<emoji document_id=5874986954180791957>📶</emoji> <b>Статус:</b> <code>{status}</code>\n"
"<emoji document_id=5451646226975955576>⌛️</emoji> <b>Подписка закончится:</b> <code>{end_dates}</code> | <code>{days_end} дней</code>\n\n"
"<emoji document_id=5877260593903177342>⚙️</emoji> <b>CPU:</b> <code>{cpu_percent} %</code>\n"
"<emoji document_id=5379652232813750191>💾</emoji> <b>RAM:</b> <code>{memory} / {max_ram} MB</code> <b>{ram_percent} %</b>"
),
"logs": (
"<emoji document_id=5188377234380954537>🌘</emoji> <b>Вот ваши логи</b>"
),
"restart": (
"<emoji document_id=5789886476472815477>✅</emoji> <b>Запрос на рестарт отправил</b>\n"
"Это сообщение не изменяется после рестарта"
),
"loading_info": "<emoji document_id=5451646226975955576>⌛️</emoji> Загрузка...",
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> Вы не указали Api Key\nЧтобы получить token.\n\n1. Перейдите в бота @hikkahost_bot\n2. Напишите /token\n3. Вставьте его в конфиг",
"condition": "работает",
}
def __init__(self):
self.name = self.strings["name"]
self.config = loader.ModuleConfig(
loader.ConfigValue(
"token",
None,
validator=loader.validators.Hidden(),
),
)
@loader.command(
ru_doc="Статус HikkaHost",
en_doc="Status HikkaHost",
)
async def hinfocmd(self, message):
message = await utils.answer(message, self.strings("loading_info"))
if self.config["token"] is None:
await utils.answer(message, self.strings("no_apikey"))
return
token = self.config["token"]
user_id = token.split(":")[0]
api = HostApi(token)
stats_data = await api.stats(user_id)
host_data = await api.host_info(user_id)
datas = await api.status(user_id)
memory = bytes_to_megabytes(stats_data["stats"]["memory_stats"]["usage"])
cpu_percent = (
round(
(
stats_data["stats"]["cpu_stats"]["cpu_usage"]["total_usage"]
/ stats_data["stats"]["cpu_stats"]["system_cpu_usage"]
)
* 100.0,
2,
)
if stats_data["stats"]["cpu_stats"]["cpu_usage"]["total_usage"]
and stats_data["stats"]["cpu_stats"]["system_cpu_usage"]
else None
)
ram_percent = round(
bytes_to_megabytes(
stats_data["stats"]["memory_stats"]["usage"] / self.MAX_RAM
)
* 100,
2,
)
server_id = host_data["host"]["server_id"]
target_data = datetime.fromisoformat(
host_data["host"]["end_date"].replace("Z", "+00:00")
).replace(tzinfo=timezone.utc)
current_data = datetime.now(timezone.utc)
days_end = (target_data - current_data).days
end_dates = (current_data + timedelta(days=days_end)).strftime("%d-%m-%Y")
if "status" in datas and datas["status"] == "running":
status = self.strings("condition")
await utils.answer(
message,
self.strings("info").format(
server_id=server_id,
id=user_id,
status=status,
end_dates=end_dates,
days_end=days_end,
cpu_percent=cpu_percent,
memory=memory,
max_ram=self.MAX_RAM,
ram_percent=ram_percent,
),
)
@loader.command(
ru_doc="Логи HikkaHost",
en_doc="Logs HikkaHost",
)
async def hlogscmd(self, message):
if self.config["token"] is None:
await utils.answer(message, self.strings("no_apikey"))
return
token = self.config["token"]
user_id = token.split(":")[0]
api = HostApi(token)
data = await api.logs(user_id, token)
files_log = data["logs"]
with open("log.txt", "w") as log_file:
json.dump(files_log, log_file)
await utils.answer_file(message, "log.txt", self.strings("logs"))
@loader.command(
ru_doc="Рестарт HikkaHost",
en_doc="Restart HikkaHost",
)
async def hrestartcmd(self, message):
await utils.answer(message, self.strings("restart"))
if self.config["token"] is None:
await utils.answer(message, self.strings("no_apikey"))
return
token = self.config["token"]
user_id = token.split(":")[0]
api = HostApi(token)
data = await api.action(user_id, token)

View File

@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>H:Mods</title>
<link type=text/css href="https://github.com/C0dwiz/H.Modules/raw/assets/style.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<h1>H:Mods modules</h1>
<ul class="module-list">
<li class=module-item><a href="ASCIIArt.py" class=module-link>ASCIIArt.py</a></li>
<li class=module-item><a href="AccountData.py" class=module-link>AccountData.py</a></li>
<li class=module-item><a href="AniLibria.py" class=module-link>AniLibria.py</a></li>
<li class=module-item><a href="AnimeQuotes.py" class=module-link>AnimeQuotes.py</a></li>
<li class=module-item><a href="Article.py" class=module-link>Article.py</a></li>
<li class=module-item><a href="AutofarmCookies.py" class=module-link>AutofarmCookies.py</a></li>
<li class=module-item><a href="BirthdayTime.py" class=module-link>BirthdayTime.py</a></li>
<li class=module-item><a href="CheckSpamBan.py" class=module-link>CheckSpamBan.py</a></li>
<li class=module-item><a href="CryptoCurrency.py" class=module-link>CryptoCurrency.py</a></li>
<li class=module-item><a href="EnvsSH.py" class=module-link>EnvsSH.py</a></li>
<li class=module-item><a href="FakeActions.py" class=module-link>FakeActions.py</a></li>
<li class=module-item><a href="FakeWallet.py" class=module-link>FakeWallet.py</a></li>
<li class=module-item><a href="GigaChat.py" class=module-link>GigaChat.py</a></li>
<li class=module-item><a href="H.py" class=module-link>H.py</a></li>
<li class=module-item><a href="HAFK.py" class=module-link>HAFK.py</a></li>
<li class=module-item><a href="HModsLibrary.py" class=module-link>HModsLibrary.py</a></li>
<li class=module-item><a href="InlineButton.py" class=module-link>InlineButton.py</a></li>
<li class=module-item><a href="InlineCoin.py" class=module-link>InlineCoin.py</a></li>
<li class=module-item><a href="InlineHelper.py" class=module-link>InlineHelper.py</a></li>
<li class=module-item><a href="IrisSimpleMod.py" class=module-link>IrisSimpleMod.py</a></li>
<li class=module-item><a href="KBSwapper.py" class=module-link>KBSwapper.py</a></li>
<li class=module-item><a href="Memes.py" class=module-link>Memes.py</a></li>
<li class=module-item><a href="MooFarmRC1.py" class=module-link>MooFarmRC1.py</a></li>
<li class=module-item><a href="Music.py" class=module-link>Music.py</a></li>
<li class=module-item><a href="PastebinAPI.py" class=module-link>PastebinAPI.py</a></li>
<li class=module-item><a href="ReplaceVowels.py" class=module-link>ReplaceVowels.py</a></li>
<li class=module-item><a href="SMAcrhiver.py" class=module-link>SMAcrhiver.py</a></li>
<li class=module-item><a href="SafetyMod.py" class=module-link>SafetyMod.py</a></li>
<li class=module-item><a href="TaskManager.py" class=module-link>TaskManager.py</a></li>
<li class=module-item><a href="TelegramStatusCodes.py" class=module-link>TelegramStatusCodes.py</a></li>
<li class=module-item><a href="TempChat.py" class=module-link>TempChat.py</a></li>
<li class=module-item><a href="Text2File.py" class=module-link>Text2File.py</a></li>
<li class=module-item><a href="Text_Sticker.py" class=module-link>Text_Sticker.py</a></li>
<li class=module-item><a href="TikTokDownloader.py" class=module-link>TikTokDownloader.py</a></li>
<li class=module-item><a href="UserbotAvast.py" class=module-link>UserbotAvast.py</a></li>
<li class=module-item><a href="Video2GIF.py" class=module-link>Video2GIF.py</a></li>
<li class=module-item><a href="VirusTotal.py" class=module-link>VirusTotal.py</a></li>
<li class=module-item><a href="VoiceDL.py" class=module-link>VoiceDL.py</a></li>
<li class=module-item><a href="Weather.py" class=module-link>Weather.py</a></li>
<li class=module-item><a href="WindowsKeys.py" class=module-link>WindowsKeys.py</a></li>
<li class=module-item><a href="animals.py" class=module-link>animals.py</a></li>
<li class=module-item><a href="face.py" class=module-link>face.py</a></li>
<li class=module-item><a href="globalrestrict.py" class=module-link>globalrestrict.py</a></li>
<li class=module-item><a href="hikkahost.py" class=module-link>hikkahost.py</a></li>
<li class=module-item><a href="jacques.py" class=module-link>jacques.py</a></li>
<li class=module-item><a href="novoice.py" class=module-link>novoice.py</a></li>
<li class=module-item><a href="nsfwart.py" class=module-link>nsfwart.py</a></li>
<li class=module-item><a href="numbersapi.py" class=module-link>numbersapi.py</a></li>
<li class=module-item><a href="profile.py" class=module-link>profile.py</a></li>
<li class=module-item><a href="search.py" class=module-link>search.py</a></li>
<li class=module-item><a href="shortener.py" class=module-link>shortener.py</a></li>
</ul>
</div>
</body>
</html>

View File

@@ -1,109 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Жаконизатор
# Description: Жаконизатор
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Жаконизатор
# scope: Жаконизатор 0.0.1
# ---------------------------------------------------------------------------------
import aiohttp
import io
from PIL import Image, ImageDraw, ImageFont
from textwrap import wrap
from .. import loader, utils
@loader.tds
class JacquesMod(loader.Module):
"""Жаконизатор"""
strings = {"name": "Жаконизатор", "usage": "Write <code>.help Жаконизатор</code>"}
strings_ru = {"usage": "Напиши <code>.help Жаконизатор</code>"}
def __init__(self):
self.name = self.strings["name"]
self._me = None
self._ratelimit = []
self.config = loader.ModuleConfig(
loader.ConfigValue(
"font",
"https://github.com/Codwizer/ReModules/blob/main/assets/OpenSans-Light.ttf?raw=true",
lambda: "добавьте ссылку на нужный вам шрифт",
),
loader.ConfigValue(
"location",
"center",
"Можно указать left, right или center",
validator=loader.validators.Choice(["left", "right", "center"]),
),
)
@loader.command(
ru_doc="<реплай на сообщение/свой текст>",
en_doc="<reply to the message/your own text>",
)
async def ionicmd(self, message):
reply = await message.get_reply_message()
args = utils.get_args_raw(message)
if not args:
if not reply:
await utils.answer(message, self.strings("usage", message))
return
else:
txt = reply.raw_text
else:
txt = args
async with aiohttp.ClientSession() as session:
async with session.get(self.config["font"]) as font_response:
font_data = await font_response.read()
async with session.get("https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/IMG_20231128_152538.jpg") as pic_response:
pic_data = await pic_response.read()
img = Image.open(io.BytesIO(pic_data)).convert("RGB")
wrapped_text = "\n".join(wrap(txt, 19)) + "\n"
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(io.BytesIO(font_data), 32, encoding="UTF-8")
text_size = draw.multiline_textsize(wrapped_text, font=font)
imtext = Image.new("RGBA", (text_size[0] + 10, text_size[1] + 10), (0, 0, 0, 0))
draw_imtext = ImageDraw.Draw(imtext)
draw_imtext.multiline_text((10, 10), wrapped_text, (0, 0, 0), font=font, align=self.config["location"])
imtext.thumbnail((350, 195))
img.paste(imtext, (10, 10), imtext)
out = io.BytesIO()
out.name = "hikka_mods.jpg"
img.save(out)
out.seek(0)
await message.client.send_file(message.to_id, out, reply_to=reply)
await message.delete()

View File

@@ -1,159 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: NoVoice
# Description: A module for prohibiting the sending of voice and video messages
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: NoVoice
# scope: NoVoice 0.0.1
# ---------------------------------------------------------------------------------
import logging
from telethon.tl.custom import Message
from .. import loader, utils
logger = logging.INFO(__name__)
@loader.tds
class NoVoiceMod(loader.Module):
"""A module for prohibiting the sending of voice and video messages"""
strings = {
"name": "NoVoice",
"novoice_true": "❌ Voice messages are disabled for all users!",
"novoice_false": "✅ Voice messages are allowed for all users again!",
"novoice_no_args": "Usage: .novoice [on/off]",
"novoiceuser_no_reply": "Usage: .novoiceuser [username/reply]",
"novoiceuser_true": "❌ User {user_id} is now forbidden to send voice messages!",
"novoicerm_no_reply": "Usage: .novoicerm [username/reply]",
"novoicerm_yes": "✅ User {user_id} is now allowed to send voice messages again!",
"novoicerm_no": "⚠️ User {user_id} not found in the banned list.",
"text": "❌ I do not accept voice messages!",
}
strings_ru = {
"novoice_true": "❌ Голосовые сообщения отключены для всех пользователей!",
"novoice_false": "✅ Голосовые сообщения снова разрешены для всех пользователей!",
"novoice_no_args": "Использование: .novoice [on/off]",
"novoiceuser_no_reply": "Использование: .novoiceuser [username/reply]",
"novoiceuser_true": "❌ Пользователю {user_id} запрещено отправлять голосовые сообщения!",
"novoicerm_no_reply": "Использование: .novoicerm [username/reply]",
"novoicerm_yes": "✅ Пользователю {user_id} снова разрешено отправлять голосовые сообщения!",
"novoicerm_no": "⚠ Пользователь {user_id} не найден в списке запрещенных.",
"text": "❌ Я не принимаю голосовые сообщения!",
}
async def client_ready(self, client, db):
self.client = client
self.db = db
self.novoice_global = self.db.get("NoVoice", "global", False)
self.banned_users = self.db.get("NoVoice", "banned_users", {})
@loader.command(
ru_doc="[on/off] — запрещает/разрешает всем пользователям отправку голосовых и видеосообщений.",
en_doc="[on/off] — prohibits/allows all users to send voice and video messages.",
)
async def novoice(self, message):
args = utils.get_args_raw(message)
if args == "on":
self.novoice_global = True
self.db.set("NoVoice", "global", self.novoice_global)
await utils.answer(message, self.strings("novoice_true"))
elif args == "off":
self.novoice_global = False
self.db.set("NoVoice", "global", self.novoice_global)
await utils.answer(message, self.strings("novoice_false"))
else:
await utils.answer(message, self.strings("novoice_no_args"))
@loader.command(
ru_doc="[username/reply] — запрещает пользователю отправку голосовых и видеосообщений.",
en_doc="[username/reply] — prohibits the user from sending voice and video messages.",
)
async def novoiceuser(self, message):
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if not args and not reply:
return await utils.answer(message, self.strings("novoiceuser_no_reply"))
if reply:
user_id = reply.from_id
else:
user = await self.client.get_entity(args)
user_id = user.id
self.banned_users[user_id] = True
self.db.set("NoVoice", "banned_users", self.banned_users)
await utils.answer(
message, self.strings("novoiceuser_true").format(user_id=user_id)
)
@loader.command(
ru_doc="[username/reply] — разрешает пользователю отправку голосовых и видеосообщений.",
en_doc="[username/reply] — allows the user to send voice and video messages.",
)
async def novoicerm(self, message):
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if not args and not reply:
return await utils.answer(message, self.strings("novoicerm_no_reply"))
user_id = None
if reply:
user_id = reply.sender_id
else:
try:
user = await self.client.get_entity(args)
user_id = user.id
except Exception as e:
logger.error(f"Failed to get entity for {args}: {e}")
if user_id in self.banned_users:
del self.banned_users[user_id]
self.db.set("NoVoice", "banned_users", self.banned_users)
await utils.answer(
message, self.strings("novoicerm_yes").format(user_id=user_id)
)
else:
await utils.answer(
message, self.strings("novoicerm_no").format(user_id=user_id)
)
async def watcher(self, message: Message):
"""Обрабатывает входящие сообщения"""
if (
isinstance(message, Message)
and not message.out
and message.is_private
and (self.novoice_global or message.sender_id in self.banned_users)
and (message.voice or message.video_note)
):
await message.delete()
await utils.answer(message, self.strings("text"))
logger.debug(
"Deleted voice/video message from user %s in chat %s",
message.sender_id,
message.chat_id,
)

View File

@@ -1,97 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: NSFWArt
# Description: Sends cute anime nsfw-art
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api NSFWArt
# scope: Api NSFWArt 0.0.1
# ---------------------------------------------------------------------------------
import functools
import requests
from typing import List
from .. import loader, utils
async def photos(tags: str, subreddit: str, quantity: int) -> List[str]:
ans = (
await utils.run_sync(
requests.get,
f"https://api.lolicon.app/setu/v2?tag={tags}",
json={
"query": (
" query SubredditQuery( $url: String! $filter: SubredditPostFilter"
" $iterator: String ) { getSubreddit(url: $url) { children("
f" limit: {quantity} iterator: $iterator filter: $filter"
" disabledHosts: null ) { iterator items {url subredditTitle"
" isNsfw mediaSources { url } } } } } "
),
"variables": {"url": subreddit, "filter": None, "hostsDown": None},
"authorization": None,
},
)
).json()
return [ans["data"][0]["urls"]["original"]]
@loader.tds
class NSFWArtMod(loader.Module):
"""Sends cute anime nsfw-art"""
strings = {
"name": "NSFWArt",
"sreddit404": "🚫 <b>Subreddit not found</b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"tags",
"drool",
lambda: "tag: masturbation, drool, completely, sleeping, yuri",
)
)
@loader.command(
ru_doc="Отправьте симпатичный nsfw-арт",
en_doc="Send cute nsfw-art",
)
async def nsfwartcmd(self, message):
tags = self.config["tags"]
subreddit = f"/v2?tag={tags}"
ans = await utils.run_sync(
requests.get, f"https://api.lolicon.app/setu{subreddit}"
)
if ans.status_code != 200:
await utils.answer(message, self.strings("sreddit404", message))
return
await self.inline.gallery(
message=message,
next_handler=functools.partial(
photos, tags, subreddit=subreddit, quantity=15
),
caption=f"<i>{utils.ascii_face()}</i>",
)

View File

@@ -1,92 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: NumbersAPI
# Description: Many interesting facts about numbers.
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: NumbersAPI
# scope: NumbersAPI 0.0.1
# ---------------------------------------------------------------------------------
import aiohttp
from datetime import datetime
from .. import loader, utils
async def get_fact_about_number(number, fact_type):
url = f"http://numbersapi.com/{number}/{fact_type}"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
return await response.text()
else:
return "Извините, не удалось получить факт."
async def get_fact_about_date(month, day):
date_str = datetime.now().replace(month=month, day=day).strftime("%m/%d")
url = f"http://numbersapi.com/{date_str}/date"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
return await response.text()
else:
return "Извините, не удалось получить факт."
@loader.tds
class NumbersAPI(loader.Module):
"""Many interesting facts about numbers."""
strings = {"name": "NumbersAPI"}
@loader.command(
ru_doc="Дает интересный факт про число или дату\nНапример: .num 10 math или .num 01.01 date",
en_doc="Gives an interesting fact about a number or date\nexample: .num 10 math or .num 01.01 date",
)
async def num(self, message):
args = utils.get_args_raw(message).split()
if len(args) < 2:
await utils.answer(message, "Использование: .num <число или дата> <тип>")
return
num_or_date = args[0]
fact_type = args[1]
if "." in num_or_date:
try:
month, day = map(int, num_or_date.split("."))
result = await get_fact_about_date(month, day)
except ValueError:
await utils.answer(message, "Ошибка: некорректный формат даты. Используйте: месяц.день")
return
else:
try:
number = int(num_or_date)
result = await get_fact_about_number(number, fact_type)
except ValueError:
await utils.answer(message, "Ошибка: некорректный ввод числа.")
return
await utils.answer(message, result)

View File

@@ -1,97 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Profile
# Description: This module can change your Telegram profile
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Profile
# scope: Profile 0.0.1
# ---------------------------------------------------------------------------------
from telethon.errors.rpcerrorlist import UsernameOccupiedError
from telethon.tl.functions.account import UpdateProfileRequest, UpdateUsernameRequest
from .. import loader, utils
@loader.tds
class ProfileEditorMod(loader.Module):
"""This module can change your Telegram profile."""
strings = {
"name": "Profile",
"error_format": "Incorrect format of args. Try again.",
"done_name": "The new name was successfully unstalled!",
"done_bio": "The new bio was successfully unstaled!",
"done_username": "The new username was succesfully installed!",
"error_occupied": "The new username is already occupied!",
}
strings_ru = {
"error_format": "Неправильный формат аргумента. Попробуйте еще раз.",
"done_name": "Новое имя успешно настроено!",
"done_bio": "Новое био успешно настроено!",
"done_username": "Новое имя пользователя успешно установлено!",
"error_occupied": "Новое имя пользователя уже занято!",
}
@loader.command(
ru_doc="для того, чтобы сменить свое имя/отчество",
en_doc="for change your first/second name",
)
async def namecmd(self, message):
args = utils.get_args_raw(message).split("/")
if len(args) < 1 or len(args) > 2:
return await utils.answer(message, self.strings("error_format"))
firstname = args[0]
lastname = args[1] if len(args) == 2 else ""
await message.client(
UpdateProfileRequest(first_name=firstname, last_name=lastname)
)
await utils.answer(message, self.strings("done_name"))
@loader.command(
ru_doc="чтобы изменить свою биографию",
en_doc="for change your bio",
)
async def aboutcmd(self, message):
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings("error_format"))
await message.client(UpdateProfileRequest(about=args))
await utils.answer(message, self.strings("done_bio"))
@loader.command(
ru_doc="для изменения вашего имени пользователя. Введите значение без '@'",
en_doc="for change your username. Enter value without '@'",
)
async def usercmd(self, message):
"""- for change your username. Enter value without "@"."""
args = utils.get_args_raw(message)
if not args:
return await utils.answer(message, self.strings("error_format"))
try:
await message.client(UpdateUsernameRequest(args))
await utils.answer(message, self.strings("done_username"))
except UsernameOccupiedError:
await utils.answer(message, self.strings("error_occupied"))

View File

@@ -1,204 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Search
# Description: Search for your question on the Internet
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Api Search
# scope: Api Search 0.0.1
# ---------------------------------------------------------------------------------
from .. import loader, utils
@loader.tds
class Search(loader.Module):
"""Поисковик"""
strings = {
"name": "Search",
"search": "<emoji document_id=5188311512791393083>🌎</emoji><b> I searched for information for you</b>",
"isearch": "🔎<b> I searched for information for you</b> ",
"link": "🗂️ Link to your request",
"close": "❌ Close",
}
strings_ru = {
"search": "<emoji document_id=5188311512791393083>🌎</emoji><b> Я поискал информацию за тебя</b>",
"isearch": "🔎<b> Я поискал информацию за тебя</b> ",
"link": "🗂️ Ссылка на ваш запрос",
"close": "❌ Закрыть",
}
@loader.command(
ru_doc="Поискать в Google",
en_doc="Search on Google",
)
async def google(self, message):
await self.search_engine(message, "https://google.com/search?q=")
@loader.command(
ru_doc="Поискать в Yandex",
en_doc="Search on Yandex",
)
async def yandex(self, message):
await self.search_engine(message, "https://yandex.ru/?q=")
@loader.command(
ru_doc="Поискать в Duckduckgo",
en_doc="Search on Duckduckgo",
)
async def duckduckgo(self, message):
await self.search_engine(message, "https://duckduckgo.com/?q=")
@loader.command(
ru_doc="Поискать в Bing",
en_doc="Search on Bing",
)
async def bing(self, message):
await self.search_engine(message, "https://bing.com/?q=")
@loader.command(
ru_doc="Поискать в You",
en_doc="Search on You",
)
async def you(self, message):
await self.search_engine(message, "https://you.com/?q=")
async def search_engine(self, message, base_url: str) -> None:
"""Searches on a given search engine."""
query = utils.get_args_raw(message)
search_url = f"{base_url}{query}"
await utils.answer(message, self.strings("search") + f": <a href={search_url}>link</a>")
@loader.command(
ru_doc="Поискать в Google инлайн",
en_doc="Search on Google inline",
)
async def igoogle(self, message):
g = utils.get_args_raw(message)
google = f"https://google.com/search?q={g}"
await self.inline.form(
text=self.strings("isearch"),
message=message,
reply_markup=[
[
{
"text": self.strings("link"),
"url": google,
}
],
[{"text": self.strings("close"), "action": "close"}],
],
silent=True,
)
@loader.command(
ru_doc="Поискать в Yandex инлайн",
en_doc="Search on Yandex inline",
)
async def iyandex(self, message):
y = utils.get_args_raw(message)
yandex = f"https://yandex.ru/?q={y}"
await self.inline.form(
text=self.strings("isearch"),
message=message,
reply_markup=[
[
{
"text": self.strings("link"),
"url": yandex,
}
],
[{"text": self.strings("close"), "action": "close"}],
],
silent=True,
)
@loader.command(
ru_doc="Поискать в Duckduckgo инлайн",
en_doc="Search on Duckduckgo inline",
)
async def iduckduckgo(self, message):
d = utils.get_args_raw(message)
duckduckgo = f"https://duckduckgo.com/?q={d}"
await self.inline.form(
text=self.strings("isearch"),
message=message,
reply_markup=[
[
{
"text": self.strings("link"),
"url": duckduckgo,
}
],
[{"text": self.strings("close"), "action": "close"}],
],
silent=True,
)
@loader.command(
ru_doc="Поискать в Bing инлайн",
en_doc="Search on Bing inline",
)
async def ibing(self, message):
b = utils.get_args_raw(message)
bing = f"https://bing.com/?q={b}"
await self.inline.form(
text=self.strings("isearch"),
message=message,
reply_markup=[
[
{
"text": self.strings("link"),
"url": bing,
}
],
[{"text": self.strings("close"), "action": "close"}],
],
silent=True,
)
@loader.command(
ru_doc="Поискать в You инлайн",
en_doc="Search on You inline",
)
async def iyou(self, message):
y = utils.get_args_raw(message)
you = f"https://you.com/?q={y}"
await self.inline.form(
text=self.strings("isearch"),
message=message,
reply_markup=[
[
{
"text": self.strings("link"),
"url": you,
}
],
[{"text": self.strings("close"), "action": "close"}],
],
silent=True,
)
async def close(self, call):
"""Callback button"""
await call.delete()

View File

@@ -1,90 +0,0 @@
# Proprietary License Agreement
# Copyright (c) 2024-29 CodWiz
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
# ---------------------------------------------------------------------------------
# Name: Shortener
# Description: shortening the link
# Author: @hikka_mods
# ---------------------------------------------------------------------------------
# meta developer: @hikka_mods
# scope: Shortener
# scope: Shortener 0.0.1
# requires: pyshorteners
# ---------------------------------------------------------------------------------
import pyshorteners
from .. import loader, utils
@loader.tds
class Shortener(loader.Module):
"""Module for working with the api bit.ly"""
strings = {
"name": "Shortener",
"no_api": "<emoji document_id=5854929766146118183>❌</emoji> You have not specified an API token from the site <a href='https://app.bitly.com/settings/api/'>bit.ly</a>",
"statclcmd": "<emoji document_id=5787384838411522455>📊</emoji> <b>Statistics on clicks for this link:</b> {c}",
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Your shortened link is ready:</b> <code>{c}</code>",
}
strings_ru = {
"no_api": "<emoji document_id=5854929766146118183>❌</emoji> Вы не указали api токен с сайта <a href='https://app.bitly.com/settings/api/'>bit.ly</a>",
"statclcmd": "<emoji document_id=5787384838411522455>📊</emoji> <b>Статистика о переходе по этой ссылке:</b> {c}",
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Ваша сокращённая ссылка готова:</b> <code>{c}</code>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"token",
None,
lambda: "Need a token with https://app.bitly.com/settings/api/",
validator=loader.validators.Hidden(),
)
)
@loader.command(
ru_doc="Сократить ссылку через bit.ly",
en_doc="Shorten the link via bit.ly",
)
async def shortencmd(self, message):
if self.config["token"] is None:
await utils.answer(message, self.strings("no_api"))
return
s = pyshorteners.Shortener(api_key=self.config["token"])
args = utils.get_args_raw(message)
await utils.answer(
message, self.strings("shortencmd").format(c=s.bitly.short(args))
)
@loader.command(
ru_doc="Посмотреть статистику ссылки через bit.ly",
en_doc="View link statistics via bit.ly",
)
async def statclcmd(self, message):
if self.config["token"] is None:
await utils.answer(message, self.strings("no_api"))
return
s = pyshorteners.Shortener(api_key=self.config["token"])
args = utils.get_args_raw(message)
await utils.answer(
message, self.strings("statclcmd").format(c=s.bitly.total_clicks(args))
)

View File

@@ -3,7 +3,9 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
__version__ = (5, 2, 6) # Meow~
__version__ = (5, 6, 0) # Емае, ну это уже слишком
#фырфырфырфырфырфыр
# meta developer: @SenkoGuardianModules
@@ -25,7 +27,17 @@ import aiohttp
import tempfile
from markdown_it import MarkdownIt
import pytz
# New SDK
from google import genai as google_genai_sdk
try:
from google.genai import types as new_types
NEW_SDK_AVAILABLE = True
except ImportError:
NEW_SDK_AVAILABLE = False
# Old SDK
import google.ai.generativelanguage as glm
import google.generativeai as old_genai
from google.generativeai import types as old_types
from telethon import types
from telethon.tl.types import Message, DocumentAttributeFilename
from telethon.utils import get_display_name, get_peer_id
@@ -35,17 +47,26 @@ from telethon.errors.rpcerrorlist import (
UserNotParticipantError,
ChannelPrivateError
)
try:
import google.generativeai as genai
import google.ai.generativelanguage
import google.api_core.exceptions as google_exceptions
GOOGLE_AVAILABLE = True
except ImportError:
GOOGLE_AVAILABLE = False
from .. import loader, utils
from ..inline.types import InlineCall
# requires: google-generativeai google-api-core pytz markdown_it_py
logger = logging.getLogger(__name__)
DB_HISTORY_KEY = "gemini_conversations_v4"
DB_GAUTO_HISTORY_KEY = "gemini_gauto_conversations_v1"
DB_IMPERSONATION_KEY = "gemini_impersonation_chats"
GEMINI_TIMEOUT = 840
MAX_FFMPEG_SIZE = 90 * 1024 * 1024
# requires: google-generativeai google-genai google-api-core pytz markdown_it_py
# рекомендованные версии: google-generativeai==0.8.5 google-genai==1.52.0 google-api-core==2.28.1
logger = logging.getLogger(__name__)
@@ -69,6 +90,8 @@ class Gemini(loader.Module):
"cfg_impersonation_prompt_doc": "Промпт для режима авто-ответа. {my_name} и {chat_history} будут заменены.",
"cfg_impersonation_history_limit_doc": "Сколько последних сообщений из чата отправлять в качестве контекста для авто-ответа.",
"cfg_impersonation_reply_chance_doc": "Вероятность ответа в режиме gauto (от 0.0 до 1.0). 0.2 = 20% шанс.",
"cfg_temperature_doc": "Температура генерации (креативность). От 0.0 до 2.0. По умолчанию 1.0.",
"cfg_google_search_doc": "Включить поиск Google (Grounding) для актуальной информации.",
"no_api_key": '❗️ <b>Api ключ(и) не настроен(ы).</b>\nПолучить Api ключ можно <a href="https://aistudio.google.com/app/apikey">здесь</a>.\n<b>Добавьте ключ(и) в конфиге модуля:</b> <code>.cfg gemini api_key</code>',
"invalid_api_key": '❗️ <b>Предоставленный API ключ недействителен.</b>\nУбедитесь, что он правильно скопирован из <a href="https://aistudio.google.com/app/apikey">Google AI Studio</a> и что для него включен Gemini API.',
"all_keys_exhausted": "❗️ <b>Все доступные API ключи ({}) исчерпали свою квоту.</b>\nПопробуйте позже или добавьте новые ключи в конфиге: <code>.cfg gemini api_key</code>",
@@ -124,6 +147,14 @@ class Gemini(loader.Module):
"gmodel_img_warn": "⚠️ <b>Текущая модель ({}) не может генерировать изображения(или не доступна по API).</b>\nРекомендуем: <code>gemini-2.5-flash-image</code>",
"gme_chat_not_found": "🚫 <b>Не удалось найти чат для экспорта:</b> <code>{}</code>",
"gme_sent_to_saved": "💾 История экспортирована в избранное.",
"new_sdk_missing": "⚠️ <b>Для работы поиска (Grounding) нужна библиотека google-genai.</b>\nВыполните: <code>pip install google-genai</code>",
"gprompt_usage": " <b>Использование:</b>\n<code>.gprompt <текст></code> — установить промпт.\n<code>.gprompt -c</code> — очистить.\nИли ответьте на <b>.txt</b> файл.",
"gprompt_updated": "✅ <b>Системный промпт обновлен!</b>\nДлина: {} симв.",
"gprompt_cleared": "🗑 <b>Системный промпт очищен.</b>",
"gprompt_current": "📝 <b>Текущий системный промпт:</b>",
"gprompt_file_error": "❗️ <b>Ошибка чтения файла:</b> {}",
"gprompt_file_too_big": "❗️ <b>Файл слишком большой</b> (лимит 1 МБ).",
"gprompt_not_text": "❗️ Это не похоже на текстовый файл.(txt)",
}
TEXT_MIME_TYPES = {
"text/plain", "text/markdown", "text/html", "text/css", "text/csv",
@@ -131,39 +162,41 @@ class Gemini(loader.Module):
"application/javascript", "application/x-sh",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"api_key", "", self.strings["cfg_api_key_doc"],
validator=loader.validators.Hidden()
),
loader.ConfigValue("model_name", "gemini-1.5-flash", self.strings["cfg_model_name_doc"]),
loader.ConfigValue("interactive_buttons", True, self.strings["cfg_buttons_doc"], validator=loader.validators.Boolean()),
loader.ConfigValue("system_instruction", "", self.strings["cfg_system_instruction_doc"], validator=loader.validators.String()),
loader.ConfigValue("max_history_length", 10, self.strings["cfg_max_history_length_doc"], validator=loader.validators.Integer(minimum=0)),
loader.ConfigValue("timezone", "Europe/Moscow", self.strings["cfg_timezone_doc"]),
loader.ConfigValue("proxy", "", self.strings["cfg_proxy_doc"]),
loader.ConfigValue(
"impersonation_prompt",
(
"Ты — это я, пользователь Telegram по имени {my_name}. Твоя задача — отвечать в чате от моего имени, основываясь на последних сообщениях. "
"Веди себя естественно, как обычный человек, а не как ассистент. Не признавайся, что ты — ИИ, даже если тебя спросят напрямую. "
"Твоя личность: [Здесь опишите свой характер: остроумный, серьезный, саркастичный, дружелюбный и т.д.].\n"
"Правила:\n- Отвечай кратко и по делу.\n- Используй неформальный язык, сленг.\n- Не отвечай на каждое сообщение.\n- На медиа (стикер, фото) реагируй как человек ('лол', 'ору', 'жиза').\n- Не используй префиксы и кавычки.\n\n"
"ИСТОРИЯ ЧАТА:\n{chat_history}\n\n{my_name}:"
self.config = loader.ModuleConfig(
loader.ConfigValue(
"api_key", "", self.strings["cfg_api_key_doc"],
validator=loader.validators.Hidden()
),
self.strings["cfg_impersonation_prompt_doc"],
validator=loader.validators.String(),
),
loader.ConfigValue("impersonation_history_limit", 20, self.strings["cfg_impersonation_history_limit_doc"], validator=loader.validators.Integer(minimum=5, maximum=100)),
loader.ConfigValue("impersonation_reply_chance", 0.25, self.strings["cfg_impersonation_reply_chance_doc"], validator=loader.validators.Float(minimum=0.0, maximum=1.0)),
loader.ConfigValue("gauto_in_pm", False, "Разрешить авто-ответы в личных сообщениях (ЛС).", validator=loader.validators.Boolean()),
)
self.conversations = {}
self.gauto_conversations = {}
self.last_requests = {}
self.impersonation_chats = set()
self._lock = asyncio.Lock()
self.memory_disabled_chats = set()
loader.ConfigValue("model_name", "gemini-2.5-flash", self.strings["cfg_model_name_doc"]),
loader.ConfigValue("interactive_buttons", True, self.strings["cfg_buttons_doc"], validator=loader.validators.Boolean()),
loader.ConfigValue("system_instruction", "", self.strings["cfg_system_instruction_doc"], validator=loader.validators.String()),
loader.ConfigValue("max_history_length", 800, self.strings["cfg_max_history_length_doc"], validator=loader.validators.Integer(minimum=0)),
loader.ConfigValue("timezone", "Europe/Moscow", self.strings["cfg_timezone_doc"]),
loader.ConfigValue("proxy", "", self.strings["cfg_proxy_doc"]),
loader.ConfigValue(
"impersonation_prompt",
(
"Ты — это я, пользователь Telegram по имени {my_name}. Твоя задача — отвечать в чате от моего имени, основываясь на последних сообщениях. "
"Веди себя естественно, как обычный человек, а не как ассистент. Не признавайся, что ты — ИИ, даже если тебя спросят напрямую. "
"Твоя личность: [Здесь опишите свой характер: остроумный, серьезный, саркастичный, дружелюбный и т.д.].\n"
"Правила:\n- Отвечай кратко и по делу.\n- Используй неформальный язык, сленг.\n- Не отвечай на каждое сообщение.\n- На медиа (стикер, фото) реагируй как человек ('лол', 'ору', 'жиза').\n- Не используй префиксы и кавычки.\n\n"
"ИСТОРИЯ ЧАТА:\n{chat_history}\n\n{my_name}:"
),
self.strings["cfg_impersonation_prompt_doc"],
validator=loader.validators.String(),
),
loader.ConfigValue("impersonation_history_limit", 20, self.strings["cfg_impersonation_history_limit_doc"], validator=loader.validators.Integer(minimum=5, maximum=100)),
loader.ConfigValue("impersonation_reply_chance", 0.25, self.strings["cfg_impersonation_reply_chance_doc"], validator=loader.validators.Float(minimum=0.0, maximum=1.0)),
loader.ConfigValue("gauto_in_pm", False, "Разрешить авто-ответы в личных сообщениях (ЛС).", validator=loader.validators.Boolean()),
loader.ConfigValue("google_search", False, self.strings["cfg_google_search_doc"], validator=loader.validators.Boolean()),
loader.ConfigValue("temperature", 1.0, self.strings["cfg_temperature_doc"], validator=loader.validators.Float(minimum=0.0, maximum=2.0)),
)
self.conversations = {}
self.gauto_conversations = {}
self.last_requests = {}
self.impersonation_chats = set()
self._lock = asyncio.Lock()
self.memory_disabled_chats = set()
async def client_ready(self, client, db):
self.client = client
@@ -296,116 +329,143 @@ class Gemini(loader.Module):
except Exception: msg_obj=None
else:
chat_id=utils.get_chat_id(message); base_message_id=message.id; msg_obj=message
api_key_str = self.config["api_key"]
self.api_keys = [k.strip() for k in api_key_str.split(",") if k.strip()] if api_key_str else []
try:
if not self.api_keys:
if not impersonation_mode and status_msg:
await utils.answer(status_msg, self.strings['no_api_key'])
if not impersonation_mode and status_msg: await utils.answer(status_msg, self.strings['no_api_key'])
return None if impersonation_mode else ""
tools_list=[]
if use_url_context:
try: tools_list.append(genai.types.Tool(url_context=genai.types.UrlContext()))
except AttributeError: logger.error("Инструмент UrlContext не поддерживается вашей версией библиотеки.")
system_instruction_to_use=None; api_history_content=[]
if impersonation_mode:
my_name=get_display_name(self.me); chat_history_text=await self._get_recent_chat_text(chat_id); system_instruction_to_use=self.config["impersonation_prompt"].format(my_name=my_name, chat_history=chat_history_text)
raw_history=self._get_structured_history(chat_id, gauto=True); api_history_content=[glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history]
else:
system_instruction_val=self.config["system_instruction"]; system_instruction_to_use=(system_instruction_val.strip() if isinstance(system_instruction_val, str) else "") or None
raw_history=self._get_structured_history(chat_id, gauto=False)
if regeneration: raw_history=raw_history[:-2]
api_history_content=[glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history]
full_request_content=list(api_history_content)
if not impersonation_mode:
from datetime import datetime
try: user_timezone=pytz.timezone(self.config["timezone"])
except pytz.UnknownTimeZoneError: user_timezone=pytz.utc
now=datetime.now(user_timezone); time_str=now.strftime("%Y-%m-%d %H:%M:%S %Z"); time_note=f"[System note: Current time is {time_str}]"
text_part_found=False
for p in parts:
if hasattr(p, 'text'): p.text=f"{time_note}\n\n{p.text}"; text_part_found=True; break
if not text_part_found: parts.insert(0, glm.Part(text=time_note))
api_key = self.api_keys[self.current_api_key_index]
if regeneration:
current_turn_parts,request_text_for_display=self.last_requests.get(f"{chat_id}:{base_message_id}", (parts, "[регенерация]"))
current_turn_parts, request_text_for_display = self.last_requests.get(f"{chat_id}:{base_message_id}", (parts, "[регенерация]"))
else:
current_turn_parts=parts; request_text_for_display=display_prompt or (self.strings["media_reply_placeholder"] if any("inline_data" in str(p) for p in parts) else ""); self.last_requests[f"{chat_id}:{base_message_id}"]=(current_turn_parts, request_text_for_display)
if current_turn_parts: full_request_content.append(glm.Content(role="user", parts=current_turn_parts))
if not full_request_content and not system_instruction_to_use:
if not impersonation_mode and status_msg: await utils.answer(status_msg, self.strings["no_prompt_or_media"])
return None if impersonation_mode else ""
response = None
error_to_report = None
max_retries = len(self.api_keys)
for i in range(max_retries):
current_key_index = (self.current_api_key_index + i) % max_retries
api_key = self.api_keys[current_key_index]
current_turn_parts = parts
request_text_for_display = display_prompt or (self.strings["media_reply_placeholder"] if any("inline_data" in str(p) for p in parts) else "")
self.last_requests[f"{chat_id}:{base_message_id}"] = (current_turn_parts, request_text_for_display)
has_media = False
for p in current_turn_parts:
if hasattr(p, 'inline_data') and p.inline_data:
has_media = True; break
should_use_new_sdk = (self.config["google_search"] or use_url_context) and NEW_SDK_AVAILABLE and not has_media
result_text = ""; was_successful = False; search_icon = ""
if should_use_new_sdk:
try:
genai.configure(api_key=api_key)
sanitized_model_name = self.config["model_name"].lower().replace(" ", "-")
model = genai.GenerativeModel(
sanitized_model_name,
safety_settings=self.safety_settings,
system_instruction=system_instruction_to_use
)
api_response = await asyncio.wait_for(
model.generate_content_async(full_request_content, tools=tools_list or None),
timeout=GEMINI_TIMEOUT
)
response = api_response
self.current_api_key_index = current_key_index
break
except google_exceptions.GoogleAPIError as e:
msg = str(e)
if "quota" in msg.lower() or "exceeded" in msg.lower():
if max_retries == 1:
error_to_report = e
break
logger.warning(f"Ключ Gemini API №{current_key_index + 1} исчерпал квоту. Пробую следующий.")
if i == max_retries - 1:
error_to_report = RuntimeError("Все ключи исчерпали квоту.")
continue
client = google_genai_sdk.Client(api_key=api_key); sys_instruct = None
if impersonation_mode:
my_name = get_display_name(self.me); chat_history_text = await self._get_recent_chat_text(chat_id)
sys_instruct = self.config["impersonation_prompt"].format(my_name=my_name, chat_history=chat_history_text)
else:
error_to_report = e
break
sys_val = self.config["system_instruction"]; sys_instruct = (sys_val.strip() if isinstance(sys_val, str) else "") or None
new_contents = []; raw_hist = self._get_structured_history(chat_id, gauto=impersonation_mode)
if regeneration and raw_hist: raw_hist = raw_hist[:-2]
for item in raw_hist:
new_contents.append(new_types.Content(role=item['role'], parts=[new_types.Part(text=item['content'])]))
new_parts = []
for p in current_turn_parts:
if hasattr(p, 'text'): new_parts.append(new_types.Part(text=p.text))
new_contents.append(new_types.Content(role="user", parts=new_parts))
tools = []
if self.config["google_search"] or use_url_context:
tools.append(new_types.Tool(google_search=new_types.GoogleSearch()))
config_new = new_types.GenerateContentConfig(tools=tools if tools else None, temperature=self.config["temperature"], system_instruction=sys_instruct)
response = await asyncio.to_thread(client.models.generate_content, model=self.config["model_name"], contents=new_contents, config=config_new)
if response.text:
result_text = response.text; was_successful = True
if self.config["google_search"]: search_icon = " 🌐"
else: result_text = "⚠️ Пустой ответ от модели (возможно, блокировка безопасности)."
except Exception as e:
error_to_report = e
break
if error_to_report:
raise error_to_report
if response is None:
raise RuntimeError("Не удалось получить ответ от Gemini.")
result_text,was_successful="",False
try:
if response.prompt_feedback.block_reason: result_text=f"🚫 <b>Запрос был заблокирован Google.</b>\nПричина: <code>{response.prompt_feedback.block_reason.name}</code>."
except AttributeError: pass
if not result_text:
try:
result_text = re.sub(r"</?emoji[^>]*>", "", response.text)
was_successful=True
except ValueError:
reason="Неизвестная причина"
logger.error(f"New SDK Error: {e}")
if "quota" in str(e).lower(): raise e
was_successful = False
if not was_successful:
if (self.config["google_search"] or use_url_context) and not NEW_SDK_AVAILABLE and not has_media:
if status_msg: await utils.answer(status_msg, self.strings["new_sdk_missing"])
return None
tools_list = []
if use_url_context and not has_media:
try: tools_list.append(old_types.Tool(url_context=old_types.UrlContext()))
except AttributeError: pass
system_instruction_to_use = None; api_history_content = []
if impersonation_mode:
my_name = get_display_name(self.me); chat_history_text = await self._get_recent_chat_text(chat_id)
system_instruction_to_use = self.config["impersonation_prompt"].format(my_name=my_name, chat_history=chat_history_text)
raw_history = self._get_structured_history(chat_id, gauto=True)
api_history_content = [glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history]
else:
system_instruction_val = self.config["system_instruction"]
system_instruction_to_use = (system_instruction_val.strip() if isinstance(system_instruction_val, str) else "") or None
raw_history = self._get_structured_history(chat_id, gauto=False)
if regeneration: raw_history = raw_history[:-2]
api_history_content = [glm.Content(role=e["role"], parts=[glm.Part(text=e['content'])]) for e in raw_history]
full_request_content = list(api_history_content)
if not impersonation_mode:
from datetime import datetime
try: user_timezone = pytz.timezone(self.config["timezone"])
except pytz.UnknownTimeZoneError: user_timezone = pytz.utc
now = datetime.now(user_timezone); time_str = now.strftime("%Y-%m-%d %H:%M:%S %Z"); time_note = f"[System note: Current time is {time_str}]"
request_content_parts = list(current_turn_parts)
if request_content_parts and hasattr(request_content_parts[0], 'text'):
original_text = request_content_parts[0].text
request_content_parts[0] = glm.Part(text=f"{time_note}\n\n{original_text}")
else: request_content_parts.insert(0, glm.Part(text=time_note))
else: request_content_parts = current_turn_parts
if request_content_parts: full_request_content.append(glm.Content(role="user", parts=request_content_parts))
if not full_request_content and not system_instruction_to_use:
if not impersonation_mode and status_msg: await utils.answer(status_msg, self.strings["no_prompt_or_media"])
return None if impersonation_mode else ""
error_to_report = None; max_retries = len(self.api_keys)
generation_cfg = old_types.GenerationConfig(temperature=self.config["temperature"])
for i in range(max_retries):
current_key_index = (self.current_api_key_index + i) % max_retries
api_key = self.api_keys[current_key_index]
try:
if response.candidates: reason=response.candidates[0].finish_reason.name
except(IndexError, AttributeError): pass
result_text=f"❗️ Gemini не смог сгенерировать ответ.\nПричина завершения: <code>{reason}</code>."
if was_successful and self._is_memory_enabled(str(chat_id)): self._update_history(chat_id, current_turn_parts, result_text, regeneration, msg_obj, gauto=impersonation_mode)
old_genai.configure(api_key=api_key)
sanitized_model_name = self.config["model_name"].lower().replace(" ", "-")
model = old_genai.GenerativeModel(sanitized_model_name, safety_settings=self.safety_settings, system_instruction=system_instruction_to_use)
api_response = await asyncio.wait_for(model.generate_content_async(full_request_content, tools=tools_list or None, generation_config=generation_cfg), timeout=GEMINI_TIMEOUT)
response = api_response; self.current_api_key_index = current_key_index; break
except google_exceptions.GoogleAPIError as e:
msg = str(e)
if "quota" in msg.lower() or "exceeded" in msg.lower():
if max_retries == 1: error_to_report = e; break
logger.warning(f"Ключ №{current_key_index + 1} исчерпал квоту.")
if i == max_retries - 1: error_to_report = RuntimeError("Все ключи исчерпали квоту."); continue
else: error_to_report = e; break
except Exception as e: error_to_report = e; break
if error_to_report: raise error_to_report
if response is None: raise RuntimeError("Не удалось получить ответ.")
try:
if response.prompt_feedback.block_reason: result_text = f"🚫 <b>Запрос заблокирован.</b>\nПричина: <code>{response.prompt_feedback.block_reason.name}</code>."
except AttributeError: pass
if not result_text:
try:
result_text = re.sub(r"</?emoji[^>]*>", "", response.text); was_successful = True
except ValueError: result_text = "❗️ Gemini не смог сгенерировать ответ (возможно, только safety ratings)."
if was_successful and self._is_memory_enabled(str(chat_id)):
self._update_history(chat_id, current_turn_parts, result_text, regeneration, msg_obj, gauto=impersonation_mode)
if impersonation_mode: return result_text if was_successful else None
hist_len_pairs=len(self._get_structured_history(chat_id, gauto=False)) // 2; limit=self.config["max_history_length"]; mem_indicator=self.strings["memory_status_unlimited"].format(hist_len_pairs) if limit <= 0 else self.strings["memory_status"].format(hist_len_pairs, limit)
question_html=f"<blockquote>{utils.escape_html(request_text_for_display[:200])}</blockquote>"; response_html=self._markdown_to_html(result_text); formatted_body=self._format_response_with_smart_separation(response_html)
header=f"{mem_indicator}\n\n{self.strings['question_prefix']}\n{question_html}\n\n{self.strings['response_prefix']}\n"; text_to_send=f"{header}{formatted_body}"
buttons=self._get_inline_buttons(chat_id, base_message_id) if self.config["interactive_buttons"] else None
hist_len_pairs = len(self._get_structured_history(chat_id, gauto=False)) // 2
limit = self.config["max_history_length"]
mem_indicator = self.strings["memory_status_unlimited"].format(hist_len_pairs) if limit <= 0 else self.strings["memory_status"].format(hist_len_pairs, limit)
question_html = f"<blockquote>{utils.escape_html(request_text_for_display[:200])}</blockquote>"
response_html = self._markdown_to_html(result_text)
formatted_body = self._format_response_with_smart_separation(response_html)
header = f"{mem_indicator}\n\n{self.strings['question_prefix']}\n{question_html}\n\n{self.strings['response_prefix']}{search_icon}\n"
text_to_send = f"{header}{formatted_body}"
buttons = self._get_inline_buttons(chat_id, base_message_id) if self.config["interactive_buttons"] else None
if len(text_to_send) > 4096:
file_content=(f"Вопрос: {display_prompt}\n\n════════════════════\n\nОтвет Gemini:\n{result_text}")
file=io.BytesIO(file_content.encode("utf-8")); file.name="Gemini_response.txt"
file_content = (f"Вопрос: {display_prompt}\n\n════════════════════\n\nОтвет Gemini:\n{result_text}")
file = io.BytesIO(file_content.encode("utf-8")); file.name = "Gemini_response.txt"
if call:
await call.answer("Ответ слишком длинный, отправляю файлом...", show_alert=False); await self.client.send_file(call.chat_id, file, caption=self.strings["response_too_long"], reply_to=call.message_id); await call.edit(f"{self.strings['response_too_long']}", reply_markup=None)
await call.answer("Ответ длинный, отправляю файлом...", show_alert=False); await self.client.send_file(call.chat_id, file, caption=self.strings["response_too_long"], reply_to=call.message_id); await call.edit(f"{self.strings['response_too_long']}", reply_markup=None)
elif status_msg:
await status_msg.delete(); await self.client.send_file(chat_id, file, caption=self.strings["response_too_long"], reply_to=base_message_id)
else:
if call: await call.edit(text_to_send, reply_markup=buttons)
elif status_msg: await utils.answer(status_msg, text_to_send, reply_markup=buttons)
except Exception as e:
error_text=self._handle_error(e)
if impersonation_mode: logger.error(f"Gauto | Ошибка авто-ответа: {error_text}")
error_text = self._handle_error(e)
if impersonation_mode: logger.error(f"Gauto | Ошибка: {error_text}")
elif call: await call.edit(error_text, reply_markup=None)
elif status_msg: await utils.answer(status_msg, error_text)
return None if impersonation_mode else ""
@@ -491,10 +551,13 @@ class Gemini(loader.Module):
current_key_index = (self.current_api_key_index + i) % max_retries
api_key = self.api_keys[current_key_index]
try:
genai.configure(api_key=api_key)
old_genai.configure(api_key=api_key)
sanitized_model_name = self.config["model_name"].lower().replace(" ", "-")
model = genai.GenerativeModel(sanitized_model_name, safety_settings=self.safety_settings)
api_response = await asyncio.wait_for(model.generate_content_async(full_prompt), timeout=GEMINI_TIMEOUT)
generation_cfg = old_types.GenerationConfig(
temperature=self.config["temperature"]
)
model = old_genai.GenerativeModel(sanitized_model_name, safety_settings=self.safety_settings)
api_response = await asyncio.wait_for(model.generate_content_async(full_prompt, generation_config=generation_cfg), timeout=GEMINI_TIMEOUT)
response = api_response
self.current_api_key_index = current_key_index
break
@@ -526,6 +589,41 @@ class Gemini(loader.Module):
except Exception as e:
await utils.answer(status_msg, self._handle_error(e))
@loader.command()
async def gprompt(self, message: Message):
"""[текст / -c / ответ на файл] — [-c (очистить)] / (ничего. увидеть промпт) Установить системный промпт (инструкцию/system_instruction)."""
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if args == "-c":
self.config["system_instruction"] = ""
return await utils.answer(message, self.strings["gprompt_cleared"])
new_prompt = None
if reply and reply.file:
if reply.file.size > 1024 * 1024:
return await utils.answer(message, self.strings["gprompt_file_too_big"])
try:
file_data = await self.client.download_file(reply.media, bytes)
try:
new_prompt = file_data.decode("utf-8")
except UnicodeDecodeError:
return await utils.answer(message, self.strings["gprompt_not_text"])
except Exception as e:
return await utils.answer(message, self.strings["gprompt_file_error"].format(e))
elif args:
new_prompt = args
if new_prompt is not None:
self.config["system_instruction"] = new_prompt
return await utils.answer(message, self.strings["gprompt_updated"].format(len(new_prompt)))
current_prompt = self.config["system_instruction"]
if not current_prompt:
return await utils.answer(message, self.strings["gprompt_usage"])
if len(current_prompt) > 4000:
file = io.BytesIO(current_prompt.encode("utf-8"))
file.name = "system_instruction.txt"
await utils.answer(message, self.strings["gprompt_current"], file=file)
else:
await utils.answer(message, f"{self.strings['gprompt_current']}\n<code>{utils.escape_html(current_prompt)}</code>")
@loader.command()
async def gauto(self, message: Message):
"""<on/off/[id]> — Вкл/выкл авто-ответ в чате."""
@@ -811,9 +909,9 @@ class Gemini(loader.Module):
status_msg = await utils.answer(message, self.strings["processing"])
try:
api_key = self.api_keys[self.current_api_key_index]
genai.configure(api_key=api_key)
old_genai.configure(api_key=api_key)
models_list = []
for model_obj in genai.list_models():
for model_obj in old_genai.list_models():
model_name = model_obj.name
display_name = model_obj.display_name or "Неизвестно"
methods = ", ".join(model_obj.supported_generation_methods) if model_obj.supported_generation_methods else "Нет"
@@ -875,8 +973,9 @@ class Gemini(loader.Module):
if message.is_private and not self.config["gauto_in_pm"]:
return
is_from_self_user = isinstance(message.from_id, types.PeerUser) and message.from_id.user_id == self.me.id
is_command = message.text and message.text.startswith(self.get_prefix())
if message.out or is_from_self_user or is_command:
if message.out or is_from_self_user:
return
if message.text and message.text.startswith(self.get_prefix()):
return
sender = await message.get_sender()
is_sender_a_bot = isinstance(sender, types.User) and sender.bot
@@ -891,8 +990,20 @@ class Gemini(loader.Module):
return
response_text = await self._send_to_gemini(message=message, parts=parts, impersonation_mode=True)
if response_text and response_text.strip():
await asyncio.sleep(random.uniform(1.0, 2.5))
await message.reply(response_text.strip())
clean_text = response_text.strip()
reaction_delay = random.uniform(2.0, 10.0)
await asyncio.sleep(reaction_delay)
try:
await self.client.send_read_acknowledge(message.chat_id, message=message)
except Exception:
pass
char_count = len(clean_text)
typing_speed = random.uniform(0.1, 0.25)
typing_duration = char_count * typing_speed
typing_duration = max(1.5, min(typing_duration, 25.0))
async with message.client.action(message.chat_id, "typing"):
await asyncio.sleep(typing_duration)
await message.reply(clean_text)
def _load_history_from_db(self, db_key: str) -> dict:
raw_conversations=self.db.get(self.strings["name"], db_key, {})

View File

@@ -1,4 +1,4 @@
__version__ = (2, 0, 0)
__version__ = (2, 0, 6)
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
@@ -7,24 +7,25 @@ __version__ = (2, 0, 0)
#H:Mods Team [💎]
# meta developer: @nullmod
# requires: python-chess
# requires: python-chess gdown
# packurl: https://github.com/ZetGoHack/TestingModules/raw/main/chess.yml
from .. import loader, utils
from ..inline.types import BotInlineCall, InlineCall, InlineMessage
import asyncio
import chess
import chess.engine
import chess.pgn
import copy
import logging
import os
import random as r
import time
from datetime import datetime, timezone
from telethon.tl.types import PeerUser, User, Message
from datetime import datetime, timezone
from typing import TypedDict
from .. import loader, utils
from ..inline.types import BotInlineCall, InlineCall, InlineMessage
class Timer:
def __init__(self, scnds):
@@ -120,19 +121,65 @@ class Game(TypedDict):
state: str
reason: str
add_params: GameParams
bot: chess.engine.SimpleEngine | None
class GameObj(TypedDict):
game_id: str
vs_bot: bool
bot_elo: int
game: Game
sender: Player
opponent: Player
Timer: TimerDict
time: int
host_plays: bool
host_plays: bool # True - white, False - black
style: dict[str, str]
GamesDict = dict[str, GameObj]
logger = logging.getLogger(__name__)
def install_stockfish() -> str | None:
import platform
import gdown
import zipfile
system = platform.system()
if system == "Windows":
url = "https://github.com/official-stockfish/Stockfish/releases/latest/download/stockfish-windows-x86-64.zip"
elif system == "Linux":
if platform.machine().lower() == "aarch64":
url = "https://github.com/official-stockfish/Stockfish/releases/latest/download/stockfish-android-armv8.tar"
else:
url = "https://github.com/official-stockfish/Stockfish/releases/latest/download/stockfish-ubuntu-x86-64.tar"
else:
return None
file_name = url.split("/")[-1]
try:
gdown.download(url, file_name, quiet=True)
if file_name.endswith(".zip"):
with zipfile.ZipFile(file_name, 'r') as file:
file.extractall()
elif file_name.endswith(".tar"):
import tarfile
with tarfile.open(file_name, 'r') as file:
file.extractall() # noqa: S202
os.remove(file_name)
return find_stfsh_exe()
except Exception:
logger.exception("Failed to install Stockfish")
return None
def find_stfsh_exe() -> str | None:
for root, _, files in os.walk("./stockfish"):
for file in files:
if "stockfish" in file.lower():
return os.path.join(root, file)
def check_path(path: str) -> bool:
return os.path.isfile(path)
@loader.tds
class Chess(loader.Module):
"""A reworked version of the Chess module"""
@@ -148,10 +195,16 @@ class Chess(loader.Module):
False,
"Allows you to make moves without turn checks (also, you can play with yourself)",
validator=loader.validators.Boolean(),
)
),
loader.ConfigValue(
"stockfish_path",
None,
"Path to stockfish engine",
),
)
async def client_ready(self):
self._last_backup = 0
self.styles = {
"figures-with-circles": {
"symbol": "[♔⚪] ",
@@ -195,9 +248,6 @@ class Chess(loader.Module):
for col in "hgfedcba"
}
self.games: GamesDict = self.get("games_backup", {})
self.gsettings = {
"style": "figures-with-circles",
}
self.pgn = {
'Event': "Chess Play In Module",
'Site': "https://t.me/nullmod/",
@@ -206,8 +256,10 @@ class Chess(loader.Module):
'White': "{player}",
'Black': "{player}",
}
if (path := find_stfsh_exe()):
self.config["stockfish_path"] = path
async def _check_player(self, call: InlineCall, game_id: str, only_opponent: bool = False, skip_turn_check: bool = False) -> bool:
async def _check_player(self, call: InlineCall | Message | None, game_id: str, only_opponent: bool = False, skip_turn_check: bool = False) -> bool:
if isinstance(call, (BotInlineCall, InlineCall, InlineMessage)):
game = self.games[game_id]
_from_id = call.from_user.id
@@ -215,8 +267,7 @@ class Chess(loader.Module):
if game.get("game", None) and game["game"]["state"] == "the_end":
await call.answer(self.strings["game_ended"], show_alert=True)
return
if _from_id != game["sender"]["id"]:
if _from_id != game["opponent"]["id"]:
if _from_id != game["sender"]["id"] and _from_id != game["opponent"]["id"]:
await call.answer(self.strings["not_available"])
return False
if _from_id == game["sender"]["id"] and only_opponent and not self.config["play_self"]:
@@ -231,69 +282,87 @@ class Chess(loader.Module):
return False
return True
async def get_players(self, message: Message):
sender = {
"id": message.from_id.user_id if isinstance(message.peer_id, PeerUser) else message.sender.id,
"name": (await self.client.get_entity(message.from_id if isinstance(message.peer_id, PeerUser) else message.sender.id)).first_name
}
if message.is_reply:
async def install_stockfish(self, call: InlineCall):
await utils.answer(call, self.strings["installing"])
path = install_stockfish()
if path:
self.config["stockfish_path"] = path
await utils.answer(call, self.strings["stockfish_installed"].format(path=path))
else:
await utils.answer(call, self.strings["stockfish_install_failed"])
async def get_players(self, message: Message | InlineCall, sender: dict = None, sender_only: bool = False, opponent_only: bool = False):
if not sender:
sender = {
"id": message.sender_id,
"name": (await self.client.get_entity(message.sender_id)).first_name
}
if sender_only:
return sender
if isinstance(message, InlineCall):
opp_id = message.from_user.id
opp_name = message.from_user.first_name
elif message.is_reply:
r = await message.get_reply_message()
opponent = r.sender
if not isinstance(opponent, User):
await utils.answer(message, self.strings["not_a_user"])
return (None, None)
return (sender, None)
opp_id = opponent.id
opp_name = opponent.first_name
else:
args = utils.get_args(message)
if len(args)==0:
await utils.answer(message, self.strings["noargs"])
return (None, None)
return (sender, None)
opponent = args[0]
try:
if opponent.isdigit():
opp_id = int(opponent)
opponent = await self.client.get_entity(opp_id)
if not isinstance(opponent, User):
await utils.answer(message, self.strings["not_a_user"])
return (None, None)
return (sender, None)
opp_name = opponent.first_name
else:
opponent = await self.client.get_entity(opponent)
if not isinstance(opponent, User):
await utils.answer(message, self.strings["not_a_user"])
return (None, None)
return (sender, None)
opp_name = opponent.first_name
opp_id = opponent.id
except:
except ValueError:
await utils.answer(message, self.strings["whosthat"])
return (None, None)
return (sender, None)
opponent = {
"id": opp_id,
"name": opp_name
}
if opponent_only:
return opponent
return (sender, opponent)
async def _invite(self, call: InlineCall, game_id: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
await utils.answer(
call,
self.strings["invite"].format(opponent=utils.escape_html(self.games[game_id]["opponent"]["name"])) + self.strings['settings_text'].format(
style=game['style'],
timer=self.strings['available'] if game['Timer']['available'] and not game['Timer']['timer']
else self.strings['timer'].format(game['Timer']['timer'].minutes()) if game['Timer']['timer']
else self.strings['not_available'],
color=self.strings['random'] if game['host_plays'] == 'r'
else self.strings['white'] if game['host_plays'] == True
else self.strings['black']
),
self.strings["invite_bot" if game["vs_bot"] else "invite" if not game.get("alr_accepted", False) else "not_invite"].format(
opponent=utils.escape_html(self.games[game_id]["opponent"]["name"])
) + self._get_settings_text(game_id),
reply_markup = [
[
{
"text": self.strings["yes"],
"text": self.strings["bot_yes" if game["vs_bot"] else "yes"],
"callback": self._init_game,
"args": (game_id,)
},
@@ -318,17 +387,23 @@ class Chess(loader.Module):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
reply_markup = []
if game["vs_bot"]:
reply_markup.append([
{"text": self.strings["bot_elo_btn"], "callback": self._settings, "args": (game_id, "e")}
])
if game["Timer"]["available"]:
reply_markup.append([
{"text": self.strings["time_btn"], "callback": self._settings, "args": (game_id, "t", )}
{"text": self.strings["time_btn"], "callback": self._settings, "args": (game_id, "t",)}
])
reply_markup.extend([
[
{"text": self.strings["color_btn"], "callback": self._settings, "args": (game_id, "c", )}
{"text": self.strings["color_btn"], "callback": self._settings, "args": (game_id, "c",)}
],
[
{"text": self.strings["style_btn"], "callback": self._settings, "args": (game_id, "s", )}
{"text": self.strings["style_btn"], "callback": self._settings, "args": (game_id, "s",)}
],
[
{"text": self.strings['back'], "callback": self._invite, "args": (game_id,)}
@@ -336,64 +411,72 @@ class Chess(loader.Module):
])
await utils.answer(
call,
self.strings['settings_text'].format(
style=game['style'],
timer=self.strings['available'] if game['Timer']['available'] and not game['Timer']['timer']
else self.strings['timer'].format(game['Timer']['timer'].minutes()) if game['Timer']['timer']
else self.strings['not_available'],
color=self.strings['random'] if game['host_plays'] == 'r'
else self.strings['white'] if game['host_plays'] == True
else self.strings['black']
),
self._get_settings_text(game_id),
reply_markup=reply_markup,
disable_security=True
)
async def _settings(self, call: InlineCall, game_id: str, ruleset: str | list):
async def _elo_validator(self, call: InlineCall, data, game_id: str):
reply_markup = {"text": self.strings['back'], "callback": self._settings, "args": (game_id, "e")}
if not str(data).isdigit():
return await utils.answer(call, self.strings["not_int_err"], reply_markup=reply_markup)
if not 1400 <= int(data) <= 3200:
return await utils.answer(call, self.strings["out_of_range_err"], reply_markup=reply_markup)
self.games[game_id]["bot_elo"] = int(data)
self.set("bot_elo", int(data))
await self._settings(call, game_id, "MARKASSUCCESS")
async def _settings(self, call: InlineCall, game_id: str, page: str = "", param: str = "", value = None):
reply_markup = []
text = "🍓"
if isinstance(ruleset, str):
if ruleset == "t":
text = ""
if page:
if page == "t":
text = ""
reply_markup.extend([
[
{"text": self.strings['blitz_text'], "action": "answer", "message": self.strings['blitz_message']}
],
[
{"text": self.strings['timer'].format(3), "callback":self._settings, "args": (game_id, ['Timer', 3])},
{"text": self.strings['timer'].format(5), "callback":self._settings, "args": (game_id, ['Timer', 5])},
{"text": self.strings['timer'].format(3), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 3}},
{"text": self.strings['timer'].format(5), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 5}},
],
[
{"text": self.strings['rapid_text'], "action": "answer", "message": self.strings['rapid_message']}
],
[
{"text": self.strings['timer'].format(10), "callback":self._settings, "args": (game_id, ['Timer', 10])},
{"text": self.strings['timer'].format(15), "callback":self._settings, "args": (game_id, ['Timer', 15])},
{"text": self.strings['timer'].format(30), "callback":self._settings, "args": (game_id, ['Timer', 30])},
{"text": self.strings['timer'].format(60), "callback":self._settings, "args": (game_id, ['Timer', 60])}
{"text": self.strings['timer'].format(10), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 10}},
{"text": self.strings['timer'].format(15), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 15}},
{"text": self.strings['timer'].format(30), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 30}},
{"text": self.strings['timer'].format(60), "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": 60}}
],
[
{"text": self.strings['no_clock_text'], "callback":self._settings, "args": (game_id, ['Timer', True])}
{"text": self.strings['no_clock_text'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'Timer', "value": True}}
]
])
elif ruleset == "c":
elif page == "c":
text = "♟️"
reply_markup.extend([
[
{"text": self.strings['white'], "callback":self._settings, "args": (game_id, ['host_plays', True])},
{"text": self.strings['black'], "callback":self._settings, "args": (game_id, ['host_plays', True] )}
{"text": self.strings['white'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'host_plays', "value": True}},
{"text": self.strings['black'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'host_plays', "value": False}}
],
[
{"text": self.strings['random'], "callback":self._settings, "args": (game_id, ['host_plays', 'r'])}
{"text": self.strings['random'], "callback":self._settings, "args": (game_id,), "kwargs": {"param": 'host_plays', "value": 'r'}}
]
])
elif ruleset == "s":
elif page == "s":
text = "✏️"
reply_markup.extend([
[{"text": st["symbol"] + self.strings[name], "callback":self._settings, "args": (game_id, ["style", name])}]
[{"text": st["symbol"] + self.strings[name], "callback":self._settings, "args": (game_id,), "kwargs": {"param": "style", "value": name}}]
for name, st in self.styles.items()
])
elif page == "e":
text = "🧠"
reply_markup.extend([
[{"text": self.strings["set_btn"], "input": self.strings["bot_elo_btn"], "handler": self._elo_validator, "args": (game_id,)}]
])
reply_markup.append(
[
@@ -404,48 +487,147 @@ class Chess(loader.Module):
await utils.answer(call, text, reply_markup=reply_markup, disable_security=True)
else:
await call.answer("")
if ruleset[0] == "style":
self.set('style', ruleset[1])
if ruleset[0] == "Timer" and isinstance(ruleset[1], int):
self.games[game_id]['Timer']['timer'] = Timer(ruleset[1]*60)
if param == "style":
self.set("style", value)
if param == "Timer" and isinstance(value, int):
self.games[game_id]['Timer']['timer'] = Timer(value*60)
else:
self.games[game_id][ruleset[0]] = ruleset[1]
self.games[game_id][param] = value
await self.settings(call, game_id)
def _get_settings_text(self, game_id: str):
game = self.games[game_id]
timer = game['Timer']
return (
self.strings['settings_text'].format(
style=game['style'],
@loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию")
async def chess(self, message: Message):
"""[reply/username/id] - propose a person to play a game"""
sender, opponent = await self.get_players(message)
if not sender or not opponent: return
if sender['id'] == opponent['id'] and not self.config["play_self"]:
await utils.answer(message, self.strings["playing_with_yourself?"])
return
timer=self.strings['available'] if timer['available'] and not timer['timer']
else self.strings['timer'].format(timer['timer'].minutes()) if timer['timer']
else self.strings['not_available'],
color=self.strings['random'] if game['host_plays'] == 'r'
else self.strings['white'] if game['host_plays']
else self.strings['black']
)
+ ("\n " + self.strings["bot_elo"].format(elo=game["bot_elo"]) if game["vs_bot"] else "")
)
def _get_new_game_id(self):
if self.games:
past_game = next(reversed(self.games.values()))
past_game = next(reversed(self.games.values()))
if not past_game.get("game", None):
self.games.pop(past_game['game_id'], None)
if not self.games:
game_id = str(1)
else:
game_id = str(max(map(int, self.games.keys())) + 1)
self.games[game_id] = GameObj(
game_id = game_id,
sender = sender,
opponent = opponent,
Timer = {"available": True if isinstance(message.peer_id, PeerUser) else False, "timer": None, "timer_loop": False},
time = int(time.time()),
host_plays = "r",
style = self.gsettings['style']
)
return game_id
def _create_game(self, game_id: str, _params: dict = None):
params = {
"game_id": game_id,
"vs_bot": False,
"bot_elo": self.get("bot_elo", 3400),
"sender": None,
"opponent": None,
"Timer": {
"available": False,
"timer": None,
"timer_loop": False
},
"time": int(time.time()),
"host_plays": "r",
"style": self.get("style", "figures-with-circles")
}
if _params:
params.update(_params)
self.games[game_id] = GameObj(**params)
@loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию")
async def chess(self, message: Message | InlineCall, _sender: dict = None):
"""[nothing/reply/username/id] - propose a person to play a game"""
if _sender is None:
_sender = {}
sender, opponent = await self.get_players(message, sender=_sender)
if not opponent:
r_m = {"text": self.strings["i_wanna"], "callback": self.chess, "args": (sender,)}
await utils.answer(
message,
self.strings["is_someone_wanna_play"],
reply_markup=r_m,
disable_security=True,
)
return
if sender['id'] == opponent['id'] and not self.config["play_self"]:
await utils.answer(message, self.strings["playing_with_yourself?"])
return
game_id = self._get_new_game_id()
mod_params = {
"sender": sender,
"opponent": opponent,
"Timer": {
"available": isinstance(message, Message) and isinstance(message.peer_id, PeerUser),
"timer": None,
"timer_loop": False
}
}
self._create_game(game_id, mod_params)
if _sender:
self.games[game_id]["alr_accepted"] = True
await self._invite(message, game_id)
# @loader.command(ru_doc="посмотреть текущее состояние модуля и статистику своих партий")
# async def chesstats(self, message: Message):
# """view the current state of the module and statistics of your games"""
# total_games = len(self.get("games_backup", {}))
# await utils.answer(message, f"♟️ <b>{self.strings['name']}</b> ♟️\n\nTotal games played: <b>{total_games}</b>")
# TODO: добавить кнопки для просмотра состояния каждой партии; считать победы/поражения/ничьи и прочую бесполезную статистику; проверка на наличие исполняемого файла шахматного движка для возможности игры против ИИ; возможность экспорта партии в PGN; возможность продолжить сохранённую партию
@loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию против 🐟 Stockfish")
async def stockfish(self, message: Message):
"""[reply/username/id] - propose a person to play a game against a 🐟 Stockfish"""
if not self.config["stockfish_path"] or not check_path(self.config["stockfish_path"]):
return await utils.answer(
message,
self.strings["stockfish_not_found"],
reply_markup={
"text": self.strings["install_stockfish"],
"callback": self.install_stockfish,
}
)
if message.is_reply:
player = await self.get_players(message, opponent_only=True)
else:
player = await self.get_players(message, sender_only=True)
stockfish = {
"name": "Stockfish",
"id": -42,
}
game_id = self._get_new_game_id()
mod_params = {
"vs_bot": True,
"sender": stockfish,
"opponent": player,
"Timer": {
"available": isinstance(message, Message) and isinstance(message.peer_id, PeerUser),
"timer": None,
"timer_loop": False
}
}
self._create_game(game_id, mod_params)
await self._invite(message, game_id)
############## Preparing all for game start... ##############
@@ -455,26 +637,29 @@ class Chess(loader.Module):
self.games.pop(game_id, None)
await utils.answer(call, self.strings["declined"])
return
await utils.answer(call, "🌒")
game = self.games[game_id]
await utils.answer(call, self.strings["step1"])
await asyncio.sleep(0.8)
await utils.answer(call, self.strings["step2"])
game["style"] = self.styles[game["style"]]
await asyncio.sleep(0.8)
await utils.answer(call, self.strings["step3"])
if (turn := game.pop("host_plays")) == "r":
turn = r.choice([True, False])
game["sender"]["color"] = True if turn else False
game["opponent"]["color"] = False if turn else True
await asyncio.sleep(0.8)
await utils.answer(call, self.strings["step4"])
game["sender"]["color"] = turn
game["opponent"]["color"] = not turn
game["Timer"].pop("available", None)
if game.get("alr_accepted", None):
game.pop("alr_accepted")
await asyncio.sleep(0.8)
if isinstance(self.games[game_id]["Timer"]["timer"], Timer):
await utils.answer(call, self.strings["step4.T"])
await self._set_timer(call, game_id, call._units[call.unit_id]['chat'])
await asyncio.sleep(0.8)
return await utils.answer(call, self.strings["waiting_for_start"])
await self._start_game(call, game_id)
async def _set_timer(self, board_call: InlineCall, game_id: str, chat_id):
@@ -486,7 +671,11 @@ class Chess(loader.Module):
""
),
chat_id,
reply_markup = {"text": self.strings["start_timer"], "callback": self._start_timer, "args": (board_call, game_id,)},
reply_markup = {
"text": self.strings["start_timer"],
"callback": self._start_timer,
"args": (board_call, game_id,)
},
disable_security = True,
)
)
@@ -496,23 +685,26 @@ class Chess(loader.Module):
for game_id in self.games:
if not self.games[game_id].get("backup", False) and self.games[game_id]["Timer"]["timer_loop"] and not self.games[game_id]["Timer"]["timer_is_set"]:
async def timer_loop(game_id):
timer = self.games[game_id]["Timer"]["timer"]
await timer.start()
self.games[game_id]["Timer"]["timer_is_set"] = True
while self.games[game_id]["Timer"]["timer_loop"]:
if not all([await timer.white_time(), await timer.black_time()]):
self.games[game_id]["Timer"]["timer_loop"] = False
game = self.games[game_id]["game"]
timer = self.games[game_id]["Timer"]
timer_c = self.games[game_id]["Timer"]["timer"]
await timer_c.start()
timer["timer_is_set"] = True
while timer["timer_loop"]:
if not all([await timer_c.white_time(), await timer_c.black_time()]):
timer["timer_loop"] = False
self.the_end(game_id, "time_is_up")
elif self.games[game_id]["game"]["state"] == "the_end":
self.games[game_id]["Timer"]["timer_loop"] = False
elif game["state"] == "the_end":
timer["timer_loop"] = False
loser, winner = self._get_loser_and_winner(game_id)
await self.games[game_id]["Timer"]["message"].edit(self.strings["timer_text"].format(
int(await timer.white_time()),
int(await timer.black_time()),
"" if self.games[game_id]["game"]["state"] != "the_end"
else "⏹️ " + self.strings[self.games[game_id]["game"]["add_params"]["reason_of_ending"]].format(
await timer["message"].edit(self.strings["timer_text"].format(
int(await timer_c.white_time()),
int(await timer_c.black_time()),
"" if game["state"] != "the_end"
else "⏹️ " + self.strings[game["add_params"]["reason_of_ending"]].format(
loser, winner
)
),
@@ -528,31 +720,34 @@ class Chess(loader.Module):
]["always_allow"] = True # для ругающегося на эту строку гпт - по неизвестно какой причине фреймворк в какое-то время попросту
# забывает про отключение его проверки. мне это нужно, чтобы сам модуль брал на себя ответсвенность
# проверки, кто может управлять доской, а до кого очередь ещё не дошла
games_backup = {}
games = self.games
for game_id, game in games.items():
if game.get("game", None):
game_copy = game
if not game.get("backup", None):
game_copy = {}
game_copy["backup"] = True
# FIXME: оно, похоже, всё ещё забывает про always_allow, патч не помогает... нужно выйти на эту ошибку и посмотреть, прочему права пропадают
game_copy["game"] = {
k: v for k, v in game["game"].items()
if k not in ("message", "root_node", "curr_node", "board")
}
game_copy["game"]["node"] = str(game["game"]["root_node"])
games_backup = {}
if time.time() - self._last_backup >= 10:
for game_id, game in self.games.items():
if game.get("game", None):
game_copy = game
if not game.get("backup", None):
game_copy = {}
game_copy["backup"] = True
if game.get("Timer", None) and game["Timer"].get("timer", None):
game_copy["Timer"] = game["Timer"]["timer"].backup()
game_copy["game"] = {
k: v for k, v in game["game"].items()
if k not in ("message", "root_node", "curr_node", "board", "bot")
}
game_copy["game"]["node"] = str(game["game"]["root_node"])
for key, value in game.items():
if key not in ("game", "Timer"):
game_copy[key] = value
if game.get("Timer", None) and game["Timer"].get("timer", None):
game_copy["Timer"] = game["Timer"]["timer"].backup()
games_backup[game_id] = game_copy
for key, value in game.items():
if key not in ("game", "Timer"):
game_copy[key] = value
self.set("games_backup", games_backup)
games_backup[game_id] = game_copy
self.set("games_backup", games_backup)
self._last_backup = time.time()
############## Starting game... ##############
@@ -562,6 +757,14 @@ class Chess(loader.Module):
timer["timer_loop"] = True
await self._start_game(board_call, game_id)
async def _init_bot(self, game_id: str, params: dict):
if not self.games[game_id]["vs_bot"]: return
engine = chess.engine.SimpleEngine.popen_uci(self.config["stockfish_path"])
engine.configure({"UCI_LimitStrength": True, "UCI_Elo": params["elo"]})
self.games[game_id]["game"]["bot"] = engine
async def _start_game(self, call: InlineCall, game_id: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
@@ -573,21 +776,23 @@ class Chess(loader.Module):
pgn["Black"] = game["opponent"]["name"] if game["sender"]["color"] else game["sender"]["name"]
pgn["Result"] = "*"
node.headers.update(pgn)
game["game"] = {
"board": chess.Board(),
"message": call,
"root_node": node,
"curr_node": node,
"state": "idle",
"add_params": {
"chosen_figure_coord": "",
"reason_of_ending": "",
"promotion_move": "",
"winner_color": None,
"resigner_color": None,
"draw_offerer": None,
}
}
game["game"] = Game(
board = chess.Board(),
message = call,
root_node = node,
curr_node = node,
state = "idle",
add_params = GameParams(
chosen_figure_coord = "",
reason_of_ending = "",
promotion_move = "",
winner_color = None,
resigner_color = None,
draw_offerer = None,
),
bot = None,
)
await self._init_bot(game_id, {"elo": game["bot_elo"]})
await self.update_board(game_id)
def idle(self, game_id: str):
@@ -621,6 +826,8 @@ class Chess(loader.Module):
"0-1" if winner is False else
"1/2-1/2"
)
if self.games[game_id]["vs_bot"]:
game["bot"].quit()
def _get_loser_and_winner(self, game_id: str) -> tuple[str, str]:
game = self.games[game_id]
@@ -673,6 +880,7 @@ class Chess(loader.Module):
def _get_reply_markup(self, game_id: str, promotion: bool = False, resign_confirm: bool = False, draw_confirm: bool = False) -> list[list[dict]]:
game = self.games[game_id]
is_end = game["game"]["state"] == "the_end"
reply_markup = utils.chunks(
[
{
@@ -754,12 +962,16 @@ class Chess(loader.Module):
"callback": self.resign,
"args": (game_id,),
},
{
"text": "🤝",
"callback": self.draw,
"args": (game_id,),
}
]
if not game["vs_bot"]:
resign.append(
{
"text": "🤝",
"callback": self.draw,
"args": (game_id,),
}
)
reply_markup.append(resign)
return reply_markup
@@ -795,6 +1007,33 @@ class Chess(loader.Module):
reply_markup=reply_markup,
)
if game["vs_bot"] and game["game"]["board"].turn == game["sender"]["color"] and game["game"]["state"] == "idle":
await self._bot_process_board(game_id)
async def _bot_process_board(self, game_id: str):
if not (game := self.games[game_id])["vs_bot"]:
return
board = game["game"]["board"]
bot = game["game"]["bot"]
_d = game["bot_elo"] // 100 - 10
depth = r.randint(_d, _d + 15)
result = bot.play(board, limit=chess.engine.Limit(time=0.1, depth=depth))
move = result.move
from_coord = chess.square_name(result.move.from_square)
to_coord = chess.square_name(result.move.to_square)
await asyncio.sleep(r.randint(1, 3))
await self.choose_coord(None, game_id, from_coord)
await asyncio.sleep(0.7)
await self.choose_coord(None, game_id, to_coord)
if move.promotion:
await asyncio.sleep(0.7)
await self.pawn_promotion(None, game_id, chess.piece_symbol(move.promotion))
def make_move(self, game_id: str, move: str):
game = self.games[game_id]["game"]
move = chess.Move.from_uci(move)
@@ -816,27 +1055,35 @@ class Chess(loader.Module):
await self.update_board(game_id)
async def resign(self, call: InlineCall, game_id: str, confirm: bool = False):
if not await self._check_player(call, game_id): return
if not await self._check_player(call, game_id, skip_turn_check=True): return
game = self.games[game_id]
if not confirm:
game["game"]["add_params"]["resign_offerer"] = self._get_color_by_player(
game["game"]["add_params"]["resigner_color"] = self._get_color_by_player(
game_id,
call.from_user.id
)
return await self.update_board(game_id, resign_confirm=True)
self.the_end(game_id, "resign", winner=not game["game"]["board"].turn)
resigner = self._get_player_by_color(
game_id, game["game"]["add_params"]["resigner_color"]
)
if call.from_user.id != resigner["id"]:
return await call.answer(self.strings["resign_not_you"])
self.the_end(game_id, "resign", winner=not resigner["color"])
await self.update_board(game_id)
async def draw(self, call: InlineCall, game_id: str, accept: bool = False):
if not await self._check_player(call, game_id, skip_turn_check=True): return
game = self.games[game_id]
if accept:
offerer_id = self._get_player_by_color(
offerer = self._get_player_by_color(
game_id,
game["game"]["add_params"]["draw_offerer"]
)["id"]
)
if call.from_user.id == offerer_id:
if call.from_user.id == offerer["id"]:
await call.answer(self.strings["draw_not_you"])
return
self.the_end(game_id, "draw")

View File

@@ -1,4 +1,4 @@
__version__ = (1,1,7)
__version__ = (1,1,8)
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
@@ -71,6 +71,12 @@ class HaremManager(loader.Module):
"Интервал между автобонусом",
validator=loader.validators.Float(2.0)
),
loader.ConfigValue(
"skip-requests",
True,
"Пропускать ли ссылки с заявками? *(подачи заявки вам может начать спамить рекламный бот, что не многим может понравиться)",
validator=loader.validators.Boolean()
),
)
async def client_ready(self):
@@ -270,69 +276,75 @@ class HaremManager(loader.Module):
a = r.buttons
for i in a:
for button in i: # каждая кнопка...
if button.url:
alr = False # "уже зашёл"
if "addlist/" in button.url: # добавление папок
slug = button.url.split("addlist/")[-1]
peers = await self.client(CheckChatlistInviteRequest(slug=slug))
if peers:
peers = peers.peers
try:
if button.url:
alr = False # "уже зашёл"
if "addlist/" in button.url: # добавление папок
slug = button.url.split("addlist/")[-1]
peers = await self.client(CheckChatlistInviteRequest(slug=slug))
if peers:
peers = peers.peers
try:
a = await self.client(JoinChatlistInviteRequest(slug=slug, peers=peers))
chats_in_folders.append(peers) # для выхода
for update in a.updates:
if isinstance(update, UpdateDialogFilter):
folders.append(InputChatlistDialogFilter(filter_id=update.id)) # для удаления папки
except: pass
continue
if "t.me/boost" in button.url: # бустить не обязательно
wait_boost = True
continue
if not bool(re.match(r"^https?:\/\/t\.me\/[^\/]+\/?$", button.url)): # дополнительные вложения отметаем
continue
if "t.me/+" in button.url: # приватные чаты
try:
a = await self.client(JoinChatlistInviteRequest(slug=slug, peers=peers))
chats_in_folders.append(peers) # для выхода
for update in a.updates:
if isinstance(update, UpdateDialogFilter):
folders.append(InputChatlistDialogFilter(filter_id=update.id)) # для удаления папки
except: pass
continue
if "t.me/boost" in button.url: # бустить не обязательно
wait_boost = True
continue
if not bool(re.match(r"^https?:\/\/t\.me\/[^\/]+\/?$", button.url)): # дополнительные вложения отметаем
continue
if "t.me/+" in button.url: # приватные чаты
try:
a = await self.client(CheckChatInviteRequest(button.url.split("+")[-1]))
if not hasattr(a, "request_needed") or not a.request_needed: # получить айди приватного чата/канала с приглашениями без входа невозможно
pass
else:
url = button.url.split("?")[0] if "?" in button.url else button.url
try:
await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))
except InviteRequestSentError: pass
await asyncio.sleep(3)
try:
entity = await self.client.get_entity(url)
except ValueError:
try:
await asyncio.sleep(15)
entity = await self.client.get_entity(url)
except:
continue
except:
a = await self.client(CheckChatInviteRequest(button.url.split("+")[-1]))
if not hasattr(a, "request_needed") or not a.request_needed: # получить айди приватного чата/канала с приглашениями без входа невозможно
pass
alr = True
except: continue
url = button.url.split("?")[0] if "?" in button.url else button.url
if not alr:
try:
entity = await self.client.get_entity(url)
except:
entity = (await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))).chats[0] #gotten class Updates
alr = True
if hasattr(entity, "broadcast"):
else:
if self.config["skip-requests"]:
continue
url = button.url.split("?")[0] if "?" in button.url else button.url
try:
await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))
except InviteRequestSentError: pass
await asyncio.sleep(3)
try:
entity = await self.client.get_entity(url)
except ValueError:
try:
await asyncio.sleep(15)
entity = await self.client.get_entity(url)
except:
continue
except:
pass
alr = True
except: continue
url = button.url.split("?")[0] if "?" in button.url else button.url
if not alr:
await self.client(JoinChannelRequest(button.url))
to_leave.append(entity.id)
else:
to_leave.append(entity.chat.id) if hasattr(entity,"chat") else to_leave.append(entity.id) if hasattr(entity,"id") else None
elif hasattr(entity, "bot"):
username = entity.username if entity.username is not None else entity.usernames[0].username
try:
await self.client(UnblockRequest(username))
except: print("блин")
await self.client.send_message(entity, "/start")
to_block.append(username)
try:
entity = await self.client.get_entity(url)
except:
entity = (await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))).chats[0] #gotten class Updates
alr = True
if hasattr(entity, "broadcast"):
if not alr:
await self.client(JoinChannelRequest(button.url))
to_leave.append(entity.id)
else:
to_leave.append(entity.chat.id) if hasattr(entity,"chat") else to_leave.append(entity.id) if hasattr(entity,"id") else None
elif hasattr(entity, "bot"):
username = entity.username if entity.username is not None else entity.usernames[0].username
try:
await self.client(UnblockRequest(username))
except: print("блин")
await self.client.send_message(entity, "/start")
to_block.append(username)
except:
continue
flyer_messages = await self.client.get_messages(id, limit=1)
if wait_boost:
await asyncio.sleep(150)
@@ -348,7 +360,7 @@ class HaremManager(loader.Module):
for channel in to_leave:
try:
await self.client(LeaveChannelRequest(channel))
except Exception as e:
except Exception:
pass
count = 0
if not self.get(f"last_lout-{bot}") or int(time.time()) - self.get(f"last_lout-{bot}") > 43200:
@@ -424,7 +436,7 @@ class HaremManager(loader.Module):
await utils.answer(message, "<emoji document_id=5472146462362048818>💡</emoji>")
clicks = self._solution(pattern)
if not clicks:
await utils.answer(message, "Иди код трейси гений.")
await utils.answer(message, "⚠️ Если вы получили такое сообщение, обратитесь в чат @AnyModule. такое поведение некорректно для команды")
return #*смачный пинок кодеру под зад.*
for i in range(len(clicks)):
if clicks[i] == 1: