mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-17 14:54:18 +02:00
Merge pull request #149 from MuRuLOSE:update-submodules_1c99e02dd0d6aea77015f2b572b71ea751c066c0
Update of repositories 2026-01-10 01:11:19
This commit is contained in:
BIN
archquise/H.Modules/.DS_Store
vendored
Normal file
BIN
archquise/H.Modules/.DS_Store
vendored
Normal file
Binary file not shown.
3
archquise/H.Modules/.gitignore
vendored
3
archquise/H.Modules/.gitignore
vendored
@@ -3,4 +3,5 @@ autocleaner.py
|
|||||||
silent.py
|
silent.py
|
||||||
|
|
||||||
# Ruff Format
|
# Ruff Format
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
.idea
|
||||||
|
|||||||
@@ -27,12 +27,15 @@
|
|||||||
# requires: pillow
|
# requires: pillow
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class ASCIIArtMod(loader.Module):
|
class ASCIIArtMod(loader.Module):
|
||||||
@@ -99,7 +102,7 @@ class ASCIIArtMod(loader.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating ASCII art: {e}")
|
logger.error(f"Error generating ASCII art: {e}")
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
if image_path and os.path.exists(image_path):
|
if image_path and os.path.exists(image_path):
|
||||||
|
|||||||
@@ -26,15 +26,29 @@
|
|||||||
# scope: Api AccountData 0.0.1
|
# scope: Api AccountData 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class AccountData(loader.Module):
|
class AccountData(loader.Module):
|
||||||
"""Find out the approximate date of registration of the telegram account"""
|
"""Find out the approximate date of registration of the telegram account"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = loader.ModuleConfig(
|
||||||
|
loader.ConfigValue(
|
||||||
|
"api_token",
|
||||||
|
"7518491974:1ea2284eec9dc40a9838cfbcb48a2b36",
|
||||||
|
"API token for datereg.pro",
|
||||||
|
validator=loader.validators.String(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "AccountData",
|
"name": "AccountData",
|
||||||
"_cls_doc": "Find out the approximate date of registration of the telegram account",
|
"_cls_doc": "Find out the approximate date of registration of the telegram account",
|
||||||
@@ -51,7 +65,10 @@ class AccountData(loader.Module):
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def get_creation_date(self, user_id: int) -> str:
|
async def get_creation_date(self, user_id: int) -> str:
|
||||||
api_token = "7518491974:1ea2284eec9dc40a9838cfbcb48a2b36"
|
api_token = self.config.get("api_token", "")
|
||||||
|
if not api_token:
|
||||||
|
return {"error": "API token not configured"}
|
||||||
|
|
||||||
url = "https://api.datereg.pro/api/v1/users/getCreationDateFast"
|
url = "https://api.datereg.pro/api/v1/users/getCreationDateFast"
|
||||||
params = {"token": api_token, "user_id": user_id}
|
params = {"token": api_token, "user_id": user_id}
|
||||||
|
|
||||||
@@ -76,16 +93,22 @@ class AccountData(loader.Module):
|
|||||||
async def accdata(self, message):
|
async def accdata(self, message):
|
||||||
if reply := await message.get_reply_message():
|
if reply := await message.get_reply_message():
|
||||||
result = await self.get_creation_date(user_id=reply.sender.id)
|
result = await self.get_creation_date(user_id=reply.sender.id)
|
||||||
month, year = map(int, result['creation_date'].split('.'))
|
|
||||||
date_object = datetime(year, month, 1)
|
if "error" in result or not result.get("creation_date"):
|
||||||
formatted = date_object.strftime('%B %Y')
|
error_msg = result.get("error", "Unknown error occurred")
|
||||||
|
await utils.answer(message, f"Ошибка: {error_msg}")
|
||||||
if "error" in result:
|
return
|
||||||
await utils.answer(message, f"Ошибка: {result['error']}")
|
|
||||||
else:
|
try:
|
||||||
|
month, year = map(int, result['creation_date'].split('.'))
|
||||||
|
date_object = datetime(year, month, 1)
|
||||||
|
formatted = date_object.strftime('%B %Y')
|
||||||
|
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
message,
|
message,
|
||||||
f"{self.strings('date_text').format(data=formatted, accuracy=result['accuracy_percent'])}\n\n{self.strings('date_text_ps')}",
|
f"{self.strings('date_text').format(data=formatted, accuracy=result['accuracy_percent'])}\n\n{self.strings('date_text_ps')}",
|
||||||
)
|
)
|
||||||
|
except (ValueError, KeyError) as e:
|
||||||
|
await utils.answer(message, f"Ошибка обработки данных: {str(e)}")
|
||||||
else:
|
else:
|
||||||
await utils.answer(message, self.strings("no_reply"))
|
await utils.answer(message, self.strings("no_reply"))
|
||||||
|
|||||||
243
archquise/H.Modules/AniLiberty.py
Normal file
243
archquise/H.Modules/AniLiberty.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Proprietary License Agreement
|
||||||
|
|
||||||
|
# Copyright (c) 2024-29 Archquise
|
||||||
|
|
||||||
|
# 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 archquise@gmail.com
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Name: Aniliberty
|
||||||
|
# Description: Searches and gives random anime on the Aniliberty database.
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# requires: dacite
|
||||||
|
# scope: AniLiberty
|
||||||
|
# scope: AniLiberty 0.0.1
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aiogram.types import CallbackQuery, InlineQueryResultPhoto
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from json import JSONDecodeError
|
||||||
|
from dacite import from_dict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from .. import loader
|
||||||
|
from ..inline.types import InlineQuery
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BASE_API_URL = "https://aniliberty.top/api/v1"
|
||||||
|
|
||||||
|
# Датаклассы для парсинга и хранения json
|
||||||
|
@dataclass
|
||||||
|
class Genre:
|
||||||
|
name: str
|
||||||
|
@dataclass
|
||||||
|
class Name:
|
||||||
|
main: str
|
||||||
|
@dataclass
|
||||||
|
class Type:
|
||||||
|
description: str
|
||||||
|
@dataclass
|
||||||
|
class Poster:
|
||||||
|
preview: str
|
||||||
|
thumbnail: str
|
||||||
|
@dataclass
|
||||||
|
class ReleaseInfo:
|
||||||
|
id: int
|
||||||
|
genres: Optional[list[Genre]]
|
||||||
|
name: Name
|
||||||
|
is_ongoing: bool
|
||||||
|
type: Type
|
||||||
|
description: str
|
||||||
|
added_in_users_favorites: int
|
||||||
|
alias: str
|
||||||
|
poster: Poster
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class AniLibertyMod(loader.Module):
|
||||||
|
"""Ищет и возвращает случайное аниме из базы Aniliberty"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "AniLiberty",
|
||||||
|
"announce": "<b>The announcement</b>:",
|
||||||
|
"ongoing": "<b>Ongoing</b>:",
|
||||||
|
"type": "<b>Type</b>:",
|
||||||
|
"genres": "<b>Genres</b>:",
|
||||||
|
"favorite": "<b>Favourites <3</b>:", # < == <
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"announce": "<b>Анонс</b>:",
|
||||||
|
"ongoing": "<b>Онгоинг</b>:",
|
||||||
|
"type": "<b>Тип</b>:",
|
||||||
|
"genres": "<b>Жанры</b>:",
|
||||||
|
"favorite": "<b>Избранное <3</b>:", # < == <
|
||||||
|
}
|
||||||
|
|
||||||
|
async def search_title(self, query):
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(f'{BASE_API_URL}/app/search/releases?query={query}&include=id%2Cname.main%2Cis_ongoing%2Ctype.description%2Cdescription%2Cadded_in_users_favorites%2Calias%2Cposter.preview%2Cposter.thumbnail') as resp:
|
||||||
|
json_answer = await resp.json()
|
||||||
|
results = []
|
||||||
|
for i in json_answer:
|
||||||
|
obj = from_dict(data_class=ReleaseInfo, data=i)
|
||||||
|
results.append(obj)
|
||||||
|
return results
|
||||||
|
|
||||||
|
async def get_title(self, release_id):
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(f'{BASE_API_URL}/anime/releases/{release_id}?include=id%2Cgenres.name%2Cname.main%2Cis_ongoing%2Ctype.description%2Cdescription%2Cadded_in_users_favorites%2Calias%2Cposter.preview%2Cposter.thumbnail') as resp:
|
||||||
|
try:
|
||||||
|
json_answer = await resp.json()
|
||||||
|
data = from_dict(data_class=ReleaseInfo, data=json_answer)
|
||||||
|
return data
|
||||||
|
except JSONDecodeError:
|
||||||
|
logger.error("Ошибка парсинга JSON!")
|
||||||
|
|
||||||
|
async def get_random_title(self):
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(f'{BASE_API_URL}/anime/releases/random?limit=1&include=id') as resp:
|
||||||
|
randid = await resp.json()
|
||||||
|
"""
|
||||||
|
Приходится запрашивать по второму кругу, т.к. API в рандомных релизах не отдает жанры, даже если попросить через include
|
||||||
|
"""
|
||||||
|
data = await self.get_title(randid[0]['id'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Возвращает случайный релиз из базы",
|
||||||
|
en_doc="Returns a random release from the database",
|
||||||
|
)
|
||||||
|
async def arandom(self, message) -> None:
|
||||||
|
anime_release = await self.get_random_title()
|
||||||
|
genres_str = ""
|
||||||
|
for genre in anime_release.genres[:-1]:
|
||||||
|
genres_str += f'{genre.name}, '
|
||||||
|
genres_str += anime_release.genres[-1].name
|
||||||
|
|
||||||
|
|
||||||
|
text = f"{anime_release.name.main} \n"
|
||||||
|
text += f"{self.strings['ongoing']} {'Да' if anime_release.is_ongoing else 'Нет'}\n\n"
|
||||||
|
text += f"{self.strings['type']} {anime_release.type.description}\n"
|
||||||
|
text += f"{self.strings['genres']} {genres_str}\n\n"
|
||||||
|
|
||||||
|
text += f"<code>{anime_release.description}</code>\n\n"
|
||||||
|
text += f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}"
|
||||||
|
|
||||||
|
kb = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "Ссылка",
|
||||||
|
"url": f"https://aniliberty.top/anime/releases/release/{anime_release.alias}/episodes",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}])
|
||||||
|
kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}])
|
||||||
|
|
||||||
|
await self.inline.form(
|
||||||
|
text=text,
|
||||||
|
photo=f"https://aniliberty.top{anime_release.poster.preview}",
|
||||||
|
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_releases = await self.search_title(text)
|
||||||
|
|
||||||
|
inline_query = []
|
||||||
|
for anime_release in anime_releases:
|
||||||
|
"""
|
||||||
|
Приходится запрашивать по второму кругу, т.к. API в поиске не отдает жанры, даже если попросить через include
|
||||||
|
"""
|
||||||
|
release_genres = await self.get_title(anime_release.id)
|
||||||
|
genres_str = ""
|
||||||
|
for genre in release_genres.genres[:-1]:
|
||||||
|
genres_str += f'{genre.name}, '
|
||||||
|
genres_str += release_genres.genres[-1].name
|
||||||
|
release_text = (
|
||||||
|
f"{anime_release.name.main}\n"
|
||||||
|
f"{self.strings['ongoing']} {"Да" if anime_release.is_ongoing else "Нет"}\n\n"
|
||||||
|
f"{self.strings['type']} {anime_release.type.description}\n"
|
||||||
|
f"{self.strings['genres']} {genres_str}\n\n"
|
||||||
|
f"<code>{anime_release.description}</code>\n\n"
|
||||||
|
f"{self.strings['favorite']} {anime_release.added_in_users_favorites}"
|
||||||
|
)
|
||||||
|
|
||||||
|
inline_query.append(
|
||||||
|
InlineQueryResultPhoto(
|
||||||
|
id=str(anime_release.id),
|
||||||
|
title=anime_release.name.main,
|
||||||
|
description=anime_release.type.description,
|
||||||
|
caption=release_text,
|
||||||
|
thumbnail_url=f"https://aniliberty.top{anime_release.poster.thumbnail}",
|
||||||
|
photo_url=f"https://aniliberty.top{anime_release.poster.preview}",
|
||||||
|
parse_mode="html",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
method = query.answer(inline_query, cache_time=0)
|
||||||
|
await method.as_(self.inline.bot)
|
||||||
|
|
||||||
|
async def inline__close(self, call: CallbackQuery) -> None:
|
||||||
|
await call.delete()
|
||||||
|
|
||||||
|
async def inline__update(self, call: CallbackQuery) -> None:
|
||||||
|
anime_release = await self.get_random_title()
|
||||||
|
genres_str = ""
|
||||||
|
for genre in anime_release.genres[:-1]:
|
||||||
|
genres_str += f'{genre.name}, '
|
||||||
|
genres_str += anime_release.genres[-1].name
|
||||||
|
|
||||||
|
text = f"{anime_release.name.main} \n"
|
||||||
|
text += f"{self.strings['ongoing']} {"Да" if anime_release.is_ongoing else "Нет"}\n\n"
|
||||||
|
text += f"{self.strings['type']} {anime_release.type.description}\n"
|
||||||
|
text += f"{self.strings['genres']} {genres_str}\n\n"
|
||||||
|
|
||||||
|
text += f"<code>{anime_release.description}</code>\n\n"
|
||||||
|
text += f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}"
|
||||||
|
|
||||||
|
kb = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "Ссылка",
|
||||||
|
"url": f"https://aniliberty.top/anime/releases/release/{anime_release.alias}/episodes",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}])
|
||||||
|
kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}])
|
||||||
|
|
||||||
|
await call.edit(
|
||||||
|
text=text,
|
||||||
|
photo=f"https://aniliberty.top{anime_release.poster.preview}",
|
||||||
|
reply_markup=kb,
|
||||||
|
)
|
||||||
@@ -1,187 +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,
|
|
||||||
)
|
|
||||||
@@ -27,10 +27,13 @@
|
|||||||
# requires: requests
|
# requires: requests
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class AnimeQuotesMod(loader.Module):
|
class AnimeQuotesMod(loader.Module):
|
||||||
|
|||||||
@@ -27,13 +27,16 @@
|
|||||||
# requires: requests
|
# requires: requests
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import requests
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class ArticleMod(loader.Module):
|
class ArticleMod(loader.Module):
|
||||||
|
|||||||
@@ -26,15 +26,17 @@
|
|||||||
# scope: AutofarmCookies 0.0.1
|
# scope: AutofarmCookies 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from telethon import functions
|
from telethon import functions
|
||||||
|
from telethon.tl.custom import Message
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
__version__ = (1, 0, 0)
|
__version__ = (1, 0, 0)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class AutofarmCookiesMod(loader.Module):
|
class AutofarmCookiesMod(loader.Module):
|
||||||
@@ -197,8 +199,7 @@ class AutofarmCookiesMod(loader.Module):
|
|||||||
async def ckies(self, message):
|
async def ckies(self, message):
|
||||||
chelp = """
|
chelp = """
|
||||||
🍀| <b>Помощь по командам:</b>
|
🍀| <b>Помощь по командам:</b>
|
||||||
.cookon - Включает авто фарм.
|
.cookon - Включает авто-фарм.
|
||||||
.cookoff - Выключает авто фарм.
|
.cookoff - Выключает авто-фарм.
|
||||||
.farm - Показывает сколько вы нафармили.
|
.me - Показывает ваш мешок"""
|
||||||
.me - Показывает ваш ммешок"""
|
|
||||||
await utils.answer(message, chelp)
|
await utils.answer(message, chelp)
|
||||||
|
|||||||
@@ -26,17 +26,20 @@
|
|||||||
# scope: Api BirthdayTime 0.0.1
|
# scope: Api BirthdayTime 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import random
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import calendar
|
import calendar
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
from datetime import datetime
|
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 telethon.errors.rpcerrorlist import UserPrivacyRestrictedError
|
||||||
|
from telethon.tl.functions.account import UpdateProfileRequest
|
||||||
|
from telethon.tl.functions.users import GetFullUserRequest
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
D_MSG = [
|
D_MSG = [
|
||||||
"Ждешь его?",
|
"Ждешь его?",
|
||||||
"Осталось немного)",
|
"Осталось немного)",
|
||||||
@@ -158,9 +161,9 @@ class DaysToMyBirthday(loader.Module):
|
|||||||
self.db.set(__name__, "last_name", name)
|
self.db.set(__name__, "last_name", name)
|
||||||
except UserPrivacyRestrictedError:
|
except UserPrivacyRestrictedError:
|
||||||
self.db.set(__name__, "change_name", False)
|
self.db.set(__name__, "change_name", False)
|
||||||
print("Error: Can't change name due to privacy settings.")
|
logger.error("Error: Can't change name due to privacy settings.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in checker: {e}")
|
logger.error(f"Error in checker: {e}")
|
||||||
finally:
|
finally:
|
||||||
await asyncio.sleep(60)
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
@@ -173,7 +176,7 @@ class DaysToMyBirthday(loader.Module):
|
|||||||
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
|
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
|
||||||
name = user.users[0].last_name or ""
|
name = user.users[0].last_name or ""
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting user info: {e}")
|
logger.error(f"Error getting user info: {e}")
|
||||||
await utils.answer(message, self.strings("error"))
|
await utils.answer(message, self.strings("error"))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -191,7 +194,7 @@ class DaysToMyBirthday(loader.Module):
|
|||||||
except UserPrivacyRestrictedError:
|
except UserPrivacyRestrictedError:
|
||||||
await utils.answer(message, self.strings("name_privacy_error"))
|
await utils.answer(message, self.strings("name_privacy_error"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error removing name: {e}")
|
logger.error(f"Error removing name: {e}")
|
||||||
await utils.answer(message, self.strings("error"))
|
await utils.answer(message, self.strings("error"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -238,5 +241,5 @@ class DaysToMyBirthday(loader.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in bt command: {e}")
|
logger.error(f"Error in bt command: {e}")
|
||||||
await utils.answer(message, self.strings("error"))
|
await utils.answer(message, self.strings("error"))
|
||||||
|
|||||||
94
archquise/H.Modules/CodeShare.py
Normal file
94
archquise/H.Modules/CodeShare.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Proprietary License Agreement
|
||||||
|
|
||||||
|
# Copyright (c) 2026-2029 Archquise
|
||||||
|
|
||||||
|
# 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 archquise@gmail.com
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Name: CodeShare
|
||||||
|
# Description: Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# requires: aiofiles
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import aiofiles
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
from telethon.types import MessageMediaDocument
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class CodeShareMod(loader.Module):
|
||||||
|
"""Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "CodeShare",
|
||||||
|
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> There is no arguments or reply with a file, or they are invalid",
|
||||||
|
"_cls_doc": "Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)",
|
||||||
|
"link_ready": "<emoji document_id=5854762571659218443>✅</emoji> <b>Code uploaded! Link:</b> <code>{}</code>",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"_cls_doc": "Загружает ваш код на kmi.aeza.net (альтернатива Pastebin и GitHub Gist)",
|
||||||
|
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> Нет аргументов или реплая с файлом, или они неверны",
|
||||||
|
"link_ready": "<emoji document_id=5854762571659218443>✅</emoji> <b>Код загружен! Ссылка:</b> <code>{}</code>",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def upload_to_kmi(self, content: str) -> str:
|
||||||
|
url = "https://kmi.aeza.net"
|
||||||
|
data = aiohttp.FormData()
|
||||||
|
data.add_field('kmi', content)
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(url, data=data) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
link = await response.text()
|
||||||
|
return link
|
||||||
|
else:
|
||||||
|
logger.error(f"Error occurred! Status code: {response.status}")
|
||||||
|
return
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Загрузка кода на сайт",
|
||||||
|
en_doc="Upload code to the site",
|
||||||
|
)
|
||||||
|
async def codesharecmd(self, message):
|
||||||
|
args = utils.get_args(message)
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if args:
|
||||||
|
link = await self.upload_to_kmi(args)
|
||||||
|
await utils.answer(message, self.strings['link_ready'].format(link))
|
||||||
|
return
|
||||||
|
if reply and isinstance(reply.media, MessageMediaDocument):
|
||||||
|
file_name = await reply.download_media()
|
||||||
|
async with aiofiles.open(file_name, mode='r') as f:
|
||||||
|
content = await f.read()
|
||||||
|
link = await self.upload_to_kmi(content)
|
||||||
|
os.remove(file_name)
|
||||||
|
await utils.answer(message, self.strings['link_ready'].format(link))
|
||||||
|
return
|
||||||
|
await utils.answer(message, self.strings['invalid_args'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -26,10 +26,13 @@
|
|||||||
# scope: Api CryptoCurrency 0.0.1
|
# scope: Api CryptoCurrency 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class CryptoCurrencyMod(loader.Module):
|
class CryptoCurrencyMod(loader.Module):
|
||||||
|
|||||||
337
archquise/H.Modules/EmojiStickerBlocker.py
Normal file
337
archquise/H.Modules/EmojiStickerBlocker.py
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# 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: EmojiStickerBlocker
|
||||||
|
# Description: Block emojis, stickers and sticker packs
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# scope: EmojiStickerBlocker
|
||||||
|
# scope: EmojiStickerBlocker0.0.1
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from typing import Optional, Set
|
||||||
|
|
||||||
|
from telethon.errors import FloodWaitError, MessageDeleteForbiddenError
|
||||||
|
from telethon.tl.functions.messages import DeleteMessagesRequest
|
||||||
|
from telethon.tl.types import Message, MessageMediaDocument
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class EmojiStickerBlocker(loader.Module):
|
||||||
|
"""Block emojis, stickers and sticker packs with enhanced functionality"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "EmojiStickerBlocker",
|
||||||
|
"no_permission": "<emoji document_id=5854929766146118183>❌</emoji> Need delete messages permission",
|
||||||
|
"pack_blocked": "<emoji document_id=5854762571659218443>✅</emoji> Pack blocked",
|
||||||
|
"pack_not_found": "<emoji document_id=5854929766146118183>❌</emoji> Pack not found",
|
||||||
|
"sticker_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Sticker blocked",
|
||||||
|
"emoji_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Emoji blocked",
|
||||||
|
"pack_unblocked": "<emoji document_id=5854762571659218443>✅</emoji> Pack unblocked",
|
||||||
|
"item_unblocked": "<emoji document_id=5854929766146118183>❌</emoji> Item unblocked",
|
||||||
|
"not_found": "<emoji document_id=5854929766146118183>❌</emoji> Not in blocklist",
|
||||||
|
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Reply to a sticker or emoji",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Specify pack link or name",
|
||||||
|
"list_packs": "📦 Blocked packs: {}",
|
||||||
|
"list_stickers": "🖼 Blocked stickers: {}",
|
||||||
|
"list_emojis": "😀 Blocked emojis: {}",
|
||||||
|
"all_cleared": "✅ All blocks cleared",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"no_permission": "<emoji document_id=5854929766146118183>❌</emoji> Нужны права на удаление сообщений",
|
||||||
|
"pack_blocked": "<emoji document_id=5188311512791393083>✅</emoji> Пак заблокирован",
|
||||||
|
"pack_not_found": "<emoji document_id=5854929766146118183>❌</emoji> Пак не найден",
|
||||||
|
"sticker_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Стикер заблокирован",
|
||||||
|
"emoji_blocked": "<emoji document_id=5854929766146118183>❌</emoji> Эмодзи заблокирован",
|
||||||
|
"pack_unblocked": "<emoji document_id=5854762571659218443>✅</emoji> Пак разблокирован",
|
||||||
|
"item_unblocked": "<emoji document_id=5854929766146118183>❌</emoji> Элемент разблокирован",
|
||||||
|
"not_found": "<emoji document_id=5854929766146118183>❌</emoji> Не найден в блоклисте",
|
||||||
|
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Ответьте на стикер или эмодзи",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите ссылку или название пака",
|
||||||
|
"list_packs": "📦 Заблокированные паки: {}",
|
||||||
|
"list_stickers": "🖼 Заблокированные стикеры: {}",
|
||||||
|
"list_emojis": "😀 Заблокированные эмодзи: {}",
|
||||||
|
"all_cleared": "✅ Все блоки очищены",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.blocked_packs: Set[str] = set()
|
||||||
|
self.blocked_stickers: Set[str] = set()
|
||||||
|
self.blocked_emojis: Set[str] = set()
|
||||||
|
|
||||||
|
self._load_blocklists()
|
||||||
|
|
||||||
|
def _load_blocklists(self):
|
||||||
|
"""Load blocklists from database"""
|
||||||
|
self.blocked_packs = set(self._db.get(__name__, "blocked_packs", []))
|
||||||
|
self.blocked_stickers = set(self._db.get(__name__, "blocked_stickers", []))
|
||||||
|
self.blocked_emojis = set(self._db.get(__name__, "blocked_emojis", []))
|
||||||
|
|
||||||
|
def _save_blocklists(self):
|
||||||
|
"""Save blocklists to database"""
|
||||||
|
self._db.set(__name__, "blocked_packs", list(self.blocked_packs))
|
||||||
|
self._db.set(__name__, "blocked_stickers", list(self.blocked_stickers))
|
||||||
|
self._db.set(__name__, "blocked_emojis", list(self.blocked_emojis))
|
||||||
|
|
||||||
|
def _extract_pack_name(self, message: Message) -> Optional[str]:
|
||||||
|
"""Extract pack name from sticker or emoji"""
|
||||||
|
if not message.media:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if message.sticker:
|
||||||
|
if hasattr(message.sticker, "set_name") and message.sticker.set_name:
|
||||||
|
return message.sticker.set_name.lower()
|
||||||
|
|
||||||
|
if isinstance(message.media, MessageMediaDocument):
|
||||||
|
if hasattr(message.media.document, "attributes"):
|
||||||
|
for attr in message.media.document.attributes:
|
||||||
|
if hasattr(attr, "stickerset") and attr.stickerset:
|
||||||
|
return attr.stickerset.title.lower()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_emoji_text(self, message: Message) -> Optional[str]:
|
||||||
|
"""Extract emoji text from message"""
|
||||||
|
if not message.message:
|
||||||
|
return None
|
||||||
|
|
||||||
|
emoji_pattern = re.compile(
|
||||||
|
r"[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF\U00002702-\U000027B0\U000024C2-\U0001F251]"
|
||||||
|
)
|
||||||
|
emojis = emoji_pattern.findall(message.message)
|
||||||
|
|
||||||
|
if emojis:
|
||||||
|
return emojis[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _delete_message(self, message: Message) -> bool:
|
||||||
|
"""Delete message with error handling"""
|
||||||
|
try:
|
||||||
|
await self._client(DeleteMessagesRequest([message.id]))
|
||||||
|
return True
|
||||||
|
except MessageDeleteForbiddenError:
|
||||||
|
await utils.answer(message, self.strings["no_permission"])
|
||||||
|
return False
|
||||||
|
except FloodWaitError as e:
|
||||||
|
logger.warning(f"Flood wait when deleting message: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error deleting message: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _should_block_message(self, message: Message) -> tuple[bool, str]:
|
||||||
|
"""Check if message should be blocked and return reason"""
|
||||||
|
pack_name = self._extract_pack_name(message)
|
||||||
|
emoji_text = self._extract_emoji_text(message)
|
||||||
|
|
||||||
|
if pack_name and pack_name in self.blocked_packs:
|
||||||
|
return True, f"pack: {pack_name}"
|
||||||
|
|
||||||
|
if message.sticker:
|
||||||
|
sticker_id = str(message.sticker.id)
|
||||||
|
if sticker_id in self.blocked_stickers:
|
||||||
|
return True, f"sticker: {sticker_id}"
|
||||||
|
|
||||||
|
if emoji_text and emoji_text in self.blocked_emojis:
|
||||||
|
return True, f"emoji: {emoji_text}"
|
||||||
|
|
||||||
|
return False, ""
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[link/название пака] — блокирует эмодзипак/стикерпак в личных сообщениях",
|
||||||
|
en_doc="[link/pack name] — block emoji pack/sticker pack in private messages",
|
||||||
|
)
|
||||||
|
async def packblock(self, message: Message):
|
||||||
|
"""Block emoji pack/sticker pack"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, self.strings["no_args"])
|
||||||
|
|
||||||
|
pack_name = args.lower().strip()
|
||||||
|
|
||||||
|
# Add to blocked packs
|
||||||
|
self.blocked_packs.add(pack_name)
|
||||||
|
self._save_blocklists()
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["pack_blocked"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[reply] — блокирует определенный стикер",
|
||||||
|
en_doc="[reply] — block specific sticker",
|
||||||
|
)
|
||||||
|
async def stickblock(self, message: Message):
|
||||||
|
"""Block sticker from reply"""
|
||||||
|
if not message.is_reply:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
reply_msg = await message.get_reply_message()
|
||||||
|
if not reply_msg or not reply_msg.sticker:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
sticker_id = str(reply_msg.sticker.id)
|
||||||
|
self.blocked_stickers.add(sticker_id)
|
||||||
|
self._save_blocklists()
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["sticker_blocked"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[reply/enter] — блокирует определенное эмодзи",
|
||||||
|
en_doc="[reply/enter] — block specific emoji",
|
||||||
|
)
|
||||||
|
async def emojiblock(self, message: Message):
|
||||||
|
"""Block emoji from reply or input"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
|
||||||
|
if args:
|
||||||
|
emoji_text = args.strip()
|
||||||
|
if not emoji_text:
|
||||||
|
return await utils.answer(message, self.strings["no_args"])
|
||||||
|
else:
|
||||||
|
if not message.is_reply:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
reply_msg = await message.get_reply_message()
|
||||||
|
if not reply_msg:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
emoji_text = self._extract_emoji_text(reply_msg)
|
||||||
|
if not emoji_text:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
self.blocked_emojis.add(emoji_text)
|
||||||
|
self._save_blocklists()
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["emoji_blocked"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="— снимает блокировку с эмодзипака/стикерпака",
|
||||||
|
en_doc="— unblock emoji pack/sticker pack",
|
||||||
|
)
|
||||||
|
async def ublpack(self, message: Message):
|
||||||
|
"""Unblock emoji pack/sticker pack"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, self.strings["no_args"])
|
||||||
|
|
||||||
|
pack_name = args.lower().strip()
|
||||||
|
|
||||||
|
if pack_name in self.blocked_packs:
|
||||||
|
self.blocked_packs.remove(pack_name)
|
||||||
|
self._save_blocklists()
|
||||||
|
await utils.answer(message, self.strings["pack_unblocked"])
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["not_found"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[reply/enter] — снимает блокировку с определенного эмодзи/стикера",
|
||||||
|
en_doc="[reply/enter] — unblock specific emoji/sticker",
|
||||||
|
)
|
||||||
|
async def ublthis(self, message: Message):
|
||||||
|
"""Unblock emoji/sticker from reply or input"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
|
||||||
|
if args:
|
||||||
|
item = args.strip()
|
||||||
|
if not item:
|
||||||
|
return await utils.answer(message, self.strings["no_args"])
|
||||||
|
else:
|
||||||
|
if not message.is_reply:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
reply_msg = await message.get_reply_message()
|
||||||
|
if not reply_msg:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
if reply_msg.sticker:
|
||||||
|
item = str(reply_msg.sticker.id)
|
||||||
|
else:
|
||||||
|
item = self._extract_emoji_text(reply_msg)
|
||||||
|
|
||||||
|
if not item:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
unblocked = False
|
||||||
|
if item in self.blocked_stickers:
|
||||||
|
self.blocked_stickers.remove(item)
|
||||||
|
unblocked = True
|
||||||
|
if item in self.blocked_emojis:
|
||||||
|
self.blocked_emojis.remove(item)
|
||||||
|
unblocked = True
|
||||||
|
|
||||||
|
if unblocked:
|
||||||
|
self._save_blocklists()
|
||||||
|
await utils.answer(message, self.strings["item_unblocked"])
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["not_found"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="— показать список заблокированных паков/стикеров/эмодзи",
|
||||||
|
en_doc="— show list of blocked packs/stickers/emojis",
|
||||||
|
)
|
||||||
|
async def blocklist(self, message: Message):
|
||||||
|
"""Show blocklist"""
|
||||||
|
packs_list = ", ".join(self.blocked_packs) if self.blocked_packs else "нет"
|
||||||
|
stickers_list = (
|
||||||
|
", ".join(self.blocked_stickers) if self.blocked_stickers else "нет"
|
||||||
|
)
|
||||||
|
emojis_list = ", ".join(self.blocked_emojis) if self.blocked_emojis else "нет"
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if packs_list:
|
||||||
|
result.append(self.strings["list_packs"].format(packs_list))
|
||||||
|
if stickers_list:
|
||||||
|
result.append(self.strings["list_stickers"].format(stickers_list))
|
||||||
|
if emojis_list:
|
||||||
|
result.append(self.strings["list_emojis"].format(emojis_list))
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await utils.answer(message, "\n".join(result))
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["all_cleared"])
|
||||||
|
|
||||||
|
@loader.command(ru_doc="— очистить все блокировки", en_doc="— clear all blocks")
|
||||||
|
async def clearblocks(self, message: Message):
|
||||||
|
"""Clear all blocks"""
|
||||||
|
self.blocked_packs.clear()
|
||||||
|
self.blocked_stickers.clear()
|
||||||
|
self.blocked_emojis.clear()
|
||||||
|
self._save_blocklists()
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["all_cleared"])
|
||||||
|
|
||||||
|
async def watcher(self, message: Message):
|
||||||
|
"""Monitor messages and block unwanted content"""
|
||||||
|
|
||||||
|
if message.is_group or message.is_channel:
|
||||||
|
return
|
||||||
|
|
||||||
|
should_block, reason = await self._should_block_message(message)
|
||||||
|
|
||||||
|
if should_block:
|
||||||
|
logger.info(f"Blocking message: {reason}")
|
||||||
|
await self._delete_message(message)
|
||||||
@@ -1,83 +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://files.archquise.ru/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)
|
|
||||||
)
|
|
||||||
@@ -27,9 +27,11 @@
|
|||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class FakeActionsMod(loader.Module):
|
class FakeActionsMod(loader.Module):
|
||||||
|
|||||||
@@ -27,8 +27,11 @@
|
|||||||
# scope: hikka_min 1.4.2
|
# scope: hikka_min 1.4.2
|
||||||
# -----------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class FakeWallet(loader.Module):
|
class FakeWallet(loader.Module):
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
# meta developer: @hikka_mods
|
# meta developer: @hikka_mods
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from telethon import functions
|
from telethon import functions
|
||||||
from telethon.tl.types import DialogFilter, InputPeerChannel
|
from telethon.tl.types import DialogFilter, InputPeerChannel
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,11 @@
|
|||||||
# scope: Api GigaChat 0.0.1
|
# scope: Api GigaChat 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class GigaChatMod(loader.Module):
|
class GigaChatMod(loader.Module):
|
||||||
|
|||||||
@@ -26,10 +26,10 @@
|
|||||||
# scope: HAFK 0.0.1
|
# scope: HAFK 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from telethon import types
|
from telethon import types
|
||||||
from telethon.utils import get_peer_id
|
from telethon.utils import get_peer_id
|
||||||
@@ -135,7 +135,6 @@ class HAFK(loader.Module):
|
|||||||
|
|
||||||
async def _afk_toggle(self, message, global_afk: bool):
|
async def _afk_toggle(self, message, global_afk: bool):
|
||||||
chat_id = utils.get_chat_id(message)
|
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"
|
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_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"
|
afk_on_reason_string = "afk_on_reason" if global_afk else "afk_here_on_reason"
|
||||||
|
|||||||
107
archquise/H.Modules/HInstall.py
Normal file
107
archquise/H.Modules/HInstall.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# 🔐 Licensed under the GNU AGPLv3.
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Name: HInstall
|
||||||
|
# Description: Provides H:Mods modules installation trough buttons
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# requires: PyCryptodome
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# #################################################################################
|
||||||
|
# ########## This module is based on @hikariatama 's hikkamods_socket!! ###########
|
||||||
|
# #################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = (1, 0, 0)
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Hash import SHA256
|
||||||
|
from Crypto.Signature import pkcs1_15
|
||||||
|
|
||||||
|
from telethon.tl.types import Message
|
||||||
|
from telethon import functions, types
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
pubkey_data = """
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvekpGqKiD2HZwY/J7jZv
|
||||||
|
PwGRobAS2TaC9HU5LUNRDg90jA/r8xgoFhlCBJocq8+XvJIWpgmIEYWJCz0KpCXu
|
||||||
|
Meu42bAXvLqniDOqnOt8FjXFapGZvEMLen1CLCRr1OQhVNpRlPjjWo7PM+YpUnbw
|
||||||
|
giqEZ9nA5DQ5Gi0vsSHXAnBa+ZIsxaY3EwosHMvUUhnnijcbBpkyYRJ8atvsT9AX
|
||||||
|
cNS+NjDE4Kj8jSnArQ1D1Ct1pcZEXD6DUk2k3HAD4OlZS5nY5IFchWEcpLT/Fjbt
|
||||||
|
BzGBZCJZ+rp8qR1tCVvVTV3itACc8O0Pirmptkrxb3A4pC0S8oxYBFQcnZAlIiw3
|
||||||
|
uX36O90AkRwbsdnsp2JVg5AAPUYvdsMoCGG+cSGZC73arqcrvn0VFo7EhsYq/1Ds
|
||||||
|
CevorFI4TiLVbSlFSVnX5baqmTj+XNhgaWWmiY/+mhErzsWtpCOHYFitf1xqp3zD
|
||||||
|
9O2Vs7lQIxMsHFISAEhn8BqQxvlwslfcjmbuJxkYriqAHXQGS3IZDXhEZXwouOUV
|
||||||
|
HGN2YD5aLK0L8OuTNY5cf1TN8C5xgVZoEodAKqAva/i1v/F6IQk3iEo0ncgypeyg
|
||||||
|
NM1TUudkQ+f1wXqLj2YaVKqRdKswl9vgYpUCHjGZfN+WYT4DbOMrJm1OFeen6geo
|
||||||
|
xqON1/xeRBgkE3tna3RuhmUCAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
pubkey = RSA.importKey(pubkey_data.strip())
|
||||||
|
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class HInstallMod(loader.Module):
|
||||||
|
"""Provides H:Mods modules installation trough buttons"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "HInstall",
|
||||||
|
"_cls_doc": "Provides H:Mods modules installation trough buttons",
|
||||||
|
"module_downloaded": "Module downloaded!"
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"_cls_doc": "Позволяет устанавливать модули от H:Mods через кнопки",
|
||||||
|
"module_downloaded": "Модуль загружен!"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def on_dlmod(self, client, db):
|
||||||
|
ent = await self.client(functions.users.GetFullUserRequest('@hinstall_bot'))
|
||||||
|
if ent.full_user.blocked:
|
||||||
|
await self.client(functions.contacts.UnblockRequest('@hinstall_bot'))
|
||||||
|
await self.client.send_message('@hinstall_bot', '/start')
|
||||||
|
await self.client.delete_dialog('@hinstall_bot')
|
||||||
|
|
||||||
|
|
||||||
|
async def _load_module(self, url: str, message: Optional[Message] = None):
|
||||||
|
loader_m = self.lookup("loader")
|
||||||
|
|
||||||
|
await loader_m.download_and_install(url, None)
|
||||||
|
|
||||||
|
if getattr(loader_m, "_fully_loaded", getattr(loader_m, "fully_loaded", False)):
|
||||||
|
getattr(
|
||||||
|
loader_m,
|
||||||
|
"_update_modules_in_db",
|
||||||
|
getattr(loader_m, "update_modules_in_db", lambda: None),
|
||||||
|
)()
|
||||||
|
|
||||||
|
|
||||||
|
async def watcher(self, message: Message):
|
||||||
|
if not isinstance(message, Message):
|
||||||
|
return
|
||||||
|
if message.sender_id == 8104671142 and message.raw_text.startswith("#install"):
|
||||||
|
await message.delete()
|
||||||
|
fileref = (
|
||||||
|
message.raw_text.split("#install:")[1].strip().splitlines()[0].strip()
|
||||||
|
)
|
||||||
|
sig = base64.b64decode(message.raw_text.splitlines()[1].strip().encode())
|
||||||
|
try:
|
||||||
|
h = SHA256.new(fileref.encode("utf-8"))
|
||||||
|
pkcs1_15.new(pubkey).verify(h, sig)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
logger.error(f"Got message with non-verified signature ({fileref=})")
|
||||||
|
return
|
||||||
|
await self._load_module(f"https://raw.githubusercontent.com/archquise/H.Modules/refs/heads/main/{fileref}", message)
|
||||||
|
await self.client.send_message('@hinstall_bot', self.strings['module_downloaded'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Proprietary License Agreement
|
# Proprietary License Agreement
|
||||||
|
|
||||||
# Copyright (c) 2024-29 Archquise
|
# 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:
|
# 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:
|
||||||
|
|
||||||
@@ -23,11 +23,10 @@
|
|||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
# meta developer: @hikka_mods
|
# meta developer: @hikka_mods
|
||||||
|
|
||||||
from .. import loader, utils
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import asyncio
|
|
||||||
|
from .. import loader
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -39,8 +38,13 @@ class InfoBannersManagerMod(loader.Module):
|
|||||||
strings = {"name": "InfoBannersManager"}
|
strings = {"name": "InfoBannersManager"}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.changer_instance = None
|
|
||||||
self.config = loader.ModuleConfig(
|
self.config = loader.ModuleConfig(
|
||||||
|
loader.ConfigValue(
|
||||||
|
"enabled",
|
||||||
|
False,
|
||||||
|
"Включить автоматическую смену баннеров",
|
||||||
|
validator=loader.validators.Boolean(),
|
||||||
|
),
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"delay",
|
"delay",
|
||||||
60,
|
60,
|
||||||
@@ -56,49 +60,49 @@ class InfoBannersManagerMod(loader.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def banner_changer(self):
|
async def banner_changer(self):
|
||||||
while True:
|
"""Change banner periodically"""
|
||||||
try:
|
try:
|
||||||
if not self.config["bannerslist"]:
|
if not self.config["bannerslist"]:
|
||||||
logger.warning("Banners list is empty!")
|
logger.warning("Banners list is empty!")
|
||||||
await asyncio.sleep(10)
|
return
|
||||||
return
|
|
||||||
|
|
||||||
banner = random.choice(self.config["bannerslist"])
|
banner = random.choice(self.config["bannerslist"])
|
||||||
instance = self.lookup("HerokuInfo")
|
instance = self.lookup("HerokuInfo")
|
||||||
if not instance:
|
if not instance:
|
||||||
instance = self.lookup("HikkaInfo")
|
instance = self.lookup("HikkaInfo")
|
||||||
|
|
||||||
|
if instance:
|
||||||
instance.config["banner_url"] = banner
|
instance.config["banner_url"] = banner
|
||||||
|
logger.info(f"Banner changed to: {banner}")
|
||||||
|
else:
|
||||||
|
logger.warning("Info module not found!")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Caught exception: {e}")
|
logger.exception(f"Error changing banner: {e}")
|
||||||
await asyncio.sleep(10)
|
|
||||||
await asyncio.sleep(self.config["delay"])
|
|
||||||
|
|
||||||
async def on_unload(self):
|
@loader.loop(interval=60, autostart=False)
|
||||||
if self.changer_instance:
|
async def banner_loop(self):
|
||||||
self.changer_instance.cancel()
|
"""Main banner changing loop"""
|
||||||
self.changer_instance = None
|
if not self.config["enabled"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.banner_changer()
|
||||||
|
|
||||||
|
# Update interval from config
|
||||||
|
self.banner_loop.set_interval(self.config["delay"])
|
||||||
|
|
||||||
@loader.command(
|
async def client_ready(self):
|
||||||
ru_doc="Включить или выключить модуль",
|
"""Initialize the banner changer loop"""
|
||||||
)
|
if self.config["enabled"]:
|
||||||
async def autobannertoggle(self, message):
|
self.banner_loop.start()
|
||||||
if not self.db.get(__name__, "enabled", False):
|
|
||||||
try:
|
|
||||||
if self.changer_instance:
|
|
||||||
self.changer_instance.cancel()
|
|
||||||
|
|
||||||
self.db.set(__name__, "enabled", True)
|
def on_config_update(self, config_key, new_value):
|
||||||
self.changer_instance = asyncio.create_task(self.banner_changer())
|
"""Handle config updates"""
|
||||||
await utils.answer(message, "Модуль запущен!")
|
if config_key == "enabled":
|
||||||
except Exception as e:
|
if new_value:
|
||||||
logger.exception(f"Caught exception: {e}")
|
self.banner_loop.start()
|
||||||
else:
|
else:
|
||||||
try:
|
self.banner_loop.stop()
|
||||||
self.db.set(__name__, "enabled", False)
|
elif config_key == "delay":
|
||||||
await utils.answer(message, "Модуль остановлен!")
|
# Update interval immediately
|
||||||
if self.changer_instance:
|
self.banner_loop.set_interval(new_value)
|
||||||
self.changer_instance.cancel()
|
|
||||||
self.changer_instance = None
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f"Caught exception: {e}")
|
|
||||||
|
|||||||
@@ -26,28 +26,35 @@
|
|||||||
# scope: InlineButton 0.0.1
|
# scope: InlineButton 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
from ..inline.types import InlineQuery
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
from ..inline.types import InlineQuery
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class InlineButtonMod(loader.Module):
|
class InlineButtonMod(loader.Module):
|
||||||
"""Create inline button"""
|
"""Create inline buttons with enhanced functionality"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "InlineButton",
|
"name": "InlineButton",
|
||||||
"titles": "Create a message with the Inline Button",
|
"titles": "🔘 Create message with Inline Button",
|
||||||
"error_title": "Error",
|
"error_title": "<emoji document_id=5854929766146118183>❌</emoji> Error",
|
||||||
"error_description": "Invalid input format. Please provide exactly three comma-separated values.",
|
"error_description": "<emoji document_id=5854929766146118183>❌</emoji> Invalid input format. Please provide exactly three comma-separated values: message, name, url.",
|
||||||
"error_message": "Make sure your input is formatted as: message, name, url.",
|
"error_message": "<emoji document_id=5854929766146118183>❌</emoji> Make sure your input is formatted as: message, name, url.",
|
||||||
|
"button_created": "<emoji document_id=5854762571659218443>✅</emoji> Button created successfully!",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Please provide arguments: message, name, url.",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"titles": "Создай сообщение с Inline Кнопкой",
|
"titles": "🔘 Создать сообщение с Inline Кнопкой",
|
||||||
"error_title": "Ошибка",
|
"error_title": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка",
|
||||||
"error_description": "Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми.",
|
"error_description": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми: сообщение, имя, url.",
|
||||||
"error_message": "Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, url.",
|
"error_message": "<emoji document_id=5854929766146118183>❌</emoji> Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, url.",
|
||||||
|
"button_created": "<emoji document_id=5854762571659218443>✅</emoji> Кнопка успешно создана!",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите аргументы: сообщение, имя, url.",
|
||||||
}
|
}
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
@@ -57,21 +64,25 @@ class InlineButtonMod(loader.Module):
|
|||||||
async def crinl_inline_handler(self, query: InlineQuery):
|
async def crinl_inline_handler(self, query: InlineQuery):
|
||||||
args = utils.get_args_raw(query.query)
|
args = utils.get_args_raw(query.query)
|
||||||
|
|
||||||
if args:
|
if not args:
|
||||||
args_list = [arg.strip() for arg in args.split(",")]
|
return {
|
||||||
|
"title": self.strings("error_title"),
|
||||||
|
"description": self.strings("error_description"),
|
||||||
|
"message": self.strings("no_args"),
|
||||||
|
}
|
||||||
|
|
||||||
if len(args_list) == 3:
|
args_list = [arg.strip() for arg in args.split(",")]
|
||||||
message, name, url = args_list
|
|
||||||
|
|
||||||
return {
|
if len(args_list) != 3:
|
||||||
"title": self.strings("titles"),
|
return {
|
||||||
"description": f"{message}, {name}, {url}",
|
"title": self.strings("error_title"),
|
||||||
"message": message,
|
"description": self.strings("error_description"),
|
||||||
"reply_markup": [{"text": name, "url": url}],
|
"message": self.strings("error_message"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
message, name, url = args_list
|
||||||
"title": self.strings("error_title"),
|
return True, {
|
||||||
"description": self.strings("error_description"),
|
"message": message,
|
||||||
"message": self.strings("error_message"),
|
"reply_markup": [{"text": name, "url": url}],
|
||||||
|
"description": self.strings("button_created"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,49 +26,67 @@
|
|||||||
# scope: InlineCoin 0.0.1
|
# scope: InlineCoin 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from ..inline.types import InlineQuery
|
|
||||||
from .. import loader
|
from .. import loader
|
||||||
|
from ..inline.types import InlineQuery
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class CoinSexMod(loader.Module):
|
class CoinFlipMod(loader.Module):
|
||||||
"""Mini game heads or tails"""
|
"""Mini coin flip game"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "InlineCoin",
|
"name": "InlineCoin",
|
||||||
"titles": "Heads or tails?",
|
"titles": "🪙 Heads or Tails?",
|
||||||
"description": "Let's find out!",
|
"description": "🎲 Let's find out!",
|
||||||
"heads": "🌚 An eagle fell out!",
|
"heads": "🦅 An eagle fell out!",
|
||||||
"tails": "🌝 Tails fell out!",
|
"tails": "🪙 Tails fell out!",
|
||||||
"edge": "🙀 Miraculously, the coin remained on the edge!",
|
"edge": "🙀 Miraculously, the coin remained on its edge!",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Please provide a command to flip.",
|
||||||
|
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> An error occurred: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"titles": "Орёл или решка?",
|
"titles": "🪙 Орёл или решка?",
|
||||||
"description": "Давай узнаем!",
|
"description": "🎲 Давай узнаем!",
|
||||||
"heads": "🌚 Выпал орёл!",
|
"heads": "🦅 Выпал орёл!",
|
||||||
"tails": "🌝 Выпала решка!",
|
"tails": "🪙 Выпала решка!",
|
||||||
"edge": "🙀 Чудо, монетка осталась на ребре!",
|
"edge": "🙀 Чудо, монетка осталась на ребре!",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите команду для подбрасывания монетки.",
|
||||||
|
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_coin_flip_result(self) -> dict:
|
def get_coin_flip_result(self) -> Dict[str, str]:
|
||||||
results = [self.strings("heads"), self.strings("tails")]
|
"""Get coin flip result with better formatting"""
|
||||||
if random.random() < 0.1:
|
return {
|
||||||
return self.strings("edge")
|
"title": self.strings["titles"],
|
||||||
else:
|
"description": self.strings["description"],
|
||||||
return random.choice(results)
|
"message": f"<b>{random.choice([self.strings['heads'], self.strings['tails']])}</b>",
|
||||||
|
"thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png",
|
||||||
|
}
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Подбросит монетку ",
|
ru_doc="Подбросить монетку",
|
||||||
en_doc="Flip a coin",
|
en_doc="Flip a coin",
|
||||||
)
|
)
|
||||||
async def coin_inline_handler(self, query: InlineQuery):
|
async def coin_inline_handler(self, query: InlineQuery):
|
||||||
|
"""Handle coin flip inline query"""
|
||||||
|
if not query.args:
|
||||||
|
return {
|
||||||
|
"title": self.strings["titles"],
|
||||||
|
"description": self.strings["no_args"],
|
||||||
|
"message": self.strings["no_args"],
|
||||||
|
}
|
||||||
|
|
||||||
result = self.get_coin_flip_result()
|
result = self.get_coin_flip_result()
|
||||||
return {
|
return {
|
||||||
"title": self.strings("titles"),
|
"title": self.strings["titles"],
|
||||||
"description": self.strings("description"),
|
"description": self.strings["description"],
|
||||||
"message": f"<b>{result}</b>",
|
"message": result["message"],
|
||||||
"thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png",
|
"thumb": result["thumb"],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,15 @@
|
|||||||
# scope: InlineHelper 0.0.1
|
# scope: InlineHelper 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .. import loader, main, utils
|
||||||
from ..inline.types import InlineQuery
|
from ..inline.types import InlineQuery
|
||||||
|
|
||||||
from .. import loader, utils, main
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
@@ -42,77 +43,118 @@ class InlineHelperMod(loader.Module):
|
|||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "InlineHelper",
|
"name": "InlineHelper",
|
||||||
"call_restart": "Restarting...",
|
"call_restart": "<emoji document_id=5188311512791393083>🔄</emoji> Restarting...",
|
||||||
"call_update": "Updating...",
|
"call_update": "<emoji document_id=5188311512791393083>🔄</emoji> Updating...",
|
||||||
"res_prefix": "Successfully reset prefix to default",
|
"res_prefix": "<emoji document_id=5854762571659218443>✅</emoji> Prefix successfully reset to default",
|
||||||
"restart_inline_handler_title": "Restart Userbot",
|
"restart_inline_handler_title": "🔄 Restart Userbot",
|
||||||
"restart_inline_handler_description": "Restart your userbot via inline",
|
"restart_inline_handler_description": "Restart your userbot via inline",
|
||||||
"restart_inline_handler_message": "Press the button below to restart your userbot",
|
"restart_inline_handler_message": "🔄 Restart",
|
||||||
"restart_inline_handler_reply_text": "Restart",
|
"update_inline_handler_title": "🔄 Update Userbot",
|
||||||
"update_inline_handler_title": "Update Userbot",
|
|
||||||
"update_inline_handler_description": "Update your userbot via inline",
|
"update_inline_handler_description": "Update your userbot via inline",
|
||||||
"update_inline_handler_message": "Press the button below to update your userbot",
|
"update_inline_handler_message": "🔄 Update",
|
||||||
"update_inline_handler_reply_text": "Update",
|
"terminal_inline_handler_title": "💻 Command Executed",
|
||||||
"terminal_inline_handler_title": "Command Executed!",
|
|
||||||
"terminal_inline_handler_description": "Command executed successfully",
|
"terminal_inline_handler_description": "Command executed successfully",
|
||||||
"terminal_inline_handler_message": "Command {text} executed successfully in terminal",
|
"terminal_inline_handler_message": "Command <code>{text}</code> executed successfully in terminal",
|
||||||
"modules_inline_handler_title": "Modules",
|
"modules_inline_handler_title": "📦 Modules",
|
||||||
"modules_inline_handler_description": "List all installed modules",
|
"modules_inline_handler_description": "List all installed modules",
|
||||||
"modules_inline_handler_result": "☘️ Installed modules:\n",
|
"modules_inline_handler_result": "📦 All installed modules:\n\n",
|
||||||
"resetprefix_inline_handler_title": "Reset Prefix",
|
"resetprefix_inline_handler_title": "⚠️ Reset Prefix",
|
||||||
"resetprefix_inline_handler_description": "Reset your prefix back to default",
|
"resetprefix_inline_handler_description": "Reset your prefix back to default (be careful!)",
|
||||||
"resetprefix_inline_handler_message": "Are you sure you want to reset your prefix to default dot?",
|
"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_yes": "Yes, reset it",
|
||||||
"resetprefix_inline_handler_reply_text_no": "No",
|
"resetprefix_inline_handler_reply_text_no": "No, cancel",
|
||||||
|
"error_no_module": "<emoji document_id=5854929766146118183>❌</emoji> Module not found: {module}",
|
||||||
|
"error_command_failed": "<emoji document_id=5854929766146118183>❌</emoji> Command execution failed: {error}",
|
||||||
|
"error_git_failed": "<emoji document_id=5854929766146118183>❌</emoji> Git operation failed: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"call_restart": "Перезагружаю...",
|
"call_restart": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагружаю...",
|
||||||
"call_update": "Обновляю...",
|
"call_update": "<emoji document_id=5188311512791393083>🔄</emoji> Обновляю...",
|
||||||
"res_prefix": "Префикс успешно сброшен по умолчанию",
|
"res_prefix": "<emoji document_id=5854762571659218443>✅</emoji> Префикс успешно сброшен по умолчанию",
|
||||||
"restart_inline_handler_title": "Перезагрузить юзербота",
|
"restart_inline_handler_title": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагрузить юзербота",
|
||||||
"restart_inline_handler_description": "Перезагрузить юзербота через инлайн",
|
"restart_inline_handler_description": "Перезагрузить юзербота через инлайн",
|
||||||
"restart_inline_handler_message": "<b>Нажмите на кнопку ниже для рестарта юзербота</b>",
|
"restart_inline_handler_message": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагрузка",
|
||||||
"restart_inline_handler_reply_text": "Перезапуск",
|
"update_inline_handler_title": "<emoji document_id=5188311512791393083>🔄</emoji> Обновить юзербота",
|
||||||
"update_inline_handler_title": "Обновить юзербота",
|
|
||||||
"update_inline_handler_description": "Обновить юзербота через инлайн",
|
"update_inline_handler_description": "Обновить юзербота через инлайн",
|
||||||
"update_inline_handler_message": "<b>Нажмите на кнопку ниже для обновления юзербота</b>",
|
"update_inline_handler_message": "<emoji document_id=5188311512791393083>🔄</emoji> Обновить",
|
||||||
"update_inline_handler_reply_text": "Обновить",
|
"terminal_inline_handler_title": "<emoji document_id=5854762571659218443>💻</emoji> Команда выполнена!",
|
||||||
"terminal_inline_handler_title": "Команда выполнена!",
|
"terminal_inline_handler_description": "Команда успешно выполнена.",
|
||||||
"terminal_inline_handler_description": "Команда завершена.",
|
|
||||||
"terminal_inline_handler_message": "Команда <code>{text}</code> была успешно выполнена в терминале",
|
"terminal_inline_handler_message": "Команда <code>{text}</code> была успешно выполнена в терминале",
|
||||||
"modules_inline_handler_title": "Модули",
|
"modules_inline_handler_title": "<emoji document_id=5854762571659218443>📦</emoji> Модули",
|
||||||
"modules_inline_handler_description": "Вывести список установленных моудей",
|
"modules_inline_handler_description": "Вывести список установленных модулей",
|
||||||
"modules_inline_handler_result": "☘️ Все установленные модули:\n",
|
"modules_inline_handler_result": "<emoji document_id=5854762571659218443>📦</emoji> Все установленные модули:\n\n",
|
||||||
"resetprefix_inline_handler_title": "Сбросить префикс",
|
"resetprefix_inline_handler_title": "<emoji document_id=5854929766146118183>⚠️</emoji> Сбросить префикс",
|
||||||
"resetprefix_inline_handler_description": "Сбросить префикс по умолчанию",
|
"resetprefix_inline_handler_description": "Сбросить префикс по умолчанию (осторожно!)",
|
||||||
"resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?",
|
"resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?",
|
||||||
"resetprefix_inline_handler_reply_text_yes": "Да",
|
"resetprefix_inline_handler_reply_text_yes": "Да, сбросить",
|
||||||
"resetprefix_inline_handler_reply_text_no": "Нет",
|
"resetprefix_inline_handler_reply_text_no": "Нет, отменить",
|
||||||
|
"error_no_module": "<emoji document_id=5854929766146118183>❌</emoji> Модуль не найден: {module}",
|
||||||
|
"error_command_failed": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка выполнения команды: {error}",
|
||||||
|
"error_git_failed": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка git операции: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client = None
|
||||||
|
self.db = None
|
||||||
|
self._base_dir = utils.get_base_dir()
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
self.client = client
|
self.client = client
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def restart(self, call):
|
async def restart(self, call):
|
||||||
"""Restart callback"""
|
"""Restart callback"""
|
||||||
logging.error("InlineHelper: restarting userbot...")
|
logger.info("InlineHelper: Restarting userbot...")
|
||||||
await call.edit(self.strings("call_restart"))
|
try:
|
||||||
await sys.exit(0)
|
await call.edit(self.strings["call_restart"])
|
||||||
|
|
||||||
|
await asyncio.create_subprocess_exec(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
"-c",
|
||||||
|
f"cd {self._base_dir} && git reset --hard HEAD && git pull",
|
||||||
|
],
|
||||||
|
cwd=self._base_dir,
|
||||||
|
)
|
||||||
|
await call.edit(self.strings["call_update"])
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
await asyncio.create_subprocess_exec(
|
||||||
|
[sys.executable, "-c", f"cd {self._base_dir} && git pull"],
|
||||||
|
cwd=self._base_dir,
|
||||||
|
)
|
||||||
|
await call.edit(self.strings["res_prefix"])
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Restart failed: {e}")
|
||||||
|
await call.edit(self.strings["error_git_failed"].format(error=str(e)))
|
||||||
|
|
||||||
async def update(self, call):
|
async def update(self, call):
|
||||||
"""Update callback"""
|
"""Update callback"""
|
||||||
logging.error("InlineHelper: updating userbot...")
|
logger.info("InlineHelper: Updating userbot...")
|
||||||
os.system(f"cd {utils.get_base_dir()} && cd .. && git reset --hard HEAD")
|
try:
|
||||||
os.system("git pull")
|
await call.edit(self.strings["call_update"])
|
||||||
await call.edit(self.strings("call_update"))
|
|
||||||
await sys.exit(0)
|
await asyncio.create_subprocess_exec(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
"-c",
|
||||||
|
f"cd {self._base_dir} && git reset --hard HEAD && git pull",
|
||||||
|
],
|
||||||
|
cwd=self._base_dir,
|
||||||
|
)
|
||||||
|
await call.edit(self.strings["res_prefix"])
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Update failed: {e}")
|
||||||
|
await call.edit(self.strings["error_git_failed"].format(error=str(e)))
|
||||||
|
|
||||||
async def reset_prefix(self, call):
|
async def reset_prefix(self, call):
|
||||||
"""Reset prefix"""
|
"""Reset prefix callback"""
|
||||||
self.db.set(main.__name__, "command_prefix", ".")
|
try:
|
||||||
await call.edit(self.strings("res_prefix"))
|
self.db.set(main.__name__, "command_prefix", ".")
|
||||||
|
await call.edit(self.strings["res_prefix"])
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Reset prefix failed: {e}")
|
||||||
|
await call.edit(self.strings["error_command_failed"].format(error=str(e)))
|
||||||
|
|
||||||
@loader.inline_handler(
|
@loader.inline_handler(
|
||||||
ru_doc="Перезагрузить юзербота",
|
ru_doc="Перезагрузить юзербота",
|
||||||
@@ -152,42 +194,95 @@ class InlineHelperMod(loader.Module):
|
|||||||
ru_doc="Выполнить команду в терминале (лучше сразу подготовить команду и просто вставить)",
|
ru_doc="Выполнить команду в терминале (лучше сразу подготовить команду и просто вставить)",
|
||||||
en_doc="Execute the command in the terminal (it is better to prepare the command immediately and just paste it)",
|
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):
|
async def terminal_inline_handler(self, query: InlineQuery):
|
||||||
text = _.args
|
"""Execute terminal command safely"""
|
||||||
|
if not query.args:
|
||||||
|
return {
|
||||||
|
"title": self.strings["terminal_inline_handler_title"],
|
||||||
|
"description": self.strings["terminal_inline_handler_description"],
|
||||||
|
"message": self.strings["terminal_inline_handler_message"].format(
|
||||||
|
text="No command provided"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
await asyncio.create_subprocess_shell(
|
command_text = query.args.strip()
|
||||||
f"{text}",
|
if not command_text:
|
||||||
stdin=asyncio.subprocess.PIPE,
|
return {
|
||||||
stdout=asyncio.subprocess.PIPE,
|
"title": self.strings["terminal_inline_handler_title"],
|
||||||
stderr=asyncio.subprocess.PIPE,
|
"description": self.strings["terminal_inline_handler_description"],
|
||||||
cwd=utils.get_base_dir(),
|
"message": self.strings["terminal_inline_handler_message"].format(
|
||||||
)
|
text="No command provided"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
if any(char in command_text for char in ["&", "|", ";", "`", "$"]):
|
||||||
"title": self.strings("terminal_inline_handler_title"),
|
return {
|
||||||
"description": self.strings("terminal_inline_handler_description"),
|
"title": self.strings["terminal_inline_handler_title"],
|
||||||
"message": self.strings("terminal_inline_handler_message").format(
|
"description": self.strings["terminal_inline_handler_description"],
|
||||||
text=text
|
"message": self.strings["error_command_failed"].format(
|
||||||
),
|
error="Invalid characters in command"
|
||||||
}
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
args = shlex.split(command_text)
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
args,
|
||||||
|
stdin=asyncio.subprocess.PIPE,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
cwd=self._base_dir,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
stdout.decode().strip() if stdout else ""
|
||||||
|
error = stderr.decode().strip() if stderr else ""
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return {
|
||||||
|
"title": self.strings["terminal_inline_handler_title"],
|
||||||
|
"description": self.strings["terminal_inline_handler_description"],
|
||||||
|
"message": self.strings["error_command_failed"].format(error=error),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"title": self.strings["terminal_inline_handler_title"],
|
||||||
|
"description": self.strings["terminal_inline_handler_description"],
|
||||||
|
"message": self.strings["terminal_inline_handler_message"].format(
|
||||||
|
text=command_text
|
||||||
|
),
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"title": self.strings["terminal_inline_handler_title"],
|
||||||
|
"description": self.strings["terminal_inline_handler_description"],
|
||||||
|
"message": self.strings["error_command_failed"].format(error=str(e)),
|
||||||
|
}
|
||||||
|
|
||||||
@loader.inline_handler(
|
@loader.inline_handler(
|
||||||
ru_doc="Вывести список установленных модулей через инлайн",
|
ru_doc="Вывести список установленных модулей через инлайн",
|
||||||
en_doc="Display a list of installed modules via the inline",
|
en_doc="Display a list of installed modules via the inline",
|
||||||
)
|
)
|
||||||
async def modules_inline_handler(self, _: InlineQuery):
|
async def modules_inline_handler(self, query: InlineQuery):
|
||||||
result = self.strings("modules_inline_handler_result")
|
"""List all installed modules"""
|
||||||
|
try:
|
||||||
|
result = self.strings["modules_inline_handler_result"]
|
||||||
|
|
||||||
for mod in self.allmodules.modules:
|
for mod in self.allmodules.modules:
|
||||||
try:
|
try:
|
||||||
name = mod.strings["name"]
|
name = mod.strings["name"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
name = mod.__clas__.__name__
|
name = mod.__class__.__name__
|
||||||
result += f"• {name}\n"
|
result += f"• {name}\n"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error listing modules: {e}")
|
||||||
|
result = f"Error listing modules: {str(e)}"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": self.strings("modules_inline_handler_title"),
|
"title": self.strings["modules_inline_handler_title"],
|
||||||
"description": self.strings("modules_inline_handler_description"),
|
"description": self.strings["modules_inline_handler_description"],
|
||||||
"message": result,
|
"message": result,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,53 +26,108 @@
|
|||||||
# scope: IrisSimpleMod 1.0.1
|
# scope: IrisSimpleMod 1.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
__version__ = (1, 0, 1)
|
__version__ = (1, 0, 1)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class IrisSimpleMod(loader.Module):
|
class IrisSimpleMod(loader.Module):
|
||||||
"""Модуль для базового взаимодействия с Ирисом"""
|
"""Module for basic interaction with Iris bot"""
|
||||||
|
|
||||||
strings = {"name": "IrisSimpleMod"}
|
strings = {
|
||||||
|
"name": "IrisSimpleMod",
|
||||||
|
"checking_bag": "<emoji document_id=5188311512791393083>🌎</emoji> Checking bag...",
|
||||||
|
"bag_result": "<emoji document_id=5854762571659218443>✅</emoji> Your bag: <code>{}</code>",
|
||||||
|
"farming": "<emoji document_id=5188311512791393083>🌎</emoji> Farming iris-coins...",
|
||||||
|
"farm_result": "<emoji document_id=5854762571659218443>✅</emoji> Farm result: <code>{}</code>",
|
||||||
|
"getting_stats": "<emoji document_id=5188311512791393083>🌎</emoji> Getting user stats...",
|
||||||
|
"stats_result": "<emoji document_id=5854762571659218443>✅</emoji> User stats: <code>{}</code>",
|
||||||
|
"bot_stats": "<emoji document_id=5188311512791393083>🌎</emoji> Getting bot stats...",
|
||||||
|
"bot_stats_result": "<emoji document_id=5854762571659218443>✅</emoji> Bot stats: <code>{}</code>",
|
||||||
|
"error_no_response": "<emoji document_id=5854929766146118183>❌</emoji> No response from bot. Please try again.",
|
||||||
|
"error_timeout": "<emoji document_id=5854929766146118183>❌</emoji> Request timeout. Please try again.",
|
||||||
|
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> An error occurred: {error}",
|
||||||
|
}
|
||||||
|
|
||||||
@loader.command(ru_doc="Проверить мешок")
|
strings_ru = {
|
||||||
|
"checking_bag": "<emoji document_id=5188311512791393083>🌎</emoji> Проверка мешка...",
|
||||||
|
"bag_result": "<emoji document_id=5854762571659218443>✅</emoji> Ваш мешок: <code>{}</code>",
|
||||||
|
"farming": "<emoji document_id=5188311512791393083>🌎</emoji> Фарм ирис-коинов...",
|
||||||
|
"farm_result": "<emoji document_id=5854762571659218443>✅</emoji> Результат фарма: <code>{}</code>",
|
||||||
|
"getting_stats": "<emoji document_id=5188311512791393083>🌎</emoji> Получение статистики пользователя...",
|
||||||
|
"stats_result": "<emoji document_id=5854762571659218443>✅</emoji> Статистика пользователя: <code>{}</code>",
|
||||||
|
"bot_stats": "<emoji document_id=5188311512791393083>🌎</emoji> Получение статистики ботов...",
|
||||||
|
"bot_stats_result": "<emoji document_id=5854762571659218443>✅</emoji> Статистика ботов: <code>{}</code>",
|
||||||
|
"error_no_response": "<emoji document_id=5854929766146118183>❌</emoji> Нет ответа от бота. Попробуйте еще раз.",
|
||||||
|
"error_timeout": "<emoji document_id=5854929766146118183>❌</emoji> Таймаут запроса. Попробуйте еще раз.",
|
||||||
|
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка: {error}",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _send_and_delete(
|
||||||
|
self, message, command_message: str, response_timeout: int = 15
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Send command to Iris and get response with timeout"""
|
||||||
|
try:
|
||||||
|
async with self.client.conversation(
|
||||||
|
self._iris_user_id, timeout=self._timeout
|
||||||
|
) as conv:
|
||||||
|
await conv.send_message(command_message)
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
response_msg = await conv.get_response()
|
||||||
|
if response_msg:
|
||||||
|
await utils.answer(message, response_msg.text)
|
||||||
|
return response_msg.text
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in conversation: {e}")
|
||||||
|
await utils.answer(
|
||||||
|
message, self.strings["error_general"].format(error=str(e))
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Проверить мешок",
|
||||||
|
en_doc="Check bag",
|
||||||
|
)
|
||||||
async def bag(self, message):
|
async def bag(self, message):
|
||||||
"""Check bag"""
|
"""Check bag"""
|
||||||
async with self.client.conversation(5443619563) as conv:
|
await utils.answer(message, self.strings["checking_bag"])
|
||||||
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="Зафармить ирис-коины")
|
result = await self._send_and_delete(message, "мешок", response_timeout=20)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await utils.answer(message, self.strings["bag_result"].format(result))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Зафармить ирис-коины",
|
||||||
|
en_doc="Farm iris-coins",
|
||||||
|
)
|
||||||
async def farm(self, message):
|
async def farm(self, message):
|
||||||
"""Farm iris-coins"""
|
"""Farm iris-coins"""
|
||||||
async with self.client.conversation(5443619563) as conv:
|
await utils.answer(message, self.strings["farming"])
|
||||||
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="Вывести анкету")
|
result = await self._send_and_delete(message, "ферма", response_timeout=25)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await utils.answer(message, self.strings["farm_result"].format(result))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Вывести анкету",
|
||||||
|
en_doc="Display user stats",
|
||||||
|
)
|
||||||
async def irisstats(self, message):
|
async def irisstats(self, message):
|
||||||
"""Display user stats"""
|
"""Display user stats"""
|
||||||
async with self.client.conversation(5443619563) as conv:
|
await utils.answer(message, self.strings["getting_stats"])
|
||||||
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="Вывести статистику ботов")
|
result = await self._send_and_delete(message, "анкета", response_timeout=20)
|
||||||
async def irisping(self, message):
|
|
||||||
"""Display bot stats"""
|
if result:
|
||||||
async with self.client.conversation(5443619563) as conv:
|
await utils.answer(message, self.strings["stats_result"].format(result))
|
||||||
usermessage = await conv.send_message("🌺 Семейство ирисовых")
|
|
||||||
await usermessage.delete()
|
|
||||||
pingmessage = await conv.get_response()
|
|
||||||
await utils.answer(message, pingmessage.text)
|
|
||||||
await pingmessage.delete()
|
|
||||||
|
|||||||
@@ -26,11 +26,12 @@
|
|||||||
# scope: KBSwapper 0.0.1
|
# scope: KBSwapper 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
EN_TO_RU = str.maketrans(
|
EN_TO_RU = str.maketrans(
|
||||||
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
|
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
|
||||||
@@ -74,18 +75,55 @@ class KBSwapperMod(loader.Module):
|
|||||||
return
|
return
|
||||||
|
|
||||||
original_text = reply.text
|
original_text = reply.text
|
||||||
if not original_text:
|
if not original_text or original_text.isspace():
|
||||||
await utils.answer(message, self.strings("no_text"))
|
await utils.answer(message, self.strings("no_text"))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
first_char = original_text[0].lower()
|
trimmed_text = original_text.strip()
|
||||||
if first_char in string.ascii_lowercase:
|
|
||||||
fixed_text = original_text.translate(EN_TO_RU)
|
has_russian = any(
|
||||||
elif first_char in "йцукенгшщзхъфывапролджэячсмитьбю.ё":
|
char
|
||||||
|
in "йцукенгшщзхъфывапролджэячсмитьбюёЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ"
|
||||||
|
for char in trimmed_text
|
||||||
|
)
|
||||||
|
has_english = any(char in string.ascii_letters for char in trimmed_text)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Text analysis - Russian: {has_russian}, English: {has_english}, Text: {trimmed_text[:50]}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
if has_russian and not has_english:
|
||||||
fixed_text = original_text.translate(RU_TO_EN)
|
fixed_text = original_text.translate(RU_TO_EN)
|
||||||
|
logger.debug("Detected Russian text, translating to English")
|
||||||
|
elif has_english and not has_russian:
|
||||||
|
fixed_text = original_text.translate(EN_TO_RU)
|
||||||
|
logger.debug("Detected English text, translating to Russian")
|
||||||
else:
|
else:
|
||||||
fixed_text = original_text
|
first_char = (
|
||||||
|
trimmed_text[0].lower()
|
||||||
|
if trimmed_text
|
||||||
|
else original_text[0].lower()
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"Mixed/other characters detected, first char: {first_char}"
|
||||||
|
)
|
||||||
|
if first_char in string.ascii_lowercase:
|
||||||
|
fixed_text = original_text.translate(EN_TO_RU)
|
||||||
|
logger.debug("Using first char detection: English to Russian")
|
||||||
|
elif first_char in "йцукенгшщзхъфывапролджэячсмитьбюё":
|
||||||
|
fixed_text = original_text.translate(RU_TO_EN)
|
||||||
|
logger.debug("Using first char detection: Russian to English")
|
||||||
|
else:
|
||||||
|
fixed_text = original_text
|
||||||
|
logger.debug("No recognizable letters, returning as is")
|
||||||
|
|
||||||
|
if fixed_text != original_text:
|
||||||
|
logger.debug(
|
||||||
|
f"Text changed: {original_text[:30]}... → {fixed_text[:30]}..."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug("Text unchanged")
|
||||||
|
|
||||||
if message.sender_id == reply.sender_id:
|
if message.sender_id == reply.sender_id:
|
||||||
await reply.edit(fixed_text)
|
await reply.edit(fixed_text)
|
||||||
@@ -96,5 +134,5 @@ class KBSwapperMod(loader.Module):
|
|||||||
f"{self.strings('fixed_message').format(fixed=fixed_text)}",
|
f"{self.strings('fixed_message').format(fixed=fixed_text)}",
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during swap: {e}")
|
logger.error(f"Error during swap: {e}")
|
||||||
await utils.answer(message, self.strings("error"))
|
await utils.answer(message, self.strings("error"))
|
||||||
|
|||||||
@@ -27,12 +27,15 @@
|
|||||||
# scope: Meme 0.0.1
|
# scope: Meme 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
import logging
|
||||||
import aiohttp
|
import random # noqa: F401
|
||||||
import random
|
|
||||||
|
import aiohttp # noqa: F401
|
||||||
|
from bs4 import BeautifulSoup # noqa: F401
|
||||||
|
|
||||||
from .. import loader
|
from .. import loader
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class MemesMod(loader.Module):
|
class MemesMod(loader.Module):
|
||||||
|
|||||||
@@ -28,8 +28,10 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from telethon.types import Message
|
||||||
|
|
||||||
from herokutl.types import Message
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -43,50 +45,50 @@ class MessageMonitor(loader.Module):
|
|||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "MessageMonitor",
|
"name": "MessageMonitor",
|
||||||
"triggers_set": "Trigger words have been set: {}",
|
"triggers_set": "<emoji document_id=5854762571659218443>✅</emoji> Trigger words have been set: <code>{}</code>",
|
||||||
"triggers_not_set": "Trigger words have not been set",
|
"triggers_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Trigger words have not been set",
|
||||||
"target_set": "Target chat for notifications has been set",
|
"target_set": "<emoji document_id=5854762571659218443>✅</emoji> Target chat for notifications has been set",
|
||||||
"target_not_set": "Target chat for notifications has not been set",
|
"target_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Target chat for notifications has not been set",
|
||||||
"monitoring_started": "Monitoring has started",
|
"monitoring_started": "<emoji document_id=5188311512791393083>🌎</emoji> Monitoring has started",
|
||||||
"monitoring_stopped": "Monitoring has stopped",
|
"monitoring_stopped": "<emoji document_id=5854929766146118183>❌</emoji> Monitoring has stopped",
|
||||||
"monitoring_status": "Monitoring {}",
|
"monitoring_status": "<emoji document_id=5188311512791393083>🌎</emoji> Monitoring <b>{}</b>",
|
||||||
"triggers_example": "Example: <code>.triggers word1 word2</code>",
|
"triggers_example": "<emoji document_id=5854929766146118183>❌</emoji> Example: <code>.triggers word1 word2</code>",
|
||||||
"monitoring_status_on": "enabled",
|
"monitoring_status_on": "<emoji document_id=5854762571659218443>✅</emoji> enabled",
|
||||||
"monitoring_status_off": "disabled",
|
"monitoring_status_off": "<emoji document_id=5854929766146118183>❌</emoji> disabled",
|
||||||
"ignore_set": "Ignored chats have been set: {}",
|
"ignore_set": "<emoji document_id=5854762571659218443>✅</emoji> Ignored chats have been set: <code>{}</code>",
|
||||||
"ignore_none": "Ignored chats have not been set",
|
"ignore_none": "<emoji document_id=5854929766146118183>❌</emoji> Ignored chats have not been set",
|
||||||
"ignore_example": "Example: <code>.ignore 123456789 -987654321</code> (chat IDs)",
|
"ignore_example": "<emoji document_id=5854929766146118183>❌</emoji> Example: <code>.ignore 123456789 -987654321</code> (chat IDs)",
|
||||||
"no_reply": "Reply to a message in the desired chat or specify its ID",
|
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Reply to a message in the desired chat or specify its ID",
|
||||||
"monitoring_msg": (
|
"monitoring_msg": (
|
||||||
"🚨 **Trigger word detected!** 🚨\n\n"
|
"<emoji document_id=5854929766146118183>🚨</emoji> <b>Trigger word detected!</b> <emoji document_id=5854929766146118183>🚨</emoji>\n\n"
|
||||||
"**Chat:** {} (`{}`)\n"
|
"<b>Chat:</b> <code>{}</code>\n"
|
||||||
"**User:** {}\n"
|
"<b>User:</b> {}\n"
|
||||||
"**Link:** {}\n\n"
|
"<b>Link:</b> <a href='{}'>{}</a>\n\n"
|
||||||
"**Messages:**\n{}"
|
"<b>Message:</b>\n{}"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"triggers_set": "Триггерные слова установлены: {}",
|
"triggers_set": "<emoji document_id=5854762571659218443>✅</emoji> Триггерные слова установлены: <code>{}</code>",
|
||||||
"triggers_not_set": "Триггерные слова не установлены",
|
"triggers_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Триггерные слова не установлены",
|
||||||
"target_set": "Целевой чат для уведомлений установлен",
|
"target_set": "<emoji document_id=5854762571659218443>✅</emoji> Целевой чат для уведомлений установлен",
|
||||||
"target_not_set": "Целевой чат для уведомлений не установлен",
|
"target_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Целевой чат для уведомлений не установлен",
|
||||||
"monitoring_started": "Мониторинг запущен",
|
"monitoring_started": "<emoji document_id=5188311512791393083>🌎</emoji> Мониторинг запущен",
|
||||||
"monitoring_stopped": "Мониторинг остановлен",
|
"monitoring_stopped": "<emoji document_id=5854929766146118183>❌</emoji> Мониторинг остановлен",
|
||||||
"monitoring_status": "Мониторинг {}",
|
"monitoring_status": "<emoji document_id=5188311512791393083>🌎</emoji> Мониторинг <b>{}</b>",
|
||||||
"triggers_example": "Пример: <code>.triggers слово1 слово2</code>",
|
"triggers_example": "<emoji document_id=5854929766146118183>❌</emoji> Пример: <code>.triggers слово1 слово2</code>",
|
||||||
"monitoring_status_on": "включен",
|
"monitoring_status_on": "<emoji document_id=5854762571659218443>✅</emoji> включен",
|
||||||
"monitoring_status_off": "выключен",
|
"monitoring_status_off": "<emoji document_id=5854929766146118183>❌</emoji> выключен",
|
||||||
"ignore_set": "Игнорируемые чаты установлены: {}",
|
"ignore_set": "<emoji document_id=5854762571659218443>✅</emoji> Игнорируемые чаты установлены: <code>{}</code>",
|
||||||
"ignore_none": "Игнорируемые чаты не установлены",
|
"ignore_none": "<emoji document_id=5854929766146118183>❌</emoji> Игнорируемые чаты не установлены",
|
||||||
"ignore_example": "Пример: <code>.ignore 123456789 -987654321</code> (ID чатов)",
|
"ignore_example": "<emoji document_id=5854929766146118183>❌</emoji> Пример: <code>.ignore 123456789 -987654321</code> (ID чатов)",
|
||||||
"no_reply": "Ответьте на сообщение в нужном чате или укажите его ID",
|
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Ответьте на сообщение в нужном чате или укажите его ID",
|
||||||
"monitoring_msg": (
|
"monitoring_msg": (
|
||||||
"🚨 **Обнаружено триггерное слово!** 🚨\n\n"
|
"<emoji document_id=5854929766146118183>🚨</emoji> <b>Обнаружено триггерное слово!</b> <emoji document_id=5854929766146118183>🚨</emoji>\n\n"
|
||||||
"**Чат:** {} (`{}`)\n"
|
"<b>Чат:</b> <code>{}</code>\n"
|
||||||
"**Пользователь:** {}\n"
|
"<b>Пользователь:</b> {}\n"
|
||||||
"**Ссылка:** {}\n\n"
|
"<b>Ссылка:</b> <a href='{}'>{}</a>\n\n"
|
||||||
"**Сообщение:**\n{}"
|
"<b>Сообщение:</b>\n{}"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,35 +97,61 @@ class MessageMonitor(loader.Module):
|
|||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"triggers",
|
"triggers",
|
||||||
[],
|
[],
|
||||||
"Список триггерных слов",
|
"List of trigger words to monitor",
|
||||||
validator=loader.validators.Series(),
|
validator=loader.validators.Series(),
|
||||||
),
|
),
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"target_chat",
|
"target_chat",
|
||||||
None,
|
None,
|
||||||
"ID целевого чата для отправки уведомлений",
|
"Target chat ID for notifications",
|
||||||
validator=loader.validators.Integer(),
|
validator=loader.validators.Integer(),
|
||||||
),
|
),
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"ignore_chats",
|
"ignore_chats",
|
||||||
[],
|
[],
|
||||||
"Список ID чатов, которые будут игнорироваться",
|
"List of chat IDs to ignore",
|
||||||
validator=loader.validators.Series(),
|
validator=loader.validators.Series(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self._triggers = []
|
self._triggers: List[str] = []
|
||||||
self._target_chat = None
|
self._target_chat: Optional[int] = None
|
||||||
self._ignore_chats = []
|
self._ignore_chats: List[int] = []
|
||||||
|
self._compiled_patterns: List[re.Pattern] = []
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
self._triggers = self.config["triggers"]
|
"""Initialize module when client is ready"""
|
||||||
|
await self._update_config()
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
async def _update_config(self):
|
||||||
|
"""Update internal configuration and compile regex patterns"""
|
||||||
|
self._triggers = [trigger.lower() for trigger in self.config["triggers"]]
|
||||||
self._target_chat = self.config["target_chat"]
|
self._target_chat = self.config["target_chat"]
|
||||||
self._ignore_chats = [
|
self._ignore_chats = [
|
||||||
int(i)
|
int(chat_id)
|
||||||
for i in self.config["ignore_chats"]
|
for chat_id in self.config["ignore_chats"]
|
||||||
if str(i).isdigit() or str(i).startswith("-")
|
if str(chat_id).lstrip("-").isdigit()
|
||||||
]
|
]
|
||||||
self.client = client
|
|
||||||
|
self._compiled_patterns = [
|
||||||
|
re.compile(r"\b" + re.escape(trigger) + r"\b", re.IGNORECASE)
|
||||||
|
for trigger in self._triggers
|
||||||
|
]
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Показать статус мониторинга",
|
||||||
|
en_doc="Show monitoring status",
|
||||||
|
)
|
||||||
|
async def status(self, message: Message):
|
||||||
|
"""Show current monitoring status"""
|
||||||
|
status_text = (
|
||||||
|
self.strings["monitoring_status_on"]
|
||||||
|
if self._target_chat and self._triggers
|
||||||
|
else self.strings["monitoring_status_off"]
|
||||||
|
)
|
||||||
|
await utils.answer(
|
||||||
|
message, self.strings["monitoring_status"].format(status_text)
|
||||||
|
)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Установить триггерные слова. Пример: .triggers слово1 слово2",
|
ru_doc="Установить триггерные слова. Пример: .triggers слово1 слово2",
|
||||||
@@ -138,6 +166,7 @@ class MessageMonitor(loader.Module):
|
|||||||
|
|
||||||
self._triggers = [arg.lower() for arg in args]
|
self._triggers = [arg.lower() for arg in args]
|
||||||
self.config["triggers"] = self._triggers
|
self.config["triggers"] = self._triggers
|
||||||
|
await self._update_config()
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
message, self.strings["triggers_set"].format(", ".join(self._triggers))
|
message, self.strings["triggers_set"].format(", ".join(self._triggers))
|
||||||
)
|
)
|
||||||
@@ -152,12 +181,10 @@ class MessageMonitor(loader.Module):
|
|||||||
chat_id = None
|
chat_id = None
|
||||||
|
|
||||||
if getattr(message, "is_reply", False):
|
if getattr(message, "is_reply", False):
|
||||||
get_reply_method = getattr(message, "get_reply_message", None)
|
reply_message = await message.get_reply_message()
|
||||||
if get_reply_method:
|
if reply_message and hasattr(reply_message, "chat_id"):
|
||||||
reply_message = await get_reply_method()
|
chat_id = reply_message.chat_id
|
||||||
if reply_message and getattr(reply_message, "chat_id", None):
|
elif args and (args.isdigit() or (args.startswith("-") and args[1:].isdigit())):
|
||||||
chat_id = reply_message.chat_id
|
|
||||||
elif args and (args.isdigit() or args.startswith("-") and args[1:].isdigit()):
|
|
||||||
chat_id = int(args)
|
chat_id = int(args)
|
||||||
|
|
||||||
if chat_id:
|
if chat_id:
|
||||||
@@ -184,7 +211,7 @@ class MessageMonitor(loader.Module):
|
|||||||
valid_ids.append(int(arg))
|
valid_ids.append(int(arg))
|
||||||
|
|
||||||
self.config["ignore_chats"] = valid_ids
|
self.config["ignore_chats"] = valid_ids
|
||||||
self._ignore_chats = valid_ids
|
await self._update_config()
|
||||||
|
|
||||||
if valid_ids:
|
if valid_ids:
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
@@ -192,19 +219,15 @@ class MessageMonitor(loader.Module):
|
|||||||
self.strings["ignore_set"].format(", ".join(map(str, valid_ids))),
|
self.strings["ignore_set"].format(", ".join(map(str, valid_ids))),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await utils.answer(
|
await utils.answer(message, self.strings["ignore_none"])
|
||||||
message, "Не удалось распознать ID чатов. Используйте только числа."
|
|
||||||
)
|
|
||||||
|
|
||||||
@loader.watcher(out=False, only_messages=True)
|
@loader.watcher(out=False, only_messages=True)
|
||||||
async def message_watcher(self, message: Message):
|
async def message_watcher(self, message: Message):
|
||||||
"""Watch for messages containing trigger words"""
|
"""Watch for messages containing trigger words"""
|
||||||
|
|
||||||
if not self._target_chat or not self._triggers:
|
if not self._target_chat or not self._triggers:
|
||||||
return
|
return
|
||||||
|
|
||||||
chat_id = getattr(message, "chat_id", None)
|
chat_id = getattr(message, "chat_id", None)
|
||||||
|
|
||||||
if chat_id and chat_id in self._ignore_chats:
|
if chat_id and chat_id in self._ignore_chats:
|
||||||
logger.debug(f"Message in ignored chat: {chat_id}. Skipping monitoring.")
|
logger.debug(f"Message in ignored chat: {chat_id}. Skipping monitoring.")
|
||||||
return
|
return
|
||||||
@@ -213,34 +236,27 @@ class MessageMonitor(loader.Module):
|
|||||||
if not text:
|
if not text:
|
||||||
return
|
return
|
||||||
|
|
||||||
text_lower = text.lower()
|
|
||||||
|
|
||||||
found_triggers = [
|
found_triggers = [
|
||||||
trigger
|
trigger
|
||||||
for trigger in self._triggers
|
for pattern, trigger in zip(self._compiled_patterns, self._triggers)
|
||||||
if re.search(r"\b" + re.escape(trigger) + r"\b", text_lower)
|
if pattern.search(text)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not found_triggers:
|
if not found_triggers:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
get_chat_method = getattr(message, "get_chat", None)
|
chat = await message.get_chat()
|
||||||
if get_chat_method:
|
chat_title = getattr(
|
||||||
chat = await get_chat_method()
|
chat,
|
||||||
chat_title = getattr(
|
"title",
|
||||||
chat,
|
"Личные сообщения"
|
||||||
"title",
|
if getattr(message, "is_private", False)
|
||||||
"Личные сообщения"
|
else f"Чат с ID {chat_id}",
|
||||||
if getattr(message, "is_private", False)
|
)
|
||||||
else "Чат с ID " + str(chat_id),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
chat_title = "Неизвестный чат"
|
|
||||||
|
|
||||||
get_sender_method = getattr(message, "get_sender", None)
|
sender = await message.get_sender()
|
||||||
if get_sender_method:
|
if sender:
|
||||||
sender = await get_sender_method()
|
|
||||||
sender_name = sender.first_name
|
sender_name = sender.first_name
|
||||||
if getattr(sender, "last_name", None):
|
if getattr(sender, "last_name", None):
|
||||||
sender_name += f" {sender.last_name}"
|
sender_name += f" {sender.last_name}"
|
||||||
@@ -251,13 +267,7 @@ class MessageMonitor(loader.Module):
|
|||||||
else:
|
else:
|
||||||
sender_name = "Неизвестный пользователь"
|
sender_name = "Неизвестный пользователь"
|
||||||
|
|
||||||
to_id_obj = getattr(message, "to_id", None)
|
link = await self._get_message_link(message, sender)
|
||||||
if to_id_obj and getattr(to_id_obj, "channel_id", None):
|
|
||||||
link = f"https://t.me/c/{to_id_obj.channel_id}/{message.id}"
|
|
||||||
elif getattr(message, "is_private", False) and getattr(
|
|
||||||
sender, "username", None
|
|
||||||
):
|
|
||||||
link = f"https://t.me/{sender.username}/{message.id}"
|
|
||||||
|
|
||||||
await self.client.send_message(
|
await self.client.send_message(
|
||||||
self._target_chat,
|
self._target_chat,
|
||||||
@@ -268,10 +278,28 @@ class MessageMonitor(loader.Module):
|
|||||||
link,
|
link,
|
||||||
text,
|
text,
|
||||||
),
|
),
|
||||||
parse_mode="Markdown",
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Sent notification about trigger word(s) {found_triggers} to chat {self._target_chat}"
|
f"Sent notification about trigger word(s) {found_triggers} to chat {self._target_chat}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing message: {e}")
|
logger.error(f"Error processing message: {e}")
|
||||||
|
|
||||||
|
async def _get_message_link(self, message: Message, sender) -> str:
|
||||||
|
"""Generate message link based on message type"""
|
||||||
|
message_id = message.id
|
||||||
|
|
||||||
|
if getattr(message, "to_id", None):
|
||||||
|
to_id_obj = getattr(message, "to_id")
|
||||||
|
if getattr(to_id_obj, "channel_id", None):
|
||||||
|
return f"https://t.me/c/{to_id_obj.channel_id}/{message_id}"
|
||||||
|
|
||||||
|
if (
|
||||||
|
getattr(message, "is_private", False)
|
||||||
|
and sender
|
||||||
|
and getattr(sender, "username", None)
|
||||||
|
):
|
||||||
|
return f"https://t.me/{sender.username}/{message_id}"
|
||||||
|
|
||||||
|
return f"https://t.me/c/{message_id}"
|
||||||
|
|||||||
@@ -27,22 +27,19 @@
|
|||||||
# requires: aioredis
|
# requires: aioredis
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
__version__ = (0, 1, 4, 10)
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import typing
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
import aioredis
|
import aioredis
|
||||||
from typing import Optional
|
|
||||||
from telethon.tl.types import Message
|
|
||||||
from telethon.tl.types import InputDocument
|
|
||||||
from telethon.tl.types import User
|
|
||||||
from telethon import events
|
from telethon import events
|
||||||
|
from telethon.tl.types import InputDocument, Message
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
from ..inline.types import InlineCall
|
from ..inline.types import InlineCall
|
||||||
import json
|
|
||||||
|
__version__ = (0, 1, 4, 10)
|
||||||
|
|
||||||
|
|
||||||
class DebugLogger:
|
class DebugLogger:
|
||||||
@@ -127,7 +124,7 @@ class AutoFarmbotMod(loader.Module):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Todo: Автокрафт и Автолес готовы на 95%, автохавка на 45%
|
# NOTE: Автокрафт и Автолес готовы на 95%, автохавка на 45%
|
||||||
strings = {
|
strings = {
|
||||||
"name": "AutoFarmbot",
|
"name": "AutoFarmbot",
|
||||||
# Inline keys
|
# Inline keys
|
||||||
@@ -1112,7 +1109,7 @@ class AutoFarmbotMod(loader.Module):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.config[config_key] = current
|
self.config[config_key] = current
|
||||||
except Exception as e:
|
except Exception:
|
||||||
await call.answer("❌ Ошибка валидации")
|
await call.answer("❌ Ошибка валидации")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1122,7 +1119,7 @@ class AutoFarmbotMod(loader.Module):
|
|||||||
await call.answer("🔄 Синхронизация началась...")
|
await call.answer("🔄 Синхронизация началась...")
|
||||||
|
|
||||||
chat_id = self.get_chat_id
|
chat_id = self.get_chat_id
|
||||||
bot_id = self.config["config_bot_used_bot"]
|
self.config["config_bot_used_bot"]
|
||||||
|
|
||||||
msg = await self.client.send_message(chat_id, "/cow")
|
msg = await self.client.send_message(chat_id, "/cow")
|
||||||
start_id = msg.id
|
start_id = msg.id
|
||||||
@@ -1198,7 +1195,6 @@ class AutoFarmbotMod(loader.Module):
|
|||||||
|
|
||||||
async def eating_handler(self, event):
|
async def eating_handler(self, event):
|
||||||
chat_id = self.get_chat_id
|
chat_id = self.get_chat_id
|
||||||
user_id = self.tg_id
|
|
||||||
food = self.config["config_bot_eat_lvl"]
|
food = self.config["config_bot_eat_lvl"]
|
||||||
if event.chat_id != chat_id:
|
if event.chat_id != chat_id:
|
||||||
return
|
return
|
||||||
@@ -1662,7 +1658,7 @@ class AutoFarmbotMod(loader.Module):
|
|||||||
:param action:
|
:param action:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
chat_id = self.config["config_bot_used_chat_id"]
|
self.config["config_bot_used_chat_id"]
|
||||||
user_id = self.tg_id
|
user_id = self.tg_id
|
||||||
key = f"forest_task:{user_id}:{action}"
|
key = f"forest_task:{user_id}:{action}"
|
||||||
await self.redis.set(key, "pending", ex=wait_time)
|
await self.redis.set(key, "pending", ex=wait_time)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
# Name: Music
|
# Name: Music
|
||||||
# Description: Searches for music using Telegram music bots.
|
# Description: Searches for music using Telegram music bots
|
||||||
# Author: @hikka_mods
|
# Author: @hikka_mods
|
||||||
# meta developer: @hikka_mods
|
# meta developer: @hikka_mods
|
||||||
# scope: Music
|
# scope: Music
|
||||||
@@ -45,49 +45,46 @@ logger = logging.getLogger(__name__)
|
|||||||
class MusicMod(loader.Module):
|
class MusicMod(loader.Module):
|
||||||
strings = {
|
strings = {
|
||||||
"name": "Music",
|
"name": "Music",
|
||||||
"no_query": "<emoji document_id=5337117114392127164>🤷♂</emoji> <b>Provide a search query.</b>",
|
"no_query": "<emoji document_id=5337117114392127164>🤷♂</emoji> <b>Provide a search query!</b>",
|
||||||
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Searching...</b>",
|
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Searching...</b>",
|
||||||
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Possible match:</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>",
|
"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 [track name]</code>",
|
||||||
"usage": "<b>Usage:</b> <code>.music [yandex|vk] [track name]</code>",
|
|
||||||
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Error:</b> <code>{}</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>",
|
"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>",
|
"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>",
|
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Bot error: <code>{}</code></b>",
|
||||||
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>No audio.</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>",
|
"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_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_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>",
|
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Error (Yandex): {}</b>",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"name": "Music",
|
"name": "Music",
|
||||||
"no_query": "<emoji document_id=5337117114392127164>🤷♂</emoji> <b>Укажите запрос.</b>",
|
"no_query": "<emoji document_id=5337117114392127164>🤷♂</emoji> <b>Укажите запрос!</b>",
|
||||||
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Поиск...</b>",
|
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Поиск...</b>",
|
||||||
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Возможно, это оно:</b>",
|
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Возможно, это оно:</b>",
|
||||||
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Трек не найден: <code>{}</code>.</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 [название трека]</code>",
|
||||||
"usage": "<b>Использование:</b> <code>.music [yandex|vk] [название трека]</code>",
|
|
||||||
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Ошибка:</b> <code>{}</code>",
|
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Ошибка:</b> <code>{}</code>",
|
||||||
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>Нет результатов: <code>{}</code>.</b>",
|
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>Нет результатов: <code>{}</code></b>",
|
||||||
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Подождите {}с (лимиты Telegram).</b>",
|
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Подождите {}с (лимиты Telegram)</b>",
|
||||||
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Ошибка бота: <code>{}</code></b>",
|
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Ошибка бота: <code>{}</code></b>",
|
||||||
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>Нет аудио.</b>",
|
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>Нет аудио</b>",
|
||||||
"generic_result": "<emoji document_id=5336965905773504919>ℹ️</emoji> <b>Немедийный результат. Проверьте чат с ботом.</b>",
|
"generic_result": "<emoji document_id=5336965905773504919>ℹ️</emoji> <b>Немедийный результат. Проверьте чат с ботом</b>",
|
||||||
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Поиск в Яндекс.Музыке...</b>",
|
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Поиск в Яндекс.Музыке...</b>",
|
||||||
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Трек не найден в Яндекс.Музыке.</b>",
|
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Трек не найден в Яндекс.Музыке</b>",
|
||||||
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Ошибка (Яндекс): {}</b>",
|
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Ошибка (Яндекс): {}</b>",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.murglar_bot = "@murglar_bot"
|
self.murglar_bot = "@murglar_bot"
|
||||||
self.vk_bot = "@vkmusic_bot"
|
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Найти трек в Yandex Music или VK: `.music yandex {название}` или `.music vk {название}`",
|
ru_doc="Найти трек в Yandex.Music: `.music {название}`",
|
||||||
en_doc="Find a track in Yandex Music or VK: `.music yandex {name}` or `.music vk {name}`",
|
en_doc="Find a track in Yandex.Music: `.music yandex {name}`",
|
||||||
)
|
)
|
||||||
async def music(self, message):
|
async def music(self, message):
|
||||||
args = utils.get_args(message)
|
args = utils.get_args(message)
|
||||||
@@ -98,15 +95,8 @@ class MusicMod(loader.Module):
|
|||||||
else:
|
else:
|
||||||
await utils.answer(message, self.strings("usage", message))
|
await utils.answer(message, self.strings("usage", message))
|
||||||
return
|
return
|
||||||
|
|
||||||
service, query = args[0].lower(), " ".join(args[1:])
|
await self._yafind(message, query=args)
|
||||||
|
|
||||||
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):
|
async def _yafind(self, message: Message, query: str):
|
||||||
if not query:
|
if not query:
|
||||||
@@ -134,69 +124,3 @@ class MusicMod(loader.Module):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Yandex search error:")
|
logger.exception("Yandex search error:")
|
||||||
await utils.answer(message, self.strings("yafind_error", message).format(e))
|
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)
|
|
||||||
)
|
|
||||||
@@ -26,10 +26,13 @@
|
|||||||
# scope: VowelReplacer 0.0.1
|
# scope: VowelReplacer 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from telethon.tl.types import Message
|
from telethon.tl.types import Message
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class VowelReplacer(loader.Module):
|
class VowelReplacer(loader.Module):
|
||||||
|
|||||||
@@ -27,11 +27,15 @@
|
|||||||
# requires: zipfile
|
# requires: zipfile
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import zipfile
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import zipfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class SMArchiver(loader.Module):
|
class SMArchiver(loader.Module):
|
||||||
@@ -64,7 +68,7 @@ class SMArchiver(loader.Module):
|
|||||||
await utils.answer(message, self.strings["no_messages"])
|
await utils.answer(message, self.strings["no_messages"])
|
||||||
return
|
return
|
||||||
|
|
||||||
archive_path = self.create_archive(saved_messages)
|
archive_path = await self.create_archive(saved_messages)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await message.client.send_file(
|
await message.client.send_file(
|
||||||
@@ -79,7 +83,7 @@ class SMArchiver(loader.Module):
|
|||||||
finally:
|
finally:
|
||||||
self.cleanup(archive_path)
|
self.cleanup(archive_path)
|
||||||
|
|
||||||
def create_archive(self, saved_messages):
|
async def create_archive(self, saved_messages):
|
||||||
current_month = datetime.now().strftime("%B %Y")
|
current_month = datetime.now().strftime("%B %Y")
|
||||||
archive_path = "saved_messages.zip"
|
archive_path = "saved_messages.zip"
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,12 @@
|
|||||||
# scope: TaskManager 0.0.1
|
# scope: TaskManager 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
@@ -46,103 +47,138 @@ class Task:
|
|||||||
due_date: Optional[datetime.datetime] = None
|
due_date: Optional[datetime.datetime] = None
|
||||||
completed: bool = False
|
completed: bool = False
|
||||||
created_at: datetime.datetime = field(default_factory=datetime.datetime.now)
|
created_at: datetime.datetime = field(default_factory=datetime.datetime.now)
|
||||||
|
id: str = field(default_factory=lambda: f"{datetime.datetime.now().timestamp()}")
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Convert task to dictionary for JSON serialization."""
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"description": self.description,
|
||||||
|
"due_date": self.due_date.isoformat() if self.due_date else None,
|
||||||
|
"completed": self.completed,
|
||||||
|
"created_at": self.created_at.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> "Task":
|
||||||
|
"""Create task from dictionary."""
|
||||||
|
return cls(
|
||||||
|
id=data.get("id", f"{datetime.datetime.now().timestamp()}"),
|
||||||
|
description=data["description"],
|
||||||
|
due_date=datetime.datetime.fromisoformat(data["due_date"])
|
||||||
|
if data.get("due_date")
|
||||||
|
else None,
|
||||||
|
completed=data["completed"],
|
||||||
|
created_at=datetime.datetime.fromisoformat(data["created_at"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaskManager:
|
class TaskManager:
|
||||||
"""Manages tasks, storing them in a JSON file."""
|
"""Manages tasks, storing them in a JSON file."""
|
||||||
|
|
||||||
def __init__(self, data_file: str):
|
def __init__(self, data_file: str):
|
||||||
self.data_file = data_file
|
self.data_file = Path(data_file)
|
||||||
self.tasks: Dict[int, List[Task]] = {}
|
self.tasks: Dict[int, List[Task]] = {}
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
self.load_data()
|
self.load_data()
|
||||||
|
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
"""Loads task data from the JSON file."""
|
"""Loads task data from the JSON file."""
|
||||||
if os.path.exists(self.data_file):
|
if not self.data_file.exists():
|
||||||
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 = {}
|
self.tasks = {}
|
||||||
logger.info("Task data file not found. Starting empty.")
|
logger.info("Task data file not found. Starting empty.")
|
||||||
|
return
|
||||||
|
|
||||||
def save_data(self):
|
|
||||||
"""Saves task data to the JSON file."""
|
|
||||||
try:
|
try:
|
||||||
with open(self.data_file, "w") as f:
|
with open(self.data_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
self.tasks = {
|
||||||
|
int(user_id): [Task.from_dict(task) for task in task_list]
|
||||||
|
for user_id, task_list in data.items()
|
||||||
|
}
|
||||||
|
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
||||||
|
logger.warning(f"Failed to load task data: {e}. Starting empty.")
|
||||||
|
self.tasks = {}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error loading task data: {e}")
|
||||||
|
self.tasks = {}
|
||||||
|
|
||||||
|
async def save_data(self):
|
||||||
|
"""Saves task data to the JSON file."""
|
||||||
|
async with self._lock:
|
||||||
|
try:
|
||||||
|
self.data_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
data = {
|
data = {
|
||||||
user_id: [
|
str(user_id): [task.to_dict() for task in task_list]
|
||||||
{
|
|
||||||
"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()
|
for user_id, task_list in self.tasks.items()
|
||||||
}
|
}
|
||||||
json.dump(data, f, indent=4, default=str)
|
with open(self.data_file, "w", encoding="utf-8") as f:
|
||||||
except IOError as e:
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
logger.error(f"Failed to save task data: {e}")
|
except IOError as e:
|
||||||
|
logger.error(f"Failed to save task data: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error saving task data: {e}")
|
||||||
|
|
||||||
def add_task(self, user_id: int, task: Task):
|
async def add_task(self, user_id: int, task: Task):
|
||||||
self.tasks.setdefault(user_id, []).append(task)
|
self.tasks.setdefault(user_id, []).append(task)
|
||||||
self.save_data()
|
await self.save_data()
|
||||||
|
|
||||||
def remove_task(self, user_id: int, index: int):
|
async def remove_task(self, user_id: int, index: int) -> bool:
|
||||||
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
|
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
|
||||||
del self.tasks[user_id][index]
|
del self.tasks[user_id][index]
|
||||||
self.save_data()
|
await self.save_data()
|
||||||
else:
|
return True
|
||||||
logger.warning(f"Invalid index for removal: {index}, user: {user_id}")
|
logger.warning(f"Invalid index for removal: {index}, user: {user_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
def complete_task(self, user_id: int, index: int):
|
async def complete_task(self, user_id: int, index: int) -> bool:
|
||||||
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
|
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
|
||||||
self.tasks[user_id][index].completed = True
|
self.tasks[user_id][index].completed = True
|
||||||
self.save_data()
|
await self.save_data()
|
||||||
else:
|
return True
|
||||||
logger.warning(f"Invalid index for completion: {index}, user: {user_id}")
|
logger.warning(f"Invalid index for completion: {index}, user: {user_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_tasks(self, user_id: int) -> List[Task]:
|
def get_tasks(self, user_id: int, include_completed: bool = True) -> List[Task]:
|
||||||
return self.tasks.get(user_id, [])
|
tasks = self.tasks.get(user_id, [])
|
||||||
|
if not include_completed:
|
||||||
|
tasks = [task for task in tasks if not task.completed]
|
||||||
|
return tasks
|
||||||
|
|
||||||
def clear_tasks(self, user_id: int):
|
async def clear_tasks(self, user_id: int) -> bool:
|
||||||
if user_id in self.tasks:
|
if user_id in self.tasks:
|
||||||
self.tasks[user_id] = []
|
self.tasks[user_id] = []
|
||||||
self.save_data()
|
await self.save_data()
|
||||||
else:
|
return True
|
||||||
logger.info(f"No tasks to clear for user: {user_id}")
|
logger.info(f"No tasks to clear for user: {user_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_task(self, user_id: int, index: int) -> Optional[Task]:
|
def get_task(self, user_id: int, index: int) -> Optional[Task]:
|
||||||
return (
|
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
|
||||||
self.tasks[user_id][index]
|
return self.tasks[user_id][index]
|
||||||
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id])
|
return None
|
||||||
else None
|
|
||||||
)
|
def get_overdue_tasks(self, user_id: int) -> List[Task]:
|
||||||
|
"""Get overdue tasks for a user."""
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return [
|
||||||
|
task
|
||||||
|
for task in self.get_tasks(user_id)
|
||||||
|
if task.due_date and task.due_date < now and not task.completed
|
||||||
|
]
|
||||||
|
|
||||||
|
async def update_task(self, user_id: int, index: int, **kwargs) -> bool:
|
||||||
|
"""Update task properties."""
|
||||||
|
task = self.get_task(user_id, index)
|
||||||
|
if not task:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if hasattr(task, key):
|
||||||
|
setattr(task, key, value)
|
||||||
|
|
||||||
|
await self.save_data()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
@@ -198,7 +234,9 @@ class TaskManagerModule(loader.Module):
|
|||||||
self.task_manager: Optional[TaskManager] = None
|
self.task_manager: Optional[TaskManager] = None
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
self.task_manager = TaskManager(os.path.join(os.getcwd(), "tasks.json"))
|
data_dir = Path.cwd() / "data"
|
||||||
|
data_dir.mkdir(exist_ok=True)
|
||||||
|
self.task_manager = TaskManager(str(data_dir / "tasks.json"))
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>",
|
ru_doc="Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>",
|
||||||
@@ -230,7 +268,7 @@ class TaskManagerModule(loader.Module):
|
|||||||
return
|
return
|
||||||
|
|
||||||
task = Task(description=description, due_date=due_date)
|
task = Task(description=description, due_date=due_date)
|
||||||
self.task_manager.add_task(message.sender_id, task)
|
await self.task_manager.add_task(message.sender_id, task)
|
||||||
await utils.answer(message, self.strings("task_added"))
|
await utils.answer(message, self.strings("task_added"))
|
||||||
|
|
||||||
@loader.command(ru_doc="[index] - удалить задачу", en_doc="[index] - remove task")
|
@loader.command(ru_doc="[index] - удалить задачу", en_doc="[index] - remove task")
|
||||||
@@ -246,12 +284,10 @@ class TaskManagerModule(loader.Module):
|
|||||||
await utils.answer(message, self.strings("invalid_index"))
|
await utils.answer(message, self.strings("invalid_index"))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.task_manager.get_task(message.sender_id, index) is None:
|
if await self.task_manager.remove_task(message.sender_id, index):
|
||||||
|
await utils.answer(message, self.strings("task_removed"))
|
||||||
|
else:
|
||||||
await utils.answer(message, self.strings("task_not_found"))
|
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(
|
@loader.command(
|
||||||
ru_doc="[index] - Завершите задачу", en_doc="[index] - Complete task"
|
ru_doc="[index] - Завершите задачу", en_doc="[index] - Complete task"
|
||||||
@@ -268,12 +304,10 @@ class TaskManagerModule(loader.Module):
|
|||||||
await utils.answer(message, self.strings("invalid_index"))
|
await utils.answer(message, self.strings("invalid_index"))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.task_manager.get_task(message.sender_id, index) is None:
|
if await self.task_manager.complete_task(message.sender_id, index):
|
||||||
|
await utils.answer(message, self.strings("task_completed"))
|
||||||
|
else:
|
||||||
await utils.answer(message, self.strings("task_not_found"))
|
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")
|
@loader.command(ru_doc="Список задач", en_doc="List tasks")
|
||||||
async def tasklist(self, message):
|
async def tasklist(self, message):
|
||||||
@@ -342,8 +376,10 @@ class TaskManagerModule(loader.Module):
|
|||||||
|
|
||||||
async def clear_confirm(self, call):
|
async def clear_confirm(self, call):
|
||||||
"""Callback for confirming task clearing."""
|
"""Callback for confirming task clearing."""
|
||||||
self.task_manager.clear_tasks(call.from_user.id)
|
if await self.task_manager.clear_tasks(call.from_user.id):
|
||||||
await call.edit(self.strings("tasks_cleared"))
|
await call.edit(self.strings("tasks_cleared"))
|
||||||
|
else:
|
||||||
|
await call.edit(self.strings("no_tasks"))
|
||||||
|
|
||||||
async def clear_cancel(self, call):
|
async def clear_cancel(self, call):
|
||||||
"""Callback for canceling task clearing."""
|
"""Callback for canceling task clearing."""
|
||||||
|
|||||||
@@ -26,8 +26,12 @@
|
|||||||
# scope: Api TelegramStatusCodes 0.0.1
|
# scope: Api TelegramStatusCodes 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
responses = {
|
responses = {
|
||||||
300: (
|
300: (
|
||||||
"⛔ SEE_OTHER",
|
"⛔ SEE_OTHER",
|
||||||
@@ -74,6 +78,52 @@ If a client receives a 500 error, or you believe this error should not have occu
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses_ru = {
|
||||||
|
300: (
|
||||||
|
"⛔ SEE_OTHER",
|
||||||
|
"Запрос должен быть повторен, но направлен в другой дата-центр.",
|
||||||
|
),
|
||||||
|
400: (
|
||||||
|
"⛔ BAD_REQUEST",
|
||||||
|
"Запрос содержит ошибки. В случае, если запрос был создан с помощью формы и содержит данные, введенные пользователем, пользователю следует сообщить, что данные должны быть исправлены перед повторным выполнением запроса.",
|
||||||
|
),
|
||||||
|
401: (
|
||||||
|
"⛔ UNAUTHORIZED",
|
||||||
|
"Была совершена неавторизованная попытка использовать функциональность, доступную только авторизованным пользователям.",
|
||||||
|
),
|
||||||
|
403: (
|
||||||
|
"⛔ FORBIDDEN",
|
||||||
|
"Нарушение конфиденциальности. Например, попытка написать сообщение пользователю, который добавил текущего пользователя в черный список.",
|
||||||
|
),
|
||||||
|
404: (
|
||||||
|
"⛔ NOT_FOUND",
|
||||||
|
"Попытка обращения к несуществующему объекту, например, к методу.",
|
||||||
|
),
|
||||||
|
406: (
|
||||||
|
"⛔ NOT_ACCEPTABLE",
|
||||||
|
"""
|
||||||
|
Аналогично <b>400 BAD_REQUESTS</b>, но приложение должно отображать ошибку пользователю немного иначе.
|
||||||
|
Не показывайте пользователю видимую ошибку при получении конструктора <b>rpc_error</b>: вместо этого дождитесь обновления <a href="https://core.telegram.org/constructor/updateServiceNotification">updateServiceNotification</a> и обработайте его как обычно.
|
||||||
|
По сути, обновление-всплывающее окно <b>updateServiceNotification</b> будет отправлено независимо (т.е. НЕ как конструктор <b>Updates</b> внутри <b>rpc_result</b>, а как обычное обновление) сразу после выдачи 406 <b>rpc_error</b>: обновление будет содержать актуальное локализованное сообщение об ошибке для показа пользователю в интерфейсе.
|
||||||
|
|
||||||
|
Исключением является ошибка <b>AUTH_KEY_DUPLICATED</b>, которая возникает только в том случае, если любой из не-медиа DC обнаруживает, что авторизованная сессия отправляет запросы параллельно из двух отдельных TCP-соединений с одного или разных IP-адресов.
|
||||||
|
Обратите внимание, что параллельные соединения по-прежнему разрешены и фактически рекомендуются для медиа-DC.
|
||||||
|
Также обратите внимание, что под сессией понимается авторизованная сессия, идентифицируемая конструктором <a href="https://core.telegram.org/constructor/authorization">authorization</a>, которую можно получить с помощью <a href="https://core.telegram.org/method/account.getAuthorizations">account.getAuthorizations</a>, а не сессия MTProto.
|
||||||
|
|
||||||
|
Если клиент получает ошибку <b>AUTH_KEY_DUPLICATED</b>, сессия уже была аннулирована сервером, и пользователю необходимо сгенерировать новый ключ авторизации и войти снова.""",
|
||||||
|
),
|
||||||
|
420: (
|
||||||
|
"⛔ FLOOD",
|
||||||
|
"Превышено максимально допустимое количество попыток вызова данного метода с указанными входными параметрами. Например, при попытке запросить большое количество текстовых сообщений (SMS) для одного и того же номера телефона.",
|
||||||
|
),
|
||||||
|
500: (
|
||||||
|
"⛔ INTERNAL",
|
||||||
|
"""Произошла внутренняя ошибка сервера во время обработки запроса; например, произошел сбой при доступе к базе данных или файловому хранилищу.
|
||||||
|
|
||||||
|
Если клиент получает ошибку 500 или вы считаете, что эта ошибка не должна была возникнуть, пожалуйста, соберите как можно больше информации о запросе и ошибке и отправьте ее разработчикам.""",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class TelegramStatusCodes(loader.Module):
|
class TelegramStatusCodes(loader.Module):
|
||||||
@@ -91,20 +141,28 @@ class TelegramStatusCodes(loader.Module):
|
|||||||
"args_incorrect": "<b>Неверные аргументы</b>",
|
"args_incorrect": "<b>Неверные аргументы</b>",
|
||||||
"not_found": "<b>Код не найден</b>",
|
"not_found": "<b>Код не найден</b>",
|
||||||
"syntax_error": "<b>Аргументы обязательны</b>",
|
"syntax_error": "<b>Аргументы обязательны</b>",
|
||||||
"_cmd_doc_httpsc": "<код> - Получить информацию о Telegram error",
|
"_cmd_doc_httpsc": "<код> - Получить информацию о статус-коде",
|
||||||
"_cmd_doc_httpscs": "Показать все доступные коды",
|
"_cmd_doc_httpscs": "Показать все доступные коды",
|
||||||
"_cls_doc": "Словарь telegram error",
|
"_cls_doc": "Словарь статус-кодов Telegram",
|
||||||
|
"scode": "<b>{} {}</b>\n⚜️ Описание статус-кода: <i>{}</i>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self.ub_lang = self._db.get("hikka.translations", "lang")
|
||||||
|
if not self.ub_lang:
|
||||||
|
self.ub_lang = self._db.get("heroku.translations", "lang")
|
||||||
|
|
||||||
@loader.unrestricted
|
@loader.unrestricted
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="<код состояния> - Получение информации о коде состояния",
|
ru_doc="<код состояния> - Получение информации о статус-коде",
|
||||||
en_doc="<statuscode> - Get status code info",
|
en_doc="<statuscode> - Get status code info",
|
||||||
)
|
)
|
||||||
async def tgccmd(self, message):
|
async def tgccmd(self, message):
|
||||||
|
|
||||||
args = utils.get_args(message)
|
args = utils.get_args(message)
|
||||||
if not args:
|
if not args:
|
||||||
await utils.answer(message, self.strings("syntax_error", message))
|
await utils.answer(message, self.strings("syntax_error", message))
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if int(args[0]) not in responses:
|
if int(args[0]) not in responses:
|
||||||
@@ -112,17 +170,26 @@ class TelegramStatusCodes(loader.Module):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
await utils.answer(message, self.strings("args_incorrect", message))
|
await utils.answer(message, self.strings("args_incorrect", message))
|
||||||
|
|
||||||
await utils.answer(
|
if self.ub_lang != "ru":
|
||||||
message,
|
await utils.answer(
|
||||||
self.strings("scode", message).format(
|
message,
|
||||||
responses[int(args[0])][0], args[0], responses[int(args[0])][1]
|
self.strings("scode", message).format(
|
||||||
),
|
responses[int(args[0])][0], args[0], responses[int(args[0])][1]
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await utils.answer(
|
||||||
|
message,
|
||||||
|
self.strings("scode", message).format(
|
||||||
|
responses[int(args[0])][0], args[0], responses_ru[int(args[0])][1]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@loader.unrestricted
|
@loader.unrestricted
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Получите все коды статуса telegram",
|
ru_doc="Получите все статус-коды Telegram",
|
||||||
en_doc="Get all telegram status codes",
|
en_doc="Get all Telegram status codes",
|
||||||
)
|
)
|
||||||
async def tgcscmd(self, message):
|
async def tgcscmd(self, message):
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from hikkatl import functions
|
from hikkatl import functions
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|||||||
@@ -27,9 +27,12 @@
|
|||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class Text2File(loader.Module):
|
class Text2File(loader.Module):
|
||||||
@@ -63,13 +66,15 @@ class Text2File(loader.Module):
|
|||||||
args = utils.get_args_raw(message)
|
args = utils.get_args_raw(message)
|
||||||
if not args:
|
if not args:
|
||||||
await utils.answer(message, self.strings("no_args"))
|
await utils.answer(message, self.strings("no_args"))
|
||||||
else:
|
return
|
||||||
text = args
|
|
||||||
by = io.BytesIO(text.encode("utf-8"))
|
|
||||||
by.name = self.config["name"]
|
|
||||||
|
|
||||||
await utils.answer_file(
|
text = args
|
||||||
message,
|
by = io.BytesIO(text.encode("utf-8"))
|
||||||
by,
|
by.name = self.config["name"]
|
||||||
reply_to=getattr(message, "reply_to_msg_id", None),
|
|
||||||
)
|
await utils.send_file(
|
||||||
|
message.chat_id,
|
||||||
|
by,
|
||||||
|
caption=None,
|
||||||
|
reply_to=message.reply_to_msg_id,
|
||||||
|
)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -26,20 +26,21 @@
|
|||||||
# scope: Api TikTokDownloader 0.0.1
|
# scope: Api TikTokDownloader 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import warnings
|
|
||||||
import functools
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import List, Optional, Union
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from typing import Union, Optional, List
|
|
||||||
|
import aiohttp
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class data:
|
class data:
|
||||||
@@ -51,8 +52,11 @@ class data:
|
|||||||
class TikTok:
|
class TikTok:
|
||||||
def __init__(self, host: Optional[str] = None):
|
def __init__(self, host: Optional[str] = None):
|
||||||
self.headers = {
|
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) "
|
"User-Agent": (
|
||||||
"Version/4.0.4 Mobile/7B334b Safari/531.21.10"
|
"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.host = host or "https://www.tikwm.com/"
|
||||||
self.session = aiohttp.ClientSession()
|
self.session = aiohttp.ClientSession()
|
||||||
@@ -73,42 +77,25 @@ class TikTok:
|
|||||||
self.logger.addHandler(handler)
|
self.logger.addHandler(handler)
|
||||||
self.logger.setLevel(logging.INFO)
|
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):
|
async def close_session(self):
|
||||||
await self.session.close()
|
await self.session.close()
|
||||||
|
|
||||||
async def _ensure_data(self, link: str):
|
async def __ensure_data(self, link: str):
|
||||||
try:
|
if self.link != link:
|
||||||
if self.result is None or self.link != link:
|
self.link = link
|
||||||
self.link = link
|
self.result = await self._fetch_data(link)
|
||||||
self.result = await self.fetch(link)
|
self.logger.info("Successfully ensured data from the 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):
|
async def __get_images(self, download_dir: Optional[str] = None):
|
||||||
download_dir = download_dir or self.result["id"]
|
download_dir = download_dir or self.result["id"]
|
||||||
os.makedirs(download_dir, exist_ok=True)
|
os.makedirs(download_dir, exist_ok=True)
|
||||||
|
|
||||||
tasks = [
|
tasks = [
|
||||||
self._download_file(url, os.path.join(download_dir, f"image_{i + 1}.jpg"))
|
self._download_file(url, os.path.join(download_dir, f"image_{i + 1}.jpg"))
|
||||||
for i, url in enumerate(self.result["images"])
|
for i, url in enumerate(self.result["images"])
|
||||||
]
|
]
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
self.logger.info(f"Images - Downloaded and saved photos to {download_dir}")
|
self.logger.info(f"Images - Downloaded and saved photos to {download_dir}")
|
||||||
|
|
||||||
return data(
|
return data(
|
||||||
@@ -120,7 +107,7 @@ class TikTok:
|
|||||||
type="images",
|
type="images",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __getvideo(self, video_filename: Optional[str] = None, hd: bool = False):
|
async def __get_video(self, video_filename: Optional[str] = None, hd: bool = False):
|
||||||
video_url = self.result["hdplay"] if hd else self.result["play"]
|
video_url = self.result["hdplay"] if hd else self.result["play"]
|
||||||
video_filename = video_filename or f"{self.result['id']}.mp4"
|
video_filename = video_filename or f"{self.result['id']}.mp4"
|
||||||
|
|
||||||
@@ -141,7 +128,50 @@ class TikTok:
|
|||||||
dir_name=os.path.dirname(video_filename), media=video_filename, type="video"
|
dir_name=os.path.dirname(video_filename), media=video_filename, type="video"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _makerequest(self, endpoint: str, params: dict) -> dict:
|
async def _fetch_data(self, link: str) -> dict:
|
||||||
|
url = self.get_url(link)
|
||||||
|
params = {"url": url, "hd": 1}
|
||||||
|
return await self._make_request(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: 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: str, video_filename: Optional[str] = None, hd: bool = True
|
||||||
|
):
|
||||||
|
await self.__ensure_data(link)
|
||||||
|
|
||||||
|
if "images" in self.result:
|
||||||
|
return await self.__get_images(video_filename)
|
||||||
|
|
||||||
|
if "hdplay" in self.result or "play" in self.result:
|
||||||
|
return await self.__get_video(video_filename, hd)
|
||||||
|
|
||||||
|
self.logger.error("No downloadable content found in the provided link.")
|
||||||
|
raise Exception("No downloadable content found in the provided link.")
|
||||||
|
|
||||||
|
async def _make_request(self, endpoint: str, params: dict) -> dict:
|
||||||
async with self.session.get(
|
async with self.session.get(
|
||||||
urljoin(self.host, endpoint), params=params, headers=self.headers
|
urljoin(self.host, endpoint), params=params, headers=self.headers
|
||||||
) as response:
|
) as response:
|
||||||
@@ -154,87 +184,12 @@ class TikTok:
|
|||||||
urls = re.findall(r"http[s]?://[^\s]+", text)
|
urls = re.findall(r"http[s]?://[^\s]+", text)
|
||||||
return urls[0] if urls else None
|
return urls[0] if urls else None
|
||||||
|
|
||||||
@_warn()
|
@staticmethod
|
||||||
async def convert_share_urls(self, url: str) -> Optional[str]:
|
def _get_video_link(unique_id: str, aweme_id: str) -> 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}"
|
return f"https://www.tiktok.com/@{unique_id}/video/{aweme_id}"
|
||||||
|
|
||||||
def _get_uploader_link(self, unique_id: str) -> str:
|
@staticmethod
|
||||||
|
def _get_uploader_link(unique_id: str) -> str:
|
||||||
return f"https://www.tiktok.com/@{unique_id}"
|
return f"https://www.tiktok.com/@{unique_id}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
533
archquise/H.Modules/TimedEmojiStatus.py
Normal file
533
archquise/H.Modules/TimedEmojiStatus.py
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
# 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: TimedEmojiStatus
|
||||||
|
# Description: Temporary emoji status with auto-revert
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# scope: TimedEmojiStatus
|
||||||
|
# scope: TimedEmojiStatus 0.0.1
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
from telethon.tl.functions.account import UpdateEmojiStatusRequest
|
||||||
|
from telethon.tl.types import EmojiStatus, MessageEntityCustomEmoji, Message
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class TimedEmojiStatusMod(loader.Module):
|
||||||
|
"""Temporary emoji status with auto-revert using scheduler"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "TimedEmojiStatus",
|
||||||
|
"no_emoji": "<emoji document_id=5337117114392127164>❌</emoji> <b>Specify emoji or emoji document_id</b>",
|
||||||
|
"no_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Specify time (ex: 1h, 30m, 2d)</b>",
|
||||||
|
"invalid_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Invalid time format (ex: 30m, 2h, 1d, 1w)</b>",
|
||||||
|
"status_set": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status set:</b>\n<b>Current:</b> {}\n<b>Final:</b> {}\n<b>For:</b> {} ({})",
|
||||||
|
"status_updated": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status updated: {}</b>",
|
||||||
|
"no_status": "<emoji document_id=5337117114392127164>❌</emoji> <b>No active status</b>",
|
||||||
|
"status_removed": "<emoji document_id=5336965905773504919>✅</emoji> <b>Status removed</b>",
|
||||||
|
"current_status": "<emoji document_id=5348186233610711303>📊</emoji> <b>Active status:</b>\n<b>Current:</b> {}\n<b>Final:</b> {}\n<b>Until:</b> {} ({})",
|
||||||
|
"no_premium": "<emoji document_id=5337117114392127164>❌</emoji> <b>Premium required for emoji status</b>",
|
||||||
|
"error": "<emoji document_id=5337117114392127164>❌</emoji> <b>Error: {}</b>",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"no_emoji": "<emoji document_id=5337117114392127164>❌</emoji> <b>Укажите эмодзи или document_id</b>",
|
||||||
|
"no_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Укажите время (напр: 1h, 30m, 2d)</b>",
|
||||||
|
"invalid_time": "<emoji document_id=5337117114392127164>❌</emoji> <b>Неверный формат времени (напр: 30m, 2h, 1d, 1w)</b>",
|
||||||
|
"status_set": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус установлен:</b>\n<b>Текущий:</b> {}\n<b>Финальный:</b> {}\n<b>На:</b> {} ({})",
|
||||||
|
"status_updated": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус обновлён: {}</b>",
|
||||||
|
"no_status": "<emoji document_id=5337117114392127164>❌</emoji> <b>Нет активного статуса</b>",
|
||||||
|
"status_removed": "<emoji document_id=5336965905773504919>✅</emoji> <b>Статус удалён</b>",
|
||||||
|
"current_status": "<emoji document_id=5348186233610711303>📊</emoji> <b>Активный статус:</b>\n<b>Текущий:</b> {}\n<b>Финальный:</b> {}\n<b>До:</b> {} ({})",
|
||||||
|
"no_premium": "<emoji document_id=5337117114392127164>❌</emoji> <b>Требуется Premium для эмодзи статуса</b>",
|
||||||
|
"error": "<emoji document_id=5337117114392127164>❌</emoji> <b>Ошибка: {}</b>",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.status_data: Dict[int, Dict] = {}
|
||||||
|
self.scheduler_tasks: Dict[int, asyncio.Task] = {}
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self._client = client
|
||||||
|
self._db = db
|
||||||
|
|
||||||
|
if not self._client.hikka_me.premium:
|
||||||
|
logger.warning("Premium required for emoji status functionality")
|
||||||
|
|
||||||
|
await self._restore_active_statuses()
|
||||||
|
|
||||||
|
async def _restore_active_statuses(self):
|
||||||
|
"""Restore and reschedule active statuses after restart"""
|
||||||
|
saved = self._db.get(__name__, "statuses", {})
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
for user_id, data in saved.items():
|
||||||
|
end_time = data.get("end_time", 0)
|
||||||
|
if end_time > current_time:
|
||||||
|
remaining_time = end_time - current_time
|
||||||
|
logger.info(
|
||||||
|
f"Restoring status for user {user_id}, remaining: {remaining_time}s"
|
||||||
|
)
|
||||||
|
|
||||||
|
task = asyncio.create_task(
|
||||||
|
self._schedule_revert_sleep(user_id, remaining_time)
|
||||||
|
)
|
||||||
|
self.scheduler_tasks[user_id] = task
|
||||||
|
|
||||||
|
self.status_data[user_id] = data
|
||||||
|
else:
|
||||||
|
logger.info(f"Removing expired status for user {user_id}")
|
||||||
|
del saved[user_id]
|
||||||
|
|
||||||
|
if saved != self._db.get(__name__, "statuses", {}):
|
||||||
|
self._db.set(__name__, "statuses", saved)
|
||||||
|
|
||||||
|
def _parse_time(self, time_str: str) -> Optional[timedelta]:
|
||||||
|
"""Parse time string like 1h30m, 2d, 1w, 1mth"""
|
||||||
|
pattern = r"(\d+)([smhdwmth]+)"
|
||||||
|
matches = re.findall(pattern, time_str.lower())
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
return None
|
||||||
|
|
||||||
|
total_seconds = 0
|
||||||
|
for value, unit in matches:
|
||||||
|
value = int(value)
|
||||||
|
if unit == "s":
|
||||||
|
total_seconds += value
|
||||||
|
elif unit == "m":
|
||||||
|
total_seconds += value * 60
|
||||||
|
elif unit == "h":
|
||||||
|
total_seconds += value * 3600
|
||||||
|
elif unit == "d":
|
||||||
|
total_seconds += value * 86400
|
||||||
|
elif unit == "w":
|
||||||
|
total_seconds += value * 604800
|
||||||
|
elif unit in ["mth", "month"]:
|
||||||
|
total_seconds += value * 2592000 # 30 days
|
||||||
|
|
||||||
|
return timedelta(seconds=total_seconds)
|
||||||
|
|
||||||
|
def _format_time(self, td: timedelta) -> str:
|
||||||
|
"""Format timedelta to human readable string"""
|
||||||
|
total_days = td.days
|
||||||
|
months = total_days // 30
|
||||||
|
remaining_days = total_days % 30
|
||||||
|
|
||||||
|
if months > 0:
|
||||||
|
if remaining_days > 0:
|
||||||
|
return f"{months}mth {remaining_days}d"
|
||||||
|
return f"{months}mth"
|
||||||
|
elif total_days > 0:
|
||||||
|
return f"{total_days}d {td.seconds // 3600}h"
|
||||||
|
elif td.seconds >= 3600:
|
||||||
|
return f"{td.seconds // 3600}h {(td.seconds % 3600) // 60}m"
|
||||||
|
else:
|
||||||
|
return f"{td.seconds // 60}m"
|
||||||
|
|
||||||
|
def _extract_document_id(self, emoji_input: str) -> Optional[int]:
|
||||||
|
"""Extract document_id from emoji string"""
|
||||||
|
|
||||||
|
pattern = r"<emoji\s+document_id=(\d+)>.*?</emoji>"
|
||||||
|
match = re.search(pattern, emoji_input)
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
|
||||||
|
if emoji_input.isdigit():
|
||||||
|
return int(emoji_input)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_document_id_from_entities(self, message: Message) -> Optional[int]:
|
||||||
|
"""Extract document_id from message entities"""
|
||||||
|
if not message.entities:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for entity in message.entities:
|
||||||
|
if isinstance(entity, MessageEntityCustomEmoji):
|
||||||
|
return entity.document_id
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _safe_emoji_display(
|
||||||
|
self, emoji_str: str, document_id: Optional[int] = None
|
||||||
|
) -> str:
|
||||||
|
"""Safely display emoji without causing errors"""
|
||||||
|
if not emoji_str:
|
||||||
|
return "❌"
|
||||||
|
|
||||||
|
if document_id:
|
||||||
|
return f"<emoji document_id={document_id}>📋</emoji>"
|
||||||
|
|
||||||
|
if emoji_str.isdigit():
|
||||||
|
return f"<emoji document_id={emoji_str}>📋</emoji>"
|
||||||
|
|
||||||
|
if "<emoji document_id=" in emoji_str:
|
||||||
|
return emoji_str
|
||||||
|
|
||||||
|
if len(emoji_str) == 1 or (
|
||||||
|
len(emoji_str) <= 4 and all(ord(c) >= 0x1F000 for c in emoji_str)
|
||||||
|
):
|
||||||
|
return emoji_str
|
||||||
|
|
||||||
|
return emoji_str
|
||||||
|
|
||||||
|
async def _set_emoji_status(
|
||||||
|
self, emoji_input: str, until: datetime | None = None, message: Message = None
|
||||||
|
) -> tuple[bool, Optional[int]]:
|
||||||
|
"""Set emoji status (requires Premium). Returns (success, document_id)"""
|
||||||
|
try:
|
||||||
|
logger.info(f"Setting emoji status for: {emoji_input}")
|
||||||
|
|
||||||
|
if not self._client.hikka_me.premium:
|
||||||
|
logger.warning("Premium required for emoji status")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
if not emoji_input:
|
||||||
|
logger.info("Removing emoji status")
|
||||||
|
await self._client(UpdateEmojiStatusRequest(emoji_status=None))
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
document_id = None
|
||||||
|
|
||||||
|
if message:
|
||||||
|
document_id = self._extract_document_id_from_entities(message)
|
||||||
|
if document_id:
|
||||||
|
logger.info(
|
||||||
|
f"Found document_id from message entities: {document_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not document_id:
|
||||||
|
document_id = self._extract_document_id(emoji_input)
|
||||||
|
if document_id:
|
||||||
|
logger.info(f"Extracted document_id from text: {document_id}")
|
||||||
|
|
||||||
|
if not document_id:
|
||||||
|
try:
|
||||||
|
logger.info("Trying to get document_id from test message")
|
||||||
|
test_msg = await self._client.send_message("me", emoji_input)
|
||||||
|
document_id = self._extract_document_id_from_entities(test_msg)
|
||||||
|
await self._client.delete_messages("me", [test_msg.id])
|
||||||
|
|
||||||
|
if document_id:
|
||||||
|
logger.info(
|
||||||
|
f"Found document_id from test message: {document_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning("No document_id found in test message")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting document_id from test message: {e}")
|
||||||
|
|
||||||
|
if document_id:
|
||||||
|
try:
|
||||||
|
emoji_status = EmojiStatus(document_id=document_id, until=until)
|
||||||
|
await self._client(
|
||||||
|
UpdateEmojiStatusRequest(emoji_status=emoji_status)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Status set successfully with document_id: {document_id}"
|
||||||
|
)
|
||||||
|
return True, document_id
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error setting status: {e}")
|
||||||
|
if "PREMIUM" in str(e).upper():
|
||||||
|
return False, None
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
logger.warning("No document_id found, all methods failed")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"General error setting emoji status: {e}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
async def _revert_status(self, user_id: int):
|
||||||
|
"""Revert status to final emoji or remove"""
|
||||||
|
logger.info(f"Starting revert status for user {user_id}")
|
||||||
|
|
||||||
|
if user_id in self.scheduler_tasks:
|
||||||
|
del self.scheduler_tasks[user_id]
|
||||||
|
|
||||||
|
if user_id in self.status_data:
|
||||||
|
data = self.status_data[user_id]
|
||||||
|
final_emoji = data.get("final_emoji", "")
|
||||||
|
final_doc_id = data.get("final_doc_id")
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Reverting status for user {user_id} to: '{final_emoji}' (saved doc_id: {final_doc_id})"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if final_emoji and final_doc_id:
|
||||||
|
logger.info(
|
||||||
|
f"Setting final emoji using saved document_id: {final_doc_id}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
emoji_status = EmojiStatus(document_id=final_doc_id)
|
||||||
|
await self._client(
|
||||||
|
UpdateEmojiStatusRequest(emoji_status=emoji_status)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Successfully set final emoji with document_id: {final_doc_id}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error setting final emoji with document_id: {e}")
|
||||||
|
|
||||||
|
success, _ = await self._set_emoji_status(final_emoji)
|
||||||
|
if not success:
|
||||||
|
await self._set_emoji_status("")
|
||||||
|
elif final_emoji:
|
||||||
|
logger.info(f"Attempting to set final emoji: '{final_emoji}'")
|
||||||
|
success, final_doc_id = await self._set_emoji_status(final_emoji)
|
||||||
|
if success:
|
||||||
|
logger.info(
|
||||||
|
f"Successfully reverted to final emoji: '{final_emoji}' (doc_id: {final_doc_id})"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to set final emoji '{final_emoji}', removing status instead"
|
||||||
|
)
|
||||||
|
await self._set_emoji_status("")
|
||||||
|
else:
|
||||||
|
logger.info("No final emoji specified, removing status")
|
||||||
|
await self._set_emoji_status("")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reverting status: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._set_emoji_status("")
|
||||||
|
except Exception as e2:
|
||||||
|
logger.error(f"Error removing status: {e2}")
|
||||||
|
|
||||||
|
logger.info(f"Removing status data for user {user_id}")
|
||||||
|
del self.status_data[user_id]
|
||||||
|
|
||||||
|
saved = self._db.get(__name__, "statuses", {})
|
||||||
|
if user_id in saved:
|
||||||
|
logger.info(f"Removing saved status for user {user_id}")
|
||||||
|
del saved[user_id]
|
||||||
|
self._db.set(__name__, "statuses", saved)
|
||||||
|
|
||||||
|
logger.info(f"Revert status completed for user {user_id}")
|
||||||
|
|
||||||
|
async def _schedule_revert_sleep(self, user_id: int, delay: float):
|
||||||
|
"""Schedule status revert using asyncio.sleep"""
|
||||||
|
try:
|
||||||
|
logger.info(f"Scheduling revert for user {user_id} in {delay} seconds")
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
await self._revert_status(user_id)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
logger.info(f"Revert task cancelled for user {user_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in scheduled revert for user {user_id}: {e}")
|
||||||
|
|
||||||
|
async def _schedule_revert(self, user_id: int, data: Dict):
|
||||||
|
"""Schedule status revert"""
|
||||||
|
end_time = data.get("end_time", 0)
|
||||||
|
delay = max(0, end_time - time.time())
|
||||||
|
|
||||||
|
self.status_data[user_id] = data
|
||||||
|
|
||||||
|
await self._schedule_revert_sleep(user_id, delay)
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="<время> <эмодзи/document_id> [финальный_эмодзи/document_id] - установить временный статус",
|
||||||
|
en_doc="<time> <emoji/document_id> [final_emoji/document_id] - set temporary status",
|
||||||
|
)
|
||||||
|
async def setmoji(self, message: Message):
|
||||||
|
"""Set timed emoji status"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, self.strings["no_time"])
|
||||||
|
|
||||||
|
parts = args.split(maxsplit=2)
|
||||||
|
if len(parts) < 2:
|
||||||
|
return await utils.answer(message, self.strings["no_emoji"])
|
||||||
|
|
||||||
|
time_str, initial_emoji = parts[0], parts[1]
|
||||||
|
final_emoji = parts[2] if len(parts) > 2 else ""
|
||||||
|
|
||||||
|
td = self._parse_time(time_str)
|
||||||
|
if not td:
|
||||||
|
return await utils.answer(message, self.strings["invalid_time"])
|
||||||
|
|
||||||
|
if message.sender_id in self.scheduler_tasks:
|
||||||
|
self.scheduler_tasks[message.sender_id].cancel()
|
||||||
|
del self.scheduler_tasks[message.sender_id]
|
||||||
|
|
||||||
|
try:
|
||||||
|
success, initial_doc_id = await self._set_emoji_status(
|
||||||
|
initial_emoji, message=message
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
return await utils.answer(message, self.strings["no_premium"])
|
||||||
|
except Exception as e:
|
||||||
|
return await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
final_doc_id = None
|
||||||
|
if final_emoji:
|
||||||
|
try:
|
||||||
|
final_doc_id = self._extract_document_id(final_emoji)
|
||||||
|
if not final_doc_id:
|
||||||
|
if message and len(parts) > 2:
|
||||||
|
emoji_entities = [
|
||||||
|
e
|
||||||
|
for e in message.entities
|
||||||
|
if isinstance(e, MessageEntityCustomEmoji)
|
||||||
|
]
|
||||||
|
if len(emoji_entities) >= 2:
|
||||||
|
final_doc_id = emoji_entities[1].document_id
|
||||||
|
|
||||||
|
if not final_doc_id:
|
||||||
|
try:
|
||||||
|
test_msg = await self._client.send_message("me", final_emoji)
|
||||||
|
final_doc_id = self._extract_document_id_from_entities(test_msg)
|
||||||
|
await self._client.delete_messages("me", [test_msg.id])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Could not get document_id for final emoji: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if final_doc_id:
|
||||||
|
logger.info(f"Final emoji document_id: {final_doc_id}")
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Could not resolve document_id for final emoji: {final_emoji}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error getting final emoji document_id: {e}")
|
||||||
|
|
||||||
|
end_time = time.time() + td.total_seconds()
|
||||||
|
user_id = message.sender_id
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"initial_emoji": initial_emoji,
|
||||||
|
"final_emoji": final_emoji,
|
||||||
|
"initial_doc_id": initial_doc_id,
|
||||||
|
"final_doc_id": final_doc_id,
|
||||||
|
"end_time": end_time,
|
||||||
|
"set_time": time.time(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.status_data[user_id] = data
|
||||||
|
|
||||||
|
saved = self._db.get(__name__, "statuses", {})
|
||||||
|
saved[user_id] = data
|
||||||
|
self._db.set(__name__, "statuses", saved)
|
||||||
|
|
||||||
|
task = asyncio.create_task(
|
||||||
|
self._schedule_revert_sleep(user_id, td.total_seconds())
|
||||||
|
)
|
||||||
|
self.scheduler_tasks[user_id] = task
|
||||||
|
|
||||||
|
end_dt = datetime.fromtimestamp(end_time)
|
||||||
|
time_str = self._format_time(td)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Display formatting - initial: '{initial_emoji}' (doc_id: {initial_doc_id}), final: '{final_emoji}' (doc_id: {final_doc_id})"
|
||||||
|
)
|
||||||
|
current_display = self._safe_emoji_display(initial_emoji, initial_doc_id)
|
||||||
|
final_display = (
|
||||||
|
self._safe_emoji_display(final_emoji, final_doc_id)
|
||||||
|
if final_emoji
|
||||||
|
else "❌ (удалить)"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Display results - current: '{current_display}', final: '{final_display}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
await utils.answer(
|
||||||
|
message,
|
||||||
|
self.strings["status_set"].format(
|
||||||
|
current_display, final_display, time_str, f"{end_dt:%H:%M:%S}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@loader.command(ru_doc="Показать текущий статус", en_doc="Show current status")
|
||||||
|
async def showmoji(self, message: Message):
|
||||||
|
"""Show current emoji status"""
|
||||||
|
user_id = message.sender_id
|
||||||
|
|
||||||
|
if user_id not in self.status_data:
|
||||||
|
return await utils.answer(message, self.strings["no_status"])
|
||||||
|
|
||||||
|
data = self.status_data[user_id]
|
||||||
|
end_time = data.get("end_time", 0)
|
||||||
|
initial_emoji = data.get("initial_emoji", "")
|
||||||
|
final_emoji = data.get("final_emoji", "")
|
||||||
|
initial_doc_id = data.get("initial_doc_id")
|
||||||
|
final_doc_id = data.get("final_doc_id")
|
||||||
|
|
||||||
|
if end_time <= time.time():
|
||||||
|
return await utils.answer(message, self.strings["no_status"])
|
||||||
|
|
||||||
|
end_dt = datetime.fromtimestamp(end_time)
|
||||||
|
remaining = timedelta(seconds=end_time - time.time())
|
||||||
|
remaining_str = self._format_time(remaining)
|
||||||
|
|
||||||
|
current_display = self._safe_emoji_display(initial_emoji, initial_doc_id)
|
||||||
|
final_display = (
|
||||||
|
self._safe_emoji_display(final_emoji, final_doc_id)
|
||||||
|
if final_emoji
|
||||||
|
else "❌ (удалить)"
|
||||||
|
)
|
||||||
|
|
||||||
|
await utils.answer(
|
||||||
|
message,
|
||||||
|
self.strings["current_status"].format(
|
||||||
|
current_display, final_display, f"{end_dt:%H:%M:%S}", remaining_str
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@loader.command(ru_doc="Удалить статус", en_doc="Remove status")
|
||||||
|
async def removemoji(self, message: Message):
|
||||||
|
"""Remove emoji status"""
|
||||||
|
user_id = message.sender_id
|
||||||
|
|
||||||
|
if user_id not in self.status_data:
|
||||||
|
return await utils.answer(message, self.strings["no_status"])
|
||||||
|
|
||||||
|
if user_id in self.scheduler_tasks:
|
||||||
|
self.scheduler_tasks[user_id].cancel()
|
||||||
|
del self.scheduler_tasks[user_id]
|
||||||
|
|
||||||
|
await self._revert_status(user_id)
|
||||||
|
await utils.answer(message, self.strings["status_removed"])
|
||||||
|
|
||||||
|
async def on_unload(self):
|
||||||
|
"""Cancel all scheduled tasks on unload"""
|
||||||
|
for task in self.scheduler_tasks.values():
|
||||||
|
task.cancel()
|
||||||
|
self.scheduler_tasks.clear()
|
||||||
@@ -26,14 +26,13 @@
|
|||||||
# scope: UserbotAvast 0.0.1
|
# scope: UserbotAvast 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
|
||||||
import ast
|
import ast
|
||||||
import astor
|
|
||||||
import requests
|
|
||||||
import base64
|
import base64
|
||||||
import zlib
|
import logging
|
||||||
import re
|
import re
|
||||||
import urllib.parse
|
import zlib
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
@@ -697,7 +696,7 @@ class SecurityAnalyzer:
|
|||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if isinstance(node, ast.For):
|
if isinstance(node, ast.For):
|
||||||
for send_method in send_methods:
|
for send_method in send_methods:
|
||||||
if send_method in astor.to_source(node):
|
if send_method in ast.unparse(node):
|
||||||
issue_key = (
|
issue_key = (
|
||||||
f"Mass {send_method}",
|
f"Mass {send_method}",
|
||||||
node.lineno,
|
node.lineno,
|
||||||
@@ -715,8 +714,8 @@ class SecurityAnalyzer:
|
|||||||
)
|
)
|
||||||
self.reported_issues.add(issue_key)
|
self.reported_issues.add(issue_key)
|
||||||
|
|
||||||
if "time.sleep(" in astor.to_source(tree):
|
if "time.sleep(" in ast.unparse(tree):
|
||||||
sleep_calls = re.findall(r"time\.sleep\((.*?)\)", astor.to_source(tree))
|
sleep_calls = re.findall(r"time\.sleep\((.*?)\)", ast.unparse(tree))
|
||||||
for sleep_time in sleep_calls:
|
for sleep_time in sleep_calls:
|
||||||
try:
|
try:
|
||||||
sleep_value = float(sleep_time)
|
sleep_value = float(sleep_time)
|
||||||
|
|||||||
@@ -24,82 +24,109 @@
|
|||||||
# meta developer: @hikka_mods
|
# meta developer: @hikka_mods
|
||||||
# scope: Video2GIF
|
# scope: Video2GIF
|
||||||
# scope: Video2GIF 0.0.1
|
# scope: Video2GIF 0.0.1
|
||||||
# requires: moviepy
|
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class Video2GIF(loader.Module):
|
class Video2GIFMod(loader.Module):
|
||||||
"""Converts video to GIF"""
|
"""Convert video to high quality GIF"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "Video2GIF",
|
"name": "Video2GIF",
|
||||||
"conversion_success": "🎉 The conversion is completed!",
|
"success": "✅ GIF created",
|
||||||
"conversion_error": "❌ An error occurred when converting video to GIF.",
|
"error": "❌ Conversion failed",
|
||||||
"not_video": "⚠️ Please reply to the message with the video or send the video in one message.",
|
"no_video": "❌ Reply to a video",
|
||||||
"loading": "⏳ Conversion is underway",
|
"no_ffmpeg": "❌ FFmpeg not installed. Install: apt install ffmpeg",
|
||||||
|
"processing": "🔄 Processing video...",
|
||||||
|
"compressing": "📦 Optimizing GIF...",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"conversion_success": "🎉 Преобразование завершено!",
|
"success": "✅ GIF создан",
|
||||||
"conversion_error": "❌ Произошла ошибка при преобразовании видео в GIF.",
|
"error": "❌ Ошибка конвертации",
|
||||||
"not_video": "⚠️ Пожалуйста, ответьте на сообщение с видео или отправьте видео одним сообщением.",
|
"no_video": "❌ Ответьте на видео",
|
||||||
"loading": "⏳ Идет преобразование",
|
"no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg",
|
||||||
|
"processing": "🔄 Обрабатываю видео...",
|
||||||
|
"compressing": "📦 Оптимизирую GIF...",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._ffmpeg_check = None
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self._client = client
|
||||||
|
self._db = db
|
||||||
|
self._check_ffmpeg()
|
||||||
|
|
||||||
|
def _check_ffmpeg(self):
|
||||||
|
self._ffmpeg_check = shutil.which("ffmpeg") is not None
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="[reply | в одном сообщении с видео] — конвертирует видео в GIF.",
|
ru_doc="[ответ] [fps] [ширина] - конвертировать видео в GIF",
|
||||||
en_doc="[reply | in one message with video] — Converts video to GIF.",
|
en_doc="[reply] [fps] [width] - convert video to GIF",
|
||||||
)
|
)
|
||||||
async def gifc(self, message):
|
async def gifc(self, message):
|
||||||
video = await self.get_video_from_message(message)
|
"""Convert video to GIF"""
|
||||||
|
if not self._ffmpeg_check:
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
if not video:
|
reply = await message.get_reply_message()
|
||||||
await utils.answer(message, self.strings["not_video"])
|
if not reply or not reply.video:
|
||||||
return
|
return await utils.answer(message, self.strings["no_video"])
|
||||||
|
|
||||||
await utils.answer(message, self.strings["loading"])
|
args = utils.get_args_raw(message).split()
|
||||||
video_path = await self.client.download_media(video)
|
fps = 15 if len(args) < 1 else min(int(args[0]), 30)
|
||||||
gif_path = f"{os.path.splitext(video_path)[0]}.gif"
|
width = 480 if len(args) < 2 else min(int(args[1]), 1024)
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.convert_video_to_gif(video_path, gif_path)
|
gif_path = await self._convert_to_gif(reply, fps, width)
|
||||||
await message.client.send_file(
|
|
||||||
message.chat_id, gif_path, caption=self.strings["conversion_success"]
|
await self._client.send_file(
|
||||||
|
message.chat_id,
|
||||||
|
gif_path,
|
||||||
|
caption=self.strings["success"],
|
||||||
|
reply_to=reply.id,
|
||||||
)
|
)
|
||||||
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):
|
os.remove(gif_path)
|
||||||
"""Получает видео из сообщения."""
|
await msg.delete()
|
||||||
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:
|
except Exception:
|
||||||
"""Конвертирует видео в GIF с улучшенными параметрами."""
|
await utils.answer(message, self.strings["error"])
|
||||||
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:
|
async def _convert_to_gif(self, reply, fps: int, width: int) -> str:
|
||||||
"""Удаляет временные файлы."""
|
"""Convert video to optimized GIF"""
|
||||||
for temp_file in [video_path, gif_path]:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
if os.path.exists(temp_file):
|
video_path = os.path.join(tmpdir, "video.mp4")
|
||||||
os.remove(temp_file)
|
gif_path = os.path.join(tmpdir, "output.gif")
|
||||||
|
|
||||||
|
await reply.download_media(video_path)
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
video_path,
|
||||||
|
"-vf",
|
||||||
|
f"fps={fps},scale={width}:-1:flags=lanczos",
|
||||||
|
"-lavfi",
|
||||||
|
"[0:v]split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
|
||||||
|
"-y",
|
||||||
|
gif_path,
|
||||||
|
]
|
||||||
|
|
||||||
|
proc = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await proc.communicate()
|
||||||
|
|
||||||
|
return gif_path
|
||||||
|
|||||||
@@ -26,136 +26,214 @@
|
|||||||
# requires: json aiohttp tempfile
|
# requires: json aiohttp tempfile
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import logging
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class VirusTotalMod(loader.Module):
|
class VirusTotalMod(loader.Module):
|
||||||
"""Checks files for viruses using VirusTotal."""
|
"""Professional file scanning with VirusTotal"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "VirusTotal",
|
"name": "VirusTotal",
|
||||||
"no_file": "<emoji document_id=5210952531676504517>🚫</emoji> <b>You haven't selected a file.</b>",
|
"no_file": "🚫 Reply to a file",
|
||||||
"download": "<emoji document_id=5334677912270415274>😑</emoji> <b>Downloading...</b>",
|
"downloading": "📥 Downloading file...",
|
||||||
"scan": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Scanning...</b>",
|
"uploading": "📤 Uploading to VirusTotal...",
|
||||||
"link": "🦠 VirusTotal Link",
|
"scanning": "🔍 Scanning in progress...",
|
||||||
"no_virus": "✅ File is clean.",
|
"waiting": "⏳ Waiting for analysis...",
|
||||||
"error": "<emoji document_id=5463193238393274687>⚠️</emoji> Scan error.",
|
"no_key": "🚫 Set VirusTotal API key in config",
|
||||||
"no_format": "This format is not supported.",
|
"error": "❌ Error during scan",
|
||||||
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> You have not specified an API Key",
|
"size_limit": "📁 File exceeds 32MB limit",
|
||||||
"config": "Need a token with www.virustotal.com/gui/my-apikey",
|
"timeout": "⏰ Scan timeout",
|
||||||
"scanning": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Waiting for scan results...</b>",
|
"clean": "✅ File is clean",
|
||||||
"getting_upload_url": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Getting upload URL...</b>",
|
"suspicious": "⚠️ Suspicious file",
|
||||||
"analysis_failed": "<emoji document_id=5463193238393274687>⚠️</emoji> Analysis failed after multiple retries.",
|
"malicious": "⛔ Malicious file",
|
||||||
|
"view_report": "📊 View full report",
|
||||||
|
"close": "❌ Close",
|
||||||
|
"engines": "Scan engines",
|
||||||
|
"detections": "Detections",
|
||||||
|
"status": "Status",
|
||||||
|
"completed": "Completed",
|
||||||
|
"queued": "Queued",
|
||||||
|
"scan_date": "Scan date",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"no_file": "<emoji document_id=5210952531676504517>🚫</emoji> </b>Вы не выбрали файл.</b>",
|
"no_file": "🚫 Ответьте на файл",
|
||||||
"download": "<emoji document_id=5334677912270415274>😑</emoji> </b>Скачивание...</b>",
|
"downloading": "📥 Скачиваю файл...",
|
||||||
"scan": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Сканирую...</b>",
|
"uploading": "📤 Загружаю на VirusTotal...",
|
||||||
"link": "🦠 Ссылка на VirusTotal",
|
"scanning": "🔍 Сканирую...",
|
||||||
"no_virus": "✅ Файл чист.",
|
"waiting": "⏳ Жду анализа...",
|
||||||
"error": "<emoji document_id=5463193238393274687>⚠️</emoji> Ошибка сканирования.",
|
"no_key": "🚫 Укажите API ключ в конфиге",
|
||||||
"no_format": "Этот формат не поддерживается.",
|
"error": "❌ Ошибка при сканировании",
|
||||||
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> Вы не указали Api Key",
|
"size_limit": "📁 Файл больше 32МБ",
|
||||||
"config": "Need a token with www.virustotal.com/gui/my-apikey",
|
"timeout": "⏰ Таймаут сканирования",
|
||||||
"scanning": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Ожидание результатов сканирования...</b>",
|
"clean": "✅ Файл чистый",
|
||||||
"getting_upload_url": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Получение URL для загрузки...</b>",
|
"suspicious": "⚠️ Подозрительный файл",
|
||||||
"analysis_failed": "<emoji document_id=5463193238393274687>⚠️</emoji> Анализ не удался после нескольких попыток.",
|
"malicious": "⛔ Вредоносный файл",
|
||||||
|
"view_report": "📊 Полный отчёт",
|
||||||
|
"close": "❌ Закрыть",
|
||||||
|
"engines": "Антивирусов",
|
||||||
|
"detections": "Обнаружено",
|
||||||
|
"status": "Статус",
|
||||||
|
"completed": "Завершён",
|
||||||
|
"queued": "В очереди",
|
||||||
|
"scan_date": "Дата сканирования",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = loader.ModuleConfig(
|
self.config = loader.ModuleConfig(
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"token-vt",
|
"api_key",
|
||||||
None,
|
None,
|
||||||
lambda: "Need a token with www.virustotal.com/gui/my-apikey",
|
"VirusTotal API key from https://virustotal.com",
|
||||||
validator=loader.validators.Hidden(),
|
validator=loader.validators.Hidden(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.session: Optional[aiohttp.ClientSession] = None
|
||||||
|
self.MAX_SIZE = 32 * 1024 * 1024 # 32MB
|
||||||
|
self.TIMEOUT = 120 # seconds
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
self.hmodslib = await self.import_lib(
|
self._client = client
|
||||||
"https://files.archquise.ru/HModsLibrary.py"
|
self._db = db
|
||||||
)
|
|
||||||
|
async def on_unload(self):
|
||||||
|
if self.session:
|
||||||
|
await self.session.close()
|
||||||
|
|
||||||
|
def _get_session(self) -> aiohttp.ClientSession:
|
||||||
|
"""Get or create aiohttp session with API key"""
|
||||||
|
if not self.session:
|
||||||
|
headers = {"x-apikey": self.config["api_key"]}
|
||||||
|
self.session = aiohttp.ClientSession(headers=headers)
|
||||||
|
return self.session
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="<ответ на файл> - Проверяет файлы на наличие вирусов с использованием VirusTotal",
|
ru_doc="[ответ] - просканировать файл через VirusTotal",
|
||||||
en_doc="<file response> - Checks files for viruses using VirusTotal",
|
en_doc="[reply] - scan file with VirusTotal",
|
||||||
)
|
)
|
||||||
async def vt(self, message):
|
async def vt(self, message):
|
||||||
if not message.is_reply:
|
"""Scan file with VirusTotal"""
|
||||||
await utils.answer(message, self.strings("no_file"))
|
api_key = self.config["api_key"]
|
||||||
return
|
if not api_key:
|
||||||
|
return await utils.answer(message, self.strings["no_key"])
|
||||||
|
|
||||||
reply = await message.get_reply_message()
|
reply = await message.get_reply_message()
|
||||||
if not reply.document:
|
if not reply or not reply.document:
|
||||||
await utils.answer(message, self.strings("no_file"))
|
return await utils.answer(message, self.strings["no_file"])
|
||||||
return
|
|
||||||
|
|
||||||
api_key = self.config.get("token-vt")
|
async with self._get_session() as session:
|
||||||
if not api_key:
|
try:
|
||||||
await utils.answer(message, self.strings("no_apikey"))
|
msg = await utils.answer(message, self.strings["downloading"])
|
||||||
return
|
|
||||||
|
|
||||||
file_extension = os.path.splitext(reply.file.name)[1].lower()
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
allowed_extensions = (".jpg", ".png", ".ico", ".mp3", ".mp4", ".gif", ".txt")
|
file_path = os.path.join(tmpdir, reply.file.name)
|
||||||
if file_extension in allowed_extensions:
|
await reply.download_media(file_path)
|
||||||
await utils.answer(message, self.strings("no_format"))
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
file_size = os.path.getsize(file_path)
|
||||||
await utils.answer(message, self.strings("download"))
|
if file_size > self.MAX_SIZE:
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
return await msg.edit(self.strings["size_limit"])
|
||||||
file_path = os.path.join(temp_dir, reply.file.name)
|
|
||||||
await reply.download_media(file_path)
|
|
||||||
|
|
||||||
file_size = os.path.getsize(file_path)
|
await msg.edit(self.strings["uploading"])
|
||||||
is_large_file = file_size > 32 * 1024 * 1024
|
analysis_id = await self._upload_file(session, file_path)
|
||||||
|
|
||||||
if is_large_file:
|
await msg.edit(self.strings["waiting"])
|
||||||
await utils.answer(message, self.strings("getting_upload_url"))
|
result = await self._wait_for_analysis(session, analysis_id)
|
||||||
await utils.answer(message, self.strings("scan"))
|
|
||||||
|
|
||||||
analysis_results = await self.hmodslib.scan_file_virustotal(
|
await self._show_results(msg, analysis_id, result)
|
||||||
file_path, api_key, is_large_file
|
|
||||||
)
|
|
||||||
|
|
||||||
if analysis_results:
|
except asyncio.TimeoutError:
|
||||||
formatted_results = self.hmodslib.format_analysis_results(
|
await utils.answer(message, self.strings["timeout"])
|
||||||
analysis_results
|
except Exception as e:
|
||||||
)
|
error_text = f"{self.strings['error']}: {str(e)[:100]}"
|
||||||
try:
|
await utils.answer(message, error_text)
|
||||||
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:
|
async def _upload_file(self, session: aiohttp.ClientSession, path: str) -> str:
|
||||||
await utils.answer(message, self.strings("analysis_failed"))
|
"""Upload file to VirusTotal and return analysis ID"""
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
form = aiohttp.FormData()
|
||||||
|
form.add_field("file", f, filename=os.path.basename(path))
|
||||||
|
|
||||||
except Exception as e:
|
async with session.post(
|
||||||
logger.exception("An error occurred during the VT scan process.")
|
"https://www.virustotal.com/api/v3/files", data=form
|
||||||
await utils.answer(
|
) as response:
|
||||||
message, self.strings("error") + f"\n\n{type(e).__name__}: {str(e)}"
|
response.raise_for_status()
|
||||||
)
|
data = await response.json()
|
||||||
|
return data["data"]["id"]
|
||||||
|
|
||||||
|
async def _wait_for_analysis(
|
||||||
|
self, session: aiohttp.ClientSession, analysis_id: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Poll analysis results until completion"""
|
||||||
|
url = f"https://www.virustotal.com/api/v3/analyses/{analysis_id}"
|
||||||
|
|
||||||
|
for _ in range(20):
|
||||||
|
async with session.get(url) as response:
|
||||||
|
response.raise_for_status()
|
||||||
|
data = await response.json()
|
||||||
|
|
||||||
|
status = data["data"]["attributes"]["status"]
|
||||||
|
if status == "completed":
|
||||||
|
return data
|
||||||
|
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
|
raise asyncio.TimeoutError()
|
||||||
|
|
||||||
|
async def _show_results(self, message, analysis_id: str, result: Dict[str, Any]):
|
||||||
|
"""Display scan results in inline form"""
|
||||||
|
stats = result["data"]["attributes"]["stats"]
|
||||||
|
date = result["data"]["attributes"]["date"]
|
||||||
|
|
||||||
|
malicious = stats.get("malicious", 0)
|
||||||
|
suspicious = stats.get("suspicious", 0)
|
||||||
|
undetected = stats.get("undetected", 0)
|
||||||
|
harmless = stats.get("harmless", 0)
|
||||||
|
total = malicious + suspicious + undetected + harmless
|
||||||
|
|
||||||
|
if malicious > 0:
|
||||||
|
verdict = self.strings["malicious"]
|
||||||
|
emoji = "⛔"
|
||||||
|
elif suspicious > 0:
|
||||||
|
verdict = self.strings["suspicious"]
|
||||||
|
emoji = "⚠️"
|
||||||
|
else:
|
||||||
|
verdict = self.strings["clean"]
|
||||||
|
emoji = "✅"
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
scan_date = datetime.fromtimestamp(date).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
text = (
|
||||||
|
f"{emoji} <b>VirusTotal Scan Results</b>\n\n"
|
||||||
|
f"<b>{self.strings['status']}:</b> {verdict}\n"
|
||||||
|
f"<b>{self.strings['detections']}:</b> {malicious}\n"
|
||||||
|
f"<b>{self.strings['engines']}:</b> {total}\n"
|
||||||
|
f"<b>{self.strings['scan_date']}:</b> {scan_date}\n\n"
|
||||||
|
f"<code>Malicious: {malicious}/{total}</code>\n"
|
||||||
|
f"<code>Suspicious: {suspicious}/{total}</code>\n"
|
||||||
|
f"<code>Harmless: {harmless}/{total}</code>\n"
|
||||||
|
f"<code>Undetected: {undetected}/{total}</code>"
|
||||||
|
)
|
||||||
|
|
||||||
|
vt_url = f"https://www.virustotal.com/gui/file-analysis/{analysis_id}"
|
||||||
|
|
||||||
|
await self.inline.form(
|
||||||
|
text=text,
|
||||||
|
message=message,
|
||||||
|
reply_markup=[
|
||||||
|
[{"text": f"🔗 {self.strings['view_report']}", "url": vt_url}],
|
||||||
|
[{"text": self.strings["close"], "action": "close"}],
|
||||||
|
],
|
||||||
|
ttl=300, # 5 minutes timeout
|
||||||
|
)
|
||||||
|
|||||||
@@ -27,99 +27,92 @@
|
|||||||
# requires: tempfile
|
# requires: tempfile
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class VoiceDL(loader.Module):
|
class VoiceDLMod(loader.Module):
|
||||||
"""Voice Downloader module"""
|
"""Download voice messages as MP3"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "VoiceDL",
|
"name": "VoiceDL",
|
||||||
"download_success": "Voice message downloaded in MP3 format.",
|
"success": "✅ Voice downloaded as MP3",
|
||||||
"download_error": "Error downloading voice message.",
|
"error": "❌ Error downloading voice",
|
||||||
"no_voice_message": "Please reply to a voice message.",
|
"no_voice": "❌ Reply to a voice message",
|
||||||
"conversion_error": "Error converting to MP3.",
|
"no_ffmpeg": "❌ FFmpeg not found. Install: apt install ffmpeg",
|
||||||
"file_not_found": "File not found.",
|
|
||||||
"unsupported_format": "The file format is not supported.",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"download_success": "Голосовое сообщение загружено в формате MP3.",
|
"success": "✅ Голосовое скачано как MP3",
|
||||||
"download_error": "Ошибка при загрузке голосового сообщения.",
|
"error": "❌ Ошибка скачивания",
|
||||||
"no_voice_message": "Пожалуйста, ответьте на голосовое сообщение.",
|
"no_voice": "❌ Ответьте на голосовое",
|
||||||
"conversion_error": "Ошибка при конвертации в MP3.",
|
"no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg",
|
||||||
"file_not_found": "Файл не найден.",
|
|
||||||
"unsupported_format": "Формат файла не поддерживается.",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._ffmpeg_check = None
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self._client = client
|
||||||
|
self._db = db
|
||||||
|
self._check_ffmpeg()
|
||||||
|
|
||||||
|
def _check_ffmpeg(self):
|
||||||
|
self._ffmpeg_check = shutil.which("ffmpeg") is not None
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc=" [reply] — загружает выбранное голосовое сообщение в виде файла mp3 и кидает его в чат.",
|
ru_doc="[ответ] - скачать голосовое как MP3",
|
||||||
en_doc=" [reply] — downloads the selected voice message as an MP3 file and sends it in the chat.",
|
en_doc="[reply] - download voice as MP3",
|
||||||
)
|
)
|
||||||
async def voicedl(self, message):
|
async def voicedl(self, message):
|
||||||
|
if not self._ffmpeg_check:
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
reply = await message.get_reply_message()
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.voice:
|
||||||
|
return await utils.answer(message, self.strings["no_voice"])
|
||||||
|
|
||||||
if reply:
|
await self._process_voice(message, 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())
|
async def _process_voice(self, message, reply):
|
||||||
mp3_file_path = f"voice_message_{timestamp}.mp3"
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
try:
|
||||||
|
ogg_path = os.path.join(tmpdir, "voice.ogg")
|
||||||
|
mp3_path = os.path.join(tmpdir, "voice.mp3")
|
||||||
|
|
||||||
await self.convert_to_mp3(voice_file_path, mp3_file_path)
|
await reply.download_media(file=ogg_path)
|
||||||
|
|
||||||
await message.client.send_file(
|
proc = await asyncio.create_subprocess_exec(
|
||||||
message.chat.id,
|
"ffmpeg",
|
||||||
mp3_file_path,
|
"-i",
|
||||||
caption=self.strings("download_success"),
|
ogg_path,
|
||||||
)
|
"-codec:a",
|
||||||
|
"libmp3lame",
|
||||||
|
"-q:a",
|
||||||
|
"2",
|
||||||
|
mp3_path,
|
||||||
|
stdout=asyncio.subprocess.DEVNULL,
|
||||||
|
stderr=asyncio.subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
await proc.communicate()
|
||||||
|
|
||||||
os.remove(voice_file_path)
|
if proc.returncode != 0:
|
||||||
os.remove(mp3_file_path)
|
raise Exception("FFmpeg error")
|
||||||
|
|
||||||
except FileNotFoundError:
|
await message.client.send_file(
|
||||||
await utils.answer(
|
message.chat.id,
|
||||||
message,
|
mp3_path,
|
||||||
self.strings("download_error")
|
caption=self.strings["success"],
|
||||||
+ " "
|
reply_to=reply.id,
|
||||||
+ 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):
|
except Exception:
|
||||||
"""Convert audio file to MP3 format using FFmpeg."""
|
await utils.answer(message, self.strings["error"])
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import requests
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Union, Dict, List
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
|||||||
@@ -28,109 +28,108 @@
|
|||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import time
|
||||||
import requests
|
|
||||||
|
|
||||||
from .. import loader, utils
|
import aiohttp
|
||||||
|
|
||||||
|
from .. import loader
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class WindowsKeys(loader.Module):
|
class WindowsKeysMod(loader.Module):
|
||||||
"""Provides you Windows activation keys"""
|
"""Windows activation keys"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "WindowsKeys",
|
"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",
|
"winkey": "✅ Key: <code>{}</code>\n\n⚠ For KMS activation only",
|
||||||
"error": "❌ An error occurred while retrieving the key. Please try again later.",
|
"error": "❌ Failed to get key",
|
||||||
|
"select": "🔓 Select version:",
|
||||||
|
"close": "🎈 Close",
|
||||||
|
"loading": "⌛ Loading...",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"winkey": "✅ Ваш ключ: <code>{}</code>\n\n⚠ Внимание! Указанный ключ не является пиратским. Он взят с официального сайта Microsoft и предназначен для дальнейшей активации посредством KMS-сервера",
|
"winkey": "✅ Ключ: <code>{}</code>\n\n⚠ Только для KMS активации",
|
||||||
"error": "❌ Произошла ошибка при получении ключа. Попробуйте позже.",
|
"error": "❌ Ошибка получения",
|
||||||
|
"select": "🔓 Выберите версию:",
|
||||||
|
"close": "🎈 Закрыть",
|
||||||
|
"loading": "⌛ Загрузка...",
|
||||||
}
|
}
|
||||||
|
|
||||||
@loader.command(
|
def __init__(self):
|
||||||
ru_doc="Открывает выбор ключа для активации Windows",
|
self.cache = None
|
||||||
en_doc="Opens the Windows activation key selection",
|
self.cache_time = 0
|
||||||
)
|
self.CACHE_TTL = 3600
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self.client = client
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
@loader.command(ru_doc="Меню ключей Windows", en_doc="Windows keys menu")
|
||||||
async def winkey(self, message):
|
async def winkey(self, message):
|
||||||
await self.inline.form(
|
await self.inline.form(
|
||||||
text="🔓 Выберите версию и издание Windows, для которой вам необходим ключ",
|
self.strings["select"],
|
||||||
message=message,
|
message=message,
|
||||||
reply_markup=[
|
reply_markup=[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"text": "Windows 10/11 Pro",
|
"text": "Win 10/11 Pro",
|
||||||
"callback": self._inline__give_key,
|
"callback": self._key,
|
||||||
"args": ["win10_11pro"],
|
"args": ("win10_11pro",),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"text": "Windows 10/11 Enterprise LTSC",
|
"text": "Win 10/11 LTSC",
|
||||||
"callback": self._inline__give_key,
|
"callback": self._key,
|
||||||
"args": ["win10_11enterpriseLTSC"],
|
"args": ("win10_11enterpriseLTSC",),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"text": "Windows 8.1 Pro",
|
"text": "Win 8.1 Pro",
|
||||||
"callback": self._inline__give_key,
|
"callback": self._key,
|
||||||
"args": ["win8.1pro"],
|
"args": ("win8.1pro",),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[{"text": "Win 8 Pro", "callback": self._key, "args": ("win8pro",)}],
|
||||||
|
[{"text": "Win 7 Pro", "callback": self._key, "args": ("win7pro",)}],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"text": "Windows 8 Pro",
|
"text": "Vista Business",
|
||||||
"callback": self._inline__give_key,
|
"callback": self._key,
|
||||||
"args": ["win8pro"],
|
"args": ("winvistabusiness",),
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"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",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[{"text": self.strings["close"], "action": "close"}],
|
||||||
],
|
],
|
||||||
force_me=False,
|
|
||||||
silent=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _inline__give_key(self, call, winver):
|
async def _key(self, call, version):
|
||||||
url = "https://files.archquise.ru/winkeys.json"
|
await call.edit(self.strings["loading"])
|
||||||
|
keys = await self._get_keys()
|
||||||
|
key = keys.get(version) if keys else None
|
||||||
|
await call.edit(
|
||||||
|
self.strings["winkey"].format(key) if key else self.strings["error"],
|
||||||
|
reply_markup=[
|
||||||
|
[{"text": "← Back", "callback": self.winkey}],
|
||||||
|
[{"text": self.strings["close"], "action": "close"}],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _get_keys(self):
|
||||||
|
if time.time() - self.cache_time < self.CACHE_TTL:
|
||||||
|
return self.cache
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(url)
|
async with aiohttp.ClientSession(
|
||||||
response.raise_for_status()
|
timeout=aiohttp.ClientTimeout(10)
|
||||||
data = response.json()
|
) as session:
|
||||||
await call.edit(self.strings["winkey"].format(data[winver]))
|
async with session.get("https://files.archquise.ru/winkeys.json") as r:
|
||||||
|
self.cache = await r.json()
|
||||||
except requests.exceptions.RequestException as e:
|
self.cache_time = time.time()
|
||||||
logger.error("Request error: %e", e)
|
return self.cache
|
||||||
await call.answer(self.strings("error"), show_alert=True)
|
except Exception: # noqa: E722
|
||||||
except json.JSONDecodeError as e:
|
return None
|
||||||
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)
|
|
||||||
|
|||||||
@@ -27,10 +27,13 @@
|
|||||||
# requires: requests
|
# requires: requests
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class animals(loader.Module):
|
class animals(loader.Module):
|
||||||
@@ -38,13 +41,13 @@ class animals(loader.Module):
|
|||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "animals",
|
"name": "animals",
|
||||||
"loading": "<b>Generation is underway</b>",
|
"loading": "<b>Generation is underway</b> <emoji document_id=5215484787325676090>🕐</emoji>",
|
||||||
"done": "<b>Here is your salute</b>",
|
"done": "<b>Here is your salute</b> <emoji document_id=5436246187944460315>❤️</emoji>",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"loading": "<b>Генерация идет полным ходом</b>",
|
"loading": "<b>Генерация идет полным ходом</b> <emoji document_id=5215484787325676090>🕐</emoji>",
|
||||||
"done": "<b>Вот ваш результат</b>",
|
"done": "<b>Вот ваш результат</b> <emoji document_id=5436246187944460315>❤️</emoji>",
|
||||||
}
|
}
|
||||||
|
|
||||||
# thanks https://github.com/C0dwiz/H.Modules/pull/1
|
# thanks https://github.com/C0dwiz/H.Modules/pull/1
|
||||||
@@ -58,7 +61,7 @@ class animals(loader.Module):
|
|||||||
)
|
)
|
||||||
async def fcatcmd(self, message):
|
async def fcatcmd(self, message):
|
||||||
await utils.answer(message, self.strings("loading"))
|
await utils.answer(message, self.strings("loading"))
|
||||||
cat_url = await self.get_photo("thecat")
|
cat_url = await self.get_photo("thecatapi")
|
||||||
await utils.answer_file(
|
await utils.answer_file(
|
||||||
message, cat_url, self.strings("done"), force_document=True
|
message, cat_url, self.strings("done"), force_document=True
|
||||||
)
|
)
|
||||||
@@ -80,7 +83,7 @@ class animals(loader.Module):
|
|||||||
)
|
)
|
||||||
async def catcmd(self, message):
|
async def catcmd(self, message):
|
||||||
await utils.answer(message, self.strings("loading"))
|
await utils.answer(message, self.strings("loading"))
|
||||||
cat_url = await self.get_photo("thecat")
|
cat_url = await self.get_photo("thecatapi")
|
||||||
await utils.answer_file(
|
await utils.answer_file(
|
||||||
message, cat_url, self.strings("done"), force_document=False
|
message, cat_url, self.strings("done"), force_document=False
|
||||||
)
|
)
|
||||||
|
|||||||
254
archquise/H.Modules/coddrago/DelMessTools.py
Normal file
254
archquise/H.Modules/coddrago/DelMessTools.py
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
#░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
|
||||||
|
#░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
|
||||||
|
#░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
|
||||||
|
# Name: DelMessTools
|
||||||
|
# Description: Module to manage and delete your messages in the current chat
|
||||||
|
# Author: @codrago_m
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# 🔒 Licensed under the GNU AGPLv3
|
||||||
|
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Author: @codrago
|
||||||
|
# Commands: nopurge, purgetime, purgelength, purgekeyword, purge
|
||||||
|
# scope: hikka_only
|
||||||
|
# meta developer: @codrago_m
|
||||||
|
# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png
|
||||||
|
# meta pic: https://envs.sh/HJx.webp
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = (1, 1, 0)
|
||||||
|
|
||||||
|
from hikkatl.tl.types import Message, DocumentAttributeFilename
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
class DelMessTools(loader.Module):
|
||||||
|
"""Module to manage and delete your messages in the current chat"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "DelMessTools",
|
||||||
|
"purge_complete": "All your messages have been deleted.",
|
||||||
|
"purge_reply_complete": "Messages up to the replied message have been deleted.",
|
||||||
|
"purge_keyword_complete": "Messages containing the keyword have been deleted.",
|
||||||
|
"purge_time_complete": "Messages within the specified time range have been deleted.",
|
||||||
|
"purge_media_complete": "All your media messages have been deleted.",
|
||||||
|
"purge_length_complete": "Messages with the specified length have been deleted.",
|
||||||
|
"purge_type_complete": "Messages of the specified type have been deleted.",
|
||||||
|
"enabled": "It's not operational now anyway.",
|
||||||
|
"disabled": "Operation status changed to disabled.",
|
||||||
|
"interrupted": "The deletion was interrupted because you changed your mind.",
|
||||||
|
"none": "You didn't even intend to delete anything here, but anyway it's disabled now."
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"purge_complete": "Все ваши сообщения были удалены.",
|
||||||
|
"purge_reply_complete": "Сообщения до указанного ответа были удалены.",
|
||||||
|
"purge_keyword_complete": "Сообщения, содержащие ключевое слово, были удалены.",
|
||||||
|
"purge_time_complete": "Сообщения в указанном временном диапазоне были удалены.",
|
||||||
|
"purge_media_complete": "Все ваши медиа-сообщения были удалены.",
|
||||||
|
"purge_length_complete": "Сообщения указанной длины были удалены.",
|
||||||
|
"purge_type_complete": "Сообщения указанного типа были удалены.",
|
||||||
|
"enabled": "Оно итак сейчас не работает.",
|
||||||
|
"disabled": "Режим работы изменен на выключено.",
|
||||||
|
"interrupted": "Удаление было прервано т.к вы передумали.",
|
||||||
|
"none": "Вы даже не пытались ничего здесь удалить, в любом случае сейчас оно выключено."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def purgecmd(self, message: Message):
|
||||||
|
""" [reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to
|
||||||
|
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||||
|
"""
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
is_last = False
|
||||||
|
args, types_filter, is_each = self.get_types_filter(message)
|
||||||
|
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||||
|
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
status[message.chat.id] = True
|
||||||
|
self.db.set(__name__, "status", status)
|
||||||
|
|
||||||
|
async for i in self.client.iter_messages(message.peer_id):
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
if status.get(message.chat.id, None) is not True:
|
||||||
|
return await utils.answer(message, self.strings["interrupted"])
|
||||||
|
|
||||||
|
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i.from_id == self.tg_id and self.is_valid_type(i, types_filter):
|
||||||
|
if reply:
|
||||||
|
if is_last:
|
||||||
|
break
|
||||||
|
if i.id == reply.id:
|
||||||
|
is_last = True
|
||||||
|
await message.client.delete_messages(message.peer_id, [i.id])
|
||||||
|
|
||||||
|
if reply:
|
||||||
|
await utils.answer(message, self.strings["purge_reply_complete"])
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["purge_complete"])
|
||||||
|
|
||||||
|
async def purgekeywordcmd(self, message: Message):
|
||||||
|
""" <keyword> [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat
|
||||||
|
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||||
|
"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, "Please specify anything because you didn't.")
|
||||||
|
|
||||||
|
args, types_filter, is_each = self.get_types_filter(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, "Please specify a keyword to delete messages.")
|
||||||
|
|
||||||
|
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||||
|
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
status[message.chat.id] = True
|
||||||
|
self.db.set(__name__, "status", status)
|
||||||
|
|
||||||
|
async for i in self.client.iter_messages(message.peer_id):
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
if status.get(message.chat.id, None) is not True:
|
||||||
|
return await utils.answer(message, self.strings["interrupted"])
|
||||||
|
|
||||||
|
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i.from_id == self.tg_id and args.lower() in (i.text or '').lower() and self.is_valid_type(i, types_filter):
|
||||||
|
await message.client.delete_messages(message.chat.id, [i.id])
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["purge_keyword_complete"])
|
||||||
|
|
||||||
|
async def purgetimecmd(self, message: Message):
|
||||||
|
""" <start_time> <end_time> [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat
|
||||||
|
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||||
|
Time format: YYYY-MM-DD HH:MM:SS
|
||||||
|
"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, "Please specify anything because you didn't.")
|
||||||
|
|
||||||
|
args, types_filter, is_each = self.get_types_filter(message)
|
||||||
|
args = args.split()
|
||||||
|
|
||||||
|
if not args or len(args) < 2:
|
||||||
|
return await utils.answer(message, "Please specify the start and end time in the format: YYYY-MM-DD HH:MM:SS")
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_time = datetime.strptime(args[0], "%Y-%m-%d %H:%M:%S")
|
||||||
|
end_time = datetime.strptime(args[1], "%Y-%m-%d %H:%M:%S")
|
||||||
|
except ValueError:
|
||||||
|
return await utils.answer(message, "Invalid time format. Please use the format: YYYY-MM-DD HH:MM:SS")
|
||||||
|
|
||||||
|
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||||
|
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
status[message.chat.id] = True
|
||||||
|
self.db.set(__name__, "status", status)
|
||||||
|
|
||||||
|
async for i in self.client.iter_messages(message.peer_id):
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
if status.get(message.chat.id, None) is not True:
|
||||||
|
return await utils.answer(message, self.strings["interrupted"])
|
||||||
|
|
||||||
|
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i.from_id == self.tg_id and start_time <= i.date <= end_time and self.is_valid_type(i, types_filter):
|
||||||
|
await message.client.delete_messages(message.peer_id, [i.id])
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["purge_time_complete"])
|
||||||
|
|
||||||
|
async def purgelengthcmd(self, message: Message):
|
||||||
|
""" <length> [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat
|
||||||
|
-all - to delete messages in each topic if this is a forum otherwise the flag'll just be ingored
|
||||||
|
"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, "Please specify anything because you didn't.")
|
||||||
|
|
||||||
|
args, types_filter, is_each = self.get_types_filter(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, "Please specify a valid length.")
|
||||||
|
|
||||||
|
length = int(args)
|
||||||
|
is_forum = (await self.client.get_entity(message.chat.id)).forum
|
||||||
|
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
status[message.chat.id] = True
|
||||||
|
self.db.set(__name__, "status", status)
|
||||||
|
|
||||||
|
async for i in self.client.iter_messages(message.peer_id):
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
if status.get(message.chat.id, None) is not True:
|
||||||
|
return await utils.answer(message, self.strings["interrupted"])
|
||||||
|
|
||||||
|
if is_forum and not is_each and utils.get_topic(message) != utils.get_topic(i):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i.from_id == self.tg_id and len(i.text or '') == length and self.is_valid_type(i, types_filter):
|
||||||
|
await message.client.delete_messages(message.peer_id, [i.id])
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings["purge_length_complete"])
|
||||||
|
|
||||||
|
async def nopurgecmd(self, message: Message):
|
||||||
|
"""
|
||||||
|
Interrupt the deletion process
|
||||||
|
Use in the chat where you've previously started deletion
|
||||||
|
"""
|
||||||
|
chat_id = utils.get_chat_id(message)
|
||||||
|
|
||||||
|
status = self.db.get(__name__, "status", {})
|
||||||
|
_status = status.get(chat_id, None)
|
||||||
|
status[chat_id] = False
|
||||||
|
self.db.set(__name__, "status", status)
|
||||||
|
|
||||||
|
if _status is True:
|
||||||
|
await utils.answer(message, self.strings["disabled"])
|
||||||
|
elif _status is False:
|
||||||
|
await utils.answer(message, self.strings["enabled"])
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["none"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_types_filter(self, message: Message):
|
||||||
|
""" Get the types filter from the command arguments."""
|
||||||
|
args = utils.get_args_raw(message).split()
|
||||||
|
types_filter = []
|
||||||
|
valid_types = ["-img", "-voice", "-file", "-all"]
|
||||||
|
is_each = "-all" in args
|
||||||
|
|
||||||
|
for i, arg in enumerate(args):
|
||||||
|
if arg in valid_types:
|
||||||
|
_args = " ".join(args[:i])
|
||||||
|
args_ = " ".join(args[i:])
|
||||||
|
break
|
||||||
|
|
||||||
|
if "-img" in args_:
|
||||||
|
types_filter.append("img")
|
||||||
|
if "-voice" in args_:
|
||||||
|
types_filter.append("voice")
|
||||||
|
if "-file" in args_:
|
||||||
|
types_filter.append("file")
|
||||||
|
if "-all" in args_:
|
||||||
|
is_each = True
|
||||||
|
|
||||||
|
return _args, types_filter, is_each
|
||||||
|
|
||||||
|
def is_valid_type(self, message: Message, types_filter):
|
||||||
|
""" Check if the message matches the specified types filter. """
|
||||||
|
if not types_filter:
|
||||||
|
return True # No filtering means all types are valid
|
||||||
|
|
||||||
|
if "img" in types_filter and message.photo:
|
||||||
|
return True
|
||||||
|
if "voice" in types_filter and message.voice:
|
||||||
|
return True
|
||||||
|
if "file" in types_filter and isinstance(message.document, DocumentAttributeFilename):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -27,10 +27,15 @@
|
|||||||
# requires: aiohttp
|
# requires: aiohttp
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class face(loader.Module):
|
class face(loader.Module):
|
||||||
@@ -64,15 +69,16 @@ class face(loader.Module):
|
|||||||
async def rfacecmd(self, message):
|
async def rfacecmd(self, message):
|
||||||
await utils.answer(message, self.strings("loading"))
|
await utils.answer(message, self.strings("loading"))
|
||||||
|
|
||||||
url = "https://vsecoder.dev/api/faces"
|
url = "https://files.archquise.ru/kaomoji.txt"
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as response:
|
async with session.get(url) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
data = await response.json()
|
data = await response.text()
|
||||||
random_face = data["data"]
|
kaomoji_list = [s.strip() for s in re.split(r'[\t\r\n]+', data) if s.strip()]
|
||||||
|
kaomoji = random.choice(kaomoji_list)
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
message, self.strings("random_face").format(random_face)
|
message, self.strings("random_face").format(kaomoji)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await utils.answer(message, self.strings("error"))
|
await utils.answer(message, self.strings("error"))
|
||||||
|
|||||||
@@ -1,47 +1,54 @@
|
|||||||
|
ASCIIArt
|
||||||
AccountData
|
AccountData
|
||||||
AniLibria
|
AniLiberty
|
||||||
animals
|
|
||||||
AnimeQuotes
|
AnimeQuotes
|
||||||
Article
|
Article
|
||||||
ASCIIArt
|
|
||||||
AutofarmCookies
|
AutofarmCookies
|
||||||
BirthdayTime
|
BirthdayTime
|
||||||
CheckSpamBan
|
CheckSpamBan
|
||||||
|
CodeShare
|
||||||
CryptoCurrency
|
CryptoCurrency
|
||||||
EnvsSH
|
|
||||||
face
|
|
||||||
FakeActions
|
FakeActions
|
||||||
FakeWallet
|
FakeWallet
|
||||||
full
|
FolderAutoRead
|
||||||
GigaChat
|
GigaChat
|
||||||
globalrestrict
|
H
|
||||||
hikkahost
|
HAFK
|
||||||
|
HInstall
|
||||||
|
InfoBannersManager
|
||||||
InlineButton
|
InlineButton
|
||||||
InlineCoin
|
InlineCoin
|
||||||
InlineHelper
|
InlineHelper
|
||||||
IrisSimpleMod
|
IrisSimpleMod
|
||||||
jacques
|
|
||||||
KBSwapper
|
KBSwapper
|
||||||
Memes
|
Memes
|
||||||
|
MessageMonitor
|
||||||
|
MooFarmRC1
|
||||||
Music
|
Music
|
||||||
novoice
|
|
||||||
nsfwart
|
|
||||||
numbersapi
|
|
||||||
PastebinAPI
|
|
||||||
profile
|
|
||||||
ReplaceVowels
|
ReplaceVowels
|
||||||
SafetyMod
|
|
||||||
search
|
|
||||||
shortener
|
|
||||||
SMAcrhiver
|
SMAcrhiver
|
||||||
|
TaskManager
|
||||||
TelegramStatusCodes
|
TelegramStatusCodes
|
||||||
TempChat
|
TempChat
|
||||||
Text2File
|
Text2File
|
||||||
Text_Sticker
|
|
||||||
TikTokDownloader
|
TikTokDownloader
|
||||||
|
TimedEmojiStatus
|
||||||
|
UserbotAvast
|
||||||
Video2GIF
|
Video2GIF
|
||||||
VirusTotal
|
VirusTotal
|
||||||
VoiceDL
|
VoiceDL
|
||||||
HAFK
|
|
||||||
Weather
|
Weather
|
||||||
InfoBannersManager
|
WindowsKeys
|
||||||
|
animals
|
||||||
|
face
|
||||||
|
globalrestrict
|
||||||
|
hikkahost
|
||||||
|
mediatools
|
||||||
|
novoice
|
||||||
|
nsfwart
|
||||||
|
passgen
|
||||||
|
profile
|
||||||
|
search
|
||||||
|
shortener
|
||||||
|
timezone
|
||||||
|
ytdl
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
# scope: Api GlobalRestrict 0.0.1
|
# scope: Api GlobalRestrict 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import typing
|
import typing
|
||||||
@@ -51,6 +52,8 @@ from telethon.tl.types import (
|
|||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
BANNED_RIGHTS = {
|
BANNED_RIGHTS = {
|
||||||
"view_messages": False,
|
"view_messages": False,
|
||||||
"send_messages": False,
|
"send_messages": False,
|
||||||
|
|||||||
@@ -26,12 +26,15 @@
|
|||||||
# scope: api HikkaHost 0.0.1
|
# scope: api HikkaHost 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class HostApi:
|
class HostApi:
|
||||||
"""
|
"""
|
||||||
@@ -55,7 +58,7 @@ class HostApi:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: The API response as a dictionary.
|
dict: The API response as a dictionary.
|
||||||
"""
|
"""
|
||||||
url = "http://158.160.84.24:5000" + path
|
url = "http://api.hikka.host" + path
|
||||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||||
async with session.request(
|
async with session.request(
|
||||||
method,
|
method,
|
||||||
@@ -311,4 +314,4 @@ class HikkahostMod(loader.Module):
|
|||||||
user_id = token.split(":")[0]
|
user_id = token.split(":")[0]
|
||||||
api = HostApi(token)
|
api = HostApi(token)
|
||||||
|
|
||||||
data = await api.action(user_id, token)
|
await api.action(user_id, token)
|
||||||
|
|||||||
@@ -27,13 +27,16 @@
|
|||||||
# scope: Жаконизатор 0.0.1
|
# scope: Жаконизатор 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import io
|
import io
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
import logging
|
||||||
from textwrap import wrap
|
from textwrap import wrap
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class JacquesMod(loader.Module):
|
class JacquesMod(loader.Module):
|
||||||
|
|||||||
770
archquise/H.Modules/mediatools.py
Normal file
770
archquise/H.Modules/mediatools.py
Normal file
@@ -0,0 +1,770 @@
|
|||||||
|
# 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: MediaTools
|
||||||
|
# Description: Powerful tools for working with media files
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# scope: MediaTools
|
||||||
|
# scope: MediaTools 0.0.1
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from telethon.types import Message
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def check_ffmpeg():
|
||||||
|
return shutil.which("ffmpeg") is not None
|
||||||
|
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class MediaToolsMod(loader.Module):
|
||||||
|
"""Powerful tools for working with media files"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "MediaTools",
|
||||||
|
"no_reply": "🚫 Reply to a media file!",
|
||||||
|
"no_ffmpeg": "❌ FFmpeg is not installed! Install: apt-get install ffmpeg",
|
||||||
|
"processing": "⚙️ Processing...",
|
||||||
|
"converted": "✅ Converted to {}",
|
||||||
|
"downloaded": "✅ Voice message saved",
|
||||||
|
"gif_created": "✅ GIF created",
|
||||||
|
"cut_done": "✅ Trimmed",
|
||||||
|
"circle_done": "✅ Video circle created",
|
||||||
|
"audio_extracted": "✅ Audio extracted",
|
||||||
|
"compressed": "✅ Compressed to {}",
|
||||||
|
"split_done": "✅ Split into {} parts",
|
||||||
|
"merged": "✅ Merged",
|
||||||
|
"metadata_removed": "✅ Metadata removed",
|
||||||
|
"invalid_args": "❌ Invalid arguments",
|
||||||
|
"error": "❌ Error: {}",
|
||||||
|
"available_formats": "Available formats:\n🎵 Audio: mp3, flac, wav, aac, ogg, m4a, opus\n🎬 Video: mp4, avi, mkv, mov, wmv, flv, webm, 3gp, hevc, h264",
|
||||||
|
"cut_usage": "Usage: .cut 20s6ms:8m16s3ms",
|
||||||
|
"compress_usage": "Available qualities: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p",
|
||||||
|
"split_time_usage": "Example: .split 10m (10 minutes)",
|
||||||
|
"split_size_usage": "Usage: .split 10m or .split 5MB",
|
||||||
|
"merge_usage": "Reply to first video/audio",
|
||||||
|
"min_files": "Need at least 2 media files in chain",
|
||||||
|
"downloading": "Downloading {} files...",
|
||||||
|
"part": "Part {}/{}",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"no_reply": "🚫 Ответьте на медиафайл!",
|
||||||
|
"no_ffmpeg": "❌ FFmpeg не установлен! Установите: apt-get install ffmpeg",
|
||||||
|
"processing": "⚙️ Обрабатываю...",
|
||||||
|
"converted": "✅ Конвертировано в {}",
|
||||||
|
"downloaded": "✅ Голосовое сохранено",
|
||||||
|
"gif_created": "✅ GIF создан",
|
||||||
|
"cut_done": "✅ Обрезано",
|
||||||
|
"circle_done": "✅ Видео в кружок",
|
||||||
|
"audio_extracted": "✅ Аудио извлечено",
|
||||||
|
"compressed": "✅ Сжато до {}",
|
||||||
|
"split_done": "✅ Разделено на {} частей",
|
||||||
|
"merged": "✅ Объединено",
|
||||||
|
"metadata_removed": "✅ Метаданные удалены",
|
||||||
|
"invalid_args": "❌ Неверные аргументы",
|
||||||
|
"error": "❌ Ошибка: {}",
|
||||||
|
"available_formats": "Доступные форматы:\n🎵 Аудио: mp3, flac, wav, aac, ogg, m4a, opus\n🎬 Видео: mp4, avi, mkv, mov, wmv, flv, webm, 3gp, hevc, h264",
|
||||||
|
"cut_usage": "Используйте: .cut 20с6мс:8м16с3мс",
|
||||||
|
"compress_usage": "Доступные качества: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p",
|
||||||
|
"split_time_usage": "Пример: .split 10m (10 минут)",
|
||||||
|
"split_size_usage": "Используйте: .split 10m или .split 5MB",
|
||||||
|
"merge_usage": "Ответьте на первое видео/аудио",
|
||||||
|
"min_files": "Нужно как минимум 2 медиафайла в цепочке",
|
||||||
|
"downloading": "Скачиваю {} файлов...",
|
||||||
|
"part": "Часть {}/{}",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self._client = client
|
||||||
|
self._db = db
|
||||||
|
if not check_ffmpeg():
|
||||||
|
self.logger.warning(self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="<формат> - конвертировать медиа в указанный формат",
|
||||||
|
en_doc="<format> - convert media to specified format",
|
||||||
|
)
|
||||||
|
async def convert(self, message: Message):
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.media:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
formats = {
|
||||||
|
"mp3": "audio",
|
||||||
|
"flac": "audio",
|
||||||
|
"wav": "audio",
|
||||||
|
"aac": "audio",
|
||||||
|
"ogg": "audio",
|
||||||
|
"m4a": "audio",
|
||||||
|
"opus": "audio",
|
||||||
|
"mp4": "video",
|
||||||
|
"avi": "video",
|
||||||
|
"mkv": "video",
|
||||||
|
"mov": "video",
|
||||||
|
"wmv": "video",
|
||||||
|
"flv": "video",
|
||||||
|
"webm": "video",
|
||||||
|
"3gp": "video",
|
||||||
|
"hevc": "video",
|
||||||
|
"h264": "video",
|
||||||
|
}
|
||||||
|
|
||||||
|
if not args or args not in formats:
|
||||||
|
return await utils.answer(message, self.strings["available_formats"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_converted.{args}"
|
||||||
|
|
||||||
|
cmd = ["ffmpeg", "-i", file, "-y"]
|
||||||
|
if formats[args] == "audio":
|
||||||
|
if args == "mp3":
|
||||||
|
cmd.extend(["-codec:a", "libmp3lame", "-q:a", "2"])
|
||||||
|
elif args == "flac":
|
||||||
|
cmd.extend(["-codec:a", "flac", "-compression_level", "12"])
|
||||||
|
elif args == "opus":
|
||||||
|
cmd.extend(["-codec:a", "libopus", "-b:a", "128k"])
|
||||||
|
elif args == "aac":
|
||||||
|
cmd.extend(["-codec:a", "aac", "-b:a", "192k"])
|
||||||
|
elif formats[args] == "video":
|
||||||
|
if args in ["hevc", "h264"]:
|
||||||
|
codec = "libx265" if args == "hevc" else "libx264"
|
||||||
|
cmd.extend(["-codec:v", codec, "-preset", "medium", "-crf", "23"])
|
||||||
|
if args == "webm":
|
||||||
|
cmd.extend(["-codec:v", "libvpx-vp9", "-b:v", "1M"])
|
||||||
|
|
||||||
|
cmd.append(output)
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["converted"].format(args),
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
if os.path.exists(output):
|
||||||
|
os.remove(output)
|
||||||
|
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Скачать голосовое сообщение как файл",
|
||||||
|
en_doc="Download voice message as file",
|
||||||
|
)
|
||||||
|
async def voicedl(self, message: Message):
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.voice:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/voice.ogg")
|
||||||
|
new_file = file.replace(".ogg", ".opus")
|
||||||
|
os.rename(file, new_file)
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
new_file,
|
||||||
|
caption=self.strings["downloaded"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
voice_note=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(new_file)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(ru_doc="Преобразовать видео в GIF", en_doc="Convert video to GIF")
|
||||||
|
async def gif(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.video:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}.gif"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
file,
|
||||||
|
"-vf",
|
||||||
|
"fps=10,scale=480:-1:flags=lanczos",
|
||||||
|
"-gifflags",
|
||||||
|
"+transdiff",
|
||||||
|
"-y",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["gif_created"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(output)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
def parse_time(self, time_str: str) -> Optional[float]:
|
||||||
|
time_str = time_str.lower()
|
||||||
|
total = 0
|
||||||
|
pattern = r"(\d+\.?\d*)([мm]?[сc]|[мm][сc]?)"
|
||||||
|
matches = re.findall(pattern, time_str)
|
||||||
|
|
||||||
|
for value, unit in matches:
|
||||||
|
value = float(value)
|
||||||
|
if "м" in unit or "m" in unit:
|
||||||
|
total += value * 60
|
||||||
|
elif "с" in unit or "c" in unit:
|
||||||
|
total += value
|
||||||
|
|
||||||
|
return total if total > 0 else None
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="<начало:конец> - обрезать медиа по времени",
|
||||||
|
en_doc="<start:end> - trim media by time",
|
||||||
|
)
|
||||||
|
async def cut(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.media:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args or ":" not in args:
|
||||||
|
return await utils.answer(message, self.strings["cut_usage"])
|
||||||
|
|
||||||
|
start_str, end_str = args.split(":", 1)
|
||||||
|
start = self.parse_time(start_str)
|
||||||
|
end = self.parse_time(end_str)
|
||||||
|
|
||||||
|
if start is None or end is None or start >= end:
|
||||||
|
return await utils.answer(message, self.strings["invalid_args"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_cut.{file.rsplit('.', 1)[1]}"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
file,
|
||||||
|
"-ss",
|
||||||
|
str(start),
|
||||||
|
"-to",
|
||||||
|
str(end),
|
||||||
|
"-c",
|
||||||
|
"copy",
|
||||||
|
"-avoid_negative_ts",
|
||||||
|
"make_zero",
|
||||||
|
"-y",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["cut_done"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(output)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[начало:конец] - Видео в кружок",
|
||||||
|
en_doc="[start:end] - Convert video to circle",
|
||||||
|
)
|
||||||
|
async def vircle(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not (reply.video or reply.gif):
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
filter_args = ""
|
||||||
|
|
||||||
|
if args and ":" in args:
|
||||||
|
start_str, end_str = args.split(":", 1)
|
||||||
|
start = self.parse_time(start_str)
|
||||||
|
end = self.parse_time(end_str)
|
||||||
|
|
||||||
|
if start is not None and end is not None and start < end:
|
||||||
|
filter_args = f",trim=start={start}:end={end},setpts=PTS-STARTPTS"
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_circle.mp4"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
file,
|
||||||
|
"-vf",
|
||||||
|
f"scale=720:720:force_original_aspect_ratio=increase,crop=720:720{filter_args},format=rgba,geq='if(gt(X,360),if(gt(Y,360),if(lt(sqrt((X-360)^2+(Y-360)^2),360),p(X,Y),0),0),0)'",
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset",
|
||||||
|
"fast",
|
||||||
|
"-crf",
|
||||||
|
"23",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-y",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["circle_done"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
video_note=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(output)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[начало:конец] - Извлечь аудио из видео",
|
||||||
|
en_doc="[start:end] - Extract audio from video",
|
||||||
|
)
|
||||||
|
async def vsound(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.video:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_audio.mp3"
|
||||||
|
|
||||||
|
cmd = ["ffmpeg", "-i", file]
|
||||||
|
if args and ":" in args:
|
||||||
|
start_str, end_str = args.split(":", 1)
|
||||||
|
start = self.parse_time(start_str)
|
||||||
|
end = self.parse_time(end_str)
|
||||||
|
|
||||||
|
if start is not None and end is not None and start < end:
|
||||||
|
cmd.extend(["-ss", str(start), "-to", str(end)])
|
||||||
|
|
||||||
|
cmd.extend(["-q:a", "2", "-map", "a", "-y", output])
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["audio_extracted"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(output)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="<качество> - Сжать видео", en_doc="<quality> - Compress video"
|
||||||
|
)
|
||||||
|
async def compress(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.video:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
resolutions = {
|
||||||
|
"144p": "256x144",
|
||||||
|
"240p": "426x240",
|
||||||
|
"360p": "640x360",
|
||||||
|
"480p": "854x480",
|
||||||
|
"720p": "1280x720",
|
||||||
|
"1080p": "1920x1080",
|
||||||
|
"1440p": "2560x1440",
|
||||||
|
"2160p": "3840x2160",
|
||||||
|
}
|
||||||
|
|
||||||
|
if not args or args not in resolutions:
|
||||||
|
return await utils.answer(message, self.strings["compress_usage"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_compressed.mp4"
|
||||||
|
|
||||||
|
probe_cmd = [
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"v:0",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=bit_rate",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
file,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*probe_cmd,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
)
|
||||||
|
stdout, _ = await process.communicate()
|
||||||
|
original_bitrate = stdout.decode().strip()
|
||||||
|
|
||||||
|
scale_factor = {
|
||||||
|
"144p": 0.1,
|
||||||
|
"240p": 0.2,
|
||||||
|
"360p": 0.3,
|
||||||
|
"480p": 0.4,
|
||||||
|
"720p": 0.6,
|
||||||
|
"1080p": 0.8,
|
||||||
|
"1440p": 0.9,
|
||||||
|
"2160p": 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
target_bitrate = "500k"
|
||||||
|
if original_bitrate and original_bitrate.isdigit():
|
||||||
|
original_br = int(original_bitrate)
|
||||||
|
target_br = int(original_br * scale_factor[args] / 1000)
|
||||||
|
target_bitrate = f"{max(200, target_br)}k"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
file,
|
||||||
|
"-vf",
|
||||||
|
f"scale={resolutions[args]}:force_original_aspect_ratio=decrease",
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset",
|
||||||
|
"medium",
|
||||||
|
"-b:v",
|
||||||
|
target_bitrate,
|
||||||
|
"-maxrate",
|
||||||
|
target_bitrate,
|
||||||
|
"-bufsize",
|
||||||
|
f"{int(target_bitrate[:-1]) * 2}k",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"128k",
|
||||||
|
"-y",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["compressed"].format(args),
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(output)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="<время/размер> - Разделить медиа на части",
|
||||||
|
en_doc="<time/size> - Split media into parts",
|
||||||
|
)
|
||||||
|
async def split(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.media:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
file_ext = file.rsplit(".", 1)[1]
|
||||||
|
|
||||||
|
if "m" in args or "м" in args:
|
||||||
|
duration = self.parse_time(args)
|
||||||
|
if not duration:
|
||||||
|
await msg.edit(self.strings["split_time_usage"])
|
||||||
|
os.remove(file)
|
||||||
|
return
|
||||||
|
|
||||||
|
probe_cmd = [
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-show_entries",
|
||||||
|
"format=duration",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
file,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*probe_cmd,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
)
|
||||||
|
stdout, _ = await process.communicate()
|
||||||
|
total_duration = float(stdout.decode().strip())
|
||||||
|
|
||||||
|
parts = math.ceil(total_duration / duration)
|
||||||
|
|
||||||
|
for i in range(parts):
|
||||||
|
start = i * duration
|
||||||
|
end = min((i + 1) * duration, total_duration)
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_part{i + 1}.{file_ext}"
|
||||||
|
|
||||||
|
split_cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
file,
|
||||||
|
"-ss",
|
||||||
|
str(start),
|
||||||
|
"-to",
|
||||||
|
str(end),
|
||||||
|
"-c",
|
||||||
|
"copy",
|
||||||
|
"-avoid_negative_ts",
|
||||||
|
"make_zero",
|
||||||
|
"-y",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*split_cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["part"].format(i + 1, parts),
|
||||||
|
reply_to=reply.id if i == 0 else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(output)
|
||||||
|
|
||||||
|
await msg.edit(self.strings["split_done"].format(parts))
|
||||||
|
|
||||||
|
elif "mb" in args or "мб" in args:
|
||||||
|
await utils.answer(
|
||||||
|
message, "Size splitting requires additional implementation"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await msg.edit(self.strings["split_size_usage"])
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Объединить несколько медиафайлов", en_doc="Merge multiple media files"
|
||||||
|
)
|
||||||
|
async def merge(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply:
|
||||||
|
return await utils.answer(message, self.strings["merge_usage"])
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
current = reply
|
||||||
|
|
||||||
|
while current and current.media:
|
||||||
|
messages.append(current)
|
||||||
|
current = await current.get_reply_message()
|
||||||
|
|
||||||
|
if len(messages) < 2:
|
||||||
|
return await utils.answer(message, self.strings["min_files"])
|
||||||
|
|
||||||
|
msg = await utils.answer(
|
||||||
|
message, self.strings["downloading"].format(len(messages))
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
files = []
|
||||||
|
file_list = "temp/filelist.txt"
|
||||||
|
|
||||||
|
with open(file_list, "w") as f:
|
||||||
|
for i, msg_file in enumerate(messages):
|
||||||
|
filename = f"temp/merge_{i}.{msg_file.file.ext if msg_file.file else 'mp4'}"
|
||||||
|
await msg_file.download_media(file=filename)
|
||||||
|
files.append(filename)
|
||||||
|
f.write(f"file '{os.path.abspath(filename)}'\n")
|
||||||
|
|
||||||
|
output = "temp/merged.mp4"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-f",
|
||||||
|
"concat",
|
||||||
|
"-safe",
|
||||||
|
"0",
|
||||||
|
"-i",
|
||||||
|
file_list,
|
||||||
|
"-c",
|
||||||
|
"copy",
|
||||||
|
"-y",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["merged"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if os.path.exists(file):
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(file_list)
|
||||||
|
os.remove(output)
|
||||||
|
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Удалить метаданные из медиа", en_doc="Remove metadata from media"
|
||||||
|
)
|
||||||
|
async def removemetadata(self, message: Message):
|
||||||
|
if not check_ffmpeg():
|
||||||
|
return await utils.answer(message, self.strings["no_ffmpeg"])
|
||||||
|
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
if not reply or not reply.media:
|
||||||
|
return await utils.answer(message, self.strings["no_reply"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["processing"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await reply.download_media(file="temp/")
|
||||||
|
file_ext = file.rsplit(".", 1)[1]
|
||||||
|
output = f"{file.rsplit('.', 1)[0]}_nometa.{file_ext}"
|
||||||
|
|
||||||
|
cmd = ["ffmpeg", "-i", file, "-map_metadata", "-1", "-y", output]
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(*cmd)
|
||||||
|
await process.communicate()
|
||||||
|
|
||||||
|
await message.client.send_file(
|
||||||
|
message.peer_id,
|
||||||
|
output,
|
||||||
|
caption=self.strings["metadata_removed"],
|
||||||
|
reply_to=reply.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.remove(file)
|
||||||
|
os.remove(output)
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings["error"].format(str(e)))
|
||||||
@@ -32,7 +32,7 @@ from telethon.tl.custom import Message
|
|||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
logger = logging.INFO(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
|
|||||||
@@ -26,33 +26,15 @@
|
|||||||
# scope: Api NSFWArt 0.0.1
|
# scope: Api NSFWArt 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import functools
|
import asyncio
|
||||||
import requests
|
import logging
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
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
|
@loader.tds
|
||||||
@@ -61,7 +43,17 @@ class NSFWArtMod(loader.Module):
|
|||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "NSFWArt",
|
"name": "NSFWArt",
|
||||||
"sreddit404": "🚫 <b>Subreddit not found</b>",
|
"fetching": "<emoji document_id=5188311512791393083>🌎</emoji> Fetching NSFW art...",
|
||||||
|
"no_results": "<emoji document_id=5854929766146118183>❌</emoji> No results found for this tag.",
|
||||||
|
"api_error": "<emoji document_id=5854929766146118183>❌</emoji> API error: {error}",
|
||||||
|
"network_error": "<emoji document_id=5854929766146118183>❌</emoji> Network error. Please try again later.",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"fetching": "<emoji document_id=5188311512791393083>🌎</emoji> Получение NSFW арта...",
|
||||||
|
"no_results": "<emoji document_id=5854929766146118183>❌</emoji> Ничего не найдено для этого тега.",
|
||||||
|
"api_error": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка API: {error}",
|
||||||
|
"network_error": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка сети. Попробуйте позже.",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -69,29 +61,75 @@ class NSFWArtMod(loader.Module):
|
|||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"tags",
|
"tags",
|
||||||
"drool",
|
"drool",
|
||||||
lambda: "tag: masturbation, drool, completely, sleeping, yuri",
|
lambda: "Tag for NSFW art (e.g., drool, masturbation, yuri, etc.)",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self._session: Optional[aiohttp.ClientSession] = None
|
||||||
|
|
||||||
|
async def _get_session(self) -> aiohttp.ClientSession:
|
||||||
|
"""Get or create aiohttp session"""
|
||||||
|
if self._session is None or self._session.closed:
|
||||||
|
self._session = aiohttp.ClientSession(
|
||||||
|
timeout=aiohttp.ClientTimeout(total=30)
|
||||||
|
)
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
async def _fetch_photos(self, tags: str, quantity: int = 15) -> Optional[List[str]]:
|
||||||
|
"""Fetch photos from API"""
|
||||||
|
session = await self._get_session()
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"https://api.lolicon.app/setu/v2?tag={tags}"
|
||||||
|
|
||||||
|
async with session.get(url) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
data = await response.json()
|
||||||
|
if data.get("data") and len(data["data"]) > 0:
|
||||||
|
return data["data"][0].get("urls", {}).get("original", [])
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
logger.error(f"API error: {response.status}")
|
||||||
|
return None
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.error("API timeout")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fetch error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _handle_error(self, message, error: Exception):
|
||||||
|
"""Handle different types of errors"""
|
||||||
|
if isinstance(error, asyncio.TimeoutError):
|
||||||
|
await utils.answer(message, self.strings("network_error"))
|
||||||
|
else:
|
||||||
|
await utils.answer(
|
||||||
|
message, self.strings("api_error").format(error=str(error))
|
||||||
|
)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Отправьте симпатичный nsfw-арт",
|
ru_doc="Отправить симпатичный NSFW-арт",
|
||||||
en_doc="Send cute nsfw-art",
|
en_doc="Send cute NSFW-art",
|
||||||
)
|
)
|
||||||
async def nsfwartcmd(self, message):
|
async def nsfwartcmd(self, message):
|
||||||
|
"""Send NSFW art based on configured tags"""
|
||||||
tags = self.config["tags"]
|
tags = self.config["tags"]
|
||||||
subreddit = f"/v2?tag={tags}"
|
|
||||||
|
|
||||||
ans = await utils.run_sync(
|
if not tags:
|
||||||
requests.get, f"https://api.lolicon.app/setu{subreddit}"
|
await utils.answer(message, self.strings("no_results"))
|
||||||
)
|
|
||||||
if ans.status_code != 200:
|
|
||||||
await utils.answer(message, self.strings("sreddit404", message))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.inline.gallery(
|
await utils.answer(message, self.strings("fetching"))
|
||||||
message=message,
|
|
||||||
next_handler=functools.partial(
|
try:
|
||||||
photos, tags, subreddit=subreddit, quantity=15
|
photos = await self._fetch_photos(tags)
|
||||||
),
|
if not photos:
|
||||||
caption=f"<i>{utils.ascii_face()}</i>",
|
await utils.answer(message, self.strings("no_results"))
|
||||||
)
|
return
|
||||||
|
|
||||||
|
await self.inline.gallery(
|
||||||
|
message=message,
|
||||||
|
media=photos[:15],
|
||||||
|
caption=f"<i>{utils.ascii_face()}</i>",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
await self._handle_error(message, e)
|
||||||
|
|||||||
@@ -26,70 +26,142 @@
|
|||||||
# scope: NumbersAPI 0.0.1
|
# scope: NumbersAPI 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
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
|
@loader.tds
|
||||||
class NumbersAPI(loader.Module):
|
class NumbersAPI(loader.Module):
|
||||||
"""Many interesting facts about numbers."""
|
"""Many interesting facts about numbers."""
|
||||||
|
|
||||||
strings = {"name": "NumbersAPI"}
|
strings = {
|
||||||
|
"name": "NumbersAPI",
|
||||||
|
"usage": "<emoji document_id=5854929766146118183>❌</emoji> Usage: .num <number or date> <type>\nExamples: .num 42 math, .num 01.15 date",
|
||||||
|
"error_date_format": "<emoji document_id=5854929766146118183>❌</emoji> Invalid date format. Use: month.day (e.g., 01.15)",
|
||||||
|
"error_number_format": "<emoji document_id=5854929766146118183>❌</emoji> Invalid number format.",
|
||||||
|
"error_invalid_type": "<emoji document_id=5854929766146118183>❌</emoji> Invalid fact type. Available: math, trivia, date",
|
||||||
|
"error_api": "<emoji document_id=5854929766146118183>❌</emoji> Failed to get fact. Please try again later.",
|
||||||
|
"fetching": "<emoji document_id=5188311512791393083>🌎</emoji> Fetching fact...",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"usage": "<emoji document_id=5854929766146118183>❌</emoji> Использование: .num <число или дата> <тип>\nПримеры: .num 42 math, .num 01.15 date",
|
||||||
|
"error_date_format": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат даты. Используйте: месяц.день (например, 01.15)",
|
||||||
|
"error_number_format": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат числа.",
|
||||||
|
"error_invalid_type": "<emoji document_id=5854929766146118183>❌</emoji> Неверный тип факта. Доступны: math, trivia, date",
|
||||||
|
"error_api": "<emoji document_id=5854929766146118183>❌</emoji> Не удалось получить факт. Попробуйте позже.",
|
||||||
|
"fetching": "<emoji document_id=5188311512791393083>🌎</emoji> Получение факта...",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._session: Optional[aiohttp.ClientSession] = None
|
||||||
|
self.valid_fact_types = ["math", "trivia", "date"]
|
||||||
|
|
||||||
|
async def _get_session(self) -> aiohttp.ClientSession:
|
||||||
|
"""Get or create aiohttp session"""
|
||||||
|
if self._session is None or self._session.closed:
|
||||||
|
self._session = aiohttp.ClientSession(
|
||||||
|
timeout=aiohttp.ClientTimeout(total=15)
|
||||||
|
)
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
def _parse_date(self, date_str: str) -> Optional[tuple[int, int]]:
|
||||||
|
"""Parse date string in format MM.DD"""
|
||||||
|
try:
|
||||||
|
parts = date_str.split(".")
|
||||||
|
if len(parts) != 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
month, day = map(int, parts)
|
||||||
|
if not (1 <= month <= 12 and 1 <= day <= 31):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return month, day
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_number(self, num_str: str) -> Optional[int]:
|
||||||
|
"""Parse number string"""
|
||||||
|
try:
|
||||||
|
return int(num_str)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _fetch_fact(self, url: str) -> str:
|
||||||
|
"""Fetch fact from Numbers API"""
|
||||||
|
session = await self._get_session()
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with session.get(url) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
return await response.text()
|
||||||
|
else:
|
||||||
|
logger.error(f"Numbers API error: {response.status}")
|
||||||
|
return self.strings("error_api")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.error("Numbers API timeout")
|
||||||
|
return self.strings("error_api")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Numbers API error: {e}")
|
||||||
|
return self.strings("error_api")
|
||||||
|
|
||||||
|
async def _get_number_fact(self, number: int, fact_type: str) -> str:
|
||||||
|
"""Get fact about number"""
|
||||||
|
url = f"http://numbersapi.com/{number}/{fact_type}"
|
||||||
|
return await self._fetch_fact(url)
|
||||||
|
|
||||||
|
async def _get_date_fact(self, month: int, day: int) -> str:
|
||||||
|
"""Get fact about date"""
|
||||||
|
date_str = f"{month:02d}/{day:02d}"
|
||||||
|
url = f"http://numbersapi.com/{date_str}/date"
|
||||||
|
return await self._fetch_fact(url)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Дает интересный факт про число или дату\nНапример: .num 10 math или .num 01.01 date",
|
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",
|
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):
|
async def num(self, message):
|
||||||
args = utils.get_args_raw(message).split()
|
"""Get interesting fact about number or date"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
if len(args) < 2:
|
if not args:
|
||||||
await utils.answer(message, "Использование: .num <число или дата> <тип>")
|
await utils.answer(message, self.strings("usage"))
|
||||||
return
|
return
|
||||||
|
|
||||||
num_or_date = args[0]
|
parts = args.split(maxsplit=1)
|
||||||
fact_type = args[1]
|
if len(parts) < 2:
|
||||||
|
await utils.answer(message, self.strings("usage"))
|
||||||
|
return
|
||||||
|
|
||||||
if "." in num_or_date:
|
input_value = parts[0].strip()
|
||||||
try:
|
fact_type = parts[1].strip().lower()
|
||||||
month, day = map(int, num_or_date.split("."))
|
|
||||||
result = await get_fact_about_date(month, day)
|
if fact_type not in self.valid_fact_types:
|
||||||
except ValueError:
|
await utils.answer(message, self.strings("error_invalid_type"))
|
||||||
await utils.answer(
|
return
|
||||||
message, "Ошибка: некорректный формат даты. Используйте: месяц.день"
|
|
||||||
)
|
await utils.answer(message, self.strings("fetching"))
|
||||||
|
|
||||||
|
if "." in input_value:
|
||||||
|
date_parts = self._parse_date(input_value)
|
||||||
|
if date_parts is None:
|
||||||
|
await utils.answer(message, self.strings("error_date_format"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
month, day = date_parts
|
||||||
|
result = await self._get_date_fact(month, day)
|
||||||
else:
|
else:
|
||||||
try:
|
number = self._parse_number(input_value)
|
||||||
number = int(num_or_date)
|
if number is None:
|
||||||
result = await get_fact_about_number(number, fact_type)
|
await utils.answer(message, self.strings("error_number_format"))
|
||||||
except ValueError:
|
return
|
||||||
await utils.answer(message, "Ошибка: некорректный ввод числа.")
|
|
||||||
return
|
result = await self._get_number_fact(number, fact_type)
|
||||||
|
|
||||||
await utils.answer(message, result)
|
await utils.answer(message, result)
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
# Name: SafetyMod
|
# Name: PassgenMod
|
||||||
# Description: generate random password
|
# Description: Generates random password
|
||||||
# Author: @hikka_mods
|
# Author: @hikka_mods
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
# meta developer: @hikka_mods
|
# meta developer: @hikka_mods
|
||||||
@@ -26,11 +26,13 @@
|
|||||||
# scope: Api SafetyMod 0.0.1
|
# scope: Api SafetyMod 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import random
|
import secrets
|
||||||
import string
|
import string
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def generate_password(
|
def generate_password(
|
||||||
length: int, letters: bool = True, numbers: bool = True, symbols: bool = True
|
length: int, letters: bool = True, numbers: bool = True, symbols: bool = True
|
||||||
@@ -61,16 +63,16 @@ def generate_password(
|
|||||||
raise ValueError("At least one of letters, numbers, or symbols must be True")
|
raise ValueError("At least one of letters, numbers, or symbols must be True")
|
||||||
|
|
||||||
combined_characters = "".join(character_sets)
|
combined_characters = "".join(character_sets)
|
||||||
password = "".join(random.choice(combined_characters) for _ in range(length))
|
password = "".join(secrets.choice(combined_characters) for _ in range(length))
|
||||||
return password
|
return password
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class SafetyMod(loader.Module):
|
class PassgenMod(loader.Module):
|
||||||
"""generate random password"""
|
"""generate random password"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "Safety",
|
"name": "Passgen",
|
||||||
"pass": "<emoji document_id=5472287483318245416>*⃣</emoji> <b>Here is your secure password:</b> <code>{}</code>",
|
"pass": "<emoji document_id=5472287483318245416>*⃣</emoji> <b>Here is your secure password:</b> <code>{}</code>",
|
||||||
}
|
}
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
@@ -26,10 +26,16 @@
|
|||||||
# scope: Profile 0.0.1
|
# scope: Profile 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
from telethon.errors.rpcerrorlist import UsernameOccupiedError
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from telethon.errors.rpcerrorlist import UsernameOccupiedError, FloodWaitError
|
||||||
from telethon.tl.functions.account import UpdateProfileRequest, UpdateUsernameRequest
|
from telethon.tl.functions.account import UpdateProfileRequest, UpdateUsernameRequest
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class ProfileEditorMod(loader.Module):
|
class ProfileEditorMod(loader.Module):
|
||||||
@@ -37,61 +43,138 @@ class ProfileEditorMod(loader.Module):
|
|||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "Profile",
|
"name": "Profile",
|
||||||
"error_format": "Incorrect format of args. Try again.",
|
"error_format": "<emoji document_id=5854929766146118183>❌</emoji> Incorrect format. Try again.",
|
||||||
"done_name": "The new name was successfully unstalled!",
|
"done_name": "<emoji document_id=5854762571659218443>✅</emoji> Name successfully updated!",
|
||||||
"done_bio": "The new bio was successfully unstaled!",
|
"done_bio": "<emoji document_id=5854762571659218443>✅</emoji> Bio successfully updated!",
|
||||||
"done_username": "The new username was succesfully installed!",
|
"done_username": "<emoji document_id=5854762571659218443>✅</emoji> Username successfully updated!",
|
||||||
"error_occupied": "The new username is already occupied!",
|
"error_occupied": "<emoji document_id=5854929766146118183>❌</emoji> Username is already occupied!",
|
||||||
|
"error_invalid_username": "<emoji document_id=5854929766146118183>❌</emoji> Invalid username format!",
|
||||||
|
"error_flood": "<emoji document_id=5854929766146118183>❌</emoji> Too many requests. Try again later.",
|
||||||
|
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> An error occurred: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"error_format": "Неправильный формат аргумента. Попробуйте еще раз.",
|
"error_format": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат. Попробуйте еще раз.",
|
||||||
"done_name": "Новое имя успешно настроено!",
|
"done_name": "<emoji document_id=5854762571659218443>✅</emoji> Имя успешно обновлено!",
|
||||||
"done_bio": "Новое био успешно настроено!",
|
"done_bio": "<emoji document_id=5854762571659218443>✅</emoji> Био успешно обновлено!",
|
||||||
"done_username": "Новое имя пользователя успешно установлено!",
|
"done_username": "<emoji document_id=5854762571659218443>✅</emoji> Имя пользователя успешно обновлено!",
|
||||||
"error_occupied": "Новое имя пользователя уже занято!",
|
"error_occupied": "<emoji document_id=5854929766146118183>❌</emoji> Имя пользователя уже занято!",
|
||||||
|
"error_invalid_username": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат имени пользователя!",
|
||||||
|
"error_flood": "<emoji document_id=5854929766146118183>❌</emoji> Слишком много запросов. Попробуйте позже.",
|
||||||
|
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _validate_username(self, username: str) -> bool:
|
||||||
|
"""Validate username format"""
|
||||||
|
if not username:
|
||||||
|
return False
|
||||||
|
|
||||||
|
username = username.strip("@")
|
||||||
|
if len(username) < 5 or len(username) > 32:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return re.match(r"^[a-zA-Z0-9_]+$", username) is not None
|
||||||
|
|
||||||
|
def _sanitize_name(self, name: str) -> str:
|
||||||
|
"""Sanitize name input"""
|
||||||
|
if not name:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return " ".join(name.split())[:64]
|
||||||
|
|
||||||
|
def _sanitize_bio(self, bio: str) -> str:
|
||||||
|
"""Sanitize bio input"""
|
||||||
|
if not bio:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
bio = bio.strip()
|
||||||
|
limit = 70 if not self._client.hikka_me.premium else 140
|
||||||
|
if len(bio) < limit:
|
||||||
|
return bio[:limit]
|
||||||
|
else:
|
||||||
|
return bio[: limit - 3] + "..."
|
||||||
|
|
||||||
|
async def _handle_error(self, message, error: Exception):
|
||||||
|
"""Handle common errors"""
|
||||||
|
if isinstance(error, UsernameOccupiedError):
|
||||||
|
await utils.answer(message, self.strings("error_occupied"))
|
||||||
|
elif isinstance(error, FloodWaitError):
|
||||||
|
await utils.answer(message, self.strings("error_flood"))
|
||||||
|
else:
|
||||||
|
await utils.answer(
|
||||||
|
message, self.strings("error_general").format(error=str(error))
|
||||||
|
)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="для того, чтобы сменить свое имя/отчество",
|
ru_doc="для того, чтобы сменить свое имя/отчество",
|
||||||
en_doc="for change your first/second name",
|
en_doc="for change your first/second name",
|
||||||
)
|
)
|
||||||
async def namecmd(self, message):
|
async def namecmd(self, message):
|
||||||
args = utils.get_args_raw(message).split("/")
|
"""Change first name and last name"""
|
||||||
|
|
||||||
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)
|
args = utils.get_args_raw(message)
|
||||||
if not args:
|
if not args:
|
||||||
return await utils.answer(message, self.strings("error_format"))
|
return await utils.answer(message, self.strings("error_format"))
|
||||||
await message.client(UpdateProfileRequest(about=args))
|
|
||||||
await utils.answer(message, self.strings("done_bio"))
|
if "/" in args:
|
||||||
|
parts = args.split("/", 1)
|
||||||
|
else:
|
||||||
|
parts = args.split(" ", 1)
|
||||||
|
|
||||||
|
if len(parts) < 1:
|
||||||
|
return await utils.answer(message, self.strings("error_format"))
|
||||||
|
|
||||||
|
firstname = self._sanitize_name(parts[0])
|
||||||
|
lastname = self._sanitize_name(parts[1]) if len(parts) > 1 else ""
|
||||||
|
|
||||||
|
if not firstname:
|
||||||
|
return await utils.answer(message, self.strings("error_format"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
await message.client(
|
||||||
|
UpdateProfileRequest(first_name=firstname, last_name=lastname)
|
||||||
|
)
|
||||||
|
await utils.answer(message, self.strings("done_name"))
|
||||||
|
except Exception as e:
|
||||||
|
await self._handle_error(message, e)
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="для изменения вашего имени пользователя. Введите значение без '@'",
|
||||||
|
en_doc="for change your username. Enter value without '@'",
|
||||||
|
)
|
||||||
|
async def aboutcmd(self, message):
|
||||||
|
"""Change profile bio"""
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
if not args:
|
||||||
|
return await utils.answer(message, self.strings("error_format"))
|
||||||
|
|
||||||
|
bio = self._sanitize_bio(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await message.client(UpdateProfileRequest(about=bio))
|
||||||
|
await utils.answer(message, self.strings("done_bio"))
|
||||||
|
except Exception as e:
|
||||||
|
await self._handle_error(message, e)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="для изменения вашего имени пользователя. Введите значение без '@'",
|
ru_doc="для изменения вашего имени пользователя. Введите значение без '@'",
|
||||||
en_doc="for change your username. Enter value without '@'",
|
en_doc="for change your username. Enter value without '@'",
|
||||||
)
|
)
|
||||||
async def usercmd(self, message):
|
async def usercmd(self, message):
|
||||||
"""- for change your username. Enter value without "@"."""
|
"""Change username"""
|
||||||
args = utils.get_args_raw(message)
|
args = utils.get_args_raw(message)
|
||||||
if not args:
|
if not args:
|
||||||
return await utils.answer(message, self.strings("error_format"))
|
return await utils.answer(message, self.strings("error_format"))
|
||||||
|
|
||||||
|
username = args.strip("@")
|
||||||
|
|
||||||
|
if not self._validate_username(username):
|
||||||
|
return await utils.answer(message, self.strings("error_invalid_username"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await message.client(UpdateUsernameRequest(args))
|
await message.client(UpdateUsernameRequest(username))
|
||||||
await utils.answer(message, self.strings("done_username"))
|
await utils.answer(message, self.strings("done_username"))
|
||||||
except UsernameOccupiedError:
|
except Exception as e:
|
||||||
await utils.answer(message, self.strings("error_occupied"))
|
await self._handle_error(message, e)
|
||||||
|
|||||||
@@ -26,8 +26,13 @@
|
|||||||
# scope: Api Search 0.0.1
|
# scope: Api Search 0.0.1
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class Search(loader.Module):
|
class Search(loader.Module):
|
||||||
@@ -39,6 +44,7 @@ class Search(loader.Module):
|
|||||||
"isearch": "🔎<b> I searched for information for you</b> ",
|
"isearch": "🔎<b> I searched for information for you</b> ",
|
||||||
"link": "🗂️ Link to your request",
|
"link": "🗂️ Link to your request",
|
||||||
"close": "❌ Close",
|
"close": "❌ Close",
|
||||||
|
"no_query": "<emoji document_id=5854929766146118183>❌</emoji> Please provide a search query.",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
@@ -46,160 +52,136 @@ class Search(loader.Module):
|
|||||||
"isearch": "🔎<b> Я поискал информацию за тебя</b> ",
|
"isearch": "🔎<b> Я поискал информацию за тебя</b> ",
|
||||||
"link": "🗂️ Ссылка на ваш запрос",
|
"link": "🗂️ Ссылка на ваш запрос",
|
||||||
"close": "❌ Закрыть",
|
"close": "❌ Закрыть",
|
||||||
|
"no_query": "<emoji document_id=5854929766146118183>❌</emoji> Пожалуйста, укажите поисковый запрос.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.search_engines = {
|
||||||
|
"google": "https://google.com/search?q=",
|
||||||
|
"yandex": "https://yandex.ru/?q=",
|
||||||
|
"duckduckgo": "https://duckduckgo.com/?q=",
|
||||||
|
"bing": "https://bing.com/?q=",
|
||||||
|
"you": "https://you.com/?q=",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_search_url(self, engine: str, query: str) -> str:
|
||||||
|
"""Create search URL with proper encoding"""
|
||||||
|
if not query.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
base_url = self.search_engines.get(engine)
|
||||||
|
if not base_url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
encoded_query = urllib.parse.quote_plus(query.strip())
|
||||||
|
return f"{base_url}{encoded_query}"
|
||||||
|
|
||||||
|
def _create_inline_markup(self, search_url: str):
|
||||||
|
"""Create inline keyboard markup"""
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": self.strings("link"),
|
||||||
|
"url": search_url,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[{"text": self.strings("close"), "action": "close"}],
|
||||||
|
]
|
||||||
|
|
||||||
|
async def _search_command(self, message, engine: str, inline: bool = False):
|
||||||
|
"""Universal search command handler"""
|
||||||
|
query = utils.get_args_raw(message)
|
||||||
|
|
||||||
|
if not query.strip():
|
||||||
|
await utils.answer(message, self.strings("no_query"))
|
||||||
|
return
|
||||||
|
|
||||||
|
search_url = self._create_search_url(engine, query)
|
||||||
|
if not search_url:
|
||||||
|
await utils.answer(message, self.strings("no_query"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if inline:
|
||||||
|
await self.inline.form(
|
||||||
|
text=self.strings("isearch"),
|
||||||
|
message=message,
|
||||||
|
reply_markup=self._create_inline_markup(search_url),
|
||||||
|
silent=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await utils.answer(
|
||||||
|
message, self.strings("search") + f": <a href={search_url}>link</a>"
|
||||||
|
)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Поискать в Google",
|
ru_doc="Поискать в Google",
|
||||||
en_doc="Search on Google",
|
en_doc="Search on Google",
|
||||||
)
|
)
|
||||||
async def google(self, message):
|
async def google(self, message):
|
||||||
await self.search_engine(message, "https://google.com/search?q=")
|
await self._search_command(message, "google")
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Поискать в Yandex",
|
ru_doc="Поискать в Yandex",
|
||||||
en_doc="Search on Yandex",
|
en_doc="Search on Yandex",
|
||||||
)
|
)
|
||||||
async def yandex(self, message):
|
async def yandex(self, message):
|
||||||
await self.search_engine(message, "https://yandex.ru/?q=")
|
await self._search_command(message, "yandex")
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Поискать в Duckduckgo",
|
ru_doc="Поискать в Duckduckgo",
|
||||||
en_doc="Search on Duckduckgo",
|
en_doc="Search on Duckduckgo",
|
||||||
)
|
)
|
||||||
async def duckduckgo(self, message):
|
async def duckduckgo(self, message):
|
||||||
await self.search_engine(message, "https://duckduckgo.com/?q=")
|
await self._search_command(message, "duckduckgo")
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Поискать в Bing",
|
ru_doc="Поискать в Bing",
|
||||||
en_doc="Search on Bing",
|
en_doc="Search on Bing",
|
||||||
)
|
)
|
||||||
async def bing(self, message):
|
async def bing(self, message):
|
||||||
await self.search_engine(message, "https://bing.com/?q=")
|
await self._search_command(message, "bing")
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Поискать в You",
|
ru_doc="Поискать в You",
|
||||||
en_doc="Search on You",
|
en_doc="Search on You",
|
||||||
)
|
)
|
||||||
async def you(self, message):
|
async def you(self, message):
|
||||||
await self.search_engine(message, "https://you.com/?q=")
|
await self._search_command(message, "you")
|
||||||
|
|
||||||
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(
|
@loader.command(
|
||||||
ru_doc="Поискать в Google инлайн",
|
ru_doc="Поискать в Google инлайн",
|
||||||
en_doc="Search on Google inline",
|
en_doc="Search on Google inline",
|
||||||
)
|
)
|
||||||
async def igoogle(self, message):
|
async def igoogle(self, message):
|
||||||
g = utils.get_args_raw(message)
|
await self._search_command(message, "google", inline=True)
|
||||||
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(
|
@loader.command(
|
||||||
ru_doc="Поискать в Yandex инлайн",
|
ru_doc="Поискать в Yandex инлайн",
|
||||||
en_doc="Search on Yandex inline",
|
en_doc="Search on Yandex inline",
|
||||||
)
|
)
|
||||||
async def iyandex(self, message):
|
async def iyandex(self, message):
|
||||||
y = utils.get_args_raw(message)
|
await self._search_command(message, "yandex", inline=True)
|
||||||
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(
|
@loader.command(
|
||||||
ru_doc="Поискать в Duckduckgo инлайн",
|
ru_doc="Поискать в Duckduckgo инлайн",
|
||||||
en_doc="Search on Duckduckgo inline",
|
en_doc="Search on Duckduckgo inline",
|
||||||
)
|
)
|
||||||
async def iduckduckgo(self, message):
|
async def iduckduckgo(self, message):
|
||||||
d = utils.get_args_raw(message)
|
await self._search_command(message, "duckduckgo", inline=True)
|
||||||
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(
|
@loader.command(
|
||||||
ru_doc="Поискать в Bing инлайн",
|
ru_doc="Поискать в Bing инлайн",
|
||||||
en_doc="Search on Bing inline",
|
en_doc="Search on Bing inline",
|
||||||
)
|
)
|
||||||
async def ibing(self, message):
|
async def ibing(self, message):
|
||||||
b = utils.get_args_raw(message)
|
await self._search_command(message, "bing", inline=True)
|
||||||
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(
|
@loader.command(
|
||||||
ru_doc="Поискать в You инлайн",
|
ru_doc="Поискать в You инлайн",
|
||||||
en_doc="Search on You inline",
|
en_doc="Search on You inline",
|
||||||
)
|
)
|
||||||
async def iyou(self, message):
|
async def iyou(self, message):
|
||||||
y = utils.get_args_raw(message)
|
await self._search_command(message, "you", inline=True)
|
||||||
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):
|
async def close(self, call):
|
||||||
"""Callback button"""
|
"""Callback button"""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Proprietary License Agreement
|
# Proprietary License Agreement
|
||||||
|
|
||||||
# Copyright (c) 2024-29 CodWiz
|
# Copyright (c) 2026-2029 Archquise
|
||||||
|
|
||||||
# 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:
|
# 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:
|
||||||
|
|
||||||
@@ -14,39 +14,50 @@
|
|||||||
|
|
||||||
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
# 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.
|
# For any inquiries or requests for permissions, please contact archquise@gmail.com
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
# Name: Shortener
|
# Name: Shortener
|
||||||
# Description: shortening the link
|
# Description: Module for using bit.ly API
|
||||||
# Author: @hikka_mods
|
# Author: @hikka_mods
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
# meta developer: @hikka_mods
|
# meta developer: @hikka_mods
|
||||||
# scope: Shortener
|
# scope: Shortener
|
||||||
# scope: Shortener 0.0.1
|
# scope: Shortener 0.0.1
|
||||||
# requires: pyshorteners
|
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
import pyshorteners
|
import logging
|
||||||
|
import re
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class Shortener(loader.Module):
|
class Shortener(loader.Module):
|
||||||
"""Module for working with the api bit.ly"""
|
"""Module for using bit.ly API"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "Shortener",
|
"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>",
|
"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}",
|
"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>",
|
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Your shortened link is ready:</b> <code>{c}</code>",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Please provide a URL to shorten.",
|
||||||
|
"invalid_url": "<emoji document_id=5854929766146118183>❌</emoji> Invalid URL format.",
|
||||||
|
"api_error": "<emoji document_id=5854929766146118183>❌</emoji> API error: {error}",
|
||||||
|
"_cls_doc": "Module for using bit.ly API",
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"no_api": "<emoji document_id=5854929766146118183>❌</emoji> Вы не указали api токен с сайта <a href='https://app.bitly.com/settings/api/'>bit.ly</a>",
|
"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}",
|
"statclcmd": "<emoji document_id=5787384838411522455>📊</emoji> <b>Статистика о переходе по этой ссылке:</b> {c}",
|
||||||
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Ваша сокращённая ссылка готова:</b> <code>{c}</code>",
|
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Ваша сокращённая ссылка готова:</b> <code>{c}</code>",
|
||||||
|
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Пожалуйста, укажите URL для сокращения.",
|
||||||
|
"invalid_url": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат URL.",
|
||||||
|
"api_error": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка API: {error}",
|
||||||
|
"_cls_doc": "Модуль для использования API bit.ly",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -59,32 +70,93 @@ class Shortener(loader.Module):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _validate_url(self, url: str) -> bool:
|
||||||
|
"""Validate URL format"""
|
||||||
|
if not url:
|
||||||
|
return False
|
||||||
|
|
||||||
|
url_pattern = re.compile(
|
||||||
|
r"^https?://"
|
||||||
|
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|"
|
||||||
|
r"localhost|"
|
||||||
|
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
|
||||||
|
r"(?::\d+)?"
|
||||||
|
r"(?:/?|[/?]\S+)$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
return url_pattern.match(url) is not None
|
||||||
|
|
||||||
|
async def shorten_url(self, url: str, token: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post("https://api-ssl.bitly.com/v4/shorten", json={'long_url': url}, headers={"Authorization": f"Bearer {token}"}) as resp:
|
||||||
|
if resp.status == 201:
|
||||||
|
json_response = await resp.json()
|
||||||
|
return json_response['link']
|
||||||
|
else:
|
||||||
|
logger.error(f"Error occurred! Status code: {resp.status}")
|
||||||
|
return
|
||||||
|
|
||||||
|
async def get_bitlink_stats(self, bitlink: str, token: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(f"https://api-ssl.bitly.com/v4/bitlinks/{bitlink}/clicks/summary", headers={"Authorization": f"Bearer {token}"}) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
json_response = await resp.json()
|
||||||
|
return json_response['total_clicks']
|
||||||
|
else:
|
||||||
|
logger.error(f"Error occurred! Status code: {resp.status}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Сократить ссылку через bit.ly",
|
ru_doc="Сократить ссылку через bit.ly (ссылка с https://)",
|
||||||
en_doc="Shorten the link via bit.ly",
|
en_doc="Shorten the link via bit.ly (url with https://)",
|
||||||
)
|
)
|
||||||
async def shortencmd(self, message):
|
async def shortencmd(self, message):
|
||||||
|
"""Shorten URL using bit.ly API"""
|
||||||
if self.config["token"] is None:
|
if self.config["token"] is None:
|
||||||
await utils.answer(message, self.strings("no_api"))
|
await utils.answer(message, self.strings("no_api"))
|
||||||
return
|
return
|
||||||
|
|
||||||
s = pyshorteners.Shortener(api_key=self.config["token"])
|
|
||||||
args = utils.get_args_raw(message)
|
args = utils.get_args_raw(message)
|
||||||
await utils.answer(
|
if not args:
|
||||||
message, self.strings("shortencmd").format(c=s.bitly.short(args))
|
await utils.answer(message, self.strings("no_args"))
|
||||||
)
|
return
|
||||||
|
|
||||||
|
if not self._validate_url(args):
|
||||||
|
await utils.answer(message, self.strings("invalid_url"))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
short_url = await self.shorten_url(url=args, token=self.config['token'])
|
||||||
|
await utils.answer(message, self.strings("shortencmd").format(c=short_url))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error shortening URL: {e}")
|
||||||
|
await utils.answer(message, self.strings("api_error").format(error=str(e)))
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
ru_doc="Посмотреть статистику ссылки через bit.ly",
|
ru_doc="Посмотреть статистику ссылки через bit.ly (ссылка без https:// | Доступно только на платных аккаунтах)",
|
||||||
en_doc="View link statistics via bit.ly",
|
en_doc="View link statistics via bit.ly (link without https:// | Works only on paid accounts)",
|
||||||
)
|
)
|
||||||
async def statclcmd(self, message):
|
async def statclcmd(self, message):
|
||||||
|
"""Get click statistics for shortened URL"""
|
||||||
if self.config["token"] is None:
|
if self.config["token"] is None:
|
||||||
await utils.answer(message, self.strings("no_api"))
|
await utils.answer(message, self.strings("no_api"))
|
||||||
return
|
return
|
||||||
|
|
||||||
s = pyshorteners.Shortener(api_key=self.config["token"])
|
|
||||||
args = utils.get_args_raw(message)
|
args = utils.get_args_raw(message)
|
||||||
await utils.answer(
|
if not args:
|
||||||
message, self.strings("statclcmd").format(c=s.bitly.total_clicks(args))
|
await utils.answer(message, self.strings("no_args"))
|
||||||
)
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not args.startswith("bit.ly/"):
|
||||||
|
await utils.answer(message, self.strings("invalid_url"))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
clicks = await self.get_bitlink_stats(bitlink=args, token=self.config['token'])
|
||||||
|
await utils.answer(message, self.strings("statclcmd").format(c=clicks))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting statistics: {e}")
|
||||||
|
await utils.answer(message, self.strings("api_error").format(error=str(e)))
|
||||||
|
|||||||
480
archquise/H.Modules/silenttr.py
Normal file
480
archquise/H.Modules/silenttr.py
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
# 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: Silent T&R
|
||||||
|
# Description: Silent tags and reactions
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# scope: Silent T&R
|
||||||
|
# scope: Silent T&R 0.0.1
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from telethon.types import Message
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class SilentTRMod(loader.Module):
|
||||||
|
"""Silent tags and reactions"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "Silent T&R",
|
||||||
|
"global_reactions_on": "✅ Global silent reactions enabled",
|
||||||
|
"global_reactions_off": "❌ Global silent reactions disabled",
|
||||||
|
"global_tags_on": "✅ Global silent tags enabled",
|
||||||
|
"global_tags_off": "❌ Global silent tags disabled",
|
||||||
|
"chat_reactions_on": "✅ Silent reactions enabled in this chat",
|
||||||
|
"chat_reactions_off": "❌ Silent reactions disabled in this chat",
|
||||||
|
"chat_tags_on": "✅ Silent tags enabled in this chat",
|
||||||
|
"chat_tags_off": "❌ Silent tags disabled in this chat",
|
||||||
|
"ignore_added": "✅ User added to global ignore list",
|
||||||
|
"ignore_removed": "❌ User removed from global ignore list",
|
||||||
|
"hignore_added": "✅ User added to ignore list in this chat",
|
||||||
|
"hignore_removed": "❌ User removed from ignore list in this chat",
|
||||||
|
"no_reply": "❌ Reply to a user or specify username",
|
||||||
|
"user_not_found": "❌ User not found",
|
||||||
|
"args_error": "❌ Use: .sreacts on/off or .sreacts",
|
||||||
|
"chat_args_error": "❌ Use: .hsreacts on/off or .hsreacts",
|
||||||
|
"status": "📊 Silent T&R status:\n\nGlobal:\n Reactions: {}\n Tags: {}\n\nThis chat:\n Reactions: {}\n Tags: {}\n\nGlobal ignore: {}\nChat ignore: {}",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"global_reactions_on": "✅ Глобальные тихие реакции включены",
|
||||||
|
"global_reactions_off": "❌ Глобальные тихие реакции выключены",
|
||||||
|
"global_tags_on": "✅ Глобальные тихие упоминания включены",
|
||||||
|
"global_tags_off": "❌ Глобальные тихие упоминания выключены",
|
||||||
|
"chat_reactions_on": "✅ Тихие реакции включены в этом чате",
|
||||||
|
"chat_reactions_off": "❌ Тихие реакции выключены в этом чате",
|
||||||
|
"chat_tags_on": "✅ Тихие упоминания включены в этом чате",
|
||||||
|
"chat_tags_off": "❌ Тихие упоминания выключены в этом чате",
|
||||||
|
"ignore_added": "✅ Пользователь добавлен в глобальный игнор-лист",
|
||||||
|
"ignore_removed": "❌ Пользователь удален из глобального игнор-листа",
|
||||||
|
"hignore_added": "✅ Пользователь добавлен в игнор-лист этого чата",
|
||||||
|
"hignore_removed": "❌ Пользователь удален из игнор-листа этого чата",
|
||||||
|
"no_reply": "❌ Ответьте на пользователя или укажите username",
|
||||||
|
"user_not_found": "❌ Пользователь не найден",
|
||||||
|
"args_error": "❌ Используйте: .sreacts on/off или .sreacts",
|
||||||
|
"chat_args_error": "❌ Используйте: .hsreacts on/off или .hsreacts",
|
||||||
|
"status": "📊 Статус Silent T&R:\n\nГлобально:\n Реакции: {}\n Упоминания: {}\n\nВ этом чате:\n Реакции: {}\n Упоминания: {}\n\nГлобальный игнор: {}\nИгнор в чате: {}",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self._client = client
|
||||||
|
self._db = db
|
||||||
|
|
||||||
|
self._global_settings = self._db.get(
|
||||||
|
__name__, "global_settings", {"reactions": False, "tags": False}
|
||||||
|
)
|
||||||
|
self._chat_settings = self._db.get(__name__, "chat_settings", {})
|
||||||
|
self._global_ignore = self._db.get(__name__, "global_ignore", [])
|
||||||
|
self._chat_ignore = self._db.get(__name__, "chat_ignore", {})
|
||||||
|
|
||||||
|
client.add_event_handler(self._on_message_reaction_updated)
|
||||||
|
client.add_event_handler(self._on_new_message)
|
||||||
|
|
||||||
|
async def on_unload(self):
|
||||||
|
self._client.remove_event_handler(self._on_message_reaction_updated)
|
||||||
|
self._client.remove_event_handler(self._on_new_message)
|
||||||
|
|
||||||
|
def _save_global_settings(self):
|
||||||
|
self._db.set(__name__, "global_settings", self._global_settings)
|
||||||
|
|
||||||
|
def _save_chat_settings(self):
|
||||||
|
self._db.set(__name__, "chat_settings", self._chat_settings)
|
||||||
|
|
||||||
|
def _save_global_ignore(self):
|
||||||
|
self._db.set(__name__, "global_ignore", self._global_ignore)
|
||||||
|
|
||||||
|
def _save_chat_ignore(self):
|
||||||
|
self._db.set(__name__, "chat_ignore", self._chat_ignore)
|
||||||
|
|
||||||
|
async def _on_message_reaction_updated(self, event):
|
||||||
|
"""Обработчик обновления реакций"""
|
||||||
|
try:
|
||||||
|
message = await self._client.get_messages(
|
||||||
|
event.chat_id, ids=event.message_id
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.sender_id != (await self._client.get_me()).id:
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_id = str(event.chat_id)
|
||||||
|
user_id = event.user_id
|
||||||
|
|
||||||
|
if user_id in self._global_ignore:
|
||||||
|
return
|
||||||
|
|
||||||
|
if chat_id in self._chat_ignore and user_id in self._chat_ignore[chat_id]:
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_settings = self._chat_settings.get(
|
||||||
|
chat_id, {"reactions": None, "tags": None}
|
||||||
|
)
|
||||||
|
if chat_settings["reactions"] is None:
|
||||||
|
if not self._global_settings["reactions"]:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if not chat_settings["reactions"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._client.read_messages(event.chat_id, event.message_id)
|
||||||
|
|
||||||
|
async def _on_new_message(self, event):
|
||||||
|
"""Обработчик новых сообщений для упоминаний"""
|
||||||
|
|
||||||
|
if event.out:
|
||||||
|
return
|
||||||
|
|
||||||
|
me = await self._client.get_me()
|
||||||
|
mentioned = False
|
||||||
|
if event.mentioned:
|
||||||
|
mentioned = True
|
||||||
|
else:
|
||||||
|
if event.entities:
|
||||||
|
for entity in event.entities:
|
||||||
|
if entity.type == "mentionName" and entity.user_id == me.id:
|
||||||
|
mentioned = True
|
||||||
|
break
|
||||||
|
elif entity.type == "textMention" and entity.user_id == me.id:
|
||||||
|
mentioned = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not mentioned:
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_id = str(event.chat_id)
|
||||||
|
user_id = event.sender_id
|
||||||
|
|
||||||
|
if user_id in self._global_ignore:
|
||||||
|
return
|
||||||
|
|
||||||
|
if chat_id in self._chat_ignore and user_id in self._chat_ignore[chat_id]:
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_settings = self._chat_settings.get(
|
||||||
|
chat_id, {"reactions": None, "tags": None}
|
||||||
|
)
|
||||||
|
if chat_settings["tags"] is None:
|
||||||
|
if not self._global_settings["tags"]:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if not chat_settings["tags"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
await event.mark_read()
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[on/off] - тихие реакции во всех чатах",
|
||||||
|
en_doc="[on/off] - silent reactions in all chats",
|
||||||
|
)
|
||||||
|
async def sreacts(self, message: Message):
|
||||||
|
"""Toggle global silent reactions"""
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
if args == "on":
|
||||||
|
self._global_settings["reactions"] = True
|
||||||
|
self._save_global_settings()
|
||||||
|
await utils.answer(message, self.strings["global_reactions_on"])
|
||||||
|
elif args == "off":
|
||||||
|
self._global_settings["reactions"] = False
|
||||||
|
self._save_global_settings()
|
||||||
|
await utils.answer(message, self.strings["global_reactions_off"])
|
||||||
|
elif args == "":
|
||||||
|
status = "on" if self._global_settings["reactions"] else "off"
|
||||||
|
await utils.answer(message, f"Global silent reactions: {status}")
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["args_error"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[on/off] - тихие упоминания во всех чатах",
|
||||||
|
en_doc="[on/off] - silent tags in all chats",
|
||||||
|
)
|
||||||
|
async def stags(self, message: Message):
|
||||||
|
"""Toggle global silent tags"""
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
if args == "on":
|
||||||
|
self._global_settings["tags"] = True
|
||||||
|
self._save_global_settings()
|
||||||
|
await utils.answer(message, self.strings["global_tags_on"])
|
||||||
|
elif args == "off":
|
||||||
|
self._global_settings["tags"] = False
|
||||||
|
self._save_global_settings()
|
||||||
|
await utils.answer(message, self.strings["global_tags_off"])
|
||||||
|
elif args == "":
|
||||||
|
status = "on" if self._global_settings["tags"] else "off"
|
||||||
|
await utils.answer(message, f"Global silent tags: {status}")
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["args_error"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[on/off] - тихие реакции и упоминания во всех чатах",
|
||||||
|
en_doc="[on/off] - silent reactions and tags in all chats",
|
||||||
|
)
|
||||||
|
async def sall(self, message: Message):
|
||||||
|
"""Toggle global silent reactions and tags"""
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
if args == "on":
|
||||||
|
self._global_settings["reactions"] = True
|
||||||
|
self._global_settings["tags"] = True
|
||||||
|
self._save_global_settings()
|
||||||
|
await utils.answer(message, "✅ Global silent reactions and tags enabled")
|
||||||
|
elif args == "off":
|
||||||
|
self._global_settings["reactions"] = False
|
||||||
|
self._global_settings["tags"] = False
|
||||||
|
self._save_global_settings()
|
||||||
|
await utils.answer(message, "❌ Global silent reactions and tags disabled")
|
||||||
|
elif args == "":
|
||||||
|
status_r = "on" if self._global_settings["reactions"] else "off"
|
||||||
|
status_t = "on" if self._global_settings["tags"] else "off"
|
||||||
|
await utils.answer(
|
||||||
|
message, f"Global silent reactions: {status_r}, tags: {status_t}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["args_error"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[on/off] - тихие реакции в этом чате",
|
||||||
|
en_doc="[on/off] - silent reactions in this chat",
|
||||||
|
)
|
||||||
|
async def hsreacts(self, message: Message):
|
||||||
|
"""Toggle silent reactions in this chat"""
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
chat_id = str(message.chat_id)
|
||||||
|
|
||||||
|
# Получаем настройки чата или создаем новые
|
||||||
|
chat_settings = self._chat_settings.get(
|
||||||
|
chat_id, {"reactions": None, "tags": None}
|
||||||
|
)
|
||||||
|
|
||||||
|
if args == "on":
|
||||||
|
chat_settings["reactions"] = True
|
||||||
|
self._chat_settings[chat_id] = chat_settings
|
||||||
|
self._save_chat_settings()
|
||||||
|
await utils.answer(message, self.strings["chat_reactions_on"])
|
||||||
|
elif args == "off":
|
||||||
|
chat_settings["reactions"] = False
|
||||||
|
self._chat_settings[chat_id] = chat_settings
|
||||||
|
self._save_chat_settings()
|
||||||
|
await utils.answer(message, self.strings["chat_reactions_off"])
|
||||||
|
elif args == "":
|
||||||
|
status = chat_settings["reactions"]
|
||||||
|
if status is None:
|
||||||
|
status = (
|
||||||
|
"global ("
|
||||||
|
+ ("on" if self._global_settings["reactions"] else "off")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status = "on" if status else "off"
|
||||||
|
await utils.answer(message, f"Silent reactions in this chat: {status}")
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["chat_args_error"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[on/off] - тихие упоминания в этом чате",
|
||||||
|
en_doc="[on/off] - silent tags in this chat",
|
||||||
|
)
|
||||||
|
async def hstags(self, message: Message):
|
||||||
|
"""Toggle silent tags in this chat"""
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
chat_id = str(message.chat_id)
|
||||||
|
|
||||||
|
chat_settings = self._chat_settings.get(
|
||||||
|
chat_id, {"reactions": None, "tags": None}
|
||||||
|
)
|
||||||
|
|
||||||
|
if args == "on":
|
||||||
|
chat_settings["tags"] = True
|
||||||
|
self._chat_settings[chat_id] = chat_settings
|
||||||
|
self._save_chat_settings()
|
||||||
|
await utils.answer(message, self.strings["chat_tags_on"])
|
||||||
|
elif args == "off":
|
||||||
|
chat_settings["tags"] = False
|
||||||
|
self._chat_settings[chat_id] = chat_settings
|
||||||
|
self._save_chat_settings()
|
||||||
|
await utils.answer(message, self.strings["chat_tags_off"])
|
||||||
|
elif args == "":
|
||||||
|
status = chat_settings["tags"]
|
||||||
|
if status is None:
|
||||||
|
status = (
|
||||||
|
"global ("
|
||||||
|
+ ("on" if self._global_settings["tags"] else "off")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status = "on" if status else "off"
|
||||||
|
await utils.answer(message, f"Silent tags in this chat: {status}")
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["chat_args_error"])
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[on/off] - тихие реакции и упоминания в этом чате",
|
||||||
|
en_doc="[on/off] - silent reactions and tags in this chat",
|
||||||
|
)
|
||||||
|
async def hsall(self, message: Message):
|
||||||
|
"""Toggle silent reactions and tags in this chat"""
|
||||||
|
args = utils.get_args_raw(message).lower()
|
||||||
|
chat_id = str(message.chat_id)
|
||||||
|
|
||||||
|
chat_settings = self._chat_settings.get(
|
||||||
|
chat_id, {"reactions": None, "tags": None}
|
||||||
|
)
|
||||||
|
|
||||||
|
if args == "on":
|
||||||
|
chat_settings["reactions"] = True
|
||||||
|
chat_settings["tags"] = True
|
||||||
|
self._chat_settings[chat_id] = chat_settings
|
||||||
|
self._save_chat_settings()
|
||||||
|
await utils.answer(
|
||||||
|
message, "✅ Silent reactions and tags enabled in this chat"
|
||||||
|
)
|
||||||
|
elif args == "off":
|
||||||
|
chat_settings["reactions"] = False
|
||||||
|
chat_settings["tags"] = False
|
||||||
|
self._chat_settings[chat_id] = chat_settings
|
||||||
|
self._save_chat_settings()
|
||||||
|
await utils.answer(
|
||||||
|
message, "❌ Silent reactions and tags disabled in this chat"
|
||||||
|
)
|
||||||
|
elif args == "":
|
||||||
|
status_r = chat_settings["reactions"]
|
||||||
|
if status_r is None:
|
||||||
|
status_r = (
|
||||||
|
"global ("
|
||||||
|
+ ("on" if self._global_settings["reactions"] else "off")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status_r = "on" if status_r else "off"
|
||||||
|
status_t = chat_settings["tags"]
|
||||||
|
if status_t is None:
|
||||||
|
status_t = (
|
||||||
|
"global ("
|
||||||
|
+ ("on" if self._global_settings["tags"] else "off")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status_t = "on" if status_t else "off"
|
||||||
|
await utils.answer(
|
||||||
|
message, f"Silent reactions: {status_r}, tags: {status_t} in this chat"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await utils.answer(message, self.strings["chat_args_error"])
|
||||||
|
|
||||||
|
async def _get_user_id(self, message: Message):
|
||||||
|
"""Получить ID пользователя из аргументов или ответа"""
|
||||||
|
reply = await message.get_reply_message()
|
||||||
|
args = utils.get_args_raw(message)
|
||||||
|
|
||||||
|
if reply:
|
||||||
|
return reply.sender_id
|
||||||
|
elif args:
|
||||||
|
try:
|
||||||
|
user = await self._client.get_entity(args)
|
||||||
|
return user.id
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[ответ/username] - игнорировать пользователя глобально",
|
||||||
|
en_doc="[reply/username] - ignore user globally",
|
||||||
|
)
|
||||||
|
async def ignore(self, message: Message):
|
||||||
|
"""Toggle ignore user globally"""
|
||||||
|
user_id = await self._get_user_id(message)
|
||||||
|
if not user_id:
|
||||||
|
await utils.answer(message, self.strings["no_reply"])
|
||||||
|
return
|
||||||
|
|
||||||
|
if user_id in self._global_ignore:
|
||||||
|
self._global_ignore.remove(user_id)
|
||||||
|
await utils.answer(message, self.strings["ignore_removed"])
|
||||||
|
else:
|
||||||
|
self._global_ignore.append(user_id)
|
||||||
|
await utils.answer(message, self.strings["ignore_added"])
|
||||||
|
|
||||||
|
self._save_global_ignore()
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="[ответ/username] - игнорировать пользователя в этом чате",
|
||||||
|
en_doc="[reply/username] - ignore user in this chat",
|
||||||
|
)
|
||||||
|
async def hignore(self, message: Message):
|
||||||
|
"""Toggle ignore user in this chat"""
|
||||||
|
user_id = await self._get_user_id(message)
|
||||||
|
if not user_id:
|
||||||
|
await utils.answer(message, self.strings["no_reply"])
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_id = str(message.chat_id)
|
||||||
|
|
||||||
|
if chat_id not in self._chat_ignore:
|
||||||
|
self._chat_ignore[chat_id] = []
|
||||||
|
|
||||||
|
if user_id in self._chat_ignore[chat_id]:
|
||||||
|
self._chat_ignore[chat_id].remove(user_id)
|
||||||
|
await utils.answer(message, self.strings["hignore_removed"])
|
||||||
|
else:
|
||||||
|
self._chat_ignore[chat_id].append(user_id)
|
||||||
|
await utils.answer(message, self.strings["hignore_added"])
|
||||||
|
|
||||||
|
self._save_chat_ignore()
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Показать статус Silent T&R", en_doc="Show Silent T&R status"
|
||||||
|
)
|
||||||
|
async def strstatus(self, message: Message):
|
||||||
|
"""Show status"""
|
||||||
|
global_reactions = "on" if self._global_settings["reactions"] else "off"
|
||||||
|
global_tags = "on" if self._global_settings["tags"] else "off"
|
||||||
|
|
||||||
|
chat_id = str(message.chat_id)
|
||||||
|
chat_settings = self._chat_settings.get(
|
||||||
|
chat_id, {"reactions": None, "tags": None}
|
||||||
|
)
|
||||||
|
chat_reactions = chat_settings["reactions"]
|
||||||
|
if chat_reactions is None:
|
||||||
|
chat_reactions = "global"
|
||||||
|
else:
|
||||||
|
chat_reactions = "on" if chat_reactions else "off"
|
||||||
|
chat_tags = chat_settings["tags"]
|
||||||
|
if chat_tags is None:
|
||||||
|
chat_tags = "global"
|
||||||
|
else:
|
||||||
|
chat_tags = "on" if chat_tags else "off"
|
||||||
|
|
||||||
|
global_ignore_count = len(self._global_ignore)
|
||||||
|
chat_ignore_count = len(self._chat_ignore.get(chat_id, []))
|
||||||
|
|
||||||
|
await utils.answer(
|
||||||
|
message,
|
||||||
|
self.strings["status"].format(
|
||||||
|
global_reactions,
|
||||||
|
global_tags,
|
||||||
|
chat_reactions,
|
||||||
|
chat_tags,
|
||||||
|
global_ignore_count,
|
||||||
|
chat_ignore_count,
|
||||||
|
),
|
||||||
|
)
|
||||||
86
archquise/H.Modules/timezone.py
Normal file
86
archquise/H.Modules/timezone.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Proprietary License Agreement
|
||||||
|
|
||||||
|
# Copyright (c) 2026-2029 Archquise
|
||||||
|
|
||||||
|
# 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 archquise@gmail.com
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Name: TimeZone
|
||||||
|
# Description: Prints current time in selected timezone (UTC+n and tzdata formats supported)
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# requires: tzdata
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import tzdata
|
||||||
|
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class TimeZoneMod(loader.Module):
|
||||||
|
"""Prints current time in selected timezone (UTC+n and tzdata formats supported)"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "TimeZone",
|
||||||
|
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> There is no arguments or they are invalid",
|
||||||
|
"_cls_doc": "Prints current time in selected timezone (UTC+n and tzdata formats supported)",
|
||||||
|
"time_utc": "<emoji document_id=5276412364458059956>🕓</emoji> Current time by UTC+{}: {}",
|
||||||
|
"time_tzdata": "<emoji document_id=5276412364458059956>🕓</emoji> Current time in {}: {}"
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"_cls_doc": "Выводит текущее время в выбранном часовом поясе (поддерживаются форматы UTC+n и tzdata)",
|
||||||
|
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> Нет аргументов или они неверны",
|
||||||
|
"tzdata_error": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка при получении времени по tzdata: {}\n\nУбедитесь, что часовой пояс указан верно",
|
||||||
|
"time_utc": "<emoji document_id=5276412364458059956>🕓</emoji> Текущее время по UTC+{}: {}",
|
||||||
|
"time_tzdata": "<emoji document_id=5276412364458059956>🕓</emoji> Текущее время в {}: {}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Выводит время по UTC+n | Использование: .utc 4",
|
||||||
|
en_doc="Prints UTC+n time | Usage: .utc 4",
|
||||||
|
)
|
||||||
|
async def utccmd(self, message):
|
||||||
|
args = utils.get_args(message)
|
||||||
|
if not args or not args[0].isdigit() or len(args) > 1:
|
||||||
|
await utils.answer(message, self.strings['invalid_args'])
|
||||||
|
return
|
||||||
|
offset = timedelta(hours=int(args[0]))
|
||||||
|
tz = timezone(offset)
|
||||||
|
time = datetime.now(tz)
|
||||||
|
await utils.answer(message, self.strings['time_utc'].format(args[0], time.strftime('%H:%M:%S')))
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
ru_doc="Выводит время по часовому поясу tzdata | Использование: .tzdata Europe/Moscow",
|
||||||
|
en_doc="Prints time by tzdata timezone | Usage: .tzdata Europe/Moscow",
|
||||||
|
)
|
||||||
|
async def tzdatacmd(self, message):
|
||||||
|
args = utils.get_args(message)
|
||||||
|
if args[0].isdigit() or not args or len(args) > 1:
|
||||||
|
await utils.answer(message, self.strings['invalid_args'])
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
time = datetime.now(ZoneInfo(args[0]))
|
||||||
|
except Exception as e:
|
||||||
|
await utils.answer(message, self.strings['tzdata_error'].format(e))
|
||||||
|
logger.error(self.strings['tzdata_error'].format(e))
|
||||||
|
return
|
||||||
|
await utils.answer(message, self.strings['time_tzdata'].format(args[0], time.strftime('%H:%M:%S')))
|
||||||
243
archquise/H.Modules/ytdl.py
Normal file
243
archquise/H.Modules/ytdl.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Proprietary License Agreement
|
||||||
|
|
||||||
|
# Copyright (c) 2026-2029
|
||||||
|
|
||||||
|
# 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 archquise@gmail.com
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# Name: YTDL
|
||||||
|
# Description: Downloads and sends audio/video from YouTube
|
||||||
|
# Author: @hikka_mods
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
# meta developer: @hikka_mods
|
||||||
|
# requires: yt_dlp ffmpeg
|
||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import platform
|
||||||
|
import aiohttp
|
||||||
|
import aiofiles
|
||||||
|
import zipfile
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from yt_dlp import YoutubeDL
|
||||||
|
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class YTDLMod(loader.Module):
|
||||||
|
"""Downloads and sends audio/video from YouTube"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "YTDL",
|
||||||
|
"_cls_doc": "Downloads and sends audio/video from YouTube",
|
||||||
|
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> There is no arguments or they are invalid",
|
||||||
|
"downloading_video": "<emoji document_id=5215484787325676090>🕐</emoji> Video is downloading...",
|
||||||
|
"downloaded_video": "<emoji document_id=5854762571659218443>✅</emoji> Video is downloaded!",
|
||||||
|
"downloading_audio": "<emoji document_id=5215484787325676090>🕐</emoji> Audio is downloading...",
|
||||||
|
"downloaded_audio": "<emoji document_id=5854762571659218443>✅</emoji> Audio is downloaded!",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"_cls_doc": "Скачивает и отправляет аудио/видео с Ютуба",
|
||||||
|
"invalid_args": "<emoji document_id=5854929766146118183>❌</emoji> Нет аргументов или они неверны",
|
||||||
|
"downloading_video": "<emoji document_id=5215484787325676090>🕐</emoji> Видео скачивается...",
|
||||||
|
"downloaded_video": "<emoji document_id=5854762571659218443>✅</emoji> Видео загружено!",
|
||||||
|
"downloading_audio": "<emoji document_id=5215484787325676090>🕐</emoji> Аудио скачивается...",
|
||||||
|
"downloaded_audio": "<emoji document_id=5854762571659218443>✅</emoji> Аудио загружено!",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_url(self, url: str) -> bool:
|
||||||
|
"""Validate URL format"""
|
||||||
|
if not url:
|
||||||
|
return False
|
||||||
|
|
||||||
|
url_pattern = re.compile(
|
||||||
|
r'^(?:https?://)?(?:www\.|m\.)?(?:youtube\.com|youtu\.be|music\.youtube\.com)/(?:watch\?v=|playlist\?list=|channel/|@|live/|shorts/)?[\w-]+',
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
|
||||||
|
return url_pattern.match(url) is not None
|
||||||
|
|
||||||
|
async def get_target(self):
|
||||||
|
system = platform.system()
|
||||||
|
machine = platform.machine().lower()
|
||||||
|
|
||||||
|
if system == "Windows":
|
||||||
|
return "Windows"
|
||||||
|
|
||||||
|
if system == "Darwin":
|
||||||
|
return "aarch64-apple-darwin" if machine == "arm64" else "x86_64-apple-darwin"
|
||||||
|
|
||||||
|
if system == "Linux":
|
||||||
|
return "aarch64-unknown-linux-gnu" if machine in ("aarch64", "arm64") else "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
return "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = loader.ModuleConfig(
|
||||||
|
loader.ConfigValue(
|
||||||
|
"youtube_cookie",
|
||||||
|
None,
|
||||||
|
"Cookie вашего Ютуб-аккаунта (повышает стабильность и помогает скачивать видео с жесткими возрастными ограничениями) | Cookie of your YouTube-account (increases stability and helps downloading video with strict age rating restricrions)",
|
||||||
|
validator=loader.validators.Hidden(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
deno_path = Path("deno")
|
||||||
|
deno_which = shutil.which("deno")
|
||||||
|
if not deno_which and not deno_path.is_file():
|
||||||
|
logger.warning("Deno is not installed, attempting installation...")
|
||||||
|
target = await self.get_target()
|
||||||
|
if target == "Windows":
|
||||||
|
logger.critical("Windows platform is unsupported by this module. All future commands will fail. Please, unload the module.")
|
||||||
|
return
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
download_link = f"https://github.com/denoland/deno/releases/latest/download/deno-{target}.zip"
|
||||||
|
async with session.get(download_link) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
async with aiofiles.open("deno.zip", mode='wb') as f:
|
||||||
|
async for chunk in resp.content.iter_chunked(8192):
|
||||||
|
await f.write(chunk)
|
||||||
|
else:
|
||||||
|
logger.critical(f"Failed to download Deno: HTTP {resp.status}")
|
||||||
|
self.set("deno_source", "install_failed")
|
||||||
|
return
|
||||||
|
if Path("deno.zip").is_file():
|
||||||
|
with zipfile.ZipFile("deno.zip","r") as zip_ref:
|
||||||
|
zip_ref.extractall()
|
||||||
|
os.remove("deno.zip")
|
||||||
|
os.chmod(deno_path, 0o755)
|
||||||
|
self.set("deno_source", "file")
|
||||||
|
elif deno_which:
|
||||||
|
self.set("deno_source", deno_which)
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
en_doc="Download video",
|
||||||
|
ru_doc="Скачать видео"
|
||||||
|
)
|
||||||
|
async def ytdlvcmd(self, message):
|
||||||
|
args = utils.get_args(message)
|
||||||
|
if not args or not self._validate_url(args[0]) or len(args) > 1:
|
||||||
|
await utils.answer(message, self.strings['invalid_args'])
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
source = self.get('deno_source')
|
||||||
|
deno_path = Path('deno').resolve() if source == 'file' else source if source != 'install_failed' else None
|
||||||
|
if not deno_path:
|
||||||
|
logger.critical("Deno wasn't installed in auto-mode. Please, install it manually or resolve the issue and reboot userbot.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings['downloading_video'])
|
||||||
|
|
||||||
|
filename_prefix = f"video_{message.id}"
|
||||||
|
ydl_opts = {
|
||||||
|
'quiet': True,
|
||||||
|
'outtmpl': f'{filename_prefix}.%(ext)s',
|
||||||
|
'js_runtimes': {'deno': {'path': str(deno_path)}},
|
||||||
|
|
||||||
|
'extractor_args': {
|
||||||
|
'youtube': {
|
||||||
|
'player_client': ['mweb', 'android'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
||||||
|
|
||||||
|
'postprocessors': [{
|
||||||
|
'key': 'FFmpegVideoConvertor',
|
||||||
|
'preferedformat': 'mp4',
|
||||||
|
}],
|
||||||
|
'postprocessor_args': {
|
||||||
|
'video_convertor': [
|
||||||
|
'-c:v', 'libx264',
|
||||||
|
'-pix_fmt', 'yuv420p',
|
||||||
|
'-preset', 'veryfast',
|
||||||
|
'-crf', '23',
|
||||||
|
'-c:a', 'aac'
|
||||||
|
],
|
||||||
|
'merger': [
|
||||||
|
'-movflags', 'faststart'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if self.get('youtube_cookie'):
|
||||||
|
ydl_opts['cookiefile'] = self.get('youtube_cookie')
|
||||||
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
|
info = ydl.extract_info(args[0], download=True)
|
||||||
|
filename = ydl.prepare_filename(info)
|
||||||
|
await self.client.send_file(message.chat_id, filename, caption=self.strings['downloaded_video'])
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
@loader.command(
|
||||||
|
en_doc="Download audio",
|
||||||
|
ru_doc="Скачать аудио"
|
||||||
|
)
|
||||||
|
async def ytdlacmd(self, message):
|
||||||
|
args = utils.get_args(message)
|
||||||
|
if not args or not self._validate_url(args[0]) or len(args) > 1:
|
||||||
|
await utils.answer(message, self.strings['invalid_args'])
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
source = self.get('deno_source')
|
||||||
|
deno_path = Path('deno').resolve() if source == 'file' else source if source != 'install_failed' else None
|
||||||
|
if not deno_path:
|
||||||
|
logger.critical("Deno wasn't installed in auto-mode. Please, install it manually or resolve the issue and reboot userbot.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await utils.answer(message, self.strings['downloading_audio'])
|
||||||
|
|
||||||
|
filename_prefix = f"audio_{message.id}"
|
||||||
|
ydl_opts = {
|
||||||
|
'quiet': True,
|
||||||
|
'outtmpl': f'{filename_prefix}.%(ext)s',
|
||||||
|
'js_runtimes': {'deno': {'path': str(deno_path)}},
|
||||||
|
|
||||||
|
'postprocessors': [
|
||||||
|
{
|
||||||
|
'key': 'FFmpegExtractAudio',
|
||||||
|
'preferredcodec': 'mp3',
|
||||||
|
'preferredquality': '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'FFmpegMetadata',
|
||||||
|
'add_metadata': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': 'EmbedThumbnail',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'writethumbnail': True,
|
||||||
|
}
|
||||||
|
if self.get('youtube_cookie'):
|
||||||
|
ydl_opts['cookiefile'] = self.get('youtube_cookie')
|
||||||
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
|
info = ydl.extract_info(args[0], download=True)
|
||||||
|
filename = ydl.prepare_filename(info).split(".")[0] + ".mp3"
|
||||||
|
await self.client.send_file(message.chat_id, filename, caption=self.strings['downloaded_audio'])
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
250
fiksofficial/python-modules/aigenuser.py
Normal file
250
fiksofficial/python-modules/aigenuser.py
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
# ______ ___ ___ _ _
|
||||||
|
# ____ | ___ \ | \/ | | | | |
|
||||||
|
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||||
|
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||||
|
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||||
|
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||||
|
# \____/ __/ |
|
||||||
|
# |___/
|
||||||
|
|
||||||
|
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||||
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
|
# meta developer: @pymodule
|
||||||
|
# requires: aiohttp
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from telethon import functions
|
||||||
|
from .. import loader, utils
|
||||||
|
|
||||||
|
|
||||||
|
@loader.tds
|
||||||
|
class AiUsernameGen(loader.Module):
|
||||||
|
"""AI-powered username generation and automatic creation of public channels with available usernames. (Before you begin, set up the config: .config AiUsernameGen)"""
|
||||||
|
|
||||||
|
strings = {
|
||||||
|
"name": "AiUsernameGen",
|
||||||
|
"no_prompt": "🚫 <b>Specify a query to generate a username</b>",
|
||||||
|
"checking": "🤖 <b>Generating and checking username availability...</b>",
|
||||||
|
"created_many": "✅ <b>Public channels have been created:</b>\n{}",
|
||||||
|
"available_many": "✅ <b>Available usernames found (no auto-creation):</b>\n{}",
|
||||||
|
"no_free": "😔 <b>No available usernames found. Try a different search!</b>",
|
||||||
|
"error_ai": "❌ <b>Error requesting AI. Check your configuration.</b>",
|
||||||
|
"config_api_key": "Key API for AI (https://openrouter.ai/settings/keys)",
|
||||||
|
"config_model": "Model AI for generation",
|
||||||
|
"config_channel_title_prefix": "Prefix for channel title (use {username} to insert username)",
|
||||||
|
"config_channel_about": "Channel description",
|
||||||
|
"config_autocreate_channels": "Automatically create channels for available usernames (True/False)",
|
||||||
|
}
|
||||||
|
|
||||||
|
strings_ru = {
|
||||||
|
"_cls_doc": "Генерация username с помощью AI и автоматическое создание публичных каналов с доступными юзернеймами. (Перед началом настройте модуль: .config AiUsernameGen)",
|
||||||
|
"no_prompt": "🚫 <b>Укажите запрос для генерации username</b>",
|
||||||
|
"checking": "🤖 <b>Генерация и проверка доступности username...</b>",
|
||||||
|
"created_many": "✅ <b>Созданы публичные каналы:</b>\n{}",
|
||||||
|
"available_many": "✅ <b>Доступные username найдены (автосоздание выключено):</b>\n{}",
|
||||||
|
"no_free": "😔 <b>Свободных username не найдено. Попробуйте другой запрос!</b>",
|
||||||
|
"error_ai": "❌ <b>Ошибка при запросе к AI. Проверьте конфигурацию.</b>",
|
||||||
|
"config_api_key": "Ключ API для AI (https://openrouter.ai/settings/keys)",
|
||||||
|
"config_model": "Модель AI для генерации",
|
||||||
|
"config_channel_title_prefix": "Префикс для заголовка канала (используйте {username} для вставки username)",
|
||||||
|
"config_channel_about": "Описание канала",
|
||||||
|
"config_autocreate_channels": "Автоматически создавать каналы с доступными username (True/False)",
|
||||||
|
}
|
||||||
|
|
||||||
|
USERNAME_REGEX = re.compile(r'^[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]$')
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = (
|
||||||
|
"Ты без дополнительных слов будешь придумывать ровно 10 уникальных username. "
|
||||||
|
"Не меньше, не больше! После каждого username делай перенос строки. "
|
||||||
|
"Не используй символ @. Условие: username должен состоять из 5 или более символов "
|
||||||
|
"и соответствовать паттерну [a-zA-Z][\\w\\d]{3,30}[a-zA-Z\\d]. "
|
||||||
|
"Сделай их креативными и релевантными запросу."
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = loader.ModuleConfig(
|
||||||
|
loader.ConfigValue(
|
||||||
|
"AI_KEY",
|
||||||
|
"your_token_here",
|
||||||
|
lambda: self.strings["config_api_key"],
|
||||||
|
validator=loader.validators.String()
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"MODEL",
|
||||||
|
"deepseek/deepseek-r1-0528:free",
|
||||||
|
lambda: self.strings["config_model"],
|
||||||
|
validator=loader.validators.String()
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"CHANNEL_TITLE_PREFIX",
|
||||||
|
"Юзернейм: {username}",
|
||||||
|
lambda: self.strings["config_channel_title_prefix"],
|
||||||
|
validator=loader.validators.String()
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"CHANNEL_ABOUT",
|
||||||
|
"@pymodule",
|
||||||
|
lambda: self.strings["config_channel_about"],
|
||||||
|
validator=loader.validators.String()
|
||||||
|
),
|
||||||
|
loader.ConfigValue(
|
||||||
|
"AUTOCREATE_CHANNELS",
|
||||||
|
True,
|
||||||
|
lambda: self.strings["config_autocreate_channels"],
|
||||||
|
validator=loader.validators.Boolean()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def client_ready(self, client, db):
|
||||||
|
self.client = client
|
||||||
|
self.db = db
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.api_url = "https://openrouter.ai/api/v1/chat/completions"
|
||||||
|
|
||||||
|
async def _query_ai(self, prompt: str) -> str | None:
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.config['AI_KEY']}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"model": self.config["MODEL"],
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": self.SYSTEM_PROMPT},
|
||||||
|
{"role": "user", "content": prompt}
|
||||||
|
],
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 150
|
||||||
|
}
|
||||||
|
async with session.post(self.api_url, json=payload, headers=headers) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
self.logger.error(f"AI response status: {resp.status} - {await resp.text()}")
|
||||||
|
return None
|
||||||
|
res = await resp.json()
|
||||||
|
if "choices" not in res or not res["choices"]:
|
||||||
|
self.logger.error("Invalid AI response structure")
|
||||||
|
return None
|
||||||
|
return res["choices"][0]["message"]["content"].strip()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"AI request error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _is_username_available(self, username: str) -> bool:
|
||||||
|
try:
|
||||||
|
return await self.client(functions.account.CheckUsernameRequest(username=username))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error checking username '{username}': {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_available(self, usernames: list[str]) -> list[str]:
|
||||||
|
semaphore = asyncio.Semaphore(10)
|
||||||
|
async def check_one(u: str):
|
||||||
|
async with semaphore:
|
||||||
|
return u if await self._is_username_available(u) else None
|
||||||
|
|
||||||
|
tasks = [check_one(u) for u in usernames]
|
||||||
|
results = await asyncio.gather(*tasks)
|
||||||
|
return [r for r in results if r is not None]
|
||||||
|
|
||||||
|
async def create_channels(self, usernames: list[str]) -> list[str]:
|
||||||
|
semaphore = asyncio.Semaphore(3)
|
||||||
|
async def create_one(u: str):
|
||||||
|
async with semaphore:
|
||||||
|
return await self._create_channel_with_username(u)
|
||||||
|
|
||||||
|
tasks = [create_one(u) for u in usernames]
|
||||||
|
results = await asyncio.gather(*tasks)
|
||||||
|
return [r for r in results if r is not None]
|
||||||
|
|
||||||
|
async def _create_channel_with_username(self, username: str) -> str | None:
|
||||||
|
title = self.config["CHANNEL_TITLE_PREFIX"].format(username=username)
|
||||||
|
about = self.config["CHANNEL_ABOUT"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not await self._is_username_available(username):
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = await self.client(functions.channels.CreateChannelRequest(
|
||||||
|
title=title,
|
||||||
|
about=about,
|
||||||
|
broadcast=True
|
||||||
|
))
|
||||||
|
channel = result.chats[0]
|
||||||
|
|
||||||
|
await self.client(functions.channels.UpdateUsernameRequest(
|
||||||
|
channel=channel,
|
||||||
|
username=username
|
||||||
|
))
|
||||||
|
|
||||||
|
self.logger.info(f"Successfully created channel with username: {username}")
|
||||||
|
return username
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error creating channel for '{username}': {e}")
|
||||||
|
try:
|
||||||
|
if 'channel' in locals():
|
||||||
|
await self.client(functions.channels.DeleteChannelRequest(channel=channel))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _filter_and_validate_usernames(self, ai_text: str) -> list[str]:
|
||||||
|
lines = [u.strip() for u in ai_text.splitlines() if u.strip()]
|
||||||
|
valid = []
|
||||||
|
for u in lines[:10]:
|
||||||
|
if len(u) >= 5 and self.USERNAME_REGEX.match(u):
|
||||||
|
valid.append(u)
|
||||||
|
return valid
|
||||||
|
|
||||||
|
@loader.command(ru_doc="— <запрос> Генерирует username по запросу и (опционально) создаёт каналы")
|
||||||
|
async def genusercmd(self, message):
|
||||||
|
"""— <request> Generates usernames and optionally creates channels"""
|
||||||
|
user_query = utils.get_args_raw(message)
|
||||||
|
if not user_query:
|
||||||
|
return await utils.answer(message, self.strings["no_prompt"])
|
||||||
|
|
||||||
|
msg = await utils.answer(message, self.strings["checking"])
|
||||||
|
|
||||||
|
ai_text = await self._query_ai(user_query)
|
||||||
|
if not ai_text:
|
||||||
|
return await msg.edit(self.strings["error_ai"])
|
||||||
|
|
||||||
|
usernames = self._filter_and_validate_usernames(ai_text)
|
||||||
|
available = await self.get_available(usernames)
|
||||||
|
|
||||||
|
autocreate = self.config["AUTOCREATE_CHANNELS"]
|
||||||
|
created = []
|
||||||
|
|
||||||
|
if autocreate and available:
|
||||||
|
created = await self.create_channels(available)
|
||||||
|
|
||||||
|
if (autocreate and not created) or (not autocreate and not available):
|
||||||
|
retry_prompt = f"{user_query}. Придумай ещё 10 других уникальных username, строго по тем же правилам."
|
||||||
|
ai_text_retry = await self._query_ai(retry_prompt)
|
||||||
|
if ai_text_retry:
|
||||||
|
usernames_retry = self._filter_and_validate_usernames(ai_text_retry)
|
||||||
|
available_retry = await self.get_available(usernames_retry)
|
||||||
|
|
||||||
|
if autocreate:
|
||||||
|
created = await self.create_channels(available_retry)
|
||||||
|
else:
|
||||||
|
available = available_retry or available
|
||||||
|
|
||||||
|
if autocreate:
|
||||||
|
if created:
|
||||||
|
channels_list = "\n".join(f"• <code>t.me/{u}</code>" for u in created)
|
||||||
|
await msg.edit(self.strings["created_many"].format(channels_list))
|
||||||
|
else:
|
||||||
|
await msg.edit(self.strings["no_free"])
|
||||||
|
else:
|
||||||
|
if available:
|
||||||
|
avail_list = "\n".join(f"• <code>t.me/{u}</code>" for u in available)
|
||||||
|
await msg.edit(self.strings["available_many"].format(avail_list))
|
||||||
|
else:
|
||||||
|
await msg.edit(self.strings["no_free"])
|
||||||
@@ -21,4 +21,5 @@ multiunloadmodule
|
|||||||
tagall2.0
|
tagall2.0
|
||||||
point
|
point
|
||||||
deviceinfo
|
deviceinfo
|
||||||
mpi
|
mpi
|
||||||
|
aigenuser
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# -- version --
|
# -- version --
|
||||||
__version__ = (1, 2, 0)
|
__version__ = (1, 2, 1)
|
||||||
# -- version --
|
# -- version --
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ from herokutl.tl.functions.payments import GetPaymentFormRequest, SendStarsFormR
|
|||||||
from herokutl.tl.types import InputInvoiceStarGift, TextWithEntities
|
from herokutl.tl.types import InputInvoiceStarGift, TextWithEntities
|
||||||
from herokutl.errors.rpcerrorlist import BadRequestError
|
from herokutl.errors.rpcerrorlist import BadRequestError
|
||||||
import logging
|
import logging
|
||||||
|
import herokutl
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class SenderGifts(loader.Module):
|
class SenderGifts(loader.Module):
|
||||||
@@ -78,7 +79,7 @@ class SenderGifts(loader.Module):
|
|||||||
@loader.command()
|
@loader.command()
|
||||||
async def sendgift(self, message):
|
async def sendgift(self, message):
|
||||||
"""- <username> <text*> - отправить подарок пользователю (* - необязательный параметр.) Поддерживается реплай режим."""
|
"""- <username> <text*> - отправить подарок пользователю (* - необязательный параметр.) Поддерживается реплай режим."""
|
||||||
args = utils.get_args_raw(message)
|
args = utils.get_args_html(message)
|
||||||
reply = await message.get_reply_message()
|
reply = await message.get_reply_message()
|
||||||
if reply:
|
if reply:
|
||||||
user = reply.sender
|
user = reply.sender
|
||||||
@@ -221,12 +222,17 @@ class SenderGifts(loader.Module):
|
|||||||
self.strings["sending_gift"],
|
self.strings["sending_gift"],
|
||||||
reply_markup=None
|
reply_markup=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parse_mode = herokutl.utils.sanitize_parse_mode(
|
||||||
|
self.client.parse_mode,
|
||||||
|
)
|
||||||
|
text, entities = parse_mode.parse(text)
|
||||||
|
|
||||||
user = await self.client.get_input_entity(user_id)
|
user = await self.client.get_input_entity(user_id)
|
||||||
inv = InputInvoiceStarGift(
|
inv = InputInvoiceStarGift(
|
||||||
user,
|
user,
|
||||||
gift_id,
|
gift_id,
|
||||||
message=TextWithEntities(text, []) if text else TextWithEntities("", [])
|
message=TextWithEntities(text, entities) if text else TextWithEntities("", [])
|
||||||
)
|
)
|
||||||
form = await self.client(GetPaymentFormRequest(inv))
|
form = await self.client(GetPaymentFormRequest(inv))
|
||||||
result = await self.client(SendStarsFormRequest(form.form_id, inv))
|
result = await self.client(SendStarsFormRequest(form.form_id, inv))
|
||||||
|
|||||||
847
modules.json
847
modules.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user