mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Added and updated repositories 2025-11-30 01:13:31
This commit is contained in:
@@ -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: .
|
||||
4
C0dwiz/H.Modules/.gitignore
vendored
4
C0dwiz/H.Modules/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
full.py
|
||||
|
||||
# Ruff Format
|
||||
.ruff_cache/
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -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 <3</b>:", # < == <
|
||||
"season": "<b>Season</b>:",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"announce": "<b>Анонс</b>:",
|
||||
"status": "<b>Статус</b>:",
|
||||
"type": "<b>Тип</b>:",
|
||||
"genres": "<b>Жанры</b>:",
|
||||
"favorite": "<b>Избранное <3</b>:", # < == <
|
||||
"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,
|
||||
)
|
||||
@@ -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"])
|
||||
@@ -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 {}
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -1 +0,0 @@
|
||||
mods.codwiz.life
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -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)))
|
||||
@@ -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"))
|
||||
@@ -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}")
|
||||
@@ -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}
|
||||
@@ -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"),
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"))
|
||||
@@ -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
|
||||
@@ -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
@@ -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))
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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"))
|
||||
@@ -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()]
|
||||
),
|
||||
)
|
||||
@@ -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.")
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)}"
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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))
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
@@ -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"))
|
||||
@@ -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
|
||||
@@ -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
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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>",
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
)
|
||||
@@ -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, {})
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user