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 banner: https://te.legra.ph/file/55fa6eebae860a359ac27.jpg
__version__ = (1, 3, 2)
__version__ = (1, 3, 3)
from .. import loader, utils # type: ignore
from telethon.tl import types # type: ignore
@@ -103,70 +103,70 @@ class SendMod(loader.Module):
else:
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(
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()
# media = None
# temp_file = None
media = None
temp_file = None
# if reply and reply.media:
# doc = getattr(reply.media, "document", None)
if reply and reply.media:
doc = getattr(reply.media, "document", None)
# if doc and any(a.__class__.__name__ == "DocumentAttributeSticker" for a in doc.attributes):
# 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):
media = InputDocument(
id=doc.id,
access_hash=doc.access_hash,
file_reference=doc.file_reference
)
message_text = ""
# 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
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:
# lat_str, long_str = message_text.split(",", 1)
# try:
# gps_x = float(lat_str.strip())
# gps_y = float(long_str.strip())
# if -90 <= gps_x <= 90 and -180 <= gps_y <= 180:
# geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y)
# media = types.InputMediaGeoPoint(geo_point)
# message_text = ""
# except ValueError:
# pass
if message_text and "," in message_text:
lat_str, long_str = message_text.split(",", 1)
try:
gps_x = float(lat_str.strip())
gps_y = float(long_str.strip())
if -90 <= gps_x <= 90 and -180 <= gps_y <= 180:
geo_point = types.InputGeoPoint(lat=gps_x, long=gps_y)
media = types.InputMediaGeoPoint(geo_point)
message_text = ""
except ValueError:
pass
# if not message_text and not media:
# await utils.answer(message, self.strings["error_send_2"])
# return
if not message_text and not media:
await utils.answer(message, self.strings["error_send_2"])
return
# await message.delete()
# await message.reply(
# message_text,
# file=media if media else None,
# parse_mode="html"
# )
await message.delete()
await message.reply(
message_text,
file=media if media else None,
parse_mode="html"
)
if temp_file:
import os
try:
os.remove(temp_file)
except:
pass
# if temp_file:
# import os
# try:
# os.remove(temp_file)
# except:
# pass
@loader.command(
ru_doc="[@UserName] [text or replay] - Написать сообщение в личные сообщения",

View File

@@ -26,8 +26,10 @@
# scope: Api AccountData 0.0.1
# ---------------------------------------------------------------------------------
import asyncio
import logging
from datetime import datetime
from typing import Optional
import aiohttp
@@ -35,6 +37,7 @@ 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"""
@@ -45,9 +48,21 @@ class AccountData(loader.Module):
"api_token",
"7518491974:1ea2284eec9dc40a9838cfbcb48a2b36",
"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 = {
"name": "AccountData",
@@ -64,15 +79,16 @@ class AccountData(loader.Module):
"no_reply": "<emoji document_id=6030512294109122096>💬</emoji> Вы не ответили на сообщение пользователя",
}
async def get_creation_date(self, user_id: int) -> str:
api_token = self.config.get("api_token", "")
async def get_creation_date(self, user_id: int) -> dict:
api_token = self.config["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}
async with aiohttp.ClientSession() as session:
session = await self._get_session()
try:
async with session.get(url, params=params) as response:
if response.status == 200:
json_response = await response.json()
@@ -80,11 +96,15 @@ class AccountData(loader.Module):
return {
"creation_date": json_response["creation_date"],
"accuracy_percent": json_response["accuracy_percent"],
} # type: ignore
}
else:
return {"error": json_response["error"]["message"]} # type: ignore
return {"error": json_response["error"]["message"]}
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(
ru_doc="Узнать примерную дату регистрации Telergam-аккаунта",
@@ -100,9 +120,9 @@ class AccountData(loader.Module):
return
try:
month, year = map(int, result['creation_date'].split('.'))
month, year = map(int, result["creation_date"].split("."))
date_object = datetime(year, month, 1)
formatted = date_object.strftime('%B %Y')
formatted = date_object.strftime("%B %Y")
await utils.answer(
message,

View File

@@ -45,20 +45,29 @@ 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
@@ -71,6 +80,7 @@ class ReleaseInfo:
alias: str
poster: Poster
@loader.tds
class AniLibertyMod(loader.Module):
"""Ищет и возвращает случайное аниме из базы Aniliberty"""
@@ -92,9 +102,25 @@ class AniLibertyMod(loader.Module):
"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 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:
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:
json_answer = await resp.json()
results = []
for i in json_answer:
@@ -103,8 +129,10 @@ class AniLibertyMod(loader.Module):
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:
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:
try:
json_answer = await resp.json()
data = from_dict(data_class=ReleaseInfo, data=json_answer)
@@ -113,13 +141,12 @@ class AniLibertyMod(loader.Module):
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:
session = await self._get_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'])
data = await self.get_title(randid[0]["id"])
return data
@loader.command(
@@ -130,17 +157,18 @@ class AniLibertyMod(loader.Module):
anime_release = await self.get_random_title()
genres_str = ""
for genre in anime_release.genres[:-1]:
genres_str += f'{genre.name}, '
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)}"
text += (
f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}"
)
kb = [
[
@@ -182,11 +210,11 @@ class AniLibertyMod(loader.Module):
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 += 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['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"
@@ -214,16 +242,18 @@ class AniLibertyMod(loader.Module):
anime_release = await self.get_random_title()
genres_str = ""
for genre in anime_release.genres[:-1]:
genres_str += f'{genre.name}, '
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['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)}"
text += (
f"{self.strings['favorite']} {str(anime_release.added_in_users_favorites)}"
)
kb = [
[

View File

@@ -28,6 +28,7 @@
# ---------------------------------------------------------------------------------
import logging
from typing import Optional
import aiohttp
@@ -35,6 +36,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class AnimeQuotesMod(loader.Module):
"""A module for sending random quotes from anime"""
@@ -58,6 +60,20 @@ class AnimeQuotesMod(loader.Module):
"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(
ru_doc="Получить случайную цитату из аниме",
en_doc="Get a random quote from the anime",
@@ -66,7 +82,7 @@ class AnimeQuotesMod(loader.Module):
url = "https://api.animechan.io/v1/quotes/random"
try:
async with aiohttp.ClientSession() as session:
session = await self._get_session()
async with session.get(url) as response:
response.raise_for_status()
data = await response.json()
@@ -75,10 +91,10 @@ class AnimeQuotesMod(loader.Module):
character_name = data["data"]["character"]["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
)
await utils.answer(message, quote)
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 os
import logging
from typing import Optional
from .. import loader, utils
from telethon.types import MessageMediaDocument
@@ -53,19 +54,33 @@ class CodeShareMod(loader.Module):
"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"
data = aiohttp.FormData()
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:
if response.status == 200:
link = await response.text()
return link
else:
logger.error(f"Error occurred! Status code: {response.status}")
return
return None
@loader.command(
ru_doc="Загрузка кода на сайт",

View File

@@ -27,6 +27,7 @@
# ---------------------------------------------------------------------------------
import logging
from typing import Optional
import aiohttp
@@ -34,6 +35,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class CryptoCurrencyMod(loader.Module):
"""Module for displaying current cryptocurrency exchange rates."""
@@ -49,9 +51,23 @@ class CryptoCurrencyMod(loader.Module):
"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):
"""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:
response.raise_for_status()
return await response.json()

View File

@@ -47,7 +47,7 @@ class FolderAutoReadMod(loader.Module):
"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",
"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 = {
@@ -59,79 +59,96 @@ class FolderAutoReadMod(loader.Module):
"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",
"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):
self.tracked_folders = []
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):
self.tracked_folders = []
self.set("tracked_folders", [])
async def _read_peers(self, peers):
for peer in peers:
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)
async def read_chats_in_folders(self):
if self.tracked_folders:
all_folders = await self._client(functions.messages.GetDialogFiltersRequest())
for i in range(len(self.tracked_folders)):
match = next(
(f for f in all_folders.filters
if isinstance(f, DialogFilter) and f.title.text == self.tracked_folders[i]),
None
all_folders = await self._client(
functions.messages.GetDialogFiltersRequest()
)
for peer in match.pinned_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))
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))
for folder_name in self.tracked_folders:
match = next(
(
f
for f in all_folders.filters
if isinstance(f, DialogFilter) and f.title.text == folder_name
),
None,
)
if match is None:
continue
await self._read_peers(match.pinned_peers)
await self._read_peers(match.include_peers)
@loader.command()
@loader.command(
ru_doc="Добавить папку в список отслеживания",
en_doc="Add folder to the tracking list",
)
async def addfolder(self, message):
arg = utils.get_args_raw(message)
if arg:
all_folders = await self._client(functions.messages.GetDialogFiltersRequest())
match = next(
(f for f in all_folders.filters
if isinstance(f, DialogFilter) and f.title.text == arg),
None
all_folders = await self._client(
functions.messages.GetDialogFiltersRequest()
)
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.set("tracked_folders", self.tracked_folders)
await utils.answer(message, self.strings['addfolder'])
await utils.answer(message, self.strings("addfolder"))
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):
arg = utils.get_args_raw(message)
if arg and arg in self.tracked_folders:
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:
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):
await utils.answer(message, self.strings["listfolders"] + "\n".join(
f"{folder}" for folder in self.tracked_folders
))
await utils.answer(
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:
"""
self.client = client
self.tg_id = (await client.get_me()).id
self.db = 0
self.redis = await aioredis.from_url(
self.config["config_redis_cloud_link"],

View File

@@ -28,6 +28,7 @@
# ---------------------------------------------------------------------------------
import logging
from typing import Optional
import aiohttp
import re
@@ -37,6 +38,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class face(loader.Module):
"""random face"""
@@ -62,6 +64,20 @@ class face(loader.Module):
"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(
ru_doc="Рандом kaomoji",
en_doc="Random kaomoji",
@@ -71,14 +87,14 @@ class face(loader.Module):
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:
if response.status == 200:
data = await response.text()
kaomoji_list = [s.strip() for s in re.split(r'[\t\r\n]+', data) if s.strip()]
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(kaomoji)
)
await utils.answer(message, self.strings("random_face").format(kaomoji))
else:
await utils.answer(message, self.strings("error"))

View File

@@ -676,7 +676,7 @@ class GlobalRestrict(loader.Module):
utils.get_entity_url(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
else chats
),

View File

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

View File

@@ -30,6 +30,7 @@
import io
import logging
from textwrap import wrap
from typing import Optional
import aiohttp
from PIL import Image, ImageDraw, ImageFont
@@ -38,6 +39,7 @@ from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class JacquesMod(loader.Module):
"""Жаконизатор"""
@@ -50,6 +52,7 @@ class JacquesMod(loader.Module):
self.name = self.strings["name"]
self._me = None
self._ratelimit = []
self._session: Optional[aiohttp.ClientSession] = None
self.config = loader.ModuleConfig(
loader.ConfigValue(
"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(
ru_doc="<реплай на сообщение/свой текст>",
en_doc="<reply to the message/your own text>",
@@ -81,7 +95,7 @@ class JacquesMod(loader.Module):
else:
txt = args
async with aiohttp.ClientSession() as session:
session = await self._get_session()
async with session.get(self.config["font"]) as font_response:
font_data = await font_response.read()
@@ -96,7 +110,8 @@ class JacquesMod(loader.Module):
draw = ImageDraw.Draw(img)
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))
draw_imtext = ImageDraw.Draw(imtext)
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:
"""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)
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"
url = f"https://numbersapi.com/{date_str}/date"
return await self._fetch_fact(url)
@loader.command(

View File

@@ -28,6 +28,8 @@
import logging
import re
from typing import Optional
import aiohttp
from .. import loader, utils
@@ -69,6 +71,18 @@ class Shortener(loader.Module):
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:
"""Validate URL format"""
@@ -87,27 +101,32 @@ class Shortener(loader.Module):
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:
async def shorten_url(self, url: str, token: str) -> Optional[str]:
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:
if resp.status == 201:
json_response = await resp.json()
return json_response['link']
return json_response["link"]
else:
logger.error(f"Error occurred! Status code: {resp.status}")
return
return None
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:
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']
return json_response["total_clicks"]
else:
logger.error(f"Error occurred! Status code: {resp.status}")
return
return None
@loader.command(
ru_doc="Сократить ссылку через bit.ly (ссылка с https://)",
@@ -129,7 +148,13 @@ class Shortener(loader.Module):
return
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))
except Exception as e:
logger.error(f"Error shortening URL: {e}")
@@ -155,7 +180,17 @@ class Shortener(loader.Module):
await utils.answer(message, self.strings("invalid_url"))
return
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))
except Exception as 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] Попасть в Limoka
- [x] Попасть в команду верефицированных разработчиков модулей Heroku
- [] Soon...
- [ ] Сойти с ума...

View File

@@ -23,3 +23,4 @@ point
deviceinfo
mpi
aigenuser
github

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# -- version --
__version__ = (1, 2, 2)
__version__ = (1, 2, 3)
# -- version --
@@ -35,6 +35,7 @@ class SenderGifts(loader.Module):
"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> Баланс: {} звезд",
"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Как отправить подарок?",
"sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...",
"gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!",
@@ -46,7 +47,7 @@ class SenderGifts(loader.Module):
"btn_anon": "🕵️ Анонимно",
}
gift_categories = {
regular_gifts = {
15: [
{"id": 5170145012310081615, "emoji": "❤️", "name": "Сердце"},
{"id": 5170233102089322756, "emoji": "🧸", "name": "Мишка"},
@@ -59,8 +60,6 @@ class SenderGifts(loader.Module):
{"id": 5170144170496491616, "emoji": "🎂", "name": "Тортик"},
{"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
{"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"},
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка"},
{"id": 5956217000635139069, "emoji": "🧸", "name": "Новогодний мишка"}
],
100: [
{"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"},
@@ -69,8 +68,28 @@ class SenderGifts(loader.Module):
]
}
async def client_ready(self, client, db):
self.client = client
unique_gifts = {
"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):
try:
@@ -100,6 +119,10 @@ class SenderGifts(loader.Module):
text = parts[1] if len(parts) > 1 else ""
if username.startswith('@'):
username = username[1:]
try:
username = int(username)
except ValueError:
pass
msg = await utils.answer(message, self.strings["checking_user"])
try:
user = await self.client.get_entity(username)
@@ -116,115 +139,54 @@ class SenderGifts(loader.Module):
await utils.answer(balance_msg, self.strings["balance_error"])
return
min_price = min(self.gift_categories.keys())
min_price = min(self.regular_gifts.keys())
if balance < min_price:
await utils.answer(balance_msg, self.strings["min_stars_error"])
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)
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):
gifts = self.gift_categories[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, 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),
}])
await self._show_main_menu_logic(helper_msg, user.id, text, balance, message.id, answer=True)
async def _show_main_menu_logic(self, msg_or_call, user_id, text, balance, msg_id, answer=False):
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["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 = [
[
{
"text": self.strings["btn_public"],
"callback": self._send_gift,
"args": (user_id, gift_id, text, gift_emoji, msg_id, balance, False) # hide_name=False публично
},
{
"text": self.strings["btn_anon"],
"callback": self._send_gift,
"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)
}
]
[{
"text": "🎁 Обычные подарки",
"callback": self._show_regular_categories,
"args": (user_id, text, balance, msg_id),
}],
[{
"text": "✨ Уникальные подарки",
"callback": self._show_unique_categories,
"args": (user_id, text, balance, msg_id),
}]
]
await call.edit(
self.strings["privacy_menu"].format(gift_emoji),
reply_markup=buttons
)
text_menu = self.strings["gift_menu"].format(user_display, text if text else "-", balance)
async def _back_to_categories(self, call, user_id, text, balance, msg_id):
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 _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:
user = await self.client.get_entity(user_id)
user_display = f"@{user.username}" if user.username else user.first_name
except:
await call.answer("Ошибка получения пользователя", show_alert=True)
return
user_display = f"ID: {user_id}"
available_categories = [price for price in self.gift_categories.keys() if balance >= price]
available_categories = [price for price in self.regular_gifts.keys() if balance >= price]
buttons = []
row = []
@@ -237,16 +199,151 @@ class SenderGifts(loader.Module):
if len(row) == 2:
buttons.append(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(
f"@{user.username}" if user.username else user.first_name,
text if text else "-",
balance
),
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:
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(
self.strings["category_menu"].format(price, user_display, text if text else "-"),
reply_markup=buttons
)
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
)