mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Added and updated repositories 2026-01-10 01:09:56
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
|
||||
|
||||
# Ruff Format
|
||||
.ruff_cache/
|
||||
.ruff_cache/
|
||||
.idea
|
||||
|
||||
@@ -27,12 +27,15 @@
|
||||
# requires: pillow
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class ASCIIArtMod(loader.Module):
|
||||
@@ -99,7 +102,7 @@ class ASCIIArtMod(loader.Module):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error generating ASCII art: {e}")
|
||||
logger.error(f"Error generating ASCII art: {e}")
|
||||
return None
|
||||
finally:
|
||||
if image_path and os.path.exists(image_path):
|
||||
|
||||
@@ -26,15 +26,29 @@
|
||||
# scope: Api AccountData 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
import aiohttp
|
||||
|
||||
from datetime import datetime
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class AccountData(loader.Module):
|
||||
"""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 = {
|
||||
"name": "AccountData",
|
||||
"_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:
|
||||
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"
|
||||
params = {"token": api_token, "user_id": user_id}
|
||||
|
||||
@@ -76,16 +93,22 @@ class AccountData(loader.Module):
|
||||
async def accdata(self, message):
|
||||
if reply := await message.get_reply_message():
|
||||
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)
|
||||
formatted = date_object.strftime('%B %Y')
|
||||
|
||||
if "error" in result:
|
||||
await utils.answer(message, f"Ошибка: {result['error']}")
|
||||
else:
|
||||
|
||||
if "error" in result or not result.get("creation_date"):
|
||||
error_msg = result.get("error", "Unknown error occurred")
|
||||
await utils.answer(message, f"Ошибка: {error_msg}")
|
||||
return
|
||||
|
||||
try:
|
||||
month, year = map(int, result['creation_date'].split('.'))
|
||||
date_object = datetime(year, month, 1)
|
||||
formatted = date_object.strftime('%B %Y')
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
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:
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class AnimeQuotesMod(loader.Module):
|
||||
|
||||
@@ -27,13 +27,16 @@
|
||||
# requires: requests
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
from typing import Dict
|
||||
|
||||
import requests
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class ArticleMod(loader.Module):
|
||||
|
||||
@@ -26,15 +26,17 @@
|
||||
# scope: AutofarmCookies 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import random
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from telethon import functions
|
||||
from telethon.tl.custom import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
__version__ = (1, 0, 0)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class AutofarmCookiesMod(loader.Module):
|
||||
@@ -197,8 +199,7 @@ class AutofarmCookiesMod(loader.Module):
|
||||
async def ckies(self, message):
|
||||
chelp = """
|
||||
🍀| <b>Помощь по командам:</b>
|
||||
.cookon - Включает авто фарм.
|
||||
.cookoff - Выключает авто фарм.
|
||||
.farm - Показывает сколько вы нафармили.
|
||||
.me - Показывает ваш ммешок"""
|
||||
.cookon - Включает авто-фарм.
|
||||
.cookoff - Выключает авто-фарм.
|
||||
.me - Показывает ваш мешок"""
|
||||
await utils.answer(message, chelp)
|
||||
|
||||
@@ -26,17 +26,20 @@
|
||||
# scope: Api BirthdayTime 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import random
|
||||
import asyncio
|
||||
import calendar
|
||||
import logging
|
||||
import random
|
||||
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.tl.functions.account import UpdateProfileRequest
|
||||
from telethon.tl.functions.users import GetFullUserRequest
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
D_MSG = [
|
||||
"Ждешь его?",
|
||||
"Осталось немного)",
|
||||
@@ -158,9 +161,9 @@ class DaysToMyBirthday(loader.Module):
|
||||
self.db.set(__name__, "last_name", name)
|
||||
except UserPrivacyRestrictedError:
|
||||
self.db.set(__name__, "change_name", False)
|
||||
print("Error: Can't change name due to privacy settings.")
|
||||
logger.error("Error: Can't change name due to privacy settings.")
|
||||
except Exception as e:
|
||||
print(f"Error in checker: {e}")
|
||||
logger.error(f"Error in checker: {e}")
|
||||
finally:
|
||||
await asyncio.sleep(60)
|
||||
|
||||
@@ -173,7 +176,7 @@ class DaysToMyBirthday(loader.Module):
|
||||
user = await self.client(GetFullUserRequest(self.client.hikka_me.id))
|
||||
name = user.users[0].last_name or ""
|
||||
except Exception as e:
|
||||
print(f"Error getting user info: {e}")
|
||||
logger.error(f"Error getting user info: {e}")
|
||||
await utils.answer(message, self.strings("error"))
|
||||
return
|
||||
|
||||
@@ -191,7 +194,7 @@ class DaysToMyBirthday(loader.Module):
|
||||
except UserPrivacyRestrictedError:
|
||||
await utils.answer(message, self.strings("name_privacy_error"))
|
||||
except Exception as e:
|
||||
print(f"Error removing name: {e}")
|
||||
logger.error(f"Error removing name: {e}")
|
||||
await utils.answer(message, self.strings("error"))
|
||||
|
||||
else:
|
||||
@@ -238,5 +241,5 @@ class DaysToMyBirthday(loader.Module):
|
||||
)
|
||||
|
||||
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"))
|
||||
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
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 logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class FakeActionsMod(loader.Module):
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
# scope: hikka_min 1.4.2
|
||||
# -----------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class FakeWallet(loader.Module):
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
# meta developer: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from telethon import functions
|
||||
from telethon.tl.types import DialogFilter, InputPeerChannel
|
||||
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
# scope: Api GigaChat 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class GigaChatMod(loader.Module):
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
# scope: HAFK 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
from telethon import types
|
||||
from telethon.utils import get_peer_id
|
||||
@@ -135,7 +135,6 @@ class HAFK(loader.Module):
|
||||
|
||||
async def _afk_toggle(self, message, global_afk: bool):
|
||||
chat_id = utils.get_chat_id(message)
|
||||
db_key = "afk" if global_afk else f"afk_here_{chat_id}"
|
||||
already_afk_string = "already_afk" if global_afk else "already_afk_here"
|
||||
afk_on_string = "afk_on" if global_afk else "afk_here_on"
|
||||
afk_on_reason_string = "afk_on_reason" if global_afk else "afk_here_on_reason"
|
||||
|
||||
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
|
||||
|
||||
# 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:
|
||||
|
||||
@@ -23,11 +23,10 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
import logging
|
||||
import random
|
||||
import asyncio
|
||||
|
||||
from .. import loader
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -39,8 +38,13 @@ class InfoBannersManagerMod(loader.Module):
|
||||
strings = {"name": "InfoBannersManager"}
|
||||
|
||||
def __init__(self):
|
||||
self.changer_instance = None
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"enabled",
|
||||
False,
|
||||
"Включить автоматическую смену баннеров",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"delay",
|
||||
60,
|
||||
@@ -56,49 +60,49 @@ class InfoBannersManagerMod(loader.Module):
|
||||
)
|
||||
|
||||
async def banner_changer(self):
|
||||
while True:
|
||||
try:
|
||||
if not self.config["bannerslist"]:
|
||||
logger.warning("Banners list is empty!")
|
||||
await asyncio.sleep(10)
|
||||
return
|
||||
"""Change banner periodically"""
|
||||
try:
|
||||
if not self.config["bannerslist"]:
|
||||
logger.warning("Banners list is empty!")
|
||||
return
|
||||
|
||||
banner = random.choice(self.config["bannerslist"])
|
||||
instance = self.lookup("HerokuInfo")
|
||||
if not instance:
|
||||
instance = self.lookup("HikkaInfo")
|
||||
banner = random.choice(self.config["bannerslist"])
|
||||
instance = self.lookup("HerokuInfo")
|
||||
if not instance:
|
||||
instance = self.lookup("HikkaInfo")
|
||||
|
||||
if instance:
|
||||
instance.config["banner_url"] = banner
|
||||
logger.info(f"Banner changed to: {banner}")
|
||||
else:
|
||||
logger.warning("Info module not found!")
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Caught exception: {e}")
|
||||
await asyncio.sleep(10)
|
||||
await asyncio.sleep(self.config["delay"])
|
||||
except Exception as e:
|
||||
logger.exception(f"Error changing banner: {e}")
|
||||
|
||||
async def on_unload(self):
|
||||
if self.changer_instance:
|
||||
self.changer_instance.cancel()
|
||||
self.changer_instance = None
|
||||
@loader.loop(interval=60, autostart=False)
|
||||
async def banner_loop(self):
|
||||
"""Main banner changing loop"""
|
||||
if not self.config["enabled"]:
|
||||
return
|
||||
|
||||
await self.banner_changer()
|
||||
|
||||
# Update interval from config
|
||||
self.banner_loop.set_interval(self.config["delay"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Включить или выключить модуль",
|
||||
)
|
||||
async def autobannertoggle(self, message):
|
||||
if not self.db.get(__name__, "enabled", False):
|
||||
try:
|
||||
if self.changer_instance:
|
||||
self.changer_instance.cancel()
|
||||
async def client_ready(self):
|
||||
"""Initialize the banner changer loop"""
|
||||
if self.config["enabled"]:
|
||||
self.banner_loop.start()
|
||||
|
||||
self.db.set(__name__, "enabled", True)
|
||||
self.changer_instance = asyncio.create_task(self.banner_changer())
|
||||
await utils.answer(message, "Модуль запущен!")
|
||||
except Exception as e:
|
||||
logger.exception(f"Caught exception: {e}")
|
||||
else:
|
||||
try:
|
||||
self.db.set(__name__, "enabled", False)
|
||||
await utils.answer(message, "Модуль остановлен!")
|
||||
if self.changer_instance:
|
||||
self.changer_instance.cancel()
|
||||
self.changer_instance = None
|
||||
except Exception as e:
|
||||
logger.exception(f"Caught exception: {e}")
|
||||
def on_config_update(self, config_key, new_value):
|
||||
"""Handle config updates"""
|
||||
if config_key == "enabled":
|
||||
if new_value:
|
||||
self.banner_loop.start()
|
||||
else:
|
||||
self.banner_loop.stop()
|
||||
elif config_key == "delay":
|
||||
# Update interval immediately
|
||||
self.banner_loop.set_interval(new_value)
|
||||
|
||||
@@ -26,28 +26,35 @@
|
||||
# scope: InlineButton 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
from ..inline.types import InlineQuery
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class InlineButtonMod(loader.Module):
|
||||
"""Create inline button"""
|
||||
"""Create inline buttons with enhanced functionality"""
|
||||
|
||||
strings = {
|
||||
"name": "InlineButton",
|
||||
"titles": "Create a message with the Inline Button",
|
||||
"error_title": "Error",
|
||||
"error_description": "Invalid input format. Please provide exactly three comma-separated values.",
|
||||
"error_message": "Make sure your input is formatted as: message, name, url.",
|
||||
"titles": "🔘 Create message with Inline Button",
|
||||
"error_title": "<emoji document_id=5854929766146118183>❌</emoji> Error",
|
||||
"error_description": "<emoji document_id=5854929766146118183>❌</emoji> Invalid input format. Please provide exactly three comma-separated values: 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 = {
|
||||
"titles": "Создай сообщение с Inline Кнопкой",
|
||||
"error_title": "Ошибка",
|
||||
"error_description": "Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми.",
|
||||
"error_message": "Убедитесь, что ваш ввод имеет следующий формат: сообщение, имя, url.",
|
||||
"titles": "🔘 Создать сообщение с Inline Кнопкой",
|
||||
"error_title": "<emoji document_id=5854929766146118183>❌</emoji> Ошибка",
|
||||
"error_description": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат ввода. Пожалуйста, укажите ровно три значения, разделенных запятыми: сообщение, имя, 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(
|
||||
@@ -57,21 +64,25 @@ class InlineButtonMod(loader.Module):
|
||||
async def crinl_inline_handler(self, query: InlineQuery):
|
||||
args = utils.get_args_raw(query.query)
|
||||
|
||||
if args:
|
||||
args_list = [arg.strip() for arg in args.split(",")]
|
||||
if not args:
|
||||
return {
|
||||
"title": self.strings("error_title"),
|
||||
"description": self.strings("error_description"),
|
||||
"message": self.strings("no_args"),
|
||||
}
|
||||
|
||||
if len(args_list) == 3:
|
||||
message, name, url = args_list
|
||||
args_list = [arg.strip() for arg in args.split(",")]
|
||||
|
||||
return {
|
||||
"title": self.strings("titles"),
|
||||
"description": f"{message}, {name}, {url}",
|
||||
"message": message,
|
||||
"reply_markup": [{"text": name, "url": url}],
|
||||
}
|
||||
if len(args_list) != 3:
|
||||
return {
|
||||
"title": self.strings("error_title"),
|
||||
"description": self.strings("error_description"),
|
||||
"message": self.strings("error_message"),
|
||||
}
|
||||
|
||||
return {
|
||||
"title": self.strings("error_title"),
|
||||
"description": self.strings("error_description"),
|
||||
"message": self.strings("error_message"),
|
||||
message, name, url = args_list
|
||||
return True, {
|
||||
"message": message,
|
||||
"reply_markup": [{"text": name, "url": url}],
|
||||
"description": self.strings("button_created"),
|
||||
}
|
||||
|
||||
@@ -26,49 +26,67 @@
|
||||
# scope: InlineCoin 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import random
|
||||
from typing import Dict
|
||||
|
||||
from ..inline.types import InlineQuery
|
||||
from .. import loader
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CoinSexMod(loader.Module):
|
||||
"""Mini game heads or tails"""
|
||||
class CoinFlipMod(loader.Module):
|
||||
"""Mini coin flip game"""
|
||||
|
||||
strings = {
|
||||
"name": "InlineCoin",
|
||||
"titles": "Heads or tails?",
|
||||
"description": "Let's find out!",
|
||||
"heads": "🌚 An eagle fell out!",
|
||||
"tails": "🌝 Tails fell out!",
|
||||
"edge": "🙀 Miraculously, the coin remained on the edge!",
|
||||
"titles": "🪙 Heads or Tails?",
|
||||
"description": "🎲 Let's find out!",
|
||||
"heads": "🦅 An eagle fell out!",
|
||||
"tails": "🪙 Tails fell out!",
|
||||
"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 = {
|
||||
"titles": "Орёл или решка?",
|
||||
"description": "Давай узнаем!",
|
||||
"heads": "🌚 Выпал орёл!",
|
||||
"tails": "🌝 Выпала решка!",
|
||||
"titles": "🪙 Орёл или решка?",
|
||||
"description": "🎲 Давай узнаем!",
|
||||
"heads": "🦅 Выпал орёл!",
|
||||
"tails": "🪙 Выпала решка!",
|
||||
"edge": "🙀 Чудо, монетка осталась на ребре!",
|
||||
"no_args": "<emoji document_id=5854929766146118183>❌</emoji> Укажите команду для подбрасывания монетки.",
|
||||
"error_general": "<emoji document_id=5854929766146118183>❌</emoji> Произошла ошибка: {error}",
|
||||
}
|
||||
|
||||
def get_coin_flip_result(self) -> dict:
|
||||
results = [self.strings("heads"), self.strings("tails")]
|
||||
if random.random() < 0.1:
|
||||
return self.strings("edge")
|
||||
else:
|
||||
return random.choice(results)
|
||||
def get_coin_flip_result(self) -> Dict[str, str]:
|
||||
"""Get coin flip result with better formatting"""
|
||||
return {
|
||||
"title": self.strings["titles"],
|
||||
"description": self.strings["description"],
|
||||
"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(
|
||||
ru_doc="Подбросит монетку ",
|
||||
ru_doc="Подбросить монетку",
|
||||
en_doc="Flip a coin",
|
||||
)
|
||||
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()
|
||||
return {
|
||||
"title": self.strings("titles"),
|
||||
"description": self.strings("description"),
|
||||
"message": f"<b>{result}</b>",
|
||||
"thumb": "https://github.com/Codwizer/ReModules/blob/main/assets/images.png",
|
||||
"title": self.strings["titles"],
|
||||
"description": self.strings["description"],
|
||||
"message": result["message"],
|
||||
"thumb": result["thumb"],
|
||||
}
|
||||
|
||||
@@ -26,14 +26,15 @@
|
||||
# scope: InlineHelper 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from .. import loader, main, utils
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
from .. import loader, utils, main
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
@@ -42,77 +43,118 @@ class InlineHelperMod(loader.Module):
|
||||
|
||||
strings = {
|
||||
"name": "InlineHelper",
|
||||
"call_restart": "Restarting...",
|
||||
"call_update": "Updating...",
|
||||
"res_prefix": "Successfully reset prefix to default",
|
||||
"restart_inline_handler_title": "Restart Userbot",
|
||||
"call_restart": "<emoji document_id=5188311512791393083>🔄</emoji> Restarting...",
|
||||
"call_update": "<emoji document_id=5188311512791393083>🔄</emoji> Updating...",
|
||||
"res_prefix": "<emoji document_id=5854762571659218443>✅</emoji> Prefix successfully reset to default",
|
||||
"restart_inline_handler_title": "🔄 Restart Userbot",
|
||||
"restart_inline_handler_description": "Restart your userbot via inline",
|
||||
"restart_inline_handler_message": "Press the button below to restart your userbot",
|
||||
"restart_inline_handler_reply_text": "Restart",
|
||||
"update_inline_handler_title": "Update Userbot",
|
||||
"restart_inline_handler_message": "🔄 Restart",
|
||||
"update_inline_handler_title": "🔄 Update Userbot",
|
||||
"update_inline_handler_description": "Update your userbot via inline",
|
||||
"update_inline_handler_message": "Press the button below to update your userbot",
|
||||
"update_inline_handler_reply_text": "Update",
|
||||
"terminal_inline_handler_title": "Command Executed!",
|
||||
"update_inline_handler_message": "🔄 Update",
|
||||
"terminal_inline_handler_title": "💻 Command Executed",
|
||||
"terminal_inline_handler_description": "Command executed successfully",
|
||||
"terminal_inline_handler_message": "Command {text} executed successfully in terminal",
|
||||
"modules_inline_handler_title": "Modules",
|
||||
"terminal_inline_handler_message": "Command <code>{text}</code> executed successfully in terminal",
|
||||
"modules_inline_handler_title": "📦 Modules",
|
||||
"modules_inline_handler_description": "List all installed modules",
|
||||
"modules_inline_handler_result": "☘️ Installed modules:\n",
|
||||
"resetprefix_inline_handler_title": "Reset Prefix",
|
||||
"resetprefix_inline_handler_description": "Reset your prefix back to default",
|
||||
"modules_inline_handler_result": "📦 All installed modules:\n\n",
|
||||
"resetprefix_inline_handler_title": "⚠️ Reset Prefix",
|
||||
"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_reply_text_yes": "Yes",
|
||||
"resetprefix_inline_handler_reply_text_no": "No",
|
||||
"resetprefix_inline_handler_reply_text_yes": "Yes, reset it",
|
||||
"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 = {
|
||||
"call_restart": "Перезагружаю...",
|
||||
"call_update": "Обновляю...",
|
||||
"res_prefix": "Префикс успешно сброшен по умолчанию",
|
||||
"restart_inline_handler_title": "Перезагрузить юзербота",
|
||||
"call_restart": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагружаю...",
|
||||
"call_update": "<emoji document_id=5188311512791393083>🔄</emoji> Обновляю...",
|
||||
"res_prefix": "<emoji document_id=5854762571659218443>✅</emoji> Префикс успешно сброшен по умолчанию",
|
||||
"restart_inline_handler_title": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагрузить юзербота",
|
||||
"restart_inline_handler_description": "Перезагрузить юзербота через инлайн",
|
||||
"restart_inline_handler_message": "<b>Нажмите на кнопку ниже для рестарта юзербота</b>",
|
||||
"restart_inline_handler_reply_text": "Перезапуск",
|
||||
"update_inline_handler_title": "Обновить юзербота",
|
||||
"restart_inline_handler_message": "<emoji document_id=5188311512791393083>🔄</emoji> Перезагрузка",
|
||||
"update_inline_handler_title": "<emoji document_id=5188311512791393083>🔄</emoji> Обновить юзербота",
|
||||
"update_inline_handler_description": "Обновить юзербота через инлайн",
|
||||
"update_inline_handler_message": "<b>Нажмите на кнопку ниже для обновления юзербота</b>",
|
||||
"update_inline_handler_reply_text": "Обновить",
|
||||
"terminal_inline_handler_title": "Команда выполнена!",
|
||||
"terminal_inline_handler_description": "Команда завершена.",
|
||||
"update_inline_handler_message": "<emoji document_id=5188311512791393083>🔄</emoji> Обновить",
|
||||
"terminal_inline_handler_title": "<emoji document_id=5854762571659218443>💻</emoji> Команда выполнена!",
|
||||
"terminal_inline_handler_description": "Команда успешно выполнена.",
|
||||
"terminal_inline_handler_message": "Команда <code>{text}</code> была успешно выполнена в терминале",
|
||||
"modules_inline_handler_title": "Модули",
|
||||
"modules_inline_handler_description": "Вывести список установленных моудей",
|
||||
"modules_inline_handler_result": "☘️ Все установленные модули:\n",
|
||||
"resetprefix_inline_handler_title": "Сбросить префикс",
|
||||
"resetprefix_inline_handler_description": "Сбросить префикс по умолчанию",
|
||||
"modules_inline_handler_title": "<emoji document_id=5854762571659218443>📦</emoji> Модули",
|
||||
"modules_inline_handler_description": "Вывести список установленных модулей",
|
||||
"modules_inline_handler_result": "<emoji document_id=5854762571659218443>📦</emoji> Все установленные модули:\n\n",
|
||||
"resetprefix_inline_handler_title": "<emoji document_id=5854929766146118183>⚠️</emoji> Сбросить префикс",
|
||||
"resetprefix_inline_handler_description": "Сбросить префикс по умолчанию (осторожно!)",
|
||||
"resetprefix_inline_handler_message": "Вы действительно хотите сбросить ваш префикс и установить стандартную точку?",
|
||||
"resetprefix_inline_handler_reply_text_yes": "Да",
|
||||
"resetprefix_inline_handler_reply_text_no": "Нет",
|
||||
"resetprefix_inline_handler_reply_text_yes": "Да, сбросить",
|
||||
"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):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
async def restart(self, call):
|
||||
"""Restart callback"""
|
||||
logging.error("InlineHelper: restarting userbot...")
|
||||
await call.edit(self.strings("call_restart"))
|
||||
await sys.exit(0)
|
||||
logger.info("InlineHelper: Restarting userbot...")
|
||||
try:
|
||||
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):
|
||||
"""Update callback"""
|
||||
logging.error("InlineHelper: updating userbot...")
|
||||
os.system(f"cd {utils.get_base_dir()} && cd .. && git reset --hard HEAD")
|
||||
os.system("git pull")
|
||||
await call.edit(self.strings("call_update"))
|
||||
await sys.exit(0)
|
||||
logger.info("InlineHelper: Updating userbot...")
|
||||
try:
|
||||
await call.edit(self.strings["call_update"])
|
||||
|
||||
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):
|
||||
"""Reset prefix"""
|
||||
self.db.set(main.__name__, "command_prefix", ".")
|
||||
await call.edit(self.strings("res_prefix"))
|
||||
"""Reset prefix callback"""
|
||||
try:
|
||||
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(
|
||||
ru_doc="Перезагрузить юзербота",
|
||||
@@ -152,42 +194,95 @@ class InlineHelperMod(loader.Module):
|
||||
ru_doc="Выполнить команду в терминале (лучше сразу подготовить команду и просто вставить)",
|
||||
en_doc="Execute the command in the terminal (it is better to prepare the command immediately and just paste it)",
|
||||
)
|
||||
async def terminal_inline_handler(self, _: InlineQuery):
|
||||
text = _.args
|
||||
async def terminal_inline_handler(self, query: InlineQuery):
|
||||
"""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(
|
||||
f"{text}",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=utils.get_base_dir(),
|
||||
)
|
||||
command_text = query.args.strip()
|
||||
if not command_text:
|
||||
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"
|
||||
),
|
||||
}
|
||||
|
||||
return {
|
||||
"title": self.strings("terminal_inline_handler_title"),
|
||||
"description": self.strings("terminal_inline_handler_description"),
|
||||
"message": self.strings("terminal_inline_handler_message").format(
|
||||
text=text
|
||||
),
|
||||
}
|
||||
if any(char in command_text for char in ["&", "|", ";", "`", "$"]):
|
||||
return {
|
||||
"title": self.strings["terminal_inline_handler_title"],
|
||||
"description": self.strings["terminal_inline_handler_description"],
|
||||
"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(
|
||||
ru_doc="Вывести список установленных модулей через инлайн",
|
||||
en_doc="Display a list of installed modules via the inline",
|
||||
)
|
||||
async def modules_inline_handler(self, _: InlineQuery):
|
||||
result = self.strings("modules_inline_handler_result")
|
||||
async def modules_inline_handler(self, query: InlineQuery):
|
||||
"""List all installed modules"""
|
||||
try:
|
||||
result = self.strings["modules_inline_handler_result"]
|
||||
|
||||
for mod in self.allmodules.modules:
|
||||
try:
|
||||
name = mod.strings["name"]
|
||||
except KeyError:
|
||||
name = mod.__clas__.__name__
|
||||
result += f"• {name}\n"
|
||||
for mod in self.allmodules.modules:
|
||||
try:
|
||||
name = mod.strings["name"]
|
||||
except KeyError:
|
||||
name = mod.__class__.__name__
|
||||
result += f"• {name}\n"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing modules: {e}")
|
||||
result = f"Error listing modules: {str(e)}"
|
||||
|
||||
return {
|
||||
"title": self.strings("modules_inline_handler_title"),
|
||||
"description": self.strings("modules_inline_handler_description"),
|
||||
"title": self.strings["modules_inline_handler_title"],
|
||||
"description": self.strings["modules_inline_handler_description"],
|
||||
"message": result,
|
||||
}
|
||||
|
||||
|
||||
@@ -26,53 +26,108 @@
|
||||
# scope: IrisSimpleMod 1.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
__version__ = (1, 0, 1)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
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):
|
||||
"""Check bag"""
|
||||
async with self.client.conversation(5443619563) as conv:
|
||||
usermessage = await conv.send_message("мешок")
|
||||
await usermessage.delete()
|
||||
bagmessage = await conv.get_response()
|
||||
await utils.answer(message, "Ваш мешок:\n" + bagmessage.text)
|
||||
await bagmessage.delete()
|
||||
await utils.answer(message, self.strings["checking_bag"])
|
||||
|
||||
@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):
|
||||
"""Farm iris-coins"""
|
||||
async with self.client.conversation(5443619563) as conv:
|
||||
usermessage = await conv.send_message("ферма")
|
||||
await usermessage.delete()
|
||||
farmmessage = await conv.get_response()
|
||||
await utils.answer(message, farmmessage.text)
|
||||
await farmmessage.delete()
|
||||
await utils.answer(message, self.strings["farming"])
|
||||
|
||||
@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):
|
||||
"""Display user stats"""
|
||||
async with self.client.conversation(5443619563) as conv:
|
||||
usermessage = await conv.send_message("анкета")
|
||||
await usermessage.delete()
|
||||
statsmessage = await conv.get_response()
|
||||
await utils.answer(message, statsmessage.text)
|
||||
await statsmessage.delete()
|
||||
await utils.answer(message, self.strings["getting_stats"])
|
||||
|
||||
@loader.command(ru_doc="Вывести статистику ботов")
|
||||
async def irisping(self, message):
|
||||
"""Display bot stats"""
|
||||
async with self.client.conversation(5443619563) as conv:
|
||||
usermessage = await conv.send_message("🌺 Семейство ирисовых")
|
||||
await usermessage.delete()
|
||||
pingmessage = await conv.get_response()
|
||||
await utils.answer(message, pingmessage.text)
|
||||
await pingmessage.delete()
|
||||
result = await self._send_and_delete(message, "анкета", response_timeout=20)
|
||||
|
||||
if result:
|
||||
await utils.answer(message, self.strings["stats_result"].format(result))
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
# scope: KBSwapper 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
|
||||
import logging
|
||||
import string
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EN_TO_RU = str.maketrans(
|
||||
"qwertyuiop[]asdfghjkl;'zxcvbnm,./`" + 'QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?~',
|
||||
@@ -74,18 +75,55 @@ class KBSwapperMod(loader.Module):
|
||||
return
|
||||
|
||||
original_text = reply.text
|
||||
if not original_text:
|
||||
if not original_text or original_text.isspace():
|
||||
await utils.answer(message, self.strings("no_text"))
|
||||
return
|
||||
|
||||
try:
|
||||
first_char = original_text[0].lower()
|
||||
if first_char in string.ascii_lowercase:
|
||||
fixed_text = original_text.translate(EN_TO_RU)
|
||||
elif first_char in "йцукенгшщзхъфывапролджэячсмитьбю.ё":
|
||||
trimmed_text = original_text.strip()
|
||||
|
||||
has_russian = any(
|
||||
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)
|
||||
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:
|
||||
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:
|
||||
await reply.edit(fixed_text)
|
||||
@@ -96,5 +134,5 @@ class KBSwapperMod(loader.Module):
|
||||
f"{self.strings('fixed_message').format(fixed=fixed_text)}",
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error during swap: {e}")
|
||||
logger.error(f"Error during swap: {e}")
|
||||
await utils.answer(message, self.strings("error"))
|
||||
|
||||
@@ -27,12 +27,15 @@
|
||||
# scope: Meme 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
import aiohttp
|
||||
import random
|
||||
import logging
|
||||
import random # noqa: F401
|
||||
|
||||
import aiohttp # noqa: F401
|
||||
from bs4 import BeautifulSoup # noqa: F401
|
||||
|
||||
from .. import loader
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class MemesMod(loader.Module):
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
from telethon.types import Message
|
||||
|
||||
from herokutl.types import Message
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -43,50 +45,50 @@ class MessageMonitor(loader.Module):
|
||||
|
||||
strings = {
|
||||
"name": "MessageMonitor",
|
||||
"triggers_set": "Trigger words have been set: {}",
|
||||
"triggers_not_set": "Trigger words have not been set",
|
||||
"target_set": "Target chat for notifications has been set",
|
||||
"target_not_set": "Target chat for notifications has not been set",
|
||||
"monitoring_started": "Monitoring has started",
|
||||
"monitoring_stopped": "Monitoring has stopped",
|
||||
"monitoring_status": "Monitoring {}",
|
||||
"triggers_example": "Example: <code>.triggers word1 word2</code>",
|
||||
"monitoring_status_on": "enabled",
|
||||
"monitoring_status_off": "disabled",
|
||||
"ignore_set": "Ignored chats have been set: {}",
|
||||
"ignore_none": "Ignored chats have not been set",
|
||||
"ignore_example": "Example: <code>.ignore 123456789 -987654321</code> (chat IDs)",
|
||||
"no_reply": "Reply to a message in the desired chat or specify its ID",
|
||||
"triggers_set": "<emoji document_id=5854762571659218443>✅</emoji> Trigger words have been set: <code>{}</code>",
|
||||
"triggers_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Trigger words have not been set",
|
||||
"target_set": "<emoji document_id=5854762571659218443>✅</emoji> Target chat for notifications has been set",
|
||||
"target_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Target chat for notifications has not been set",
|
||||
"monitoring_started": "<emoji document_id=5188311512791393083>🌎</emoji> Monitoring has started",
|
||||
"monitoring_stopped": "<emoji document_id=5854929766146118183>❌</emoji> Monitoring has stopped",
|
||||
"monitoring_status": "<emoji document_id=5188311512791393083>🌎</emoji> Monitoring <b>{}</b>",
|
||||
"triggers_example": "<emoji document_id=5854929766146118183>❌</emoji> Example: <code>.triggers word1 word2</code>",
|
||||
"monitoring_status_on": "<emoji document_id=5854762571659218443>✅</emoji> enabled",
|
||||
"monitoring_status_off": "<emoji document_id=5854929766146118183>❌</emoji> disabled",
|
||||
"ignore_set": "<emoji document_id=5854762571659218443>✅</emoji> Ignored chats have been set: <code>{}</code>",
|
||||
"ignore_none": "<emoji document_id=5854929766146118183>❌</emoji> Ignored chats have not been set",
|
||||
"ignore_example": "<emoji document_id=5854929766146118183>❌</emoji> Example: <code>.ignore 123456789 -987654321</code> (chat IDs)",
|
||||
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Reply to a message in the desired chat or specify its ID",
|
||||
"monitoring_msg": (
|
||||
"🚨 **Trigger word detected!** 🚨\n\n"
|
||||
"**Chat:** {} (`{}`)\n"
|
||||
"**User:** {}\n"
|
||||
"**Link:** {}\n\n"
|
||||
"**Messages:**\n{}"
|
||||
"<emoji document_id=5854929766146118183>🚨</emoji> <b>Trigger word detected!</b> <emoji document_id=5854929766146118183>🚨</emoji>\n\n"
|
||||
"<b>Chat:</b> <code>{}</code>\n"
|
||||
"<b>User:</b> {}\n"
|
||||
"<b>Link:</b> <a href='{}'>{}</a>\n\n"
|
||||
"<b>Message:</b>\n{}"
|
||||
),
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"triggers_set": "Триггерные слова установлены: {}",
|
||||
"triggers_not_set": "Триггерные слова не установлены",
|
||||
"target_set": "Целевой чат для уведомлений установлен",
|
||||
"target_not_set": "Целевой чат для уведомлений не установлен",
|
||||
"monitoring_started": "Мониторинг запущен",
|
||||
"monitoring_stopped": "Мониторинг остановлен",
|
||||
"monitoring_status": "Мониторинг {}",
|
||||
"triggers_example": "Пример: <code>.triggers слово1 слово2</code>",
|
||||
"monitoring_status_on": "включен",
|
||||
"monitoring_status_off": "выключен",
|
||||
"ignore_set": "Игнорируемые чаты установлены: {}",
|
||||
"ignore_none": "Игнорируемые чаты не установлены",
|
||||
"ignore_example": "Пример: <code>.ignore 123456789 -987654321</code> (ID чатов)",
|
||||
"no_reply": "Ответьте на сообщение в нужном чате или укажите его ID",
|
||||
"triggers_set": "<emoji document_id=5854762571659218443>✅</emoji> Триггерные слова установлены: <code>{}</code>",
|
||||
"triggers_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Триггерные слова не установлены",
|
||||
"target_set": "<emoji document_id=5854762571659218443>✅</emoji> Целевой чат для уведомлений установлен",
|
||||
"target_not_set": "<emoji document_id=5854929766146118183>❌</emoji> Целевой чат для уведомлений не установлен",
|
||||
"monitoring_started": "<emoji document_id=5188311512791393083>🌎</emoji> Мониторинг запущен",
|
||||
"monitoring_stopped": "<emoji document_id=5854929766146118183>❌</emoji> Мониторинг остановлен",
|
||||
"monitoring_status": "<emoji document_id=5188311512791393083>🌎</emoji> Мониторинг <b>{}</b>",
|
||||
"triggers_example": "<emoji document_id=5854929766146118183>❌</emoji> Пример: <code>.triggers слово1 слово2</code>",
|
||||
"monitoring_status_on": "<emoji document_id=5854762571659218443>✅</emoji> включен",
|
||||
"monitoring_status_off": "<emoji document_id=5854929766146118183>❌</emoji> выключен",
|
||||
"ignore_set": "<emoji document_id=5854762571659218443>✅</emoji> Игнорируемые чаты установлены: <code>{}</code>",
|
||||
"ignore_none": "<emoji document_id=5854929766146118183>❌</emoji> Игнорируемые чаты не установлены",
|
||||
"ignore_example": "<emoji document_id=5854929766146118183>❌</emoji> Пример: <code>.ignore 123456789 -987654321</code> (ID чатов)",
|
||||
"no_reply": "<emoji document_id=5854929766146118183>❌</emoji> Ответьте на сообщение в нужном чате или укажите его ID",
|
||||
"monitoring_msg": (
|
||||
"🚨 **Обнаружено триггерное слово!** 🚨\n\n"
|
||||
"**Чат:** {} (`{}`)\n"
|
||||
"**Пользователь:** {}\n"
|
||||
"**Ссылка:** {}\n\n"
|
||||
"**Сообщение:**\n{}"
|
||||
"<emoji document_id=5854929766146118183>🚨</emoji> <b>Обнаружено триггерное слово!</b> <emoji document_id=5854929766146118183>🚨</emoji>\n\n"
|
||||
"<b>Чат:</b> <code>{}</code>\n"
|
||||
"<b>Пользователь:</b> {}\n"
|
||||
"<b>Ссылка:</b> <a href='{}'>{}</a>\n\n"
|
||||
"<b>Сообщение:</b>\n{}"
|
||||
),
|
||||
}
|
||||
|
||||
@@ -95,35 +97,61 @@ class MessageMonitor(loader.Module):
|
||||
loader.ConfigValue(
|
||||
"triggers",
|
||||
[],
|
||||
"Список триггерных слов",
|
||||
"List of trigger words to monitor",
|
||||
validator=loader.validators.Series(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"target_chat",
|
||||
None,
|
||||
"ID целевого чата для отправки уведомлений",
|
||||
"Target chat ID for notifications",
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_chats",
|
||||
[],
|
||||
"Список ID чатов, которые будут игнорироваться",
|
||||
"List of chat IDs to ignore",
|
||||
validator=loader.validators.Series(),
|
||||
),
|
||||
)
|
||||
self._triggers = []
|
||||
self._target_chat = None
|
||||
self._ignore_chats = []
|
||||
self._triggers: List[str] = []
|
||||
self._target_chat: Optional[int] = None
|
||||
self._ignore_chats: List[int] = []
|
||||
self._compiled_patterns: List[re.Pattern] = []
|
||||
|
||||
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._ignore_chats = [
|
||||
int(i)
|
||||
for i in self.config["ignore_chats"]
|
||||
if str(i).isdigit() or str(i).startswith("-")
|
||||
int(chat_id)
|
||||
for chat_id in self.config["ignore_chats"]
|
||||
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(
|
||||
ru_doc="Установить триггерные слова. Пример: .triggers слово1 слово2",
|
||||
@@ -138,6 +166,7 @@ class MessageMonitor(loader.Module):
|
||||
|
||||
self._triggers = [arg.lower() for arg in args]
|
||||
self.config["triggers"] = self._triggers
|
||||
await self._update_config()
|
||||
await utils.answer(
|
||||
message, self.strings["triggers_set"].format(", ".join(self._triggers))
|
||||
)
|
||||
@@ -152,12 +181,10 @@ class MessageMonitor(loader.Module):
|
||||
chat_id = None
|
||||
|
||||
if getattr(message, "is_reply", False):
|
||||
get_reply_method = getattr(message, "get_reply_message", None)
|
||||
if get_reply_method:
|
||||
reply_message = await get_reply_method()
|
||||
if reply_message and getattr(reply_message, "chat_id", None):
|
||||
chat_id = reply_message.chat_id
|
||||
elif args and (args.isdigit() or args.startswith("-") and args[1:].isdigit()):
|
||||
reply_message = await message.get_reply_message()
|
||||
if reply_message and hasattr(reply_message, "chat_id"):
|
||||
chat_id = reply_message.chat_id
|
||||
elif args and (args.isdigit() or (args.startswith("-") and args[1:].isdigit())):
|
||||
chat_id = int(args)
|
||||
|
||||
if chat_id:
|
||||
@@ -184,7 +211,7 @@ class MessageMonitor(loader.Module):
|
||||
valid_ids.append(int(arg))
|
||||
|
||||
self.config["ignore_chats"] = valid_ids
|
||||
self._ignore_chats = valid_ids
|
||||
await self._update_config()
|
||||
|
||||
if valid_ids:
|
||||
await utils.answer(
|
||||
@@ -192,19 +219,15 @@ class MessageMonitor(loader.Module):
|
||||
self.strings["ignore_set"].format(", ".join(map(str, valid_ids))),
|
||||
)
|
||||
else:
|
||||
await utils.answer(
|
||||
message, "Не удалось распознать ID чатов. Используйте только числа."
|
||||
)
|
||||
await utils.answer(message, self.strings["ignore_none"])
|
||||
|
||||
@loader.watcher(out=False, only_messages=True)
|
||||
async def message_watcher(self, message: Message):
|
||||
"""Watch for messages containing trigger words"""
|
||||
|
||||
if not self._target_chat or not self._triggers:
|
||||
return
|
||||
|
||||
chat_id = getattr(message, "chat_id", None)
|
||||
|
||||
if chat_id and chat_id in self._ignore_chats:
|
||||
logger.debug(f"Message in ignored chat: {chat_id}. Skipping monitoring.")
|
||||
return
|
||||
@@ -213,34 +236,27 @@ class MessageMonitor(loader.Module):
|
||||
if not text:
|
||||
return
|
||||
|
||||
text_lower = text.lower()
|
||||
|
||||
found_triggers = [
|
||||
trigger
|
||||
for trigger in self._triggers
|
||||
if re.search(r"\b" + re.escape(trigger) + r"\b", text_lower)
|
||||
for pattern, trigger in zip(self._compiled_patterns, self._triggers)
|
||||
if pattern.search(text)
|
||||
]
|
||||
|
||||
if not found_triggers:
|
||||
return
|
||||
|
||||
try:
|
||||
get_chat_method = getattr(message, "get_chat", None)
|
||||
if get_chat_method:
|
||||
chat = await get_chat_method()
|
||||
chat_title = getattr(
|
||||
chat,
|
||||
"title",
|
||||
"Личные сообщения"
|
||||
if getattr(message, "is_private", False)
|
||||
else "Чат с ID " + str(chat_id),
|
||||
)
|
||||
else:
|
||||
chat_title = "Неизвестный чат"
|
||||
chat = await message.get_chat()
|
||||
chat_title = getattr(
|
||||
chat,
|
||||
"title",
|
||||
"Личные сообщения"
|
||||
if getattr(message, "is_private", False)
|
||||
else f"Чат с ID {chat_id}",
|
||||
)
|
||||
|
||||
get_sender_method = getattr(message, "get_sender", None)
|
||||
if get_sender_method:
|
||||
sender = await get_sender_method()
|
||||
sender = await message.get_sender()
|
||||
if sender:
|
||||
sender_name = sender.first_name
|
||||
if getattr(sender, "last_name", None):
|
||||
sender_name += f" {sender.last_name}"
|
||||
@@ -251,13 +267,7 @@ class MessageMonitor(loader.Module):
|
||||
else:
|
||||
sender_name = "Неизвестный пользователь"
|
||||
|
||||
to_id_obj = getattr(message, "to_id", None)
|
||||
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}"
|
||||
link = await self._get_message_link(message, sender)
|
||||
|
||||
await self.client.send_message(
|
||||
self._target_chat,
|
||||
@@ -268,10 +278,28 @@ class MessageMonitor(loader.Module):
|
||||
link,
|
||||
text,
|
||||
),
|
||||
parse_mode="Markdown",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
logger.debug(
|
||||
f"Sent notification about trigger word(s) {found_triggers} to chat {self._target_chat}"
|
||||
)
|
||||
except Exception as 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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
__version__ = (0, 1, 4, 10)
|
||||
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
|
||||
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.tl.types import InputDocument, Message
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineCall
|
||||
import json
|
||||
|
||||
__version__ = (0, 1, 4, 10)
|
||||
|
||||
|
||||
class DebugLogger:
|
||||
@@ -127,7 +124,7 @@ class AutoFarmbotMod(loader.Module):
|
||||
|
||||
"""
|
||||
|
||||
# Todo: Автокрафт и Автолес готовы на 95%, автохавка на 45%
|
||||
# NOTE: Автокрафт и Автолес готовы на 95%, автохавка на 45%
|
||||
strings = {
|
||||
"name": "AutoFarmbot",
|
||||
# Inline keys
|
||||
@@ -1112,7 +1109,7 @@ class AutoFarmbotMod(loader.Module):
|
||||
|
||||
try:
|
||||
self.config[config_key] = current
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
await call.answer("❌ Ошибка валидации")
|
||||
return
|
||||
|
||||
@@ -1122,7 +1119,7 @@ class AutoFarmbotMod(loader.Module):
|
||||
await call.answer("🔄 Синхронизация началась...")
|
||||
|
||||
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")
|
||||
start_id = msg.id
|
||||
@@ -1198,7 +1195,6 @@ class AutoFarmbotMod(loader.Module):
|
||||
|
||||
async def eating_handler(self, event):
|
||||
chat_id = self.get_chat_id
|
||||
user_id = self.tg_id
|
||||
food = self.config["config_bot_eat_lvl"]
|
||||
if event.chat_id != chat_id:
|
||||
return
|
||||
@@ -1662,7 +1658,7 @@ class AutoFarmbotMod(loader.Module):
|
||||
:param action:
|
||||
:return:
|
||||
"""
|
||||
chat_id = self.config["config_bot_used_chat_id"]
|
||||
self.config["config_bot_used_chat_id"]
|
||||
user_id = self.tg_id
|
||||
key = f"forest_task:{user_id}:{action}"
|
||||
await self.redis.set(key, "pending", ex=wait_time)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: Music
|
||||
# Description: Searches for music using Telegram music bots.
|
||||
# Description: Searches for music using Telegram music bots
|
||||
# Author: @hikka_mods
|
||||
# meta developer: @hikka_mods
|
||||
# scope: Music
|
||||
@@ -45,49 +45,46 @@ logger = logging.getLogger(__name__)
|
||||
class MusicMod(loader.Module):
|
||||
strings = {
|
||||
"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>",
|
||||
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Possible match:</b>",
|
||||
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Track not found: <code>{}</code>.</b>",
|
||||
"invalid_service": "<emoji document_id=5462295343642956603>🚫</emoji> <b>Invalid service. (yandex, vk)</b>",
|
||||
"usage": "<b>Usage:</b> <code>.music [yandex|vk] [track name]</code>",
|
||||
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Track not found: <code>{}</code></b>",
|
||||
"usage": "<b>Usage:</b> <code>.music [track name]</code>",
|
||||
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Error:</b> <code>{}</code>",
|
||||
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>No results: <code>{}</code>.</b>",
|
||||
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Wait {}s (Telegram limits).</b>",
|
||||
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>No results: <code>{}</code></b>",
|
||||
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Wait {}s (Telegram limits)</b>",
|
||||
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Bot error: <code>{}</code></b>",
|
||||
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>No audio.</b>",
|
||||
"generic_result": "<emoji document_id=5336965905773504919>ℹ️</emoji> <b>Non-media result. Check the bot's chat.</b>",
|
||||
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>No audio</b>",
|
||||
"generic_result": "<emoji document_id=5336965905773504919>ℹ️</emoji> <b>Non-media result. Check the bot's chat</b>",
|
||||
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Searching Yandex.Music...</b>",
|
||||
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Track not found on Yandex.Music.</b>",
|
||||
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Track not found on Yandex.Music</b>",
|
||||
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Error (Yandex): {}</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "Music",
|
||||
"no_query": "<emoji document_id=5337117114392127164>🤷♂</emoji> <b>Укажите запрос.</b>",
|
||||
"no_query": "<emoji document_id=5337117114392127164>🤷♂</emoji> <b>Укажите запрос!</b>",
|
||||
"searching": "<emoji document_id=4918235297679934237>⌨️</emoji> <b>Поиск...</b>",
|
||||
"found": "<emoji document_id=5336965905773504919>🗣</emoji> <b>Возможно, это оно:</b>",
|
||||
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Трек не найден: <code>{}</code>.</b>",
|
||||
"invalid_service": "<emoji document_id=5462295343642956603>🚫</emoji> <b>Неверный сервис. (yandex, vk)</b>",
|
||||
"usage": "<b>Использование:</b> <code>.music [yandex|vk] [название трека]</code>",
|
||||
"not_found": "<emoji document_id=5228947933545635555>😫</emoji> <b>Трек не найден: <code>{}</code></b>",
|
||||
"usage": "<b>Использование:</b> <code>.music [название трека]</code>",
|
||||
"error": "<emoji document_id=5228947933545635555>⚠️</emoji> <b>Ошибка:</b> <code>{}</code>",
|
||||
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>Нет результатов: <code>{}</code>.</b>",
|
||||
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Подождите {}с (лимиты Telegram).</b>",
|
||||
"no_results": "<emoji document_id=5228947933545635555>😫</emoji> <b>Нет результатов: <code>{}</code></b>",
|
||||
"flood_wait": "<emoji document_id=5462295343642956603>⏳</emoji> <b>Подождите {}с (лимиты Telegram)</b>",
|
||||
"bot_error": "<emoji document_id=5228947933545635555>🤖</emoji> <b>Ошибка бота: <code>{}</code></b>",
|
||||
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>Нет аудио.</b>",
|
||||
"generic_result": "<emoji document_id=5336965905773504919>ℹ️</emoji> <b>Немедийный результат. Проверьте чат с ботом.</b>",
|
||||
"no_audio": "<emoji document_id=5228947933545635555>🎵</emoji> <b>Нет аудио</b>",
|
||||
"generic_result": "<emoji document_id=5336965905773504919>ℹ️</emoji> <b>Немедийный результат. Проверьте чат с ботом</b>",
|
||||
"yafind_searching": "<emoji document_id=5258396243666681152>🔎</emoji> <b>Поиск в Яндекс.Музыке...</b>",
|
||||
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Трек не найден в Яндекс.Музыке.</b>",
|
||||
"yafind_not_found": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Трек не найден в Яндекс.Музыке</b>",
|
||||
"yafind_error": "<emoji document_id=5843952899184398024>🚫</emoji> <b>Ошибка (Яндекс): {}</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.murglar_bot = "@murglar_bot"
|
||||
self.vk_bot = "@vkmusic_bot"
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Найти трек в Yandex Music или VK: `.music yandex {название}` или `.music vk {название}`",
|
||||
en_doc="Find a track in Yandex Music or VK: `.music yandex {name}` or `.music vk {name}`",
|
||||
ru_doc="Найти трек в Yandex.Music: `.music {название}`",
|
||||
en_doc="Find a track in Yandex.Music: `.music yandex {name}`",
|
||||
)
|
||||
async def music(self, message):
|
||||
args = utils.get_args(message)
|
||||
@@ -98,15 +95,8 @@ class MusicMod(loader.Module):
|
||||
else:
|
||||
await utils.answer(message, self.strings("usage", message))
|
||||
return
|
||||
|
||||
service, query = args[0].lower(), " ".join(args[1:])
|
||||
|
||||
if service == "yandex":
|
||||
await self._yafind(message, query)
|
||||
elif service == "vk":
|
||||
await self._vkfind(message, query)
|
||||
else:
|
||||
await utils.answer(message, self.strings("invalid_service", message))
|
||||
|
||||
await self._yafind(message, query=args)
|
||||
|
||||
async def _yafind(self, message: Message, query: str):
|
||||
if not query:
|
||||
@@ -134,69 +124,3 @@ class MusicMod(loader.Module):
|
||||
except Exception as e:
|
||||
logger.exception("Yandex search error:")
|
||||
await utils.answer(message, self.strings("yafind_error", message).format(e))
|
||||
|
||||
async def _vkfind(self, message, query: str):
|
||||
if not query:
|
||||
return await utils.answer(message, self.strings("no_query", message))
|
||||
|
||||
await utils.answer(message, self.strings("searching", message))
|
||||
|
||||
try:
|
||||
music = await message.client.inline_query(self.vk_bot, query)
|
||||
|
||||
if not music or len(music) <= 1:
|
||||
return await utils.answer(
|
||||
message, self.strings("not_found", message).format(query)
|
||||
)
|
||||
|
||||
for i in range(1, len(music), 2):
|
||||
try:
|
||||
result = music[i].result
|
||||
if hasattr(result, "audio") and result.audio:
|
||||
await message.client.send_file(
|
||||
message.to_id,
|
||||
result.audio,
|
||||
caption=self.strings("found", message),
|
||||
reply_to=utils.get_topic(message)
|
||||
if message.reply_to_msg_id
|
||||
else None,
|
||||
)
|
||||
await message.delete()
|
||||
return
|
||||
if hasattr(result, "document") and result.document:
|
||||
await message.client.send_file(
|
||||
message.to_id,
|
||||
result.document,
|
||||
caption=self.strings("found", message),
|
||||
reply_to=utils.get_topic(message)
|
||||
if message.reply_to_msg_id
|
||||
else None,
|
||||
)
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
logger.warning(f"No audio/document in result {i}")
|
||||
await utils.answer(message, self.strings("no_audio", message))
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
except MessageNotModifiedError:
|
||||
logger.warning("MessageNotModifiedError, skipping.")
|
||||
except Exception as e:
|
||||
logger.error(f"Send error: {e}")
|
||||
|
||||
await utils.answer(
|
||||
message, self.strings("not_found", message).format(query)
|
||||
)
|
||||
|
||||
except BotMethodInvalidError as e:
|
||||
logger.error(f"VK bot error: {e}")
|
||||
await utils.answer(message, self.strings("bot_error", message).format(e))
|
||||
except FloodWaitError as e:
|
||||
logger.warning(f"Flood wait: {e.seconds}s")
|
||||
await utils.answer(
|
||||
message, self.strings("flood_wait", message).format(e.seconds)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("VK search error:")
|
||||
await utils.answer(message, self.strings("error", message).format(e))
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Proprietary License Agreement
|
||||
|
||||
# Copyright (c) 2024-29 CodWiz
|
||||
|
||||
# Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use the Software for personal and non-commercial purposes, subject to the following conditions:
|
||||
|
||||
# 1. The Software may not be modified, altered, or otherwise changed in any way without the explicit written permission of the author.
|
||||
|
||||
# 2. Redistribution of the Software, in original or modified form, is strictly prohibited without the explicit written permission of the author.
|
||||
|
||||
# 3. The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the author or copyright holder be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software.
|
||||
|
||||
# 4. Any use of the Software must include the above copyright notice and this permission notice in all copies or substantial portions of the Software.
|
||||
|
||||
# 5. By using the Software, you agree to be bound by the terms and conditions of this license.
|
||||
|
||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: PastebinAPI
|
||||
# Description: fills in the code on pastebin
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
# scope: PastebinAPI
|
||||
# scope: PastebinAPI 0.0.1
|
||||
# requires: aiohttp
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class PastebinAPIMod(loader.Module):
|
||||
"""PastebinAPI"""
|
||||
|
||||
strings = {
|
||||
"name": "PastebinAPI",
|
||||
"no_reply": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> You didn't specify the text"
|
||||
),
|
||||
"no_key": "<emoji document_id=5843952899184398024>🚫</emoji> The key was not found",
|
||||
"done": "Your link with the code\n<emoji document_id=5985571061993837069>➡️</emoji> <code>{response_text}</code>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_reply": (
|
||||
"<emoji document_id=5462882007451185227>🚫</emoji> Вы не указали текст"
|
||||
),
|
||||
"no_key": "<emoji document_id=5843952899184398024>🚫</emoji> Ключ не найден",
|
||||
"done": "Ваша ссылка с кодом\n<emoji document_id=5985571061993837069>➡️</emoji> <code>{response_text}</code>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"pastebin",
|
||||
None,
|
||||
lambda: "link to get api https://pastebin.com/doc_api#1",
|
||||
validator=loader.validators.Hidden(),
|
||||
)
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Заливает код в Pastebin",
|
||||
en_doc="Uploads the code to Pastebin",
|
||||
)
|
||||
async def past(self, message):
|
||||
text = utils.get_args(message)
|
||||
|
||||
if self.config["pastebin"] is None:
|
||||
await utils.answer(message, self.strings("no_key"))
|
||||
return
|
||||
|
||||
if not text:
|
||||
await utils.answer(message, self.strings("no_reply"))
|
||||
return
|
||||
|
||||
async with aiohttp.ClientSession() as Session:
|
||||
async with Session.post(
|
||||
url="https://pastebin.com/api/api_post.php",
|
||||
data={
|
||||
"api_dev_key": self.config["pastebin"],
|
||||
"api_paste_code": text,
|
||||
"api_option": "paste",
|
||||
},
|
||||
) as response:
|
||||
response_text = await response.text()
|
||||
|
||||
await utils.answer(
|
||||
message, self.strings("done").format(response_text=response_text)
|
||||
)
|
||||
@@ -26,10 +26,13 @@
|
||||
# scope: VowelReplacer 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class VowelReplacer(loader.Module):
|
||||
|
||||
@@ -27,11 +27,15 @@
|
||||
# requires: zipfile
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import zipfile
|
||||
import logging
|
||||
import os
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SMArchiver(loader.Module):
|
||||
@@ -64,7 +68,7 @@ class SMArchiver(loader.Module):
|
||||
await utils.answer(message, self.strings["no_messages"])
|
||||
return
|
||||
|
||||
archive_path = self.create_archive(saved_messages)
|
||||
archive_path = await self.create_archive(saved_messages)
|
||||
|
||||
try:
|
||||
await message.client.send_file(
|
||||
@@ -79,7 +83,7 @@ class SMArchiver(loader.Module):
|
||||
finally:
|
||||
self.cleanup(archive_path)
|
||||
|
||||
def create_archive(self, saved_messages):
|
||||
async def create_archive(self, saved_messages):
|
||||
current_month = datetime.now().strftime("%B %Y")
|
||||
archive_path = "saved_messages.zip"
|
||||
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
# scope: TaskManager 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from .. import loader, utils
|
||||
@@ -46,103 +47,138 @@ class Task:
|
||||
due_date: Optional[datetime.datetime] = None
|
||||
completed: bool = False
|
||||
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:
|
||||
"""Manages tasks, storing them in a JSON file."""
|
||||
|
||||
def __init__(self, data_file: str):
|
||||
self.data_file = data_file
|
||||
self.data_file = Path(data_file)
|
||||
self.tasks: Dict[int, List[Task]] = {}
|
||||
self._lock = asyncio.Lock()
|
||||
self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
"""Loads task data from the JSON file."""
|
||||
if os.path.exists(self.data_file):
|
||||
try:
|
||||
with open(self.data_file, "r") as f:
|
||||
data = json.load(f)
|
||||
self.tasks = {
|
||||
int(user_id): [
|
||||
Task(
|
||||
description=task["description"],
|
||||
due_date=datetime.datetime.fromisoformat(
|
||||
task["due_date"]
|
||||
)
|
||||
if task["due_date"]
|
||||
else None,
|
||||
completed=task["completed"],
|
||||
created_at=datetime.datetime.fromisoformat(
|
||||
task["created_at"]
|
||||
),
|
||||
)
|
||||
for task in task_list
|
||||
]
|
||||
for user_id, task_list in data.items()
|
||||
}
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
logger.warning(f"Failed to load task data: {e}. Starting empty.")
|
||||
self.tasks = {}
|
||||
else:
|
||||
if not self.data_file.exists():
|
||||
self.tasks = {}
|
||||
logger.info("Task data file not found. Starting empty.")
|
||||
return
|
||||
|
||||
def save_data(self):
|
||||
"""Saves task data to the JSON file."""
|
||||
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 = {
|
||||
user_id: [
|
||||
{
|
||||
"description": task.description,
|
||||
"due_date": task.due_date.isoformat()
|
||||
if task.due_date
|
||||
else None,
|
||||
"completed": task.completed,
|
||||
"created_at": task.created_at.isoformat(),
|
||||
}
|
||||
for task in task_list
|
||||
]
|
||||
str(user_id): [task.to_dict() for task in task_list]
|
||||
for user_id, task_list in self.tasks.items()
|
||||
}
|
||||
json.dump(data, f, indent=4, default=str)
|
||||
except IOError as e:
|
||||
logger.error(f"Failed to save task data: {e}")
|
||||
with open(self.data_file, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
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.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]):
|
||||
del self.tasks[user_id][index]
|
||||
self.save_data()
|
||||
else:
|
||||
logger.warning(f"Invalid index for removal: {index}, user: {user_id}")
|
||||
await self.save_data()
|
||||
return True
|
||||
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]):
|
||||
self.tasks[user_id][index].completed = True
|
||||
self.save_data()
|
||||
else:
|
||||
logger.warning(f"Invalid index for completion: {index}, user: {user_id}")
|
||||
await self.save_data()
|
||||
return True
|
||||
logger.warning(f"Invalid index for completion: {index}, user: {user_id}")
|
||||
return False
|
||||
|
||||
def get_tasks(self, user_id: int) -> List[Task]:
|
||||
return self.tasks.get(user_id, [])
|
||||
def get_tasks(self, user_id: int, include_completed: bool = True) -> List[Task]:
|
||||
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:
|
||||
self.tasks[user_id] = []
|
||||
self.save_data()
|
||||
else:
|
||||
logger.info(f"No tasks to clear for user: {user_id}")
|
||||
await self.save_data()
|
||||
return True
|
||||
logger.info(f"No tasks to clear for user: {user_id}")
|
||||
return False
|
||||
|
||||
def get_task(self, user_id: int, index: int) -> Optional[Task]:
|
||||
return (
|
||||
self.tasks[user_id][index]
|
||||
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id])
|
||||
else None
|
||||
)
|
||||
if user_id in self.tasks and 0 <= index < len(self.tasks[user_id]):
|
||||
return self.tasks[user_id][index]
|
||||
return 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
|
||||
@@ -198,7 +234,9 @@ class TaskManagerModule(loader.Module):
|
||||
self.task_manager: Optional[TaskManager] = None
|
||||
|
||||
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(
|
||||
ru_doc="Добавить задачу:\n.taskadd <описание> | <дата (необязательно)>",
|
||||
@@ -230,7 +268,7 @@ class TaskManagerModule(loader.Module):
|
||||
return
|
||||
|
||||
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"))
|
||||
|
||||
@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"))
|
||||
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"))
|
||||
return
|
||||
|
||||
self.task_manager.remove_task(message.sender_id, index)
|
||||
await utils.answer(message, self.strings("task_removed"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[index] - Завершите задачу", en_doc="[index] - Complete task"
|
||||
@@ -268,12 +304,10 @@ class TaskManagerModule(loader.Module):
|
||||
await utils.answer(message, self.strings("invalid_index"))
|
||||
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"))
|
||||
return
|
||||
|
||||
self.task_manager.complete_task(message.sender_id, index)
|
||||
await utils.answer(message, self.strings("task_completed"))
|
||||
|
||||
@loader.command(ru_doc="Список задач", en_doc="List tasks")
|
||||
async def tasklist(self, message):
|
||||
@@ -342,8 +376,10 @@ class TaskManagerModule(loader.Module):
|
||||
|
||||
async def clear_confirm(self, call):
|
||||
"""Callback for confirming task clearing."""
|
||||
self.task_manager.clear_tasks(call.from_user.id)
|
||||
await call.edit(self.strings("tasks_cleared"))
|
||||
if await self.task_manager.clear_tasks(call.from_user.id):
|
||||
await call.edit(self.strings("tasks_cleared"))
|
||||
else:
|
||||
await call.edit(self.strings("no_tasks"))
|
||||
|
||||
async def clear_cancel(self, call):
|
||||
"""Callback for canceling task clearing."""
|
||||
|
||||
@@ -26,8 +26,12 @@
|
||||
# scope: Api TelegramStatusCodes 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
responses = {
|
||||
300: (
|
||||
"⛔ 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
|
||||
class TelegramStatusCodes(loader.Module):
|
||||
@@ -91,20 +141,28 @@ class TelegramStatusCodes(loader.Module):
|
||||
"args_incorrect": "<b>Неверные аргументы</b>",
|
||||
"not_found": "<b>Код не найден</b>",
|
||||
"syntax_error": "<b>Аргументы обязательны</b>",
|
||||
"_cmd_doc_httpsc": "<код> - Получить информацию о Telegram error",
|
||||
"_cmd_doc_httpsc": "<код> - Получить информацию о статус-коде",
|
||||
"_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.command(
|
||||
ru_doc="<код состояния> - Получение информации о коде состояния",
|
||||
ru_doc="<код состояния> - Получение информации о статус-коде",
|
||||
en_doc="<statuscode> - Get status code info",
|
||||
)
|
||||
async def tgccmd(self, message):
|
||||
|
||||
args = utils.get_args(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("syntax_error", message))
|
||||
return
|
||||
|
||||
try:
|
||||
if int(args[0]) not in responses:
|
||||
@@ -112,17 +170,26 @@ class TelegramStatusCodes(loader.Module):
|
||||
except ValueError:
|
||||
await utils.answer(message, self.strings("args_incorrect", message))
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("scode", message).format(
|
||||
responses[int(args[0])][0], args[0], responses[int(args[0])][1]
|
||||
),
|
||||
)
|
||||
if self.ub_lang != "ru":
|
||||
await utils.answer(
|
||||
message,
|
||||
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.command(
|
||||
ru_doc="Получите все коды статуса telegram",
|
||||
en_doc="Get all telegram status codes",
|
||||
ru_doc="Получите все статус-коды Telegram",
|
||||
en_doc="Get all Telegram status codes",
|
||||
)
|
||||
async def tgcscmd(self, message):
|
||||
await utils.answer(
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from hikkatl import functions
|
||||
from datetime import datetime as dt
|
||||
|
||||
@@ -27,9 +27,12 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import io
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Text2File(loader.Module):
|
||||
@@ -63,13 +66,15 @@ class Text2File(loader.Module):
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
else:
|
||||
text = args
|
||||
by = io.BytesIO(text.encode("utf-8"))
|
||||
by.name = self.config["name"]
|
||||
return
|
||||
|
||||
await utils.answer_file(
|
||||
message,
|
||||
by,
|
||||
reply_to=getattr(message, "reply_to_msg_id", None),
|
||||
)
|
||||
text = args
|
||||
by = io.BytesIO(text.encode("utf-8"))
|
||||
by.name = self.config["name"]
|
||||
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import re
|
||||
import os
|
||||
import warnings
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import List, Optional, Union
|
||||
from urllib.parse import urljoin
|
||||
from typing import Union, Optional, List
|
||||
|
||||
import aiohttp
|
||||
from tqdm import tqdm
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class data:
|
||||
@@ -51,8 +52,11 @@ class data:
|
||||
class TikTok:
|
||||
def __init__(self, host: Optional[str] = None):
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) "
|
||||
"Version/4.0.4 Mobile/7B334b Safari/531.21.10"
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) "
|
||||
"AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 "
|
||||
"Mobile/7B334b Safari/531.21.10"
|
||||
)
|
||||
}
|
||||
self.host = host or "https://www.tikwm.com/"
|
||||
self.session = aiohttp.ClientSession()
|
||||
@@ -73,42 +77,25 @@ class TikTok:
|
||||
self.logger.addHandler(handler)
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
def _warn(reason: str = "This function is NOT used but may be useful"):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warnings.warn(
|
||||
f"Warning! Deprecated: {func.__name__}\nReason: {reason}",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
async def close_session(self):
|
||||
await self.session.close()
|
||||
|
||||
async def _ensure_data(self, link: str):
|
||||
try:
|
||||
if self.result is None or self.link != link:
|
||||
self.link = link
|
||||
self.result = await self.fetch(link)
|
||||
self.logger.info("Successfully ensured data from the link")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error occurred when trying to get data from tikwm: {e}")
|
||||
raise
|
||||
async def __ensure_data(self, link: str):
|
||||
if self.link != link:
|
||||
self.link = link
|
||||
self.result = await self._fetch_data(link)
|
||||
self.logger.info("Successfully ensured data from the link")
|
||||
|
||||
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"]
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
|
||||
tasks = [
|
||||
self._download_file(url, os.path.join(download_dir, f"image_{i + 1}.jpg"))
|
||||
for i, url in enumerate(self.result["images"])
|
||||
]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
self.logger.info(f"Images - Downloaded and saved photos to {download_dir}")
|
||||
|
||||
return data(
|
||||
@@ -120,7 +107,7 @@ class TikTok:
|
||||
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_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"
|
||||
)
|
||||
|
||||
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(
|
||||
urljoin(self.host, endpoint), params=params, headers=self.headers
|
||||
) as response:
|
||||
@@ -154,87 +184,12 @@ class TikTok:
|
||||
urls = re.findall(r"http[s]?://[^\s]+", text)
|
||||
return urls[0] if urls else None
|
||||
|
||||
@_warn()
|
||||
async def convert_share_urls(self, url: str) -> Optional[str]:
|
||||
url = self.get_url(url)
|
||||
if "@" in url:
|
||||
return url
|
||||
async with self.session.get(
|
||||
url, headers=self.headers, allow_redirects=False
|
||||
) as response:
|
||||
if response.status == 301:
|
||||
return response.headers["Location"].split("?")[0]
|
||||
return None
|
||||
|
||||
@_warn()
|
||||
async def get_tiktok_video_id(self, original_url: str) -> Optional[str]:
|
||||
original_url = await self.convert_share_urls(original_url)
|
||||
matches = re.findall(r"/video|v|photo/(\d+)", original_url)
|
||||
return matches[0] if matches else None
|
||||
|
||||
async def fetch(self, link: str) -> dict:
|
||||
url = self.get_url(link)
|
||||
params = {"url": url, "hd": 1}
|
||||
return await self._makerequest(self.data_endpoint, params=params)
|
||||
|
||||
async def _download_file(self, url: str, path: str):
|
||||
async with self.session.get(url) as response:
|
||||
response.raise_for_status()
|
||||
with open(path, "wb") as file:
|
||||
while chunk := await response.content.read(1024):
|
||||
file.write(chunk)
|
||||
|
||||
async def download_sound(
|
||||
self,
|
||||
link: Union[str],
|
||||
audio_filename: Optional[str] = None,
|
||||
audio_ext: Optional[str] = ".mp3",
|
||||
):
|
||||
await self._ensure_data(link)
|
||||
|
||||
if not audio_filename:
|
||||
audio_filename = f"{self.result['music_info']['title']}{audio_ext}"
|
||||
else:
|
||||
audio_filename += audio_ext
|
||||
|
||||
await self._download_file(self.result["music_info"]["play"], audio_filename)
|
||||
self.logger.info(f"Sound - Downloaded and saved sound as {audio_filename}")
|
||||
return audio_filename
|
||||
|
||||
async def download(
|
||||
self, link: Union[str], video_filename: Optional[str] = None, hd: bool = True
|
||||
) -> data:
|
||||
"""
|
||||
Asynchronously downloads a TikTok video or photo post.
|
||||
|
||||
Args:
|
||||
video_filename (Optional[str]): The name of the file for the TikTok video or photo. If None, the file will be named based on the video or photo ID.
|
||||
hd (bool): If True, downloads the video in HD format. Defaults to False.
|
||||
|
||||
Returns:
|
||||
dir_name (str): Directory name
|
||||
media (Union[str, List[str]]): Full list of downloaded media
|
||||
type (str): The type of downloaded objects: Images or video
|
||||
|
||||
Raises:
|
||||
Exception: No downloadable content found in the provided link.
|
||||
|
||||
"""
|
||||
await self._ensure_data(link)
|
||||
if "images" in self.result:
|
||||
self.logger.info("Starting to download images")
|
||||
return await self.__getimages(video_filename)
|
||||
elif "hdplay" in self.result or "play" in self.result:
|
||||
self.logger.info("Starting to download video.")
|
||||
return await self.__getvideo(video_filename, hd)
|
||||
else:
|
||||
self.logger.error("No downloadable content found in the provided link.")
|
||||
raise Exception("No downloadable content found in the provided link.")
|
||||
|
||||
def _get_video_link(self, unique_id: str, aweme_id: str) -> str:
|
||||
@staticmethod
|
||||
def _get_video_link(unique_id: str, aweme_id: str) -> str:
|
||||
return f"https://www.tiktok.com/@{unique_id}/video/{aweme_id}"
|
||||
|
||||
def _get_uploader_link(self, unique_id: str) -> str:
|
||||
@staticmethod
|
||||
def _get_uploader_link(unique_id: str) -> str:
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import ast
|
||||
import astor
|
||||
import requests
|
||||
import base64
|
||||
import zlib
|
||||
import logging
|
||||
import re
|
||||
import urllib.parse
|
||||
import zlib
|
||||
|
||||
import requests
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
@@ -697,7 +696,7 @@ class SecurityAnalyzer:
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.For):
|
||||
for send_method in send_methods:
|
||||
if send_method in astor.to_source(node):
|
||||
if send_method in ast.unparse(node):
|
||||
issue_key = (
|
||||
f"Mass {send_method}",
|
||||
node.lineno,
|
||||
@@ -715,8 +714,8 @@ class SecurityAnalyzer:
|
||||
)
|
||||
self.reported_issues.add(issue_key)
|
||||
|
||||
if "time.sleep(" in astor.to_source(tree):
|
||||
sleep_calls = re.findall(r"time\.sleep\((.*?)\)", astor.to_source(tree))
|
||||
if "time.sleep(" in ast.unparse(tree):
|
||||
sleep_calls = re.findall(r"time\.sleep\((.*?)\)", ast.unparse(tree))
|
||||
for sleep_time in sleep_calls:
|
||||
try:
|
||||
sleep_value = float(sleep_time)
|
||||
|
||||
@@ -24,82 +24,109 @@
|
||||
# meta developer: @hikka_mods
|
||||
# scope: Video2GIF
|
||||
# scope: Video2GIF 0.0.1
|
||||
# requires: moviepy
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Video2GIF(loader.Module):
|
||||
"""Converts video to GIF"""
|
||||
class Video2GIFMod(loader.Module):
|
||||
"""Convert video to high quality GIF"""
|
||||
|
||||
strings = {
|
||||
"name": "Video2GIF",
|
||||
"conversion_success": "🎉 The conversion is completed!",
|
||||
"conversion_error": "❌ An error occurred when converting video to GIF.",
|
||||
"not_video": "⚠️ Please reply to the message with the video or send the video in one message.",
|
||||
"loading": "⏳ Conversion is underway",
|
||||
"success": "✅ GIF created",
|
||||
"error": "❌ Conversion failed",
|
||||
"no_video": "❌ Reply to a video",
|
||||
"no_ffmpeg": "❌ FFmpeg not installed. Install: apt install ffmpeg",
|
||||
"processing": "🔄 Processing video...",
|
||||
"compressing": "📦 Optimizing GIF...",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"conversion_success": "🎉 Преобразование завершено!",
|
||||
"conversion_error": "❌ Произошла ошибка при преобразовании видео в GIF.",
|
||||
"not_video": "⚠️ Пожалуйста, ответьте на сообщение с видео или отправьте видео одним сообщением.",
|
||||
"loading": "⏳ Идет преобразование",
|
||||
"success": "✅ GIF создан",
|
||||
"error": "❌ Ошибка конвертации",
|
||||
"no_video": "❌ Ответьте на видео",
|
||||
"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(
|
||||
ru_doc="[reply | в одном сообщении с видео] — конвертирует видео в GIF.",
|
||||
en_doc="[reply | in one message with video] — Converts video to GIF.",
|
||||
ru_doc="[ответ] [fps] [ширина] - конвертировать видео в GIF",
|
||||
en_doc="[reply] [fps] [width] - convert video to GIF",
|
||||
)
|
||||
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:
|
||||
await utils.answer(message, self.strings["not_video"])
|
||||
return
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.video:
|
||||
return await utils.answer(message, self.strings["no_video"])
|
||||
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
video_path = await self.client.download_media(video)
|
||||
gif_path = f"{os.path.splitext(video_path)[0]}.gif"
|
||||
args = utils.get_args_raw(message).split()
|
||||
fps = 15 if len(args) < 1 else min(int(args[0]), 30)
|
||||
width = 480 if len(args) < 2 else min(int(args[1]), 1024)
|
||||
|
||||
msg = await utils.answer(message, self.strings["processing"])
|
||||
|
||||
try:
|
||||
self.convert_video_to_gif(video_path, gif_path)
|
||||
await message.client.send_file(
|
||||
message.chat_id, gif_path, caption=self.strings["conversion_success"]
|
||||
gif_path = await self._convert_to_gif(reply, fps, width)
|
||||
|
||||
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):
|
||||
"""Получает видео из сообщения."""
|
||||
if reply := await message.get_reply_message():
|
||||
return reply.video
|
||||
return message.video
|
||||
os.remove(gif_path)
|
||||
await msg.delete()
|
||||
|
||||
def convert_video_to_gif(self, video_path: str, gif_path: str) -> None:
|
||||
"""Конвертирует видео в GIF с улучшенными параметрами."""
|
||||
command = [
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
video_path,
|
||||
"-vf",
|
||||
"fps=30,scale=640:-1:flags=lanczos",
|
||||
"-c:v",
|
||||
"gif",
|
||||
gif_path,
|
||||
]
|
||||
subprocess.run(command, check=True)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
|
||||
def cleanup_temp_files(self, video_path: str, gif_path: str) -> None:
|
||||
"""Удаляет временные файлы."""
|
||||
for temp_file in [video_path, gif_path]:
|
||||
if os.path.exists(temp_file):
|
||||
os.remove(temp_file)
|
||||
async def _convert_to_gif(self, reply, fps: int, width: int) -> str:
|
||||
"""Convert video to optimized GIF"""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
video_path = os.path.join(tmpdir, "video.mp4")
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class VirusTotalMod(loader.Module):
|
||||
"""Checks files for viruses using VirusTotal."""
|
||||
"""Professional file scanning with VirusTotal"""
|
||||
|
||||
strings = {
|
||||
"name": "VirusTotal",
|
||||
"no_file": "<emoji document_id=5210952531676504517>🚫</emoji> <b>You haven't selected a file.</b>",
|
||||
"download": "<emoji document_id=5334677912270415274>😑</emoji> <b>Downloading...</b>",
|
||||
"scan": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Scanning...</b>",
|
||||
"link": "🦠 VirusTotal Link",
|
||||
"no_virus": "✅ File is clean.",
|
||||
"error": "<emoji document_id=5463193238393274687>⚠️</emoji> Scan error.",
|
||||
"no_format": "This format is not supported.",
|
||||
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> You have not specified an API Key",
|
||||
"config": "Need a token with www.virustotal.com/gui/my-apikey",
|
||||
"scanning": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Waiting for scan results...</b>",
|
||||
"getting_upload_url": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Getting upload URL...</b>",
|
||||
"analysis_failed": "<emoji document_id=5463193238393274687>⚠️</emoji> Analysis failed after multiple retries.",
|
||||
"no_file": "🚫 Reply to a file",
|
||||
"downloading": "📥 Downloading file...",
|
||||
"uploading": "📤 Uploading to VirusTotal...",
|
||||
"scanning": "🔍 Scanning in progress...",
|
||||
"waiting": "⏳ Waiting for analysis...",
|
||||
"no_key": "🚫 Set VirusTotal API key in config",
|
||||
"error": "❌ Error during scan",
|
||||
"size_limit": "📁 File exceeds 32MB limit",
|
||||
"timeout": "⏰ Scan timeout",
|
||||
"clean": "✅ File is clean",
|
||||
"suspicious": "⚠️ Suspicious file",
|
||||
"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 = {
|
||||
"no_file": "<emoji document_id=5210952531676504517>🚫</emoji> </b>Вы не выбрали файл.</b>",
|
||||
"download": "<emoji document_id=5334677912270415274>😑</emoji> </b>Скачивание...</b>",
|
||||
"scan": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Сканирую...</b>",
|
||||
"link": "🦠 Ссылка на VirusTotal",
|
||||
"no_virus": "✅ Файл чист.",
|
||||
"error": "<emoji document_id=5463193238393274687>⚠️</emoji> Ошибка сканирования.",
|
||||
"no_format": "Этот формат не поддерживается.",
|
||||
"no_apikey": "<emoji document_id=5260342697075416641>🚫</emoji> Вы не указали Api Key",
|
||||
"config": "Need a token with www.virustotal.com/gui/my-apikey",
|
||||
"scanning": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Ожидание результатов сканирования...</b>",
|
||||
"getting_upload_url": "<emoji document_id=5325792861885570739>🫥</emoji> <b>Получение URL для загрузки...</b>",
|
||||
"analysis_failed": "<emoji document_id=5463193238393274687>⚠️</emoji> Анализ не удался после нескольких попыток.",
|
||||
"no_file": "🚫 Ответьте на файл",
|
||||
"downloading": "📥 Скачиваю файл...",
|
||||
"uploading": "📤 Загружаю на VirusTotal...",
|
||||
"scanning": "🔍 Сканирую...",
|
||||
"waiting": "⏳ Жду анализа...",
|
||||
"no_key": "🚫 Укажите API ключ в конфиге",
|
||||
"error": "❌ Ошибка при сканировании",
|
||||
"size_limit": "📁 Файл больше 32МБ",
|
||||
"timeout": "⏰ Таймаут сканирования",
|
||||
"clean": "✅ Файл чистый",
|
||||
"suspicious": "⚠️ Подозрительный файл",
|
||||
"malicious": "⛔ Вредоносный файл",
|
||||
"view_report": "📊 Полный отчёт",
|
||||
"close": "❌ Закрыть",
|
||||
"engines": "Антивирусов",
|
||||
"detections": "Обнаружено",
|
||||
"status": "Статус",
|
||||
"completed": "Завершён",
|
||||
"queued": "В очереди",
|
||||
"scan_date": "Дата сканирования",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"token-vt",
|
||||
"api_key",
|
||||
None,
|
||||
lambda: "Need a token with www.virustotal.com/gui/my-apikey",
|
||||
"VirusTotal API key from https://virustotal.com",
|
||||
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):
|
||||
self.hmodslib = await self.import_lib(
|
||||
"https://files.archquise.ru/HModsLibrary.py"
|
||||
)
|
||||
self._client = client
|
||||
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(
|
||||
ru_doc="<ответ на файл> - Проверяет файлы на наличие вирусов с использованием VirusTotal",
|
||||
en_doc="<file response> - Checks files for viruses using VirusTotal",
|
||||
ru_doc="[ответ] - просканировать файл через VirusTotal",
|
||||
en_doc="[reply] - scan file with VirusTotal",
|
||||
)
|
||||
async def vt(self, message):
|
||||
if not message.is_reply:
|
||||
await utils.answer(message, self.strings("no_file"))
|
||||
return
|
||||
"""Scan file with VirusTotal"""
|
||||
api_key = self.config["api_key"]
|
||||
if not api_key:
|
||||
return await utils.answer(message, self.strings["no_key"])
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
if not reply.document:
|
||||
await utils.answer(message, self.strings("no_file"))
|
||||
return
|
||||
if not reply or not reply.document:
|
||||
return await utils.answer(message, self.strings["no_file"])
|
||||
|
||||
api_key = self.config.get("token-vt")
|
||||
if not api_key:
|
||||
await utils.answer(message, self.strings("no_apikey"))
|
||||
return
|
||||
async with self._get_session() as session:
|
||||
try:
|
||||
msg = await utils.answer(message, self.strings["downloading"])
|
||||
|
||||
file_extension = os.path.splitext(reply.file.name)[1].lower()
|
||||
allowed_extensions = (".jpg", ".png", ".ico", ".mp3", ".mp4", ".gif", ".txt")
|
||||
if file_extension in allowed_extensions:
|
||||
await utils.answer(message, self.strings("no_format"))
|
||||
return
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
file_path = os.path.join(tmpdir, reply.file.name)
|
||||
await reply.download_media(file_path)
|
||||
|
||||
try:
|
||||
await utils.answer(message, self.strings("download"))
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
file_path = os.path.join(temp_dir, reply.file.name)
|
||||
await reply.download_media(file_path)
|
||||
file_size = os.path.getsize(file_path)
|
||||
if file_size > self.MAX_SIZE:
|
||||
return await msg.edit(self.strings["size_limit"])
|
||||
|
||||
file_size = os.path.getsize(file_path)
|
||||
is_large_file = file_size > 32 * 1024 * 1024
|
||||
await msg.edit(self.strings["uploading"])
|
||||
analysis_id = await self._upload_file(session, file_path)
|
||||
|
||||
if is_large_file:
|
||||
await utils.answer(message, self.strings("getting_upload_url"))
|
||||
await utils.answer(message, self.strings("scan"))
|
||||
await msg.edit(self.strings["waiting"])
|
||||
result = await self._wait_for_analysis(session, analysis_id)
|
||||
|
||||
analysis_results = await self.hmodslib.scan_file_virustotal(
|
||||
file_path, api_key, is_large_file
|
||||
)
|
||||
await self._show_results(msg, analysis_id, result)
|
||||
|
||||
if analysis_results:
|
||||
formatted_results = self.hmodslib.format_analysis_results(
|
||||
analysis_results
|
||||
)
|
||||
try:
|
||||
await self.inline.form(
|
||||
text=formatted_results["text"],
|
||||
message=message,
|
||||
reply_markup={
|
||||
"text": self.strings("link"),
|
||||
"url": formatted_results["url"],
|
||||
}
|
||||
if formatted_results["url"]
|
||||
else None,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error displaying inline results: {e}")
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("error_report").format(
|
||||
formatted_results["url"]
|
||||
),
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
await utils.answer(message, self.strings["timeout"])
|
||||
except Exception as e:
|
||||
error_text = f"{self.strings['error']}: {str(e)[:100]}"
|
||||
await utils.answer(message, error_text)
|
||||
|
||||
else:
|
||||
await utils.answer(message, self.strings("analysis_failed"))
|
||||
async def _upload_file(self, session: aiohttp.ClientSession, path: str) -> str:
|
||||
"""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:
|
||||
logger.exception("An error occurred during the VT scan process.")
|
||||
await utils.answer(
|
||||
message, self.strings("error") + f"\n\n{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
async with session.post(
|
||||
"https://www.virustotal.com/api/v3/files", data=form
|
||||
) as response:
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class VoiceDL(loader.Module):
|
||||
"""Voice Downloader module"""
|
||||
class VoiceDLMod(loader.Module):
|
||||
"""Download voice messages as MP3"""
|
||||
|
||||
strings = {
|
||||
"name": "VoiceDL",
|
||||
"download_success": "Voice message downloaded in MP3 format.",
|
||||
"download_error": "Error downloading voice message.",
|
||||
"no_voice_message": "Please reply to a voice message.",
|
||||
"conversion_error": "Error converting to MP3.",
|
||||
"file_not_found": "File not found.",
|
||||
"unsupported_format": "The file format is not supported.",
|
||||
"success": "✅ Voice downloaded as MP3",
|
||||
"error": "❌ Error downloading voice",
|
||||
"no_voice": "❌ Reply to a voice message",
|
||||
"no_ffmpeg": "❌ FFmpeg not found. Install: apt install ffmpeg",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"download_success": "Голосовое сообщение загружено в формате MP3.",
|
||||
"download_error": "Ошибка при загрузке голосового сообщения.",
|
||||
"no_voice_message": "Пожалуйста, ответьте на голосовое сообщение.",
|
||||
"conversion_error": "Ошибка при конвертации в MP3.",
|
||||
"file_not_found": "Файл не найден.",
|
||||
"unsupported_format": "Формат файла не поддерживается.",
|
||||
"success": "✅ Голосовое скачано как MP3",
|
||||
"error": "❌ Ошибка скачивания",
|
||||
"no_voice": "❌ Ответьте на голосовое",
|
||||
"no_ffmpeg": "❌ FFmpeg не установлен. Установите: apt install ffmpeg",
|
||||
}
|
||||
|
||||
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(
|
||||
ru_doc=" [reply] — загружает выбранное голосовое сообщение в виде файла mp3 и кидает его в чат.",
|
||||
en_doc=" [reply] — downloads the selected voice message as an MP3 file and sends it in the chat.",
|
||||
ru_doc="[ответ] - скачать голосовое как MP3",
|
||||
en_doc="[reply] - download voice as MP3",
|
||||
)
|
||||
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()
|
||||
if not reply or not reply.voice:
|
||||
return await utils.answer(message, self.strings["no_voice"])
|
||||
|
||||
if reply:
|
||||
if reply.voice:
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, suffix=".ogg"
|
||||
) as temp_voice_file:
|
||||
voice_file_path = temp_voice_file.name
|
||||
await message.client.download_file(reply.voice, voice_file_path)
|
||||
await self._process_voice(message, reply)
|
||||
|
||||
timestamp = int(time.time())
|
||||
mp3_file_path = f"voice_message_{timestamp}.mp3"
|
||||
async def _process_voice(self, message, reply):
|
||||
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(
|
||||
message.chat.id,
|
||||
mp3_file_path,
|
||||
caption=self.strings("download_success"),
|
||||
)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
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)
|
||||
os.remove(mp3_file_path)
|
||||
if proc.returncode != 0:
|
||||
raise Exception("FFmpeg error")
|
||||
|
||||
except FileNotFoundError:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("download_error")
|
||||
+ " "
|
||||
+ self.strings("file_not_found"),
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("conversion_error") + f" {e.stderr.decode()}",
|
||||
)
|
||||
except Exception as e:
|
||||
await utils.answer(
|
||||
message, self.strings("download_error") + f" {str(e)}"
|
||||
)
|
||||
else:
|
||||
await utils.answer(message, self.strings("no_voice_message"))
|
||||
else:
|
||||
await utils.answer(message, self.strings("no_voice_message"))
|
||||
await message.client.send_file(
|
||||
message.chat.id,
|
||||
mp3_path,
|
||||
caption=self.strings["success"],
|
||||
reply_to=reply.id,
|
||||
)
|
||||
|
||||
async def convert_to_mp3(self, input_file: str, output_file: str):
|
||||
"""Convert audio file to MP3 format using FFmpeg."""
|
||||
command = ["ffmpeg", "-i", input_file, output_file]
|
||||
process = subprocess.run(
|
||||
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(
|
||||
process.returncode,
|
||||
command,
|
||||
output=process.stdout,
|
||||
stderr=process.stderr,
|
||||
)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Union, Dict, List
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Union
|
||||
|
||||
import requests
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@@ -28,109 +28,108 @@
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
|
||||
from .. import loader, utils
|
||||
import aiohttp
|
||||
|
||||
from .. import loader
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class WindowsKeys(loader.Module):
|
||||
"""Provides you Windows activation keys"""
|
||||
class WindowsKeysMod(loader.Module):
|
||||
"""Windows activation keys"""
|
||||
|
||||
strings = {
|
||||
"name": "WindowsKeys",
|
||||
"winkey": "✅ Your key: <code>{}</code>\n\n⚠ Warning! This key is not a pirate key. It is taken from the official Microsoft site and is intended for further activation via KMS-server",
|
||||
"error": "❌ An error occurred while retrieving the key. Please try again later.",
|
||||
"winkey": "✅ Key: <code>{}</code>\n\n⚠ For KMS activation only",
|
||||
"error": "❌ Failed to get key",
|
||||
"select": "🔓 Select version:",
|
||||
"close": "🎈 Close",
|
||||
"loading": "⌛ Loading...",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"winkey": "✅ Ваш ключ: <code>{}</code>\n\n⚠ Внимание! Указанный ключ не является пиратским. Он взят с официального сайта Microsoft и предназначен для дальнейшей активации посредством KMS-сервера",
|
||||
"error": "❌ Произошла ошибка при получении ключа. Попробуйте позже.",
|
||||
"winkey": "✅ Ключ: <code>{}</code>\n\n⚠ Только для KMS активации",
|
||||
"error": "❌ Ошибка получения",
|
||||
"select": "🔓 Выберите версию:",
|
||||
"close": "🎈 Закрыть",
|
||||
"loading": "⌛ Загрузка...",
|
||||
}
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Открывает выбор ключа для активации Windows",
|
||||
en_doc="Opens the Windows activation key selection",
|
||||
)
|
||||
def __init__(self):
|
||||
self.cache = None
|
||||
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):
|
||||
await self.inline.form(
|
||||
text="🔓 Выберите версию и издание Windows, для которой вам необходим ключ",
|
||||
self.strings["select"],
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": "Windows 10/11 Pro",
|
||||
"callback": self._inline__give_key,
|
||||
"args": ["win10_11pro"],
|
||||
"text": "Win 10/11 Pro",
|
||||
"callback": self._key,
|
||||
"args": ("win10_11pro",),
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "Windows 10/11 Enterprise LTSC",
|
||||
"callback": self._inline__give_key,
|
||||
"args": ["win10_11enterpriseLTSC"],
|
||||
"text": "Win 10/11 LTSC",
|
||||
"callback": self._key,
|
||||
"args": ("win10_11enterpriseLTSC",),
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "Windows 8.1 Pro",
|
||||
"callback": self._inline__give_key,
|
||||
"args": ["win8.1pro"],
|
||||
"text": "Win 8.1 Pro",
|
||||
"callback": self._key,
|
||||
"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",
|
||||
"callback": self._inline__give_key,
|
||||
"args": ["win8pro"],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "Windows 7 Pro",
|
||||
"callback": self._inline__give_key,
|
||||
"args": ["win7pro"],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "Windows Vista Business",
|
||||
"callback": self._inline__give_key,
|
||||
"args": ["winvistabusiness"],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "🎈 Закрыть",
|
||||
"action": "close",
|
||||
"text": "Vista Business",
|
||||
"callback": self._key,
|
||||
"args": ("winvistabusiness",),
|
||||
}
|
||||
],
|
||||
[{"text": self.strings["close"], "action": "close"}],
|
||||
],
|
||||
force_me=False,
|
||||
silent=True,
|
||||
)
|
||||
|
||||
async def _inline__give_key(self, call, winver):
|
||||
url = "https://files.archquise.ru/winkeys.json"
|
||||
async def _key(self, call, version):
|
||||
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:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
await call.edit(self.strings["winkey"].format(data[winver]))
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error("Request error: %e", e)
|
||||
await call.answer(self.strings("error"), show_alert=True)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("JSON decode error: %e", e)
|
||||
await call.answer(self.strings("error"), show_alert=True)
|
||||
except KeyError as e:
|
||||
logger.error("Key error: %e", e)
|
||||
await call.answer(self.strings("error"), show_alert=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("An unexpected error occurred: %e", e)
|
||||
await call.answer(self.strings("error"), show_alert=True)
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(10)
|
||||
) as session:
|
||||
async with session.get("https://files.archquise.ru/winkeys.json") as r:
|
||||
self.cache = await r.json()
|
||||
self.cache_time = time.time()
|
||||
return self.cache
|
||||
except Exception: # noqa: E722
|
||||
return None
|
||||
|
||||
@@ -27,10 +27,13 @@
|
||||
# requires: requests
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class animals(loader.Module):
|
||||
@@ -38,13 +41,13 @@ class animals(loader.Module):
|
||||
|
||||
strings = {
|
||||
"name": "animals",
|
||||
"loading": "<b>Generation is underway</b>",
|
||||
"done": "<b>Here is your salute</b>",
|
||||
"loading": "<b>Generation is underway</b> <emoji document_id=5215484787325676090>🕐</emoji>",
|
||||
"done": "<b>Here is your salute</b> <emoji document_id=5436246187944460315>❤️</emoji>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"loading": "<b>Генерация идет полным ходом</b>",
|
||||
"done": "<b>Вот ваш результат</b>",
|
||||
"loading": "<b>Генерация идет полным ходом</b> <emoji document_id=5215484787325676090>🕐</emoji>",
|
||||
"done": "<b>Вот ваш результат</b> <emoji document_id=5436246187944460315>❤️</emoji>",
|
||||
}
|
||||
|
||||
# thanks https://github.com/C0dwiz/H.Modules/pull/1
|
||||
@@ -58,7 +61,7 @@ class animals(loader.Module):
|
||||
)
|
||||
async def fcatcmd(self, message):
|
||||
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(
|
||||
message, cat_url, self.strings("done"), force_document=True
|
||||
)
|
||||
@@ -80,7 +83,7 @@ class animals(loader.Module):
|
||||
)
|
||||
async def catcmd(self, message):
|
||||
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(
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
import re
|
||||
import random
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
class face(loader.Module):
|
||||
@@ -64,15 +69,16 @@ class face(loader.Module):
|
||||
async def rfacecmd(self, message):
|
||||
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 session.get(url) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
random_face = data["data"]
|
||||
data = await response.text()
|
||||
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(
|
||||
message, self.strings("random_face").format(random_face)
|
||||
message, self.strings("random_face").format(kaomoji)
|
||||
)
|
||||
else:
|
||||
await utils.answer(message, self.strings("error"))
|
||||
|
||||
@@ -1,47 +1,54 @@
|
||||
ASCIIArt
|
||||
AccountData
|
||||
AniLibria
|
||||
animals
|
||||
AniLiberty
|
||||
AnimeQuotes
|
||||
Article
|
||||
ASCIIArt
|
||||
AutofarmCookies
|
||||
BirthdayTime
|
||||
CheckSpamBan
|
||||
CodeShare
|
||||
CryptoCurrency
|
||||
EnvsSH
|
||||
face
|
||||
FakeActions
|
||||
FakeWallet
|
||||
full
|
||||
FolderAutoRead
|
||||
GigaChat
|
||||
globalrestrict
|
||||
hikkahost
|
||||
H
|
||||
HAFK
|
||||
HInstall
|
||||
InfoBannersManager
|
||||
InlineButton
|
||||
InlineCoin
|
||||
InlineHelper
|
||||
IrisSimpleMod
|
||||
jacques
|
||||
KBSwapper
|
||||
Memes
|
||||
MessageMonitor
|
||||
MooFarmRC1
|
||||
Music
|
||||
novoice
|
||||
nsfwart
|
||||
numbersapi
|
||||
PastebinAPI
|
||||
profile
|
||||
ReplaceVowels
|
||||
SafetyMod
|
||||
search
|
||||
shortener
|
||||
SMAcrhiver
|
||||
TaskManager
|
||||
TelegramStatusCodes
|
||||
TempChat
|
||||
Text2File
|
||||
Text_Sticker
|
||||
TikTokDownloader
|
||||
TimedEmojiStatus
|
||||
UserbotAvast
|
||||
Video2GIF
|
||||
VirusTotal
|
||||
VoiceDL
|
||||
HAFK
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import typing
|
||||
@@ -51,6 +52,8 @@ from telethon.tl.types import (
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
BANNED_RIGHTS = {
|
||||
"view_messages": False,
|
||||
"send_messages": False,
|
||||
|
||||
@@ -26,12 +26,15 @@
|
||||
# scope: api HikkaHost 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import aiohttp
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HostApi:
|
||||
"""
|
||||
@@ -55,7 +58,7 @@ class HostApi:
|
||||
Returns:
|
||||
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 session.request(
|
||||
method,
|
||||
@@ -311,4 +314,4 @@ class HikkahostMod(loader.Module):
|
||||
user_id = token.split(":")[0]
|
||||
api = HostApi(token)
|
||||
|
||||
data = await api.action(user_id, token)
|
||||
await api.action(user_id, token)
|
||||
|
||||
@@ -27,13 +27,16 @@
|
||||
# scope: Жаконизатор 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import aiohttp
|
||||
import io
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import logging
|
||||
from textwrap import wrap
|
||||
|
||||
import aiohttp
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@loader.tds
|
||||
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
|
||||
|
||||
logger = logging.INFO(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
|
||||
@@ -26,33 +26,15 @@
|
||||
# scope: Api NSFWArt 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import functools
|
||||
import requests
|
||||
from typing import List
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
async def photos(tags: str, subreddit: str, quantity: int) -> List[str]:
|
||||
ans = (
|
||||
await utils.run_sync(
|
||||
requests.get,
|
||||
f"https://api.lolicon.app/setu/v2?tag={tags}",
|
||||
json={
|
||||
"query": (
|
||||
" query SubredditQuery( $url: String! $filter: SubredditPostFilter"
|
||||
" $iterator: String ) { getSubreddit(url: $url) { children("
|
||||
f" limit: {quantity} iterator: $iterator filter: $filter"
|
||||
" disabledHosts: null ) { iterator items {url subredditTitle"
|
||||
" isNsfw mediaSources { url } } } } } "
|
||||
),
|
||||
"variables": {"url": subreddit, "filter": None, "hostsDown": None},
|
||||
"authorization": None,
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
return [ans["data"][0]["urls"]["original"]]
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
@@ -61,7 +43,17 @@ class NSFWArtMod(loader.Module):
|
||||
|
||||
strings = {
|
||||
"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):
|
||||
@@ -69,29 +61,75 @@ class NSFWArtMod(loader.Module):
|
||||
loader.ConfigValue(
|
||||
"tags",
|
||||
"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(
|
||||
ru_doc="Отправьте симпатичный nsfw-арт",
|
||||
en_doc="Send cute nsfw-art",
|
||||
ru_doc="Отправить симпатичный NSFW-арт",
|
||||
en_doc="Send cute NSFW-art",
|
||||
)
|
||||
async def nsfwartcmd(self, message):
|
||||
"""Send NSFW art based on configured tags"""
|
||||
tags = self.config["tags"]
|
||||
subreddit = f"/v2?tag={tags}"
|
||||
|
||||
ans = await utils.run_sync(
|
||||
requests.get, f"https://api.lolicon.app/setu{subreddit}"
|
||||
)
|
||||
if ans.status_code != 200:
|
||||
await utils.answer(message, self.strings("sreddit404", message))
|
||||
if not tags:
|
||||
await utils.answer(message, self.strings("no_results"))
|
||||
return
|
||||
|
||||
await self.inline.gallery(
|
||||
message=message,
|
||||
next_handler=functools.partial(
|
||||
photos, tags, subreddit=subreddit, quantity=15
|
||||
),
|
||||
caption=f"<i>{utils.ascii_face()}</i>",
|
||||
)
|
||||
await utils.answer(message, self.strings("fetching"))
|
||||
|
||||
try:
|
||||
photos = await self._fetch_photos(tags)
|
||||
if not photos:
|
||||
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
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
import aiohttp
|
||||
from datetime import datetime
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
async def get_fact_about_number(number, fact_type):
|
||||
url = f"http://numbersapi.com/{number}/{fact_type}"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
if response.status == 200:
|
||||
return await response.text()
|
||||
else:
|
||||
return "Извините, не удалось получить факт."
|
||||
|
||||
|
||||
async def get_fact_about_date(month, day):
|
||||
date_str = datetime.now().replace(month=month, day=day).strftime("%m/%d")
|
||||
url = f"http://numbersapi.com/{date_str}/date"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
if response.status == 200:
|
||||
return await response.text()
|
||||
else:
|
||||
return "Извините, не удалось получить факт."
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class NumbersAPI(loader.Module):
|
||||
"""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(
|
||||
ru_doc="Дает интересный факт про число или дату\nНапример: .num 10 math или .num 01.01 date",
|
||||
en_doc="Gives an interesting fact about a number or date\nexample: .num 10 math or .num 01.01 date",
|
||||
)
|
||||
async def num(self, message):
|
||||
args = utils.get_args_raw(message).split()
|
||||
|
||||
if len(args) < 2:
|
||||
await utils.answer(message, "Использование: .num <число или дата> <тип>")
|
||||
"""Get interesting fact about number or date"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("usage"))
|
||||
return
|
||||
|
||||
num_or_date = args[0]
|
||||
fact_type = args[1]
|
||||
parts = args.split(maxsplit=1)
|
||||
if len(parts) < 2:
|
||||
await utils.answer(message, self.strings("usage"))
|
||||
return
|
||||
|
||||
if "." in num_or_date:
|
||||
try:
|
||||
month, day = map(int, num_or_date.split("."))
|
||||
result = await get_fact_about_date(month, day)
|
||||
except ValueError:
|
||||
await utils.answer(
|
||||
message, "Ошибка: некорректный формат даты. Используйте: месяц.день"
|
||||
)
|
||||
input_value = parts[0].strip()
|
||||
fact_type = parts[1].strip().lower()
|
||||
|
||||
if fact_type not in self.valid_fact_types:
|
||||
await utils.answer(message, self.strings("error_invalid_type"))
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
month, day = date_parts
|
||||
result = await self._get_date_fact(month, day)
|
||||
else:
|
||||
try:
|
||||
number = int(num_or_date)
|
||||
result = await get_fact_about_number(number, fact_type)
|
||||
except ValueError:
|
||||
await utils.answer(message, "Ошибка: некорректный ввод числа.")
|
||||
return
|
||||
number = self._parse_number(input_value)
|
||||
if number is None:
|
||||
await utils.answer(message, self.strings("error_number_format"))
|
||||
return
|
||||
|
||||
result = await self._get_number_fact(number, fact_type)
|
||||
|
||||
await utils.answer(message, result)
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
# For any inquiries or requests for permissions, please contact codwiz@yandex.ru.
|
||||
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: SafetyMod
|
||||
# Description: generate random password
|
||||
# Name: PassgenMod
|
||||
# Description: Generates random password
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
@@ -26,11 +26,13 @@
|
||||
# scope: Api SafetyMod 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
import logging
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def generate_password(
|
||||
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")
|
||||
|
||||
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
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SafetyMod(loader.Module):
|
||||
class PassgenMod(loader.Module):
|
||||
"""generate random password"""
|
||||
|
||||
strings = {
|
||||
"name": "Safety",
|
||||
"name": "Passgen",
|
||||
"pass": "<emoji document_id=5472287483318245416>*⃣</emoji> <b>Here is your secure password:</b> <code>{}</code>",
|
||||
}
|
||||
strings_ru = {
|
||||
@@ -26,10 +26,16 @@
|
||||
# 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 .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class ProfileEditorMod(loader.Module):
|
||||
@@ -37,61 +43,138 @@ class ProfileEditorMod(loader.Module):
|
||||
|
||||
strings = {
|
||||
"name": "Profile",
|
||||
"error_format": "Incorrect format of args. Try again.",
|
||||
"done_name": "The new name was successfully unstalled!",
|
||||
"done_bio": "The new bio was successfully unstaled!",
|
||||
"done_username": "The new username was succesfully installed!",
|
||||
"error_occupied": "The new username is already occupied!",
|
||||
"error_format": "<emoji document_id=5854929766146118183>❌</emoji> Incorrect format. Try again.",
|
||||
"done_name": "<emoji document_id=5854762571659218443>✅</emoji> Name successfully updated!",
|
||||
"done_bio": "<emoji document_id=5854762571659218443>✅</emoji> Bio successfully updated!",
|
||||
"done_username": "<emoji document_id=5854762571659218443>✅</emoji> Username successfully updated!",
|
||||
"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 = {
|
||||
"error_format": "Неправильный формат аргумента. Попробуйте еще раз.",
|
||||
"done_name": "Новое имя успешно настроено!",
|
||||
"done_bio": "Новое био успешно настроено!",
|
||||
"done_username": "Новое имя пользователя успешно установлено!",
|
||||
"error_occupied": "Новое имя пользователя уже занято!",
|
||||
"error_format": "<emoji document_id=5854929766146118183>❌</emoji> Неверный формат. Попробуйте еще раз.",
|
||||
"done_name": "<emoji document_id=5854762571659218443>✅</emoji> Имя успешно обновлено!",
|
||||
"done_bio": "<emoji document_id=5854762571659218443>✅</emoji> Био успешно обновлено!",
|
||||
"done_username": "<emoji document_id=5854762571659218443>✅</emoji> Имя пользователя успешно обновлено!",
|
||||
"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(
|
||||
ru_doc="для того, чтобы сменить свое имя/отчество",
|
||||
en_doc="for change your first/second name",
|
||||
)
|
||||
async def namecmd(self, message):
|
||||
args = utils.get_args_raw(message).split("/")
|
||||
|
||||
if len(args) < 1 or len(args) > 2:
|
||||
return await utils.answer(message, self.strings("error_format"))
|
||||
|
||||
firstname = args[0]
|
||||
lastname = args[1] if len(args) == 2 else ""
|
||||
|
||||
await message.client(
|
||||
UpdateProfileRequest(first_name=firstname, last_name=lastname)
|
||||
)
|
||||
await utils.answer(message, self.strings("done_name"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="чтобы изменить свою биографию",
|
||||
en_doc="for change your bio",
|
||||
)
|
||||
async def aboutcmd(self, message):
|
||||
"""Change first name and last name"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings("error_format"))
|
||||
await message.client(UpdateProfileRequest(about=args))
|
||||
await utils.answer(message, self.strings("done_bio"))
|
||||
|
||||
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(
|
||||
ru_doc="для изменения вашего имени пользователя. Введите значение без '@'",
|
||||
en_doc="for change your username. Enter value without '@'",
|
||||
)
|
||||
async def usercmd(self, message):
|
||||
"""- for change your username. Enter value without "@"."""
|
||||
"""Change username"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
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:
|
||||
await message.client(UpdateUsernameRequest(args))
|
||||
await message.client(UpdateUsernameRequest(username))
|
||||
await utils.answer(message, self.strings("done_username"))
|
||||
except UsernameOccupiedError:
|
||||
await utils.answer(message, self.strings("error_occupied"))
|
||||
except Exception as e:
|
||||
await self._handle_error(message, e)
|
||||
|
||||
@@ -26,8 +26,13 @@
|
||||
# scope: Api Search 0.0.1
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import urllib.parse
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Search(loader.Module):
|
||||
@@ -39,6 +44,7 @@ class Search(loader.Module):
|
||||
"isearch": "🔎<b> I searched for information for you</b> ",
|
||||
"link": "🗂️ Link to your request",
|
||||
"close": "❌ Close",
|
||||
"no_query": "<emoji document_id=5854929766146118183>❌</emoji> Please provide a search query.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
@@ -46,160 +52,136 @@ class Search(loader.Module):
|
||||
"isearch": "🔎<b> Я поискал информацию за тебя</b> ",
|
||||
"link": "🗂️ Ссылка на ваш запрос",
|
||||
"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(
|
||||
ru_doc="Поискать в Google",
|
||||
en_doc="Search on Google",
|
||||
)
|
||||
async def google(self, message):
|
||||
await self.search_engine(message, "https://google.com/search?q=")
|
||||
await self._search_command(message, "google")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Yandex",
|
||||
en_doc="Search on Yandex",
|
||||
)
|
||||
async def yandex(self, message):
|
||||
await self.search_engine(message, "https://yandex.ru/?q=")
|
||||
await self._search_command(message, "yandex")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Duckduckgo",
|
||||
en_doc="Search on Duckduckgo",
|
||||
)
|
||||
async def duckduckgo(self, message):
|
||||
await self.search_engine(message, "https://duckduckgo.com/?q=")
|
||||
await self._search_command(message, "duckduckgo")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Bing",
|
||||
en_doc="Search on Bing",
|
||||
)
|
||||
async def bing(self, message):
|
||||
await self.search_engine(message, "https://bing.com/?q=")
|
||||
await self._search_command(message, "bing")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в You",
|
||||
en_doc="Search on You",
|
||||
)
|
||||
async def you(self, message):
|
||||
await self.search_engine(message, "https://you.com/?q=")
|
||||
|
||||
async def search_engine(self, message, base_url: str) -> None:
|
||||
"""Searches on a given search engine."""
|
||||
query = utils.get_args_raw(message)
|
||||
search_url = f"{base_url}{query}"
|
||||
await utils.answer(
|
||||
message, self.strings("search") + f": <a href={search_url}>link</a>"
|
||||
)
|
||||
await self._search_command(message, "you")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Google инлайн",
|
||||
en_doc="Search on Google inline",
|
||||
)
|
||||
async def igoogle(self, message):
|
||||
g = utils.get_args_raw(message)
|
||||
google = f"https://google.com/search?q={g}"
|
||||
await self.inline.form(
|
||||
text=self.strings("isearch"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("link"),
|
||||
"url": google,
|
||||
}
|
||||
],
|
||||
[{"text": self.strings("close"), "action": "close"}],
|
||||
],
|
||||
silent=True,
|
||||
)
|
||||
await self._search_command(message, "google", inline=True)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Yandex инлайн",
|
||||
en_doc="Search on Yandex inline",
|
||||
)
|
||||
async def iyandex(self, message):
|
||||
y = utils.get_args_raw(message)
|
||||
yandex = f"https://yandex.ru/?q={y}"
|
||||
await self.inline.form(
|
||||
text=self.strings("isearch"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("link"),
|
||||
"url": yandex,
|
||||
}
|
||||
],
|
||||
[{"text": self.strings("close"), "action": "close"}],
|
||||
],
|
||||
silent=True,
|
||||
)
|
||||
await self._search_command(message, "yandex", inline=True)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Duckduckgo инлайн",
|
||||
en_doc="Search on Duckduckgo inline",
|
||||
)
|
||||
async def iduckduckgo(self, message):
|
||||
d = utils.get_args_raw(message)
|
||||
duckduckgo = f"https://duckduckgo.com/?q={d}"
|
||||
await self.inline.form(
|
||||
text=self.strings("isearch"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("link"),
|
||||
"url": duckduckgo,
|
||||
}
|
||||
],
|
||||
[{"text": self.strings("close"), "action": "close"}],
|
||||
],
|
||||
silent=True,
|
||||
)
|
||||
await self._search_command(message, "duckduckgo", inline=True)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в Bing инлайн",
|
||||
en_doc="Search on Bing inline",
|
||||
)
|
||||
async def ibing(self, message):
|
||||
b = utils.get_args_raw(message)
|
||||
bing = f"https://bing.com/?q={b}"
|
||||
await self.inline.form(
|
||||
text=self.strings("isearch"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("link"),
|
||||
"url": bing,
|
||||
}
|
||||
],
|
||||
[{"text": self.strings("close"), "action": "close"}],
|
||||
],
|
||||
silent=True,
|
||||
)
|
||||
await self._search_command(message, "bing", inline=True)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Поискать в You инлайн",
|
||||
en_doc="Search on You inline",
|
||||
)
|
||||
async def iyou(self, message):
|
||||
y = utils.get_args_raw(message)
|
||||
you = f"https://you.com/?q={y}"
|
||||
await self.inline.form(
|
||||
text=self.strings("isearch"),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings("link"),
|
||||
"url": you,
|
||||
}
|
||||
],
|
||||
[{"text": self.strings("close"), "action": "close"}],
|
||||
],
|
||||
silent=True,
|
||||
)
|
||||
await self._search_command(message, "you", inline=True)
|
||||
|
||||
async def close(self, call):
|
||||
"""Callback button"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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:
|
||||
|
||||
@@ -14,39 +14,50 @@
|
||||
|
||||
# 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
|
||||
# Description: shortening the link
|
||||
# Description: Module for using bit.ly API
|
||||
# Author: @hikka_mods
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @hikka_mods
|
||||
# scope: Shortener
|
||||
# scope: Shortener 0.0.1
|
||||
# requires: pyshorteners
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import pyshorteners
|
||||
import logging
|
||||
import re
|
||||
import aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class Shortener(loader.Module):
|
||||
"""Module for working with the api bit.ly"""
|
||||
"""Module for using bit.ly API"""
|
||||
|
||||
strings = {
|
||||
"name": "Shortener",
|
||||
"no_api": "<emoji document_id=5854929766146118183>❌</emoji> You have not specified an API token from the site <a href='https://app.bitly.com/settings/api/'>bit.ly</a>",
|
||||
"statclcmd": "<emoji document_id=5787384838411522455>📊</emoji> <b>Statistics on clicks for this link:</b> {c}",
|
||||
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Your shortened link is ready:</b> <code>{c}</code>",
|
||||
"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 = {
|
||||
"no_api": "<emoji document_id=5854929766146118183>❌</emoji> Вы не указали api токен с сайта <a href='https://app.bitly.com/settings/api/'>bit.ly</a>",
|
||||
"statclcmd": "<emoji document_id=5787384838411522455>📊</emoji> <b>Статистика о переходе по этой ссылке:</b> {c}",
|
||||
"shortencmd": "<emoji document_id=5854762571659218443>✅</emoji> <b>Ваша сокращённая ссылка готова:</b> <code>{c}</code>",
|
||||
"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):
|
||||
@@ -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(
|
||||
ru_doc="Сократить ссылку через bit.ly",
|
||||
en_doc="Shorten the link via bit.ly",
|
||||
ru_doc="Сократить ссылку через bit.ly (ссылка с https://)",
|
||||
en_doc="Shorten the link via bit.ly (url with https://)",
|
||||
)
|
||||
async def shortencmd(self, message):
|
||||
"""Shorten URL using bit.ly API"""
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_api"))
|
||||
return
|
||||
|
||||
s = pyshorteners.Shortener(api_key=self.config["token"])
|
||||
args = utils.get_args_raw(message)
|
||||
await utils.answer(
|
||||
message, self.strings("shortencmd").format(c=s.bitly.short(args))
|
||||
)
|
||||
if not 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(
|
||||
ru_doc="Посмотреть статистику ссылки через bit.ly",
|
||||
en_doc="View link statistics via bit.ly",
|
||||
ru_doc="Посмотреть статистику ссылки через bit.ly (ссылка без https:// | Доступно только на платных аккаунтах)",
|
||||
en_doc="View link statistics via bit.ly (link without https:// | Works only on paid accounts)",
|
||||
)
|
||||
async def statclcmd(self, message):
|
||||
"""Get click statistics for shortened URL"""
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_api"))
|
||||
return
|
||||
|
||||
s = pyshorteners.Shortener(api_key=self.config["token"])
|
||||
args = utils.get_args_raw(message)
|
||||
await utils.answer(
|
||||
message, self.strings("statclcmd").format(c=s.bitly.total_clicks(args))
|
||||
)
|
||||
if not 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
|
||||
point
|
||||
deviceinfo
|
||||
mpi
|
||||
mpi
|
||||
aigenuser
|
||||
@@ -1,5 +1,5 @@
|
||||
# -- version --
|
||||
__version__ = (1, 2, 0)
|
||||
__version__ = (1, 2, 1)
|
||||
# -- version --
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from herokutl.tl.functions.payments import GetPaymentFormRequest, SendStarsFormR
|
||||
from herokutl.tl.types import InputInvoiceStarGift, TextWithEntities
|
||||
from herokutl.errors.rpcerrorlist import BadRequestError
|
||||
import logging
|
||||
import herokutl
|
||||
|
||||
@loader.tds
|
||||
class SenderGifts(loader.Module):
|
||||
@@ -78,7 +79,7 @@ class SenderGifts(loader.Module):
|
||||
@loader.command()
|
||||
async def sendgift(self, message):
|
||||
"""- <username> <text*> - отправить подарок пользователю (* - необязательный параметр.) Поддерживается реплай режим."""
|
||||
args = utils.get_args_raw(message)
|
||||
args = utils.get_args_html(message)
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
user = reply.sender
|
||||
@@ -221,12 +222,17 @@ class SenderGifts(loader.Module):
|
||||
self.strings["sending_gift"],
|
||||
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)
|
||||
inv = InputInvoiceStarGift(
|
||||
user,
|
||||
gift_id,
|
||||
message=TextWithEntities(text, []) if text else TextWithEntities("", [])
|
||||
message=TextWithEntities(text, entities) if text else TextWithEntities("", [])
|
||||
)
|
||||
form = await self.client(GetPaymentFormRequest(inv))
|
||||
result = await self.client(SendStarsFormRequest(form.form_id, inv))
|
||||
|
||||
Reference in New Issue
Block a user