Added and updated repositories 2026-03-11 01:21:45

This commit is contained in:
github-actions[bot]
2026-03-11 01:21:45 +00:00
parent 02b1aa9f68
commit 7fbb379419
20 changed files with 1680 additions and 343 deletions

View File

@@ -23,7 +23,7 @@
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png # meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
# meta banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg # meta banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg
__version__ = (1, 3, 2) __version__ = (1, 3, 3)
from .. import loader, utils # type: ignore from .. import loader, utils # type: ignore
from telethon.tl import types # type: ignore from telethon.tl import types # type: ignore
@@ -103,70 +103,70 @@ class SendMod(loader.Module):
else: else:
await message.edit(f'{self.strings["error"]} {str(e)}') await message.edit(f'{self.strings["error"]} {str(e)}')
# telegram fixed bug
# @loader.command(
# ru_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему",
# uz_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Yopiq mavzuga xabar yozing",
# de_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
# es_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Escribir un mensaje a un tema cerrado",
# )
# async def sendclosedtopic(self, message: Message):
# """[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"""
# args = utils.get_args_raw(message)
# message_text = args if args else ""
# reply = await message.get_reply_message()
@loader.command( # media = None
ru_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Написать сообщение в закрытую тему", # temp_file = None
uz_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Yopiq mavzuga xabar yozing",
de_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Schreiben Sie eine Nachricht zu einem geschlossenen Thema",
es_doc="[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Escribir un mensaje a un tema cerrado",
)
async def sendclosedtopic(self, message: Message):
"""[text or reply(media/file/sticker) or coordinates (<lat>, <long>)] - Write a message to a closed topic"""
args = utils.get_args_raw(message)
message_text = args if args else ""
reply = await message.get_reply_message()
media = None # if reply and reply.media:
temp_file = None # doc = getattr(reply.media, "document", None)
if reply and reply.media: # if doc and any(a.__class__.__name__ == "DocumentAttributeSticker" for a in doc.attributes):
doc = getattr(reply.media, "document", None) # media = InputDocument(
# id=doc.id,
# access_hash=doc.access_hash,
# file_reference=doc.file_reference
# )
# message_text = ""
if doc and any(a.__class__.__name__ == "DocumentAttributeSticker" for a in doc.attributes): # elif doc and doc.mime_type == "image/webp":
media = InputDocument( # temp_file = await reply.download_media()
id=doc.id, # media = temp_file
access_hash=doc.access_hash, # else:
file_reference=doc.file_reference # media = reply.media
) # else:
message_text = "" # media = message.media
elif doc and doc.mime_type == "image/webp":
temp_file = await reply.download_media()
media = temp_file
else:
media = reply.media
else:
media = message.media
if message_text and "," in message_text: # if message_text and "," in message_text:
lat_str, long_str = message_text.split(",", 1) # lat_str, long_str = message_text.split(",", 1)
try: # try:
gps_x = float(lat_str.strip()) # gps_x = float(lat_str.strip())
gps_y = float(long_str.strip()) # gps_y = float(long_str.strip())
if -90 <= gps_x <= 90 and -180 <= gps_y <= 180: # if -90 <= gps_x <= 90 and -180 <= gps_y <= 180:
geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y) # geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y)
media = types.InputMediaGeoPoint(geo_point) # media = types.InputMediaGeoPoint(geo_point)
message_text = "" # message_text = ""
except ValueError: # except ValueError:
pass # pass
if not message_text and not media: # if not message_text and not media:
await utils.answer(message, self.strings["error_send_2"]) # await utils.answer(message, self.strings["error_send_2"])
return # return
await message.delete() # await message.delete()
await message.reply( # await message.reply(
message_text, # message_text,
file=media if media else None, # file=media if media else None,
parse_mode="html" # parse_mode="html"
) # )
if temp_file: # if temp_file:
import os # import os
try: # try:
os.remove(temp_file) # os.remove(temp_file)
except: # except:
pass # pass
@loader.command( @loader.command(
ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения", ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения",

View File

@@ -26,8 +26,10 @@
# scope: Api AccountData 0.0.1 # scope: Api AccountData 0.0.1
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
import asyncio
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Optional
import aiohttp import aiohttp
@@ -35,6 +37,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@loader.tds @loader.tds
class AccountData(loader.Module): class AccountData(loader.Module):
"""Find out the approximate date of registration of the telegram account""" """Find out the approximate date of registration of the telegram account"""
@@ -45,9 +48,21 @@ class AccountData(loader.Module):
"api_token", "api_token",
"7518491974:1ea2284eec9dc40a9838cfbcb48a2b36", "7518491974:1ea2284eec9dc40a9838cfbcb48a2b36",
"API token for datereg.pro", "API token for datereg.pro",
validator=loader.validators.String(), validator=loader.validators.Hidden(),
) )
) )
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
strings = { strings = {
"name": "AccountData", "name": "AccountData",
@@ -64,15 +79,16 @@ class AccountData(loader.Module):
"no_reply": "<emoji document_id=6030512294109122096>💬</emoji> Вы не ответили на сообщение пользователя", "no_reply": "<emoji document_id=6030512294109122096>💬</emoji> Вы не ответили на сообщение пользователя",
} }
async def get_creation_date(self, user_id: int) -> str: async def get_creation_date(self, user_id: int) -> dict:
api_token = self.config.get("api_token", "") api_token = self.config["api_token"]
if not api_token: if not api_token:
return {"error": "API token not configured"} return {"error": "API token not configured"}
url = "https://api.datereg.pro/api/v1/users/getCreationDateFast" url = "https://api.datereg.pro/api/v1/users/getCreationDateFast"
params = {"token": api_token, "user_id": user_id} params = {"token": api_token, "user_id": user_id}
async with aiohttp.ClientSession() as session: session = await self._get_session()
try:
async with session.get(url, params=params) as response: async with session.get(url, params=params) as response:
if response.status == 200: if response.status == 200:
json_response = await response.json() json_response = await response.json()
@@ -80,11 +96,15 @@ class AccountData(loader.Module):
return { return {
"creation_date": json_response["creation_date"], "creation_date": json_response["creation_date"],
"accuracy_percent": json_response["accuracy_percent"], "accuracy_percent": json_response["accuracy_percent"],
} # type: ignore }
else: else:
return {"error": json_response["error"]["message"]} # type: ignore return {"error": json_response["error"]["message"]}
else: else:
return {"error": f"HTTP {response.status}"} # type: ignore return {"error": f"HTTP {response.status}"}
except asyncio.TimeoutError:
return {"error": "Request timed out"}
except Exception as e:
return {"error": str(e)}
@loader.command( @loader.command(
ru_doc="Узнать примерную дату регистрации Telergam-аккаунта", ru_doc="Узнать примерную дату регистрации Telergam-аккаунта",
@@ -93,17 +113,17 @@ class AccountData(loader.Module):
async def accdata(self, message): async def accdata(self, message):
if reply := await message.get_reply_message(): if reply := await message.get_reply_message():
result = await self.get_creation_date(user_id=reply.sender.id) result = await self.get_creation_date(user_id=reply.sender.id)
if "error" in result or not result.get("creation_date"): if "error" in result or not result.get("creation_date"):
error_msg = result.get("error", "Unknown error occurred") error_msg = result.get("error", "Unknown error occurred")
await utils.answer(message, f"Ошибка: {error_msg}") await utils.answer(message, f"Ошибка: {error_msg}")
return return
try: try:
month, year = map(int, result['creation_date'].split('.')) month, year = map(int, result["creation_date"].split("."))
date_object = datetime(year, month, 1) date_object = datetime(year, month, 1)
formatted = date_object.strftime('%B %Y') formatted = date_object.strftime("%B %Y")
await utils.answer( await utils.answer(
message, message,
f"{self.strings('date_text').format(data=formatted, accuracy=result['accuracy_percent'])}\n\n{self.strings('date_text_ps')}", f"{self.strings('date_text').format(data=formatted, accuracy=result['accuracy_percent'])}\n\n{self.strings('date_text_ps')}",

View File

@@ -43,26 +43,35 @@ from ..inline.types import InlineQuery
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
BASE_API_URL = "https://aniliberty.top/api/v1" BASE_API_URL = "https://aniliberty.top/api/v1"
# Датаклассы для парсинга и хранения json # Датаклассы для парсинга и хранения json
@dataclass @dataclass
class Genre: class Genre:
name: str name: str
@dataclass
@dataclass
class Name: class Name:
main: str main: str
@dataclass @dataclass
class Type: class Type:
description: str description: str
@dataclass @dataclass
class Poster: class Poster:
preview: str preview: str
thumbnail: str thumbnail: str
@dataclass @dataclass
class ReleaseInfo: class ReleaseInfo:
id: int id: int
genres: Optional[list[Genre]] genres: Optional[list[Genre]]
name: Name name: Name
is_ongoing: bool is_ongoing: bool
type: Type type: Type
@@ -71,6 +80,7 @@ class ReleaseInfo:
alias: str alias: str
poster: Poster poster: Poster
@loader.tds @loader.tds
class AniLibertyMod(loader.Module): class AniLibertyMod(loader.Module):
"""Ищет и возвращает случайное аниме из базы Aniliberty""" """Ищет и возвращает случайное аниме из базы Aniliberty"""
@@ -92,36 +102,53 @@ class AniLibertyMod(loader.Module):
"favorite": "<b>Избранное &lt;3</b>:", # &lt; == < "favorite": "<b>Избранное &lt;3</b>:", # &lt; == <
} }
def __init__(self):
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
async def search_title(self, query): async def search_title(self, query):
async with aiohttp.ClientSession() as session: session = await self._get_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: async with session.get(
json_answer = await resp.json() 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"
results = [] ) as resp:
for i in json_answer: json_answer = await resp.json()
obj = from_dict(data_class=ReleaseInfo, data=i) results = []
results.append(obj) for i in json_answer:
return results obj = from_dict(data_class=ReleaseInfo, data=i)
results.append(obj)
return results
async def get_title(self, release_id): async def get_title(self, release_id):
async with aiohttp.ClientSession() as session: session = await self._get_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: async with session.get(
try: 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"
json_answer = await resp.json() ) as resp:
data = from_dict(data_class=ReleaseInfo, data=json_answer) try:
return data json_answer = await resp.json()
except JSONDecodeError: data = from_dict(data_class=ReleaseInfo, data=json_answer)
logger.error("Ошибка парсинга JSON!") return data
except JSONDecodeError:
logger.error("Ошибка парсинга JSON!")
async def get_random_title(self): async def get_random_title(self):
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.get(f'{BASE_API_URL}/anime/releases/random?limit=1&include=id') as resp: async with session.get(
randid = await resp.json() f"{BASE_API_URL}/anime/releases/random?limit=1&include=id"
""" ) as resp:
Приходится запрашивать по второму кругу, т.к. API в рандомных релизах не отдает жанры, даже если попросить через include randid = await resp.json()
""" data = await self.get_title(randid[0]["id"])
data = await self.get_title(randid[0]['id']) return data
return data
@loader.command( @loader.command(
ru_doc="Возвращает случайный релиз из базы", ru_doc="Возвращает случайный релиз из базы",
en_doc="Returns a random release from the database", en_doc="Returns a random release from the database",
@@ -130,17 +157,18 @@ class AniLibertyMod(loader.Module):
anime_release = await self.get_random_title() anime_release = await self.get_random_title()
genres_str = "" genres_str = ""
for genre in anime_release.genres[:-1]: for genre in anime_release.genres[:-1]:
genres_str += f'{genre.name}, ' genres_str += f"{genre.name}, "
genres_str += anime_release.genres[-1].name genres_str += anime_release.genres[-1].name
text = f"{anime_release.name.main} \n" text = f"{anime_release.name.main} \n"
text += f"{self.strings['ongoing']} {'Да' if anime_release.is_ongoing else 'Нет'}\n\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['type']} {anime_release.type.description}\n"
text += f"{self.strings['genres']} {genres_str}\n\n" text += f"{self.strings['genres']} {genres_str}\n\n"
text += f"<code>{anime_release.description}</code>\n\n" text += f"<code>{anime_release.description}</code>\n\n"
text += f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}" text += (
f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}"
)
kb = [ kb = [
[ [
@@ -179,14 +207,14 @@ class AniLibertyMod(loader.Module):
""" """
Приходится запрашивать по второму кругу, т.к. API в поиске не отдает жанры, даже если попросить через include Приходится запрашивать по второму кругу, т.к. API в поиске не отдает жанры, даже если попросить через include
""" """
release_genres = await self.get_title(anime_release.id) release_genres = await self.get_title(anime_release.id)
genres_str = "" genres_str = ""
for genre in release_genres.genres[:-1]: for genre in release_genres.genres[:-1]:
genres_str += f'{genre.name}, ' genres_str += f"{genre.name}, "
genres_str += release_genres.genres[-1].name genres_str += release_genres.genres[-1].name
release_text = ( release_text = (
f"{anime_release.name.main}\n" f"{anime_release.name.main}\n"
f"{self.strings['ongoing']} {"Да" if anime_release.is_ongoing else "Нет"}\n\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['type']} {anime_release.type.description}\n"
f"{self.strings['genres']} {genres_str}\n\n" f"{self.strings['genres']} {genres_str}\n\n"
f"<code>{anime_release.description}</code>\n\n" f"<code>{anime_release.description}</code>\n\n"
@@ -214,16 +242,18 @@ class AniLibertyMod(loader.Module):
anime_release = await self.get_random_title() anime_release = await self.get_random_title()
genres_str = "" genres_str = ""
for genre in anime_release.genres[:-1]: for genre in anime_release.genres[:-1]:
genres_str += f'{genre.name}, ' genres_str += f"{genre.name}, "
genres_str += anime_release.genres[-1].name genres_str += anime_release.genres[-1].name
text = f"{anime_release.name.main} \n" text = f"{anime_release.name.main} \n"
text += f"{self.strings['ongoing']} {"Да" if anime_release.is_ongoing else "Нет"}\n\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['type']} {anime_release.type.description}\n"
text += f"{self.strings['genres']} {genres_str}\n\n" text += f"{self.strings['genres']} {genres_str}\n\n"
text += f"<code>{anime_release.description}</code>\n\n" text += f"<code>{anime_release.description}</code>\n\n"
text += f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}" text += (
f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}"
)
kb = [ kb = [
[ [

View File

@@ -28,6 +28,7 @@
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
import logging import logging
from typing import Optional
import aiohttp import aiohttp
@@ -35,6 +36,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@loader.tds @loader.tds
class AnimeQuotesMod(loader.Module): class AnimeQuotesMod(loader.Module):
"""A module for sending random quotes from anime""" """A module for sending random quotes from anime"""
@@ -58,6 +60,20 @@ class AnimeQuotesMod(loader.Module):
"error": "<b>Не удалось получить цитату. Попробуйте позже!</b>", "error": "<b>Не удалось получить цитату. Попробуйте позже!</b>",
} }
def __init__(self):
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
@loader.command( @loader.command(
ru_doc="Получить случайную цитату из аниме", ru_doc="Получить случайную цитату из аниме",
en_doc="Get a random quote from the anime", en_doc="Get a random quote from the anime",
@@ -66,19 +82,19 @@ class AnimeQuotesMod(loader.Module):
url = "https://api.animechan.io/v1/quotes/random" url = "https://api.animechan.io/v1/quotes/random"
try: try:
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.get(url) as response: async with session.get(url) as response:
response.raise_for_status() response.raise_for_status()
data = await response.json() data = await response.json()
quote_content = data["data"]["content"] quote_content = data["data"]["content"]
character_name = data["data"]["character"]["name"] character_name = data["data"]["character"]["name"]
anime_name = data["data"]["anime"]["name"] anime_name = data["data"]["anime"]["name"]
quote = self.strings["quote_template"].format( quote = self.strings("quote_template").format(
quote=quote_content, character=character_name, anime=anime_name quote=quote_content, character=character_name, anime=anime_name
) )
await utils.answer(message, quote) await utils.answer(message, quote)
except aiohttp.ClientError: except aiohttp.ClientError:
await utils.answer(message, self.strings["error"]) await utils.answer(message, self.strings("error"))

View File

@@ -29,6 +29,7 @@ import aiohttp
import aiofiles import aiofiles
import os import os
import logging import logging
from typing import Optional
from .. import loader, utils from .. import loader, utils
from telethon.types import MessageMediaDocument from telethon.types import MessageMediaDocument
@@ -53,19 +54,33 @@ class CodeShareMod(loader.Module):
"link_ready": "<emoji document_id=5854762571659218443>✅</emoji> <b>Код загружен! Ссылка:</b> <code>{}</code>", "link_ready": "<emoji document_id=5854762571659218443>✅</emoji> <b>Код загружен! Ссылка:</b> <code>{}</code>",
} }
async def upload_to_kmi(self, content: str) -> str: def __init__(self):
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
async def upload_to_kmi(self, content: str) -> Optional[str]:
url = "https://kmi.aeza.net" url = "https://kmi.aeza.net"
data = aiohttp.FormData() data = aiohttp.FormData()
data.add_field("kmi", content) data.add_field("kmi", content)
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.post(url, data=data) as response: async with session.post(url, data=data) as response:
if response.status == 200: if response.status == 200:
link = await response.text() link = await response.text()
return link return link
else: else:
logger.error(f"Error occurred! Status code: {response.status}") logger.error(f"Error occurred! Status code: {response.status}")
return return None
@loader.command( @loader.command(
ru_doc="Загрузка кода на сайт", ru_doc="Загрузка кода на сайт",

View File

@@ -27,6 +27,7 @@
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
import logging import logging
from typing import Optional
import aiohttp import aiohttp
@@ -34,6 +35,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@loader.tds @loader.tds
class CryptoCurrencyMod(loader.Module): class CryptoCurrencyMod(loader.Module):
"""Module for displaying current cryptocurrency exchange rates.""" """Module for displaying current cryptocurrency exchange rates."""
@@ -49,12 +51,26 @@ class CryptoCurrencyMod(loader.Module):
"coin_not_found": "Криптовалюта '{query}' не найдена.", "coin_not_found": "Криптовалюта '{query}' не найдена.",
} }
def __init__(self):
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
async def fetch_json(self, url): async def fetch_json(self, url):
"""Fetch JSON data from a given URL.""" """Fetch JSON data from a given URL."""
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.get(url) as response: async with session.get(url) as response:
response.raise_for_status() response.raise_for_status()
return await response.json() return await response.json()
async def get_exchange_rates(self): async def get_exchange_rates(self):
"""Get exchange rates for RUB and EUR based on USD.""" """Get exchange rates for RUB and EUR based on USD."""

View File

@@ -17,7 +17,7 @@
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
# Name: FolderAutoRead # Name: FolderAutoRead
# Description: Automatically reads chats in selected folders # Description: Automatically reads chats in selected folders
# Author: @hikka_mods # Author: @hikka_mods
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
# meta developer: @hikka_mods # meta developer: @hikka_mods
@@ -41,13 +41,13 @@ class FolderAutoReadMod(loader.Module):
"name": "FolderAutoRead", "name": "FolderAutoRead",
"not_exists_or_already_added": "<emoji document_id=5278578973595427038>🚫</emoji> <b>This folder does not exists or it is already added for tracking!</b>", "not_exists_or_already_added": "<emoji document_id=5278578973595427038>🚫</emoji> <b>This folder does not exists or it is already added for tracking!</b>",
"_cls_doc": "Automatically reads chats in selected folders every 60 seconds!", "_cls_doc": "Automatically reads chats in selected folders every 60 seconds!",
"_cmd_doc_addfolder": "Adds folder to the tracking list by it's name. Usage: .addfolder FolderName", "_cmd_doc_addfolder": "Adds folder to the tracking list by it's name. Usage: .addfolder FolderName",
"_cmd_doc_listfolders": "Prints list of tracked folders", "_cmd_doc_listfolders": "Prints list of tracked folders",
"_cmd_doc_delfolder": "Deletes folder from the tracking list", "_cmd_doc_delfolder": "Deletes folder from the tracking list",
"wrong_args": "<emoji document_id=5278578973595427038>🚫</emoji> <b>Wrong arguments!</b> Usage: .addfolder/delfolder FolderName\n\n<i>Tip: If you trying to delete the folder from the tracking list, double-check that it really still tracking using .listfolders</i>", "wrong_args": "<emoji document_id=5278578973595427038>🚫</emoji> <b>Wrong arguments!</b> Usage: .addfolder/delfolder FolderName\n\n<i>Tip: If you trying to delete the folder from the tracking list, double-check that it really still tracking using .listfolders</i>",
"listfolders": "<emoji document_id=5278227821364275264>📁</emoji> <b>List of tracked folders:</b>\n", "listfolders": "<emoji document_id=5278227821364275264>📁</emoji> <b>List of tracked folders:</b>\n",
"delfolder": "<emoji document_id=5276384644739129761>🗑</emoji> <b>Folder is successfully deleted from the tracking list!</b>", "delfolder": "<emoji document_id=5276384644739129761>🗑</emoji> <b>Folder is successfully deleted from the tracking list!</b>",
"addfolder": "<emoji document_id=5278227821364275264>📁</emoji> <b>Folder is successfully added to the tracking list!</b>" "addfolder": "<emoji document_id=5278227821364275264>📁</emoji> <b>Folder is successfully added to the tracking list!</b>",
} }
strings_ru = { strings_ru = {
@@ -59,79 +59,96 @@ class FolderAutoReadMod(loader.Module):
"wrong_args": "<emoji document_id=5278578973595427038>🚫</emoji> <b>Неверные аргументы!</b> Использование: .addfolder/delfolder НазваниеПапки\n\n<i>Совет: Если вы пытаетесь удалить папку из списка отслеживания, проверьте, что она вообще отслеживается, используя .listfolders</i>", "wrong_args": "<emoji document_id=5278578973595427038>🚫</emoji> <b>Неверные аргументы!</b> Использование: .addfolder/delfolder НазваниеПапки\n\n<i>Совет: Если вы пытаетесь удалить папку из списка отслеживания, проверьте, что она вообще отслеживается, используя .listfolders</i>",
"listfolders": "<emoji document_id=5278227821364275264>📁</emoji> <b>Список отслеживаемых папок:</b>\n", "listfolders": "<emoji document_id=5278227821364275264>📁</emoji> <b>Список отслеживаемых папок:</b>\n",
"delfolder": "<emoji document_id=5276384644739129761>🗑</emoji> <b>Папка успешно удалена из листа отслеживания!</b>", "delfolder": "<emoji document_id=5276384644739129761>🗑</emoji> <b>Папка успешно удалена из листа отслеживания!</b>",
"addfolder": "<emoji document_id=5278227821364275264>📁</emoji> <b>Папка успешно добавлена в лист отслеживания!</b>" "addfolder": "<emoji document_id=5278227821364275264>📁</emoji> <b>Папка успешно добавлена в лист отслеживания!</b>",
} }
def __init__(self): def __init__(self):
self.tracked_folders = [] self.tracked_folders = []
async def client_ready(self, client, db): async def client_ready(self, client, db):
self.tracked_folders = self.get("tracked_folders", []) self.tracked_folders = self.pointer("tracked_folders", [])
async def on_unload(self): async def _read_peers(self, peers):
self.tracked_folders = [] for peer in peers:
self.set("tracked_folders", []) try:
await self._client(functions.messages.ReadMentionsRequest(peer=peer))
await self._client(functions.messages.ReadReactionsRequest(peer=peer))
if isinstance(peer, InputPeerChannel):
await self._client(
functions.channels.ReadHistoryRequest(channel=peer, max_id=0)
)
else:
await self._client(
functions.messages.ReadHistoryRequest(peer=peer, max_id=0)
)
except Exception as e:
logger.debug(f"Failed to read peer {peer}: {e}")
@loader.loop(interval=60, autostart=True) @loader.loop(interval=60, autostart=True)
async def read_chats_in_folders(self): async def read_chats_in_folders(self):
if self.tracked_folders: if self.tracked_folders:
all_folders = await self._client(functions.messages.GetDialogFiltersRequest()) all_folders = await self._client(
for i in range(len(self.tracked_folders)): functions.messages.GetDialogFiltersRequest()
)
for folder_name in self.tracked_folders:
match = next( match = next(
(f for f in all_folders.filters (
if isinstance(f, DialogFilter) and f.title.text == self.tracked_folders[i]), f
None for f in all_folders.filters
if isinstance(f, DialogFilter) and f.title.text == folder_name
),
None,
) )
for peer in match.pinned_peers: if match is None:
await self._client(functions.messages.ReadMentionsRequest(peer=peer)) continue
await self._client(functions.messages.ReadReactionsRequest(peer=peer)) await self._read_peers(match.pinned_peers)
if isinstance(peer, InputPeerChannel): await self._read_peers(match.include_peers)
await self._client(functions.channels.ReadHistoryRequest(channel=peer, max_id=0))
else:
await self._client(functions.messages.ReadHistoryRequest(peer=peer, max_id=0))
for peer in match.include_peers:
await self._client(functions.messages.ReadMentionsRequest(peer=peer))
await self._client(functions.messages.ReadReactionsRequest(peer=peer))
if isinstance(peer, InputPeerChannel):
await self._client(functions.channels.ReadHistoryRequest(channel=peer, max_id=0))
else:
await self._client(functions.messages.ReadHistoryRequest(peer=peer, max_id=0))
@loader.command() @loader.command(
ru_doc="Добавить папку в список отслеживания",
en_doc="Add folder to the tracking list",
)
async def addfolder(self, message): async def addfolder(self, message):
arg = utils.get_args_raw(message) arg = utils.get_args_raw(message)
if arg: if arg:
all_folders = await self._client(functions.messages.GetDialogFiltersRequest()) all_folders = await self._client(
match = next( functions.messages.GetDialogFiltersRequest()
(f for f in all_folders.filters
if isinstance(f, DialogFilter) and f.title.text == arg),
None
) )
if match and match not in self.tracked_folders: match = next(
(
f
for f in all_folders.filters
if isinstance(f, DialogFilter) and f.title.text == arg
),
None,
)
if match and arg not in self.tracked_folders:
self.tracked_folders.append(arg) self.tracked_folders.append(arg)
self.set("tracked_folders", self.tracked_folders) await utils.answer(message, self.strings("addfolder"))
await utils.answer(message, self.strings['addfolder']) else:
else: await utils.answer(message, self.strings("not_exists_or_already_added"))
await utils.answer(message, self.strings["not_exists_or_already_added"]) else:
await utils.answer(message, self.strings("wrong_args"))
@loader.command()
@loader.command(
ru_doc="Удалить папку из списка отслеживания",
en_doc="Delete folder from the tracking list",
)
async def delfolder(self, message): async def delfolder(self, message):
arg = utils.get_args_raw(message) arg = utils.get_args_raw(message)
if arg and arg in self.tracked_folders: if arg and arg in self.tracked_folders:
self.tracked_folders.remove(arg) self.tracked_folders.remove(arg)
self.set("tracked_folders", self.tracked_folders) await utils.answer(message, self.strings("delfolder"))
await utils.answer(message, self.strings['delfolder'])
else: else:
await utils.answer(message, self.strings["wrong_args"]) await utils.answer(message, self.strings("wrong_args"))
@loader.command() @loader.command(
ru_doc="Список отслеживаемых папок",
en_doc="List tracked folders",
)
async def listfolders(self, message): async def listfolders(self, message):
await utils.answer(message, self.strings["listfolders"] + "\n".join( await utils.answer(
f"{folder}" for folder in self.tracked_folders message,
)) self.strings("listfolders")
+ "\n".join(f"{folder}" for folder in self.tracked_folders),
)

View File

@@ -465,6 +465,7 @@ class AutoFarmbotMod(loader.Module):
:return: :return:
""" """
self.client = client self.client = client
self.tg_id = (await client.get_me()).id
self.db = 0 self.db = 0
self.redis = await aioredis.from_url( self.redis = await aioredis.from_url(
self.config["config_redis_cloud_link"], self.config["config_redis_cloud_link"],

View File

@@ -28,6 +28,7 @@
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
import logging import logging
from typing import Optional
import aiohttp import aiohttp
import re import re
@@ -37,6 +38,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@loader.tds @loader.tds
class face(loader.Module): class face(loader.Module):
"""random face""" """random face"""
@@ -62,6 +64,20 @@ class face(loader.Module):
"error": "Произошла ошибка!", "error": "Произошла ошибка!",
} }
def __init__(self):
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
@loader.command( @loader.command(
ru_doc="Рандом kaomoji", ru_doc="Рандом kaomoji",
en_doc="Random kaomoji", en_doc="Random kaomoji",
@@ -71,14 +87,14 @@ class face(loader.Module):
url = "https://files.archquise.ru/kaomoji.txt" url = "https://files.archquise.ru/kaomoji.txt"
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.get(url) as response: async with session.get(url) as response:
if response.status == 200: if response.status == 200:
data = await response.text() data = await response.text()
kaomoji_list = [s.strip() for s in re.split(r'[\t\r\n]+', data) if s.strip()] kaomoji_list = [
kaomoji = random.choice(kaomoji_list) s.strip() for s in re.split(r"[\t\r\n]+", data) if s.strip()
await utils.answer( ]
message, self.strings("random_face").format(kaomoji) kaomoji = random.choice(kaomoji_list)
) await utils.answer(message, self.strings("random_face").format(kaomoji))
else: else:
await utils.answer(message, self.strings("error")) await utils.answer(message, self.strings("error"))

View File

@@ -676,7 +676,7 @@ class GlobalRestrict(loader.Module):
utils.get_entity_url(user), utils.get_entity_url(user),
utils.escape_html(get_full_name(user)), utils.escape_html(get_full_name(user)),
( (
self.strings("unmutes_in_n_chats").format(counter) self.strings("unmute_in_n_chats").format(counter)
if silent if silent
else chats else chats
), ),

View File

@@ -36,6 +36,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class HostApi: class HostApi:
""" """
A class for interacting with a Host API. A class for interacting with a Host API.
@@ -58,8 +59,11 @@ class HostApi:
Returns: Returns:
dict: The API response as a dictionary. dict: The API response as a dictionary.
""" """
url = "http://api.hikka.host" + path url = "https://api.hikka.host" + path
async with aiohttp.ClientSession(trust_env=True) as session: async with aiohttp.ClientSession(
trust_env=True,
timeout=aiohttp.ClientTimeout(total=15),
) as session:
async with session.request( async with session.request(
method, method,
url, url,
@@ -67,7 +71,6 @@ class HostApi:
"Content-Type": "application/json", "Content-Type": "application/json",
"token": self.token, "token": self.token,
}, },
ssl=False,
) as response: ) as response:
return await response.json() return await response.json()
@@ -290,14 +293,20 @@ class HikkahostMod(loader.Module):
token = self.config["token"] token = self.config["token"]
user_id = token.split(":")[0] user_id = token.split(":")[0]
api = HostApi(token) api = HostApi(token)
data = await api.logs(user_id, token) data = await api.logs(user_id)
files_log = data["logs"] files_log = data["logs"]
with open("log.txt", "w") as log_file: import tempfile
json.dump(files_log, log_file) import os
await utils.answer_file(message, "log.txt", self.strings("logs")) fd, tmp_path = tempfile.mkstemp(suffix=".txt", prefix="hikkahost_log_")
try:
with os.fdopen(fd, "w") as log_file:
json.dump(files_log, log_file)
await utils.answer_file(message, tmp_path, self.strings("logs"))
finally:
os.unlink(tmp_path)
@loader.command( @loader.command(
ru_doc="Рестарт HikkaHost", ru_doc="Рестарт HikkaHost",
@@ -314,4 +323,4 @@ class HikkahostMod(loader.Module):
user_id = token.split(":")[0] user_id = token.split(":")[0]
api = HostApi(token) api = HostApi(token)
await api.action(user_id, token) await api.action(user_id)

View File

@@ -30,6 +30,7 @@
import io import io
import logging import logging
from textwrap import wrap from textwrap import wrap
from typing import Optional
import aiohttp import aiohttp
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@@ -38,6 +39,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@loader.tds @loader.tds
class JacquesMod(loader.Module): class JacquesMod(loader.Module):
"""Жаконизатор""" """Жаконизатор"""
@@ -50,6 +52,7 @@ class JacquesMod(loader.Module):
self.name = self.strings["name"] self.name = self.strings["name"]
self._me = None self._me = None
self._ratelimit = [] self._ratelimit = []
self._session: Optional[aiohttp.ClientSession] = None
self.config = loader.ModuleConfig( self.config = loader.ModuleConfig(
loader.ConfigValue( loader.ConfigValue(
"font", "font",
@@ -64,6 +67,17 @@ class JacquesMod(loader.Module):
), ),
) )
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=30)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
@loader.command( @loader.command(
ru_doc="<реплай на сообщение/свой текст>", ru_doc="<реплай на сообщение/свой текст>",
en_doc="<reply to the message/your own text>", en_doc="<reply to the message/your own text>",
@@ -81,14 +95,14 @@ class JacquesMod(loader.Module):
else: else:
txt = args txt = args
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.get(self.config["font"]) as font_response: async with session.get(self.config["font"]) as font_response:
font_data = await font_response.read() font_data = await font_response.read()
async with session.get( async with session.get(
"https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/IMG_20231128_152538.jpg" "https://raw.githubusercontent.com/Codwizer/ReModules/main/assets/IMG_20231128_152538.jpg"
) as pic_response: ) as pic_response:
pic_data = await pic_response.read() pic_data = await pic_response.read()
img = Image.open(io.BytesIO(pic_data)).convert("RGB") img = Image.open(io.BytesIO(pic_data)).convert("RGB")
@@ -96,7 +110,8 @@ class JacquesMod(loader.Module):
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
font = ImageFont.truetype(io.BytesIO(font_data), 32, encoding="UTF-8") font = ImageFont.truetype(io.BytesIO(font_data), 32, encoding="UTF-8")
text_size = draw.multiline_textsize(wrapped_text, font=font) text_bbox = draw.multiline_textbbox((0, 0), wrapped_text, font=font)
text_size = (text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1])
imtext = Image.new("RGBA", (text_size[0] + 10, text_size[1] + 10), (0, 0, 0, 0)) imtext = Image.new("RGBA", (text_size[0] + 10, text_size[1] + 10), (0, 0, 0, 0))
draw_imtext = ImageDraw.Draw(imtext) draw_imtext = ImageDraw.Draw(imtext)
draw_imtext.multiline_text( draw_imtext.multiline_text(

View File

@@ -114,13 +114,13 @@ class NumbersAPI(loader.Module):
async def _get_number_fact(self, number: int, fact_type: str) -> str: async def _get_number_fact(self, number: int, fact_type: str) -> str:
"""Get fact about number""" """Get fact about number"""
url = f"http://numbersapi.com/{number}/{fact_type}" url = f"https://numbersapi.com/{number}/{fact_type}"
return await self._fetch_fact(url) return await self._fetch_fact(url)
async def _get_date_fact(self, month: int, day: int) -> str: async def _get_date_fact(self, month: int, day: int) -> str:
"""Get fact about date""" """Get fact about date"""
date_str = f"{month:02d}/{day:02d}" date_str = f"{month:02d}/{day:02d}"
url = f"http://numbersapi.com/{date_str}/date" url = f"https://numbersapi.com/{date_str}/date"
return await self._fetch_fact(url) return await self._fetch_fact(url)
@loader.command( @loader.command(

View File

@@ -28,6 +28,8 @@
import logging import logging
import re import re
from typing import Optional
import aiohttp import aiohttp
from .. import loader, utils from .. import loader, utils
@@ -69,6 +71,18 @@ class Shortener(loader.Module):
validator=loader.validators.Hidden(), validator=loader.validators.Hidden(),
) )
) )
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=15)
)
return self._session
async def on_unload(self):
if self._session and not self._session.closed:
await self._session.close()
def _validate_url(self, url: str) -> bool: def _validate_url(self, url: str) -> bool:
"""Validate URL format""" """Validate URL format"""
@@ -87,27 +101,32 @@ class Shortener(loader.Module):
return url_pattern.match(url) is not None return url_pattern.match(url) is not None
async def shorten_url(self, url: str, token: str) -> str: async def shorten_url(self, url: str, token: str) -> Optional[str]:
async with aiohttp.ClientSession() as session: session = await self._get_session()
async with session.post("https://api-ssl.bitly.com/v4/shorten", json={'long_url': url}, headers={"Authorization": f"Bearer {token}"}) as resp: async with session.post(
if resp.status == 201: "https://api-ssl.bitly.com/v4/shorten",
json_response = await resp.json() json={"long_url": url},
return json_response['link'] headers={"Authorization": f"Bearer {token}"},
else: ) as resp:
logger.error(f"Error occurred! Status code: {resp.status}") if resp.status == 201:
return json_response = await resp.json()
return json_response["link"]
async def get_bitlink_stats(self, bitlink: str, token: str) -> str: else:
async with aiohttp.ClientSession() as session: logger.error(f"Error occurred! Status code: {resp.status}")
async with session.get(f"https://api-ssl.bitly.com/v4/bitlinks/{bitlink}/clicks/summary", headers={"Authorization": f"Bearer {token}"}) as resp: return None
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
async def get_bitlink_stats(self, bitlink: str, token: str) -> Optional[int]:
session = await self._get_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 None
@loader.command( @loader.command(
ru_doc="Сократить ссылку через bit.ly (ссылка с https://)", ru_doc="Сократить ссылку через bit.ly (ссылка с https://)",
@@ -129,7 +148,13 @@ class Shortener(loader.Module):
return return
try: try:
short_url = await self.shorten_url(url=args, token=self.config['token']) short_url = await self.shorten_url(url=args, token=self.config["token"])
if short_url is None:
await utils.answer(
message,
self.strings("api_error").format(error="Failed to shorten URL"),
)
return
await utils.answer(message, self.strings("shortencmd").format(c=short_url)) await utils.answer(message, self.strings("shortencmd").format(c=short_url))
except Exception as e: except Exception as e:
logger.error(f"Error shortening URL: {e}") logger.error(f"Error shortening URL: {e}")
@@ -155,7 +180,17 @@ class Shortener(loader.Module):
await utils.answer(message, self.strings("invalid_url")) await utils.answer(message, self.strings("invalid_url"))
return return
else: else:
clicks = await self.get_bitlink_stats(bitlink=args, token=self.config['token']) clicks = await self.get_bitlink_stats(
bitlink=args, token=self.config["token"]
)
if clicks is None:
await utils.answer(
message,
self.strings("api_error").format(
error="Failed to get statistics"
),
)
return
await utils.answer(message, self.strings("statclcmd").format(c=clicks)) await utils.answer(message, self.strings("statclcmd").format(c=clicks))
except Exception as e: except Exception as e:
logger.error(f"Error getting statistics: {e}") logger.error(f"Error getting statistics: {e}")

View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: gh/fiksofficial
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -15,5 +15,5 @@
- [x] Попасть в FHeta - [x] Попасть в FHeta
- [x] Попасть в Limoka - [x] Попасть в Limoka
- [x] Попасть в команду верефицированных разработчиков модулей Heroku - [x] Попасть в команду верефицированных разработчиков модулей Heroku
- [] Soon... - [ ] Сойти с ума...

View File

@@ -22,4 +22,5 @@ tagall2.0
point point
deviceinfo deviceinfo
mpi mpi
aigenuser aigenuser
github

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# -- version -- # -- version --
__version__ = (1, 2, 2) __version__ = (1, 2, 3)
# -- version -- # -- version --
@@ -35,6 +35,7 @@ class SenderGifts(loader.Module):
"user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден", "user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден",
"gift_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Выберите категорию подарков.\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}\n<tg-emoji emoji-id=5321485469249198987>⭐️</tg-emoji> Баланс: {} звезд", "gift_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Выберите категорию подарков.\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}\n<tg-emoji emoji-id=5321485469249198987>⭐️</tg-emoji> Баланс: {} звезд",
"category_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Подарки за {}\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}", "category_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Подарки за {}\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}",
"unique_category_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> {}\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}",
"privacy_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Выбран подарок: {}\n\nКак отправить подарок?", "privacy_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Выбран подарок: {}\n\nКак отправить подарок?",
"sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...", "sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...",
"gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!", "gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!",
@@ -45,8 +46,8 @@ class SenderGifts(loader.Module):
"btn_public": "📢 Публично", "btn_public": "📢 Публично",
"btn_anon": "🕵️ Анонимно", "btn_anon": "🕵️ Анонимно",
} }
gift_categories = { regular_gifts = {
15: [ 15: [
{"id": 5170145012310081615, "emoji": "❤️", "name": "Сердце"}, {"id": 5170145012310081615, "emoji": "❤️", "name": "Сердце"},
{"id": 5170233102089322756, "emoji": "🧸", "name": "Мишка"}, {"id": 5170233102089322756, "emoji": "🧸", "name": "Мишка"},
@@ -59,8 +60,6 @@ class SenderGifts(loader.Module):
{"id": 5170144170496491616, "emoji": "🎂", "name": "Тортик"}, {"id": 5170144170496491616, "emoji": "🎂", "name": "Тортик"},
{"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"}, {"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
{"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"}, {"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"},
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка"},
{"id": 5956217000635139069, "emoji": "🧸", "name": "Новогодний мишка"}
], ],
100: [ 100: [
{"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"}, {"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"},
@@ -69,8 +68,28 @@ class SenderGifts(loader.Module):
] ]
} }
async def client_ready(self, client, db): unique_gifts = {
self.client = client "new_year": {
"name": "🎄 Новогодние подарки",
"gifts": [
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка", "price": 50},
{"id": 5956217000635139069, "emoji": "🧸", "name": "Новогодний мишка", "price": 50},
]
},
"valentines": {
"name": "💘 День святого валентина",
"gifts": [
{"id": 5800655655995968830, "emoji": "🧸", "name": "14 Февраля мишка", "price": 50},
{"id": 5801108895304779062, "emoji": "💘", "name": "14 Февраля сердце", "price": 50},
]
},
"march_8th": {
"name": "🌷 8 Марта",
"gifts": [
{"id": 5866352046986232958, "emoji": "🧸", "name": "8 Марта мишка", "price": 50},
]
}
}
async def get_star_balance(self): async def get_star_balance(self):
try: try:
@@ -100,6 +119,10 @@ class SenderGifts(loader.Module):
text = parts[1] if len(parts) > 1 else "" text = parts[1] if len(parts) > 1 else ""
if username.startswith('@'): if username.startswith('@'):
username = username[1:] username = username[1:]
try:
username = int(username)
except ValueError:
pass
msg = await utils.answer(message, self.strings["checking_user"]) msg = await utils.answer(message, self.strings["checking_user"])
try: try:
user = await self.client.get_entity(username) user = await self.client.get_entity(username)
@@ -116,115 +139,54 @@ class SenderGifts(loader.Module):
await utils.answer(balance_msg, self.strings["balance_error"]) await utils.answer(balance_msg, self.strings["balance_error"])
return return
min_price = min(self.gift_categories.keys()) min_price = min(self.regular_gifts.keys())
if balance < min_price: if balance < min_price:
await utils.answer(balance_msg, self.strings["min_stars_error"]) await utils.answer(balance_msg, self.strings["min_stars_error"])
return return
available_categories = [price for price in self.gift_categories.keys() if balance >= price]
if not available_categories:
await utils.answer(balance_msg, self.strings["no_available_gifts"])
return
buttons = []
row = []
for price in sorted(available_categories):
row.append({
"text": f"{price}",
"callback": self._show_category,
"args": (user.id, price, text, balance, message.id),
})
if len(row) == 2:
buttons.append(row)
row = []
if row:
buttons.append(row)
helper_msg = await self.inline.form("🪐", balance_msg) helper_msg = await self.inline.form("🪐", balance_msg)
await utils.answer(
helper_msg,
self.strings["gift_menu"].format(
f"@{user.username}" if user.username else user.first_name,
text if text else "-",
balance
),
reply_markup=buttons
)
async def _show_category(self, call, user_id, price, text, balance, msg_id): await self._show_main_menu_logic(helper_msg, user.id, text, balance, message.id, answer=True)
gifts = self.gift_categories[price]
buttons = [] async def _show_main_menu_logic(self, msg_or_call, user_id, text, balance, msg_id, answer=False):
row = []
for gift in gifts:
row.append({
"text": gift["emoji"],
"callback": self._select_privacy,
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance, price),
})
if len(row) == 3:
buttons.append(row)
row = []
if row:
buttons.append(row)
buttons.append([{
"text": "⬅️ Назад",
"callback": self._back_to_categories,
"args": (user_id, text, balance, msg_id),
}])
try: try:
user = await self.client.get_entity(user_id) user = await self.client.get_entity(user_id)
user_display = f"@{user.username}" if user.username else user.first_name user_display = f"@{user.username}" if user.username else user.first_name
except: except:
user_display = f"ID: {user_id}" user_display = f"ID: {user_id}"
await call.edit(
self.strings["category_menu"].format(
price,
user_display,
text if text else "-"
),
reply_markup=buttons
)
async def _select_privacy(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance, price):
buttons = [ buttons = [
[ [{
{ "text": "🎁 Обычные подарки",
"text": self.strings["btn_public"], "callback": self._show_regular_categories,
"callback": self._send_gift, "args": (user_id, text, balance, msg_id),
"args": (user_id, gift_id, text, gift_emoji, msg_id, balance, False) # hide_name=False публично }],
}, [{
{ "text": "✨ Уникальные подарки",
"text": self.strings["btn_anon"], "callback": self._show_unique_categories,
"callback": self._send_gift, "args": (user_id, text, balance, msg_id),
"args": (user_id, gift_id, text, gift_emoji, msg_id, balance, True) # hide_name=True анонимно }]
}
],
[
{
"text": "⬅️ Назад",
"callback": self._show_category,
"args": (user_id, price, text, balance, msg_id)
}
]
] ]
await call.edit( text_menu = self.strings["gift_menu"].format(user_display, text if text else "-", balance)
self.strings["privacy_menu"].format(gift_emoji),
reply_markup=buttons if answer:
) await utils.answer(msg_or_call, text_menu, reply_markup=buttons)
else:
await msg_or_call.edit(text_menu, reply_markup=buttons)
async def _back_to_categories(self, call, user_id, text, balance, msg_id): async def _show_main_menu(self, call, user_id, text, balance, msg_id):
await self._show_main_menu_logic(call, user_id, text, balance, msg_id)
async def _show_regular_categories(self, call, user_id, text, balance, msg_id):
try: try:
user = await self.client.get_entity(user_id) user = await self.client.get_entity(user_id)
user_display = f"@{user.username}" if user.username else user.first_name
except: except:
await call.answer("Ошибка получения пользователя", show_alert=True) user_display = f"ID: {user_id}"
return
available_categories = [price for price in self.regular_gifts.keys() if balance >= price]
available_categories = [price for price in self.gift_categories.keys() if balance >= price]
buttons = [] buttons = []
row = [] row = []
@@ -237,16 +199,151 @@ class SenderGifts(loader.Module):
if len(row) == 2: if len(row) == 2:
buttons.append(row) buttons.append(row)
row = [] row = []
if row:
buttons.append(row)
buttons.append([{
"text": "⬅️ Назад",
"callback": self._show_main_menu,
"args": (user_id, text, balance, msg_id),
}])
await call.edit(
self.strings["gift_menu"].format(user_display, text if text else "-", balance),
reply_markup=buttons
)
async def _show_unique_categories(self, call, user_id, text, balance, msg_id):
try:
user = await self.client.get_entity(user_id)
user_display = f"@{user.username}" if user.username else user.first_name
except:
user_display = f"ID: {user_id}"
buttons = []
for cat_id, cat_data in self.unique_gifts.items():
if any(balance >= gift["price"] for gift in cat_data["gifts"]):
buttons.append([{
"text": cat_data["name"],
"callback": self._show_unique_category_gifts,
"args": (user_id, cat_id, text, balance, msg_id),
}])
if not buttons:
buttons.append([{
"text": "❌ Нет доступных (баланс)",
"callback": self._show_main_menu,
"args": (user_id, text, balance, msg_id),
}])
buttons.append([{
"text": "⬅️ Назад",
"callback": self._show_main_menu,
"args": (user_id, text, balance, msg_id),
}])
await call.edit(
self.strings["gift_menu"].format(user_display, text if text else "-", balance),
reply_markup=buttons
)
async def _show_category(self, call, user_id, price, text, balance, msg_id):
gifts = self.regular_gifts[price]
buttons = []
row = []
for gift in gifts:
row.append({
"text": gift["emoji"],
"callback": self._select_privacy,
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance, "regular", price),
})
if len(row) == 3:
buttons.append(row)
row = []
if row: if row:
buttons.append(row) buttons.append(row)
buttons.append([{
"text": "⬅️ Назад",
"callback": self._show_regular_categories,
"args": (user_id, text, balance, msg_id),
}])
try:
user = await self.client.get_entity(user_id)
user_display = f"@{user.username}" if user.username else user.first_name
except:
user_display = f"ID: {user_id}"
await call.edit( await call.edit(
self.strings["gift_menu"].format( self.strings["category_menu"].format(price, user_display, text if text else "-"),
f"@{user.username}" if user.username else user.first_name, reply_markup=buttons
text if text else "-", )
balance
), async def _show_unique_category_gifts(self, call, user_id, cat_id, text, balance, msg_id):
category = self.unique_gifts[cat_id]
buttons = []
row = []
for gift in category["gifts"]:
if balance >= gift["price"]:
row.append({
"text": gift["emoji"],
"callback": self._select_privacy,
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance, "unique", cat_id),
})
if len(row) == 3:
buttons.append(row)
row = []
if row:
buttons.append(row)
buttons.append([{
"text": "⬅️ Назад",
"callback": self._show_unique_categories,
"args": (user_id, text, balance, msg_id),
}])
try:
user = await self.client.get_entity(user_id)
user_display = f"@{user.username}" if user.username else user.first_name
except:
user_display = f"ID: {user_id}"
await call.edit(
self.strings["unique_category_menu"].format(category["name"], user_display, text if text else "-"),
reply_markup=buttons
)
async def _select_privacy(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance, gift_type, type_arg):
if gift_type == "regular":
back_callback = self._show_category
else:
back_callback = self._show_unique_category_gifts
buttons = [
[
{
"text": self.strings["btn_public"],
"callback": self._send_gift,
"args": (user_id, gift_id, text, gift_emoji, msg_id, balance, False)
},
{
"text": self.strings["btn_anon"],
"callback": self._send_gift,
"args": (user_id, gift_id, text, gift_emoji, msg_id, balance, True)
}
],
[
{
"text": "⬅️ Назад",
"callback": back_callback,
"args": (user_id, type_arg, text, balance, msg_id)
}
]
]
await call.edit(
self.strings["privacy_menu"].format(gift_emoji),
reply_markup=buttons reply_markup=buttons
) )