Compare commits

...

57 Commits

Author SHA1 Message Date
github-actions[bot]
430f49f345 Updated modules.json after parse 2026-02-14 06:58:27 2026-02-14 06:58:27 +00:00
54229a8266 nvm 2026-02-14 09:54:25 +03:00
Zahar Vanilovv
6cb625acd8 Merge pull request #183 from MuRuLOSE/update-submodules_61ebb7fa472caa2abd4d6c7523bcf8830258167c
Update of repositories 2026-02-09 01:29:55
2026-02-09 17:25:41 +03:00
github-actions[bot]
bf03595075 Updated modules.json after parse 2026-02-09 01:29:33 2026-02-09 01:29:33 +00:00
github-actions[bot]
8bef604170 Added and updated repositories 2026-02-09 01:29:03 2026-02-09 01:29:03 +00:00
61ebb7fa47 Legacy version is here! 2026-02-08 20:17:50 +03:00
Zahar Vanilovv
2702cd2356 Merge pull request #182 from MuRuLOSE/update-submodules_ab5aaf579b3117a79d44591859b2cd30a6fcd5a1
Update of repositories 2026-02-08 01:59:39
2026-02-08 08:01:47 +03:00
github-actions[bot]
37f53375bc Updated modules.json after parse 2026-02-08 01:59:16 2026-02-08 01:59:16 +00:00
github-actions[bot]
23dade9b9e Added and updated repositories 2026-02-08 01:58:46 2026-02-08 01:58:46 +00:00
Macsim
ab5aaf579b Merge pull request #181 from MuRuLOSE/update-submodules_78a9b0cb76b2d495a5ff23a25614d91699a8757d
Update of repositories 2026-02-07 07:57:35
2026-02-07 11:05:32 +03:00
github-actions[bot]
170809519a Updated modules.json after parse 2026-02-07 07:57:13 2026-02-07 07:57:13 +00:00
78a9b0cb76 added full tds attribute 2026-02-07 10:54:30 +03:00
Macsim
c1d407f885 Merge pull request #180 from MuRuLOSE/update-submodules_7cda8f2685159651f9c2c9ff238215f2b5894d6c
Update of repositories 2026-02-07 07:41:22
2026-02-07 10:43:12 +03:00
github-actions[bot]
cf230d2922 Updated modules.json after parse 2026-02-07 07:41:02 2026-02-07 07:41:02 +00:00
7cda8f2685 fix f-strings while parsing 2026-02-07 10:35:19 +03:00
Macsim
92bdd1510d Merge pull request #179 from MuRuLOSE/update-submodules_a7b8c740b42879d9b3c0d577d275d5b42e4db254
Update of repositories 2026-02-07 01:23:55
2026-02-07 10:34:25 +03:00
github-actions[bot]
52f6fc53c3 Updated modules.json after parse 2026-02-07 01:23:38 2026-02-07 01:23:38 +00:00
github-actions[bot]
5c25eaad81 Added and updated repositories 2026-02-07 01:23:08 2026-02-07 01:23:08 +00:00
a7b8c740b4 Now index locking should be not a problem 2026-02-06 12:27:31 +03:00
44ca8633c6 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-02-06 12:03:00 +03:00
e076d4ab28 lang and dev_username hotfix 2026-02-06 12:02:48 +03:00
Macsim
634861943a Merge pull request #177 from MuRuLOSE/update-submodules_f68d97d09b6ca907775228791cde9446509954c8
Update of repositories 2026-02-06 01:25:04
2026-02-06 07:43:55 +03:00
github-actions[bot]
d20d019c32 Updated modules.json after parse 2026-02-06 01:24:40 2026-02-06 01:24:40 +00:00
github-actions[bot]
041e6ec5f7 Added and updated repositories 2026-02-06 01:24:12 2026-02-06 01:24:12 +00:00
f68d97d09b bolded some text 2026-02-05 20:30:35 +03:00
e624861b64 Blacklisted some module 2026-02-05 20:17:09 +03:00
Macsim
d3f035ec58 Merge pull request #176 from MuRuLOSE/update-submodules_5c2de322d31171b50a220dcbb9862aea611ab15a
Update of repositories 2026-02-05 01:23:39
2026-02-05 20:07:37 +03:00
Macsim
1ced5efae6 Update SpotifyMod.py 2026-02-05 20:01:13 +03:00
github-actions[bot]
3bf476c8a9 Updated modules.json after parse 2026-02-05 01:23:15 2026-02-05 01:23:15 +00:00
github-actions[bot]
abff91c013 Added and updated repositories 2026-02-05 01:22:45 2026-02-05 01:22:45 +00:00
5c2de322d3 new developer 2026-01-30 20:29:03 +03:00
5b2fe145ff b 2026-01-30 17:17:10 +03:00
64d8ceb354 clear histroy 2026-01-30 16:58:22 +03:00
537ae4c485 againfix 2026-01-30 16:50:55 +03:00
52018c8e14 fix not found PeerUser bcs of id 2026-01-30 15:42:57 +03:00
6f95292df1 hotfix again 2026-01-30 15:31:04 +03:00
4a5d4883a9 version bump 2026-01-30 15:22:55 +03:00
ce59b13c94 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-01-30 15:22:26 +03:00
91351cb83d hotfix (i am scared...) 2026-01-30 15:22:01 +03:00
Macsim
118a800856 Merge pull request #170 from MuRuLOSE/update-submodules_51bbb98711dbacc9d018d87706b7f28c77463cc4
Update of repositories 2026-01-30 11:59:59
2026-01-30 15:00:33 +03:00
github-actions[bot]
f14a99c640 Updated modules.json after parse 2026-01-30 11:59:36 2026-01-30 11:59:36 +00:00
github-actions[bot]
c76dcc0fc1 Added and updated repositories 2026-01-30 11:59:07 2026-01-30 11:59:07 +00:00
51bbb98711 Merge branch 'main' of https://github.com/MuRuLOSE/limoka 2026-01-30 14:56:57 +03:00
edd70ad020 бь 2026-01-30 14:56:48 +03:00
Macsim
2c33b08ea0 Merge pull request #169 from MuRuLOSE/update-submodules_88ab2657552874370ef8600b9655bbfe90c2decd
Update of repositories 2026-01-30 11:50:56
2026-01-30 14:52:28 +03:00
github-actions[bot]
e9bb89cf62 Updated modules.json after parse 2026-01-30 11:50:33 2026-01-30 11:50:33 +00:00
github-actions[bot]
51055e6427 Added and updated repositories 2026-01-30 11:50:03 2026-01-30 11:50:03 +00:00
88ab265755 fixed workflow 2026-01-30 14:46:22 +03:00
Macsim
baada2d019 Merge pull request #168 from MuRuLOSE/update-submodules_2dd772b52d052144384a8e0c71f2d33b7c21ff1d
Update of repositories 2026-01-30 11:40:55
2026-01-30 14:42:26 +03:00
github-actions[bot]
ad782a6f46 Updated modules.json after parse 2026-01-30 11:40:21 2026-01-30 11:40:21 +00:00
github-actions[bot]
2435df880e Added and updated repositories 2026-01-30 11:39:54 2026-01-30 11:39:54 +00:00
2dd772b52d fix (wrong json structure) 2026-01-30 14:38:18 +03:00
c9ed00bb78 removed categories 2026-01-30 14:28:51 +03:00
504d1f32e9 Limoka 1.4.0 2026-01-30 14:24:43 +03:00
48d3e46d79 replaced wrong keyword 2026-01-29 19:36:37 +03:00
d48c269916 cooking update... 2026-01-27 19:15:25 +03:00
Macsim
8f52e0c18c Merge pull request #166 from MuRuLOSE:update-submodules_7cb3c70695a897525680a149b0a9e66c751ded33
Update of repositories 2026-01-27 01:19:05
2026-01-27 13:15:07 +03:00
28 changed files with 88480 additions and 45854 deletions

View File

@@ -10,8 +10,10 @@ on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, closed]
workflow_dispatch: # Allows manual triggering from GitHub UI
# Environment variables available to all jobs
env:
BRANCH_NAME: "update-submodules_${{ github.sha }}"
@@ -137,9 +139,8 @@ jobs:
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install requests scikit-learn tqdm
pip install requests
python3 parse.py
python3 categories.py
git add modules.json
git commit -m "Updated modules.json after parse $(date +'%Y-%m-%d %H:%M:%S')" || echo "No changes for modules.json"
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${REPO_URL}"
@@ -206,61 +207,54 @@ jobs:
echo "Branch ${{ env.BRANCH_NAME }} does not exist in remote repository, skipping PR creation."
fi
notify_diffs:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: parse
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install dependencies
run: pip install aiohttp
- name: Send module diffs to channel
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID_UPDATE }}
run: |
LAST_COMMIT_MESSAGE=$(git log -1 --pretty=%B)
if [[ "$LAST_COMMIT_MESSAGE" == Merge* ]]; then
python3 update_diffs.py --token ${{ secrets.TELEGRAM_BOT_TOKEN }} --chat_id ${{ secrets.TELEGRAM_CHAT_ID_UPDATE }} --base_commit HEAD~1
fi
backup:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
needs: parse
steps:
- name: Configure Git for github-actions[bot]
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git config --global --list
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: ${{ env.GIT_DEPTH }}
- name: Create and send backup to Telegram
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install Python dependencies
run: pip install aiohttp
- name: Run backup script
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
echo "Creating .zip file of the repository with maximum compression..."
git archive --format=zip --output=repository-original.zip HEAD
zip -9 repository.zip repository-original.zip
rm repository-original.zip
echo "File size of the created .zip file:"
du -sh repository.zip
echo "Splitting the .zip file into 8 parts..."
split -b 49M repository.zip repository-part-
echo "Files created after split:"
ls repository-part-*
COMMIT_MESSAGE="$(git log -1 --pretty=%B)"
COMMIT_DATE="$(date --date="$(git log -1 --pretty=%ci)" +'%Y-%m-%d %H:%M:%S')"
COMMIT_HASH="$(git rev-parse --short=6 HEAD)"
COMMIT_URL="https://${REPO_URL}/commit/$(git rev-parse HEAD)"
MESSAGE="Commit Date: $COMMIT_DATE, Commit Message: $COMMIT_MESSAGE, Commit Hash: [\`$COMMIT_HASH\`]($COMMIT_URL)"
echo "Sending .zip file parts to Telegram..."
FIRST_PART=true
for part in $(ls repository-part-* | sort); do
if $FIRST_PART; then
TELEGRAM_API_URL="https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendDocument"
echo "Sending first file to Telegram: $TELEGRAM_API_URL"
curl -X POST "$TELEGRAM_API_URL" \
-F chat_id=$TELEGRAM_CHAT_ID \
-F document=@$part \
-F caption="$MESSAGE" \
-F parse_mode="Markdown"
FIRST_PART=false
else
TELEGRAM_API_URL="https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendDocument"
echo "Sending file to Telegram: $TELEGRAM_API_URL"
curl -X POST "$TELEGRAM_API_URL" \
-F chat_id=$TELEGRAM_CHAT_ID \
-F document=@$part
fi
done
echo "Files sent to Telegram successfully!"
python backup.py --token ${{ secrets.TELEGRAM_BOT_TOKEN }} --chat_id ${{ secrets.TELEGRAM_CHAT_ID }}

1099
Limoka.py

File diff suppressed because it is too large Load Diff

1420
LimokaLegacy.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -23,4 +23,4 @@ class K(loader.Module):
"""K"""
raise Exception("Testing error handling")
await utils.answer(message, "K")
# why
# why FUCK YOU BILL GATES ЛЛЛЛ

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
#░░░█░░░█░░░░█░░█░█░█░█
#░░░███░███░░█░░███░███
# meta developer: @nullmod
__version__ = (1, 0, 1)
import io
import math
from PIL import Image, ImageDraw
from herokutl.tl.custom import Message
from herokutl.tl.functions.help import (
GetPeerProfileColorsRequest
)
from herokutl.tl.types import (
EmojiStatusCollectible
)
from .. import loader, utils
def resize_image(image: Image.Image, max_size: int = 1280) -> Image.Image:
w, h = image.size
if max(w, h) <= max_size:
return image
else:
scale = max_size / max(w, h)
new_w = int(w * scale)
new_h = int(h * scale)
return image.resize((new_w, new_h), Image.LANCZOS)
# Source: https://gist.github.com/weihanglo/1e754ec47fdd683a42fdf6a272904535#file-draw_gradient_pillow-py
def get_gradient(size: tuple, color1: tuple, color2: tuple, gradient_type: str = "linear") -> Image.Image:
def interpolate(f_co, t_co, interval):
if interval <= 1:
yield list(t_co)
return
det_co = [(t - f) / (interval - 1) for f, t in zip(f_co, t_co)]
for i in range(interval):
yield [round(f + det * i) for f, det in zip(f_co, det_co)]
gradient = Image.new('RGB', size, color=(0, 0, 0))
draw = ImageDraw.Draw(gradient)
if gradient_type == "linear":
top_color, bottom_color = color1, color2
for y, color in enumerate(interpolate(top_color, bottom_color, max(1, size[1]))):
draw.line([(0, y), (size[0], y)], fill=tuple(color), width=1)
elif gradient_type == "radial":
center_color, edge_color = color1, color2
max_radius = math.hypot(size[0], size[1]) / 2.0
interval = max(1, int(math.ceil(max_radius)) + 1)
colors = list(interpolate(center_color, edge_color, interval))
cx = size[0] / 2
cy = size[1] / 2
for r_index, color in enumerate(colors):
r = interval - 1 - r_index
if r < 0:
continue
bbox = [
int(round(cx - r)),
int(round(cy - r)),
int(round(cx + r)),
int(round(cy + r))
]
draw.ellipse(bbox, fill=tuple(color))
return gradient
def set_gradient(im: io.BytesIO, gradient: Image.Image) -> io.BytesIO:
img = resize_image(Image.open(im).convert('RGBA'))
max_size = max(img.width, img.height)
gradient = gradient.resize((max_size, max_size), Image.LANCZOS).convert('RGBA')
left = (max_size - img.width) // 2
top = (max_size - img.height) // 2
gradient.paste(img, (left, top), img)
buffer = io.BytesIO()
gradient.save(buffer, format='PNG')
buffer.seek(0)
return buffer
def crop_by_bbox(img: Image.Image, bbox: tuple = None):
img_w, img_h = img.size
x, y, w, h = bbox or BBOX_TGA_TGD
left = int(round(x * img_w))
top = int(round(y * img_h))
right = int(round((x + w) * img_w))
bottom = int(round((y + h) * img_h))
return img.crop((left, top, right, bottom))
def hex_to_rgb(value: int):
return ((value >> 16) & 255, (value >> 8) & 255, value & 255)
def hexes_to_rgbs(value: list):
if len(value) > 1:
res = list()
for i in value:
res.append(hex_to_rgb(i))
return tuple(res)
else:
res = hex_to_rgb(value[0])
return (res, res)
SHAPES = {
# TODO: фигуры для создания масок на авы
}
BBOX_TGA_TGD = (
2894 / 8268,
1260 / 8268,
2504 / 8268,
2504 / 8268,
)
@loader.tds
class Gradientor(loader.Module):
strings = {
"name": "Gradientor",
"_cls_doc": "A module to create your profile picture with a background from your profile",
"gradient_creating": "<tg-emoji emoji-id=5886667040432853038>🔁</tg-emoji> Creating gradient...",
"gradient_created": "<tg-emoji emoji-id=5818804345247894731>✅</tg-emoji> Gradient created!",
}
strings_ru = {
"_cls_doc": "Модуль для создания вашей аватарки на фоне из вашего профиля",
"gradient_creating": "<tg-emoji emoji-id=5886667040432853038>🔁</tg-emoji> Создание градиента...",
"gradient_created": "<tg-emoji emoji-id=5818804345247894731>✅</tg-emoji> Градиент создан!",
}
async def client_ready(self):
self.colors = self.get("PROFILE_COLORS", None)
if not self.colors or not self.colors.get("light", None):
raw_colors = (await self.client(GetPeerProfileColorsRequest(0))).colors
self.colors = {
"dark": {
str(col.color_id): hexes_to_rgbs(col.dark_colors.bg_colors) for col
in raw_colors
},
"light": {
str(col.color_id): hexes_to_rgbs(col.colors.bg_colors) for col
in raw_colors
},
}
self.set("PROFILE_COLORS", self.colors)
@loader.command(
ru_doc="[фотография/reply] - создать аватарку с градиентом из цвета профиля\n"
"--update-cache - обновить кеш профиля, если вы только что сменили фон профиля\n"
"--linear - использовать линейный градиент\n"
"--light - использовать светлую тему"
)
async def makepp(self, message: Message):
"""[photo/reply] - create a profile picture with a gradient from profile color\n
--update-cache - update profile cache if you just changed profile background\n
--linear - use linear gradient\n
--light - use light theme"""
reply: Message = await message.get_reply_message()
args = utils.get_args(message)
if "--update-cache" in args:
upd_cache = True
args.remove("--update-cache")
else:
upd_cache = False
if "--linear" in args:
force_linear = True
args.remove("--linear")
else:
force_linear = False
if "--light" in args:
theme = "light"
args.remove("--light")
else:
theme = "dark"
if "--full" in args:
_full = True
args.remove("--full")
else:
_full = False
user = None
background_only = False
if args:
user = await self.client.get_entity(int(args[0]) if args[0].isdigit() else args[0])
photo_source = (
message
if (not reply or not (reply.photo or reply.document and "image/" in getattr(reply.document, "mime_type", "")))
else reply
)
if not (photo_source.photo or photo_source.document and "image/" in getattr(photo_source.document, "mime_type", "")):
background_only = True
if not user:
if upd_cache:
user = self.client.hikka_me = await self.client.get_me()
elif reply:
user = reply.sender
else:
user = self.client.hikka_me
if not user.premium:
color1, color2 = (28, 28, 28), (28, 28, 28)
elif user.emoji_status and isinstance(user.emoji_status, EmojiStatusCollectible):
color1, color2 = (
user.emoji_status.edge_color, user.emoji_status.center_color
)
color1 = hex_to_rgb(color1)
color2 = hex_to_rgb(color2)
elif user.profile_color:
color_variant = user.profile_color.color
color1, color2 = self.colors.get(theme).get(
str(color_variant),
((28, 28, 28), (28, 28, 28))
)
else:
color1, color2 = (28, 28, 28), (28, 28, 28)
await utils.answer(message, self.strings["gradient_creating"])
gradient = get_gradient((1280, 1280), color1, color2, "linear" if force_linear else "radial")
if not _full:
gradient = crop_by_bbox(gradient)
if not background_only and not _full:
p_b = await photo_source.download_media(bytes)
p_b_io = io.BytesIO(p_b)
p_b_io.seek(0)
result = set_gradient(p_b_io, gradient)
else:
result = io.BytesIO()
gradient.save(result, format='PNG')
result.seek(0)
result.name = "grad @nullmod.png"
await utils.answer(message, self.strings["gradient_created"], file=result, force_document=True)

View File

@@ -1,3 +1,4 @@
Chess
HaremManager
SchedulePlus
Gradientor

74
backup.py Normal file
View File

@@ -0,0 +1,74 @@
import asyncio
import aiohttp
import argparse
import subprocess
import os
import glob
parser = argparse.ArgumentParser(description="Backup Script")
parser.add_argument(
"--token",
type=str,
required=True,
help="Token of Telegram bot",
)
parser.add_argument(
"--api_url",
type=str,
default="https://api.telegram.org",
help="API URL of Telegram API",
)
parser.add_argument(
"--chat_id",
type=str,
required=True,
help="Chat ID to send backup message to",
)
arguments = parser.parse_args()
async def send_file(session, file_path, caption=None):
url = f"{arguments.api_url}/bot{arguments.token}/sendDocument"
with open(file_path, 'rb') as f:
data = aiohttp.FormData()
data.add_field('chat_id', arguments.chat_id)
data.add_field('document', f, filename=os.path.basename(file_path))
if caption:
data.add_field('caption', caption)
data.add_field('parse_mode', 'Markdown')
async with session.post(url, data=data) as response:
return await response.json()
async def main():
# Get commit info
commit_message = subprocess.check_output(['git', 'log', '-1', '--pretty=%B']).decode().strip()
commit_date = subprocess.check_output(['git', 'log', '-1', '--pretty=%ci']).decode().strip()
commit_hash = subprocess.check_output(['git', 'rev-parse', '--short=6', 'HEAD']).decode().strip()
commit_url = f"https://github.com/MuRuLOSE/limoka/commit/{subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip()}"
message = f"Commit Date: {commit_date}, Commit Message: {commit_message}, Commit Hash: [`{commit_hash}`]({commit_url})"
# Create zip
subprocess.run(['git', 'archive', '--format=zip', '--output=repository-original.zip', 'HEAD'])
subprocess.run(['zip', '-9', 'repository.zip', 'repository-original.zip'])
os.remove('repository-original.zip')
# Split zip
subprocess.run(['split', '-b', '49M', 'repository.zip', 'repository-part-'])
# Send parts
async with aiohttp.ClientSession() as session:
parts = sorted(glob.glob('repository-part-*'))
first = True
for part in parts:
caption = message if first else None
result = await send_file(session, part, caption)
print(f"Sent {part}: {result}")
first = False
# Cleanup
os.remove('repository.zip')
for part in parts:
os.remove(part)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,126 +0,0 @@
import json
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.multiclass import OneVsRestClassifier
from sklearn.preprocessing import MultiLabelBinarizer
from tqdm import tqdm
import numpy as np
# Тренировочные данные (48 модулей)
training_data = {
"MuRuLOSE/HikkaModulesRepo/filters.py": ["Tools", "Chat"],
"MuRuLOSE/HikkaModulesRepo/autogiveawayjoin.py": ["Automation", "Social"],
"MuRuLOSE/HikkaModulesRepo/HTTPCat.py": ["Fun"],
"MuRuLOSE/HikkaModulesRepo/CustomPing.py": ["Tools", "Networking"],
"MuRuLOSE/HikkaModulesRepo/FuckTagOne.py": ["Moderation"],
"MuRuLOSE/HikkaModulesRepo/InlineButtons.py": ["Tools", "Chat"],
"MuRuLOSE/HikkaModulesRepo/YoutubeDL.py": ["Media"],
"MuRuLOSE/HikkaModulesRepo/youtubesearcher.py": ["Media", "Tools"],
"MuRuLOSE/HikkaModulesRepo/INumber.py": ["Fun", "Info"],
"MuRuLOSE/HikkaModulesRepo/RandomDog.py": ["Fun"],
"MuRuLOSE/HikkaModulesRepo/RemoveLinks.py": ["Moderation", "Chat"],
"MuRuLOSE/HikkaModulesRepo/SteamClient.py": ["Games", "Tools"],
"MuRuLOSE/HikkaModulesRepo/PinMoreChats.py": ["Chat", "Productivity"],
"MuRuLOSE/HikkaModulesRepo/MindGameCheat.py": ["Games", "Tools"],
"MuRuLOSE/HikkaModulesRepo/NasaImages.py": ["Media", "Info"],
"MuRuLOSE/HikkaModulesRepo/autoreader.py": ["Automation", "Chat"],
"MuRuLOSE/HikkaModulesRepo/K.py": ["Fun"],
"MuRuLOSE/HikkaModulesRepo/Genshin.py": ["Games"],
"MuRuLOSE/HikkaModulesRepo/compliments.py": ["Social", "Fun"],
"MuRuLOSE/HikkaModulesRepo/AutoLeave.py": ["Automation", "Chat"],
"MuRuLOSE/HikkaModulesRepo/ToTHosting.py": ["Tools", "Admin"],
"MuRuLOSE/HikkaModulesRepo/PasswordUtils.py": ["Security", "Tools"],
"MuRuLOSE/HikkaModulesRepo/FuckJoins.py": ["Security", "Chat"],
"MuRuLOSE/HikkaModulesRepo/SpyEVO.py": ["Tools", "Info"],
"MuRuLOSE/HikkaModulesRepo/FindID.py": ["Tools", "Admin"],
"MuRuLOSE/HikkaModulesRepo/ChannelCheck.py": ["Tools", "Social"],
"MuRuLOSE/HikkaModulesRepo/controlspam.py": ["Chat", "Tools"],
"MuRuLOSE/HikkaModulesRepo/VKMusic.py": ["Media"],
"MuRuLOSE/HikkaModulesRepo/morse.py": ["Tools", "Fun"],
"MuRuLOSE/HikkaModulesRepo/YamiManager.py": ["Chat", "Tools"],
"MuRuLOSE/HikkaModulesRepo/SearchersGenQuery.py": ["Tools", "Info"],
"MuRuLOSE/HikkaModulesRepo/Limoka.py": ["Utilities", "Tools"],
"MuRuLOSE/HikkaModulesRepo/CheckTime.py": ["Productivity", "Info"],
"MuRuLOSE/HikkaModulesRepo/ReplaceWords.py": ["Chat", "Customization"],
"MuRuLOSE/HikkaModulesRepo/TempJoinChannel.py": ["Chat", "Automation"],
"MuRuLOSE/HikkaModulesRepo/timer.py": ["Productivity", "Tools"],
"den4ikSuperOstryyPer4ik/astro-modules/astroafk.py": ["Automation", "Customization"],
"den4ikSuperOstryyPer4ik/astro-modules/akinator.py": ["Games"],
"den4ikSuperOstryyPer4ik/astro-modules/Emotions.py": ["Social", "Fun"],
"den4ikSuperOstryyPer4ik/astro-modules/RandomStatuses.py": ["Social", "Fun"],
"den4ikSuperOstryyPer4ik/astro-modules/RandomTrack.py": ["Media", "Fun"],
"den4ikSuperOstryyPer4ik/astro-modules/minesweeper.py": ["Games"],
"den4ikSuperOstryyPer4ik/astro-modules/inline_bot_manager.py": ["Tools", "Automation"],
"MuRuLOSE/HikkaModulesRepo/ReplaceWords.py": ["Customization", "Chat"],
"MuRuLOSE/HikkaModulesRepo/CheckTime.py": ["Productivity"],
"MuRuLOSE/HikkaModulesRepo/SearchersGenQuery.py": ["Utilities", "Info"]
}
all_categories = [
"Utilities", "Fun", "Admin", "Media", "Games", "Tools", "Security", "Social",
"Automation", "Info", "Chat", "Moderation", "Productivity", "Customization",
"Networking", "Education", "Finance", "Health", "Creative", "Other"
]
def get_module_text(module_path, module_data):
name = module_data.get("name", "").lower()
description = (module_data.get("description", "") or module_data.get("meta", {}).get("desc", "")).lower()
if not description or description == "desc":
description = ""
commands_text = " ".join([f"{cmd} {desc}".lower() for func in module_data.get("commands", []) for cmd, desc in func.items()])
new_commands_text = " ".join([f"{cmd} {data.get('doc', '')} {data.get('ru_doc', '') or ''}".lower()
for func in module_data.get("new_commands", []) for cmd, data in func.items()])
file_path = module_path.lower()
file_name = file_path.split("/")[-1]
return f"{file_name} {name} {description} {file_path} {commands_text} {new_commands_text}".strip()
with open("modules.json", "r", encoding="utf-8") as f:
modules = json.load(f)
# Подготовка тренировочных данных
train_texts = [get_module_text(path, modules[path]) for path in training_data.keys() if path in modules]
train_labels = [training_data[path] for path in training_data.keys() if path in modules]
# Векторизация текста
vectorizer = TfidfVectorizer(max_features=2000)
X_train = vectorizer.fit_transform(train_texts)
# Преобразование меток
mlb = MultiLabelBinarizer(classes=all_categories)
y_train = mlb.fit_transform(train_labels)
# Обучение модели с балансировкой классов
base_clf = LogisticRegression(class_weight="balanced", max_iter=1000)
clf = OneVsRestClassifier(base_clf)
clf.fit(X_train, y_train)
# Обработка всех модулей
print("Classifying all modules...")
texts = [get_module_text(path, data) for path, data in modules.items()]
X_all = vectorizer.transform(texts)
# Предсказание вероятностей
probs = clf.predict_proba(X_all)
# Присваивание категорий
threshold = 0.2 # Снижаем порог для большего разнообразия
for module_path, prob_vector in tqdm(zip(modules.keys(), probs), total=len(modules), desc="Assigning categories"):
module_data = modules[module_path]
sorted_indices = np.argsort(prob_vector)[::-1]
sorted_probs = prob_vector[sorted_indices]
sorted_labels = mlb.classes_[sorted_indices]
selected_categories = [label for label, prob in zip(sorted_labels, sorted_probs) if prob >= threshold][:2]
if not selected_categories:
selected_categories = ["Other"]
module_data["category"] = selected_categories
print(f"Module: {module_path} -> Categories: {selected_categories} (top probs: {[f'{p:.2f}' for p in sorted_probs[:3]]})")
# Сохранение результата
with open("modules.json", "w", encoding="utf-8") as f:
json.dump(modules, f, ensure_ascii=False, indent=2)
print("Done! Check modules_categorized.json.")

View File

@@ -1,8 +1,8 @@
__version__ = (3, 1, 1)
# meta banner: https://raw.githubusercontent.com/kamekuro/hikka-mods/main/banners/yamusic.png
# packurl: https://raw.githubusercontent.com/coddrago/assets/refs/heads/main/modules/yamusic.yml
# meta pic: https://raw.githubusercontent.com/kamekuro/hikka-mods/main/icons/yamusic.png
# meta developer: @codrago
# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png
# meta developer: @codrago_m
# old meta dev: @kamekuro xuesos
# scope: heroku_only
# scope: heroku_min 1.7.2
@@ -41,6 +41,7 @@ class Banners:
meta_info: str = "Music",
is_liked: bool = False,
repeat_mode: str = "NONE",
blur: int = 0,
):
self.title = title
self.artists = artists
@@ -52,6 +53,7 @@ class Banners:
self.meta_info = meta_info
self.is_liked = is_liked
self.repeat_mode = repeat_mode
self.blur = blur
def ultra(self) -> io.BytesIO:
WIDTH, HEIGHT = 2560, 1220
@@ -96,7 +98,9 @@ class Banners:
background = background.crop((0, offset, bg_w, offset + new_h))
background = background.resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
background = background.filter(ImageFilter.GaussianBlur(radius=0))
if self.blur > 0:
background = background.filter(ImageFilter.GaussianBlur(radius=self.blur))
dark_overlay = Image.new("RGBA", (WIDTH, HEIGHT), (0, 0, 0, 180))
background = Image.alpha_composite(background, dark_overlay)
@@ -296,30 +300,6 @@ class Banners:
(heart_x, icon_y_center + heart_size + 5),
]
if self.is_liked:
draw.ellipse(c1_box, fill="red", outline="red")
draw.ellipse(c2_box, fill="red", outline="red")
draw.polygon(tri_points, fill="red", outline="red")
else:
draw.ellipse(c1_box, fill=None, outline="red", width=3)
draw.ellipse(c2_box, fill=None, outline="red", width=3)
draw.line(
[
(heart_x - c_r * 2 + 1, icon_y_center),
(heart_x, icon_y_center + heart_size + 5),
],
fill="red",
width=3,
)
draw.line(
[
(heart_x + c_r * 2 - 1, icon_y_center),
(heart_x, icon_y_center + heart_size + 5),
],
fill="red",
width=3,
)
by = io.BytesIO()
background.save(by, format="PNG")
by.seek(0)
@@ -378,8 +358,13 @@ class YaMusicMod(loader.Module):
option="banner_version",
default="ultra",
doc=lambda: self.strings["_cfg"]["banner_version"],
validator=loader.validators.Choice(["old", "new", "ultra"]),
),)
validator=loader.validators.Choice(["ultra"]),
),
loader.ConfigValue(
option="blur",
default=0,
),
)
self.ym_client = None
self.device_id = "".join(random.choices(string.ascii_lowercase, k=16))
@@ -392,6 +377,7 @@ class YaMusicMod(loader.Module):
#"now_play", self._now_play_placeholder, "placeholder for nowplay music"
# Heroku 2.0.0 feature
#)
#utils.register_placeholder("duration", self._duration_placeholder, "progress bar")
if not self.get("guide_sent", False):
await self.inline.bot.send_message(self._tg_id, self.strings("iguide"))
@@ -437,7 +423,7 @@ class YaMusicMod(loader.Module):
me = await self._client.get_me()
self._premium = me.premium if hasattr(me, "premium") else False
@loader.loop(30)
@loader.loop(15)
async def autobio(self):
if not self.config["token"]:
self.autobio.stop()
@@ -547,6 +533,88 @@ class YaMusicMod(loader.Module):
),
)
async def _duration_placeholder(self):
"""Placeholder for {duration} with custom emoji bar"""
if not self.config["token"]:
return "No Token"
try:
now = await self.__get_now_playing()
if not now or now.get("paused"):
return "<code>Not Playing</code>"
duration = now.get("duration_ms", 0)
progress = now.get("progress_ms", 0)
if duration == 0:
return "0%"
percent = (progress / duration) * 100
s_less_10 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=6158923355173949539>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6158753257289158944>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_10_to_20 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=6159095673556840262>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6156933677214341691>⭐</emoji>"
"<emoji document_id=6158753257289158944>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_30_to_40 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6158923355173949539>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_over_50 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6156933677214341691>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_over_80 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
if percent < 10:
return s_less_10
elif percent < 20:
return s_10_to_20
elif percent < 30:
return s_10_to_20
elif percent < 40:
return s_30_to_40
elif percent < 50:
return s_30_to_40
elif percent < 80:
return s_over_50
else:
return s_over_80
except Exception as e:
return f"Error: {e}"
async def _download_bytes(self, url: str) -> typing.Optional[bytes]:
try:
async with aiohttp.ClientSession() as session:
@@ -686,8 +754,10 @@ class YaMusicMod(loader.Module):
meta_info=meta_info,
is_liked=is_liked,
repeat_mode=repeat_mode,
blur=self.config["blur"],
)
file = await utils.run_sync(
getattr(banners, self.config["banner_version"], banners.ultra)
)

View File

@@ -17,7 +17,6 @@ figlet
promoclaimer
passwordgen
send
lastfm
dbmod
chatmodule
stats

View File

@@ -1,124 +0,0 @@
# ---------------------------------------------------------------------------------
#░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
#░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
#░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
# Name: LastFM
# Description: Module for music from different services
# Author: @codrago_m
# ---------------------------------------------------------------------------------
# 🔒 Licensed under the GNU AGPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# ---------------------------------------------------------------------------------
# Author: @codrago
# Commands: nowplay
# scope: heroku_only
# meta developer: @codrago_m
# meta banner: https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png
# meta pic: https://envs.sh/Hob.webp
# ---------------------------------------------------------------------------------
from .. import loader, utils
from herokutl import events
import requests
import asyncio
@loader.tds
class lastfmmod(loader.Module):
"""Module for music from different services"""
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"username_lastfm",
None,
lambda: self.strings["_doc_username_lastfm"],
),
loader.ConfigValue(
"text",
"<emoji document_id=6007938409857815902>🎧</emoji> <b>now playing...</b>\n"
"<emoji document_id=5915480455603295660>🎶</emoji><b> playlist: </b><code>{song_album}</code>\n"
"<emoji document_id=5891249688933305846>🎵</emoji> <b>track:</b> <code>{song_name}</code>\n"
"<emoji document_id=5897554554894946515>🎤</emoji> <b>artist:</b> <code>{song_artist}</code>",
lambda: self.strings["_doc_text"],
),
)
strings = {
"name": "LastFm",
"loading":"<emoji document_id=5873204392429096339>⌨️</emoji> Loading song...",
"bot_no_result": "<emoji document_id=5465665476971471368>❌</emoji> Nothing found.\nTitle: {song_name}\nAuthor: {song_artist}\nAlbum:{song_album}",
"_doc_text": "The text that will be written next to the file",
"_doc_username_lastfm": "Your username from last.fm",
"nick_error": "<emoji document_id=5465665476971471368>❌</emoji> Put your nickname from last.fm",
"tutorial": "Go to last.fm and register.\nBE SURE to remember the username and password, they will come in handy later.\nLet's look at the VK version\nAfter that, go to the @vkxci channel, download VK X and log in to your VK account, then go to settings and click «Integrations», select Last FM.\nEnter the username and password.\nThen you're almost done!\nWrite <code>{prefix}fcfg lastfm username_lastfm</code> {username}\nUse the <code>{prefix}nowplay</code> command and enjoy life!",
}
strings_ru = {
"name": "LastFm",
"loading": "<emoji document_id=5873204392429096339>⌨️</emoji> Загрузка трека...",
"bot_no_result": "<emoji document_id=5465665476971471368>❌</emoji> Ничего не найдено.\nНазвание: {song_name}\nИсполнитель: {song_artist}\nАльбом: {song_album}",
"_doc_text": "Текст, который будет написан рядом с файлом",
"_doc_username_lastfm": "Ваш username с last.fm",
"nick_error": "<emoji document_id=5465665476971471368>❌</emoji> Укажите ваш никнейм с last.fm",
"tutorial": "Зайдите на last.fm и зарегистрируйтесь.\nОБЯЗАТЕЛЬНО запомните логин и пароль, они пригодятся позже.\nРассмотрим вариант для VK\nПосле этого зайдите в канал @vkxci, скачайте VK X и авторизуйтесь в своём аккаунте VK, затем зайдите в настройки и нажмите «Интеграции», выберите Last FM.\nВведите логин и пароль.\nЗатем вы почти закончили!\nНапишите <code>{prefix}fcfg lastfm username_lastfm</code> {username}\nИспользуйте команду <code>{prefix}nowplay</code> и наслаждайтесь жизнью!",
}
@loader.command(alias="np")
async def nowplay(self, message):
"""| send playing track"""
lastfm_username = self.config["username_lastfm"]
API_KEY = "460cda35be2fbf4f28e8ea7a38580730" # Облегчение жизни школьникам
if not lastfm_username:
response_text = self.strings["nick_error"]
await self.invoke("config", "lastfm", message=message)
await utils.answer(message, response_text)
else:
try:
current_track_url = f'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&nowplaying=true&user={lastfm_username}&api_key={API_KEY}&format=json'
response = requests.get(current_track_url)
data = response.json()
if 'recenttracks' in data and 'track' in data['recenttracks'] and data['recenttracks']['track']:
nowplaying_track = None
for track in data['recenttracks']['track']:
if '@attr' in track and 'nowplaying' in track['@attr']:
nowplaying_track = track
break
if nowplaying_track:
song_name = nowplaying_track.get('name', 'Unknown song')
song_artist = nowplaying_track.get('artist', {}).get('#text', 'Unknown Artist')
if nowplaying_track.get('album', {}).get('#text') == nowplaying_track.get('name'):
song_album = "single"
else:
song_album = nowplaying_track.get('album', {}).get('#text', 'Unknown Album')
response_text = f"/search {song_name} - {song_artist}"
try:
async with message.client.conversation("@LyaDownbot") as conv:
await conv.send_message(response_text)
while True:
response_bot = await conv.get_response()
if "Не удалось найти трек" in response_bot.text:
await utils.answer(message, self.strings["bot_no_result"])
return
if "Ищем треки..." in response_bot.text:
await utils.answer(message, self.strings["loading"])
if response_bot.media:
await message.client.send_file(message.chat_id, response_bot.media, caption = self.config["text"].format(song_artist=song_artist, song_album=song_album, song_name=song_name))
await message.delete()
return
except Exception as e:
await utils.answer(message, f"<pre><code class='language-python'>{e}</code></pre>")
except Exception as e:
await utils.answer(message, f"<pre><code class='language-python'>{e}</code></pre>")
@loader.command()
async def tutorl(self, message):
"""| tutorial"""
await utils.answer(message, self.strings['tutorial'].format(prefix = self.get_prefix(), username="{username}"))

View File

@@ -1,10 +1,13 @@
# meta developer: @codrago_m
# scope: heroku_min 2.0.0
import logging
from .. import utils, loader, main
from telethon.tl.functions.messages import MarkDialogUnreadRequest
from .. import loader, main, utils
logger = logging.getLogger("TagWatcher")
@@ -124,6 +127,7 @@ class TagWatcher(loader.Module):
description="Here will be notifications about mentions in chats.",
icon_emoji_id=5409025823388741707,
)
self.xdlib = await self.import_lib(
"https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/libs/xdlib.py",
suspend_on_error=True,

View File

@@ -106,7 +106,7 @@ class DeviceInfo(loader.Module):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"api_base_url",
"https://mobilespecs.fiksofficial.fun",
"https://gmsarena.vercel.app/",
lambda: "API Url",
validator=loader.validators.String()
),

View File

@@ -1,5 +1,5 @@
# -- version --
__version__ = (1, 2, 1)
__version__ = (1, 2, 2)
# -- version --
@@ -9,7 +9,7 @@ __version__ = (1, 2, 1)
# ██║╚██╔╝██║██╔══╝░░██╔══██║██║░░██║██║░░██║░░████╔═████║░░╚═══██╗░╚═══██╗
# ██║░╚═╝░██║███████╗██║░░██║██████╔╝╚█████╔╝░░╚██╔╝░╚██╔╝░██████╔╝██████╔╝
# ╚═╝░░░░░╚═╝╚══════╝╚═╝░░╚═╝╚═════╝░░╚════╝░░░░╚═╝░░░╚═╝░░╚═════╝░╚═════╝░
# © Copyright 2025
# © Copyright 2026
# ✈ https://t.me/mead0wssMods
@@ -33,14 +33,17 @@ class SenderGifts(loader.Module):
"checking_user": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка пользователя...",
"checking_balance": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка баланса...",
"user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден",
"gift_menu": "<emoji document_id=5931696400982088015>🎁</emoji> Выберите категорию подарков.\n\n<emoji document_id=6032693626394382504>👤</emoji> Пользователь: {}\n<emoji document_id=5873153278023307367>📄</emoji> Текст: {}\n<emoji document_id=5951810621887484519>⭐</emoji> Баланс: {} звезд",
"category_menu": "<emoji document_id=5931696400982088015>🎁</emoji> Подарки за {}\n\n<emoji document_id=6032693626394382504>👤</emoji> Пользователь: {}\n<emoji document_id=5873153278023307367>📄</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> Текст: {}",
"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> Подарок успешно отправлен!",
"not_enough_stars": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки подарка {}!",
"min_stars_error": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки минимального подарка!",
"no_available_gifts": "<emoji document_id=4958526153955476488>❌</emoji> Нет доступных подарков для вашего баланса",
"balance_error": "<emoji document_id=4958526153955476488>❌</emoji> Ошибка при проверке баланса",
"btn_public": "📢 Публично",
"btn_anon": "🕵️ Анонимно",
}
gift_categories = {
@@ -57,6 +60,7 @@ class SenderGifts(loader.Module):
{"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
{"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"},
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка"},
{"id": 5956217000635139069, "emoji": "🧸", "name": "Новогодний мишка"}
],
100: [
{"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"},
@@ -136,8 +140,10 @@ class SenderGifts(loader.Module):
if row:
buttons.append(row)
helper_msg = await self.inline.form("🪐", balance_msg)
await utils.answer(
balance_msg,
helper_msg,
self.strings["gift_menu"].format(
f"@{user.username}" if user.username else user.first_name,
text if text else "-",
@@ -153,8 +159,8 @@ class SenderGifts(loader.Module):
for gift in gifts:
row.append({
"text": gift["emoji"],
"callback": self._send_gift,
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance),
"callback": self._select_privacy,
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance, price),
})
if len(row) == 3:
buttons.append(row)
@@ -183,6 +189,34 @@ class SenderGifts(loader.Module):
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)
}
]
]
await call.edit(
self.strings["privacy_menu"].format(gift_emoji),
reply_markup=buttons
)
async def _back_to_categories(self, call, user_id, text, balance, msg_id):
try:
user = await self.client.get_entity(user_id)
@@ -216,7 +250,7 @@ class SenderGifts(loader.Module):
reply_markup=buttons
)
async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance):
async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance, hide_name):
try:
await call.edit(
self.strings["sending_gift"],
@@ -227,11 +261,11 @@ class SenderGifts(loader.Module):
self.client.parse_mode,
)
text, entities = parse_mode.parse(text)
user = await self.client.get_input_entity(user_id)
inv = InputInvoiceStarGift(
user,
gift_id,
hide_name=hide_name,
message=TextWithEntities(text, entities) if text else TextWithEntities("", [])
)
form = await self.client(GetPaymentFormRequest(inv))

126347
modules.json

File diff suppressed because one or more lines are too long

497
parse.py
View File

@@ -1,171 +1,376 @@
import os
import ast
import json
import os
import logging
from typing import Dict, Any, Optional, List
from clone_repos import repos
from typing import Dict
logging.basicConfig(level=logging.WARNING, format="%(message)s")
logger = logging.getLogger(__name__)
# TODO: ADD VENV IGNORE
def safe_unparse(node: ast.AST) -> str:
try:
return ast.unparse(node)
except Exception:
if hasattr(node, "id"):
return str(node.id)
return str(node)
def load_blacklist(file_path):
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
repositories = data.get("repositories", [])
blacklisted_modules = {}
def get_module_info(module_path):
"""Парсит Python-модуль и извлекает информацию о нем."""
with open(module_path, "r", encoding="utf-8") as f:
module_content = f.read()
for i in repositories:
path = i.get("path", "")
blacklist = i.get("blacklist", [])
if path and blacklist:
blacklisted_modules[path] = blacklist
meta_info = {"pic": None, "banner": None}
for line in module_content.split("\n"):
if line.startswith("# meta"):
key, value = line.replace("# meta ", "").split(": ")
meta_info[key] = value
return blacklisted_modules
tree = ast.parse(module_content)
def is_loader_tds(deco: ast.AST) -> bool:
return (
isinstance(deco, ast.Attribute)
and isinstance(deco.value, ast.Name)
and deco.value.id == "loader"
and deco.attr in {"tds", "translatable_docstring"}
)
def get_decorator_names(decorator_list):
return [ast.unparse(decorator) for decorator in decorator_list]
def extract_string_value(node: ast.AST) -> Optional[str]:
try:
if isinstance(node, ast.Constant) and isinstance(node.value, str):
return node.value
def extract_loader_command_args(decorator):
"""Извлекает аргументы `ru_doc` и `en_doc` из `@loader.command`."""
if (
isinstance(decorator, ast.Call)
and hasattr(decorator.func, "attr")
and decorator.func.attr == "command"
):
ru_doc = None
en_doc = None
for keyword in decorator.keywords:
if keyword.arg == "ru_doc":
ru_doc = ast.literal_eval(keyword.value)
elif keyword.arg == "en_doc":
en_doc = ast.literal_eval(keyword.value)
return ru_doc, en_doc
return None, None
if isinstance(node, ast.Str):
return node.s
result = {}
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
decorators = get_decorator_names(node.decorator_list)
is_tds_mod = [d for d in decorators if "loader.tds" in d]
if "Mod" not in node.name and not is_tds_mod:
if isinstance(node, ast.JoinedStr):
parts = []
for v in node.values:
if isinstance(v, ast.Constant) and isinstance(v.value, str):
parts.append(v.value)
elif isinstance(v, ast.FormattedValue):
parts.append("{" + safe_unparse(v.value) + "}")
return "".join(parts)
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Attribute):
return f"{safe_unparse(node.value)}.{node.attr}"
return None
except Exception:
return None
def extract_loader_command_args(decorator: ast.Call) -> Dict[str, Any]:
args = {"lang_docs": {}, "aliases": [], "usage": None}
try:
for kw in decorator.keywords:
arg_name = kw.arg
if not arg_name:
continue
if arg_name.endswith("_doc"):
lang = arg_name[:-4]
args["lang_docs"][lang] = extract_string_value(kw.value)
elif arg_name == "aliases":
try:
val = ast.literal_eval(kw.value)
if isinstance(val, (list, tuple)):
args["aliases"] = list(val)
except (ValueError, SyntaxError):
pass
elif arg_name == "usage":
args["usage"] = extract_string_value(kw.value)
except Exception:
pass
return args
class_docstring = ast.get_docstring(node)
class_info = {
"name": node.name,
"description": class_docstring,
"meta": meta_info,
"commands": [],
"new_commands": [],
}
def get_module_info(module_path: str) -> Optional[Dict[str, Any]]:
try:
with open(module_path, "r", encoding="utf-8") as f:
source = f.read()
except Exception as e:
logger.warning(f"Skipping {module_path}: read failed — {e}")
return None
for class_body_node in node.body:
if isinstance(class_body_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
decorators = get_decorator_names(class_body_node.decorator_list)
is_loader_command = [d for d in decorators if "command" in d]
if not is_loader_command and "cmd" not in class_body_node.name:
continue
source = source.lstrip('\ufeff')
source = ''.join(c for c in source if ord(c) >= 32 or c in '\n\r\t') if source else source
method_docstring = ast.get_docstring(class_body_node)
command_name = class_body_node.name
ru_doc, en_doc = None, None
meta = {"pic": None, "banner": None, "developer": None}
for line in source.splitlines():
line = line.strip()
if line.startswith("# meta "):
try:
key, val = line[len("# meta "):].split(":", 1)
meta[key.strip()] = val.strip()
except ValueError:
pass
for decorator in class_body_node.decorator_list:
ru_doc_tmp, en_doc_tmp = extract_loader_command_args(decorator)
if ru_doc_tmp:
ru_doc = ru_doc_tmp
if en_doc_tmp:
en_doc = en_doc_tmp
try:
tree = ast.parse(source, filename=module_path)
except SyntaxError as e:
logger.warning(f"Skipping {module_path}: syntax error — {e}")
return {
"name": module_path.split(os.sep)[-1].replace(".py", ""),
"description": "",
"cls_doc": {},
"meta": meta,
"commands": [],
"new_commands": [],
"inline_handlers": [],
"strings": {},
"has_on_load": False,
"has_on_unload": False,
"class_cmd_names": {},
}
descriptions = []
if method_docstring:
descriptions.append(method_docstring)
if ru_doc:
descriptions.append(ru_doc)
if en_doc:
descriptions.append(en_doc)
module_data = None
class_info["commands"].append(
{command_name: ' '.join(descriptions)}
)
command_name = command_name.replace('cmd', '')
class_info["new_commands"].append(
{
command_name: {
"ru_doc": ru_doc,
"en_doc": en_doc,
"doc": method_docstring,
}
}
)
result = class_info
return result
def parse_developers(base_dir: str) -> Dict[str, list]:
developers = {
"repo": set(), # используем set внутри функции
"channel": set()
}
for repo_url in repos:
repo_path = repo_url.replace("https://github.com/", "")
try:
owner, repo_name = repo_path.split("/")
developers["repo"].add(owner)
except ValueError:
print(f"Incorrect URL of repository: {repo_url}")
for node in ast.walk(tree):
if not isinstance(node, ast.ClassDef):
continue
for root, _, files in os.walk(base_dir):
for file in files:
if file.endswith(".py"):
file_path = os.path.join(root, file)
try:
module_info = get_module_info(file_path)
if module_info and "meta" in module_info:
developer = module_info["meta"].get('developer')
if developer: # Проверяем, что developer не None
# Разделяем строки с запятыми, &, | и пробелами
for dev in developer.replace(',', ' ').replace('&', ' ').replace('|', ' ').split():
# Добавляем только элементы, начинающиеся с @
if dev.startswith('@'):
developers["channel"].add(dev.strip())
except Exception as e:
print(f"Ошибка при парсинге файла {file_path}: {e}")
is_module_class = (
"Mod" in node.name or
any(is_loader_tds(d) for d in node.decorator_list) or
any(isinstance(d, ast.Name) and d.id == "loader" for d in node.decorator_list)
)
# Преобразуем set в list перед возвратом
return {
"repo": list(developers["repo"]),
"channel": list(developers["channel"])
if not is_module_class:
continue
info = {
"name": node.name,
"description": ast.get_docstring(node) or "",
"cls_doc": {},
"meta": meta,
"commands": [],
"new_commands": [],
"inline_handlers": [],
"strings": {},
"has_on_load": False,
"has_on_load": False,
"has_on_unload": False,
"class_cmd_names": {},
}
for item in node.body:
if isinstance(item, ast.Assign):
for target in item.targets:
if isinstance(target, ast.Name) and (target.id == "strings" or target.id.startswith("strings_")):
try:
lit = ast.literal_eval(item.value)
if isinstance(lit, dict):
if target.id == "strings":
info["strings"].update(lit)
if "_cls_doc" in lit:
info["cls_doc"]["default"] = lit["_cls_doc"]
else:
lang = target.id.split("_", 1)[1] if "_" in target.id else None
if lang:
for k, v in lit.items():
if isinstance(k, str) and isinstance(v, str):
if k == "_cls_doc":
info["cls_doc"][lang] = v
elif k.startswith("_cmd_doc_"):
rest = k[len("_cmd_doc_"):]
info["strings"][f"_cmd_doc_{lang}_{rest}"] = v
info["strings"][f"_cmd_doc_{rest}_{lang}"] = v
elif k.startswith("_ihandle_doc_"):
rest = k[len("_ihandle_doc_"):]
info["strings"][f"_ihandle_doc_{lang}_{rest}"] = v
info["strings"][f"_ihandle_doc_{rest}_{lang}"] = v
elif k.startswith("_cls_cmd_"):
info["class_cmd_names"][lang] = v
else:
info["strings"][f"{k}_{lang}"] = v
except Exception:
pass
if "_cls_doc" in info["strings"]:
info["cls_doc"]["default"] = info["strings"]["_cls_doc"]
for func in node.body:
if not isinstance(func, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
name = func.name
if name == "on_load":
info["has_on_load"] = True
continue
if name == "on_unload":
info["has_on_unload"] = True
continue
is_decorated = any(
isinstance(d, ast.Call) and hasattr(d.func, 'attr') and
d.func.attr in ("command", "inline_handler", "unrestricted", "owner")
for d in func.decorator_list
)
if name.startswith("_") and not is_decorated:
continue
cmd = {
"name": name,
"doc": ast.get_docstring(func) or "",
"lang_docs": {},
"aliases": [],
"usage": None,
"inline": False,
"is_inline_handler": False,
"decorators": [],
"cmd_names": {},
}
for dec in func.decorator_list:
if isinstance(dec, ast.Call) and hasattr(dec.func, 'attr'):
attr = dec.func.attr
if attr == "command":
cmd.update(extract_loader_command_args(dec))
elif attr == "inline_handler":
cmd["inline"] = True
cmd["is_inline_handler"] = True
elif attr in ("unrestricted", "owner", "support"):
cmd["decorators"].append(attr)
for stmt in func.body:
if isinstance(stmt, ast.Assign):
for target in stmt.targets:
if isinstance(target, ast.Attribute):
attr = target.attr
val = extract_string_value(stmt.value)
if not val:
continue
if attr == "_cmd":
cmd["name"] = val
elif attr == "_doc":
cmd["doc"] = val
elif attr == "_cls_doc":
info["cls_doc"]["default"] = val
elif attr.startswith("_cls_doc_"):
lang = attr[len("_cls_doc_"):]
info["cls_doc"][lang] = val
elif attr.startswith("_cmd_"):
lang = attr[len("_cmd_"):]
cmd["cmd_names"][lang] = val
is_command_name = "cmd" in name and not name.startswith("__")
if not (is_decorated or is_command_name):
continue
clean_name = cmd["name"].replace("cmd", "").replace("_", "")
descs = []
legacy_key = f"_cmd_doc_{clean_name}"
legacy_doc = info["strings"].get(legacy_key)
base_doc = legacy_doc if legacy_doc else cmd["doc"]
if base_doc:
descs.append(base_doc)
for lang, text in cmd["lang_docs"].items():
if text:
descs.append(f"({lang.upper()}) {text}")
for k, v in info["strings"].items():
if k.startswith("_cmd_doc_") and clean_name in k and v:
if k.endswith(f"_{clean_name}"):
lang_part = k[len("_cmd_doc_"):-len(f"_{clean_name}")-1]
if lang_part:
descs.append(f"({lang_part.upper()}) {v}")
elif k.startswith(f"_cmd_doc_{clean_name}_"):
lang_part = k[len(f"_cmd_doc_{clean_name}_"):]
if lang_part:
descs.append(f"({lang_part.upper()}) {v}")
full_desc = " | ".join(filter(None, descs))
info["commands"].append({clean_name: full_desc})
desc_map = {"default": legacy_doc or cmd["doc"]}
desc_map.update(cmd["lang_docs"])
for k, v in info["strings"].items():
if k.startswith("_cmd_doc_") and clean_name in k and v:
if k.endswith(f"_{clean_name}"):
lang_part = k[len("_cmd_doc_"):-len(f"_{clean_name}")-1]
if lang_part:
desc_map[lang_part] = v
elif k.startswith(f"_cmd_doc_{clean_name}_"):
lang_part = k[len(f"_cmd_doc_{clean_name}_"):]
if lang_part:
desc_map[lang_part] = v
info["new_commands"].append({
"name": clean_name,
"original_name": cmd["name"],
"description": desc_map,
"cmd_names": cmd["cmd_names"],
"aliases": cmd["aliases"],
"usage": cmd["usage"],
"inline": cmd["inline"],
"is_inline_handler": cmd["is_inline_handler"],
"decorators": cmd["decorators"],
})
if cmd["is_inline_handler"]:
inline_desc_map = {"default": cmd["doc"]}
inline_desc_map.update(cmd["lang_docs"])
for k, v in info["strings"].items():
if k.startswith("_ihandle_doc_") and clean_name in k and v:
if k.endswith(f"_{clean_name}"):
lang_part = k[len("_ihandle_doc_"):-len(f"_{clean_name}")-1]
if lang_part:
inline_desc_map[lang_part] = v
elif k.startswith(f"_ihandle_doc_{clean_name}_"):
lang_part = k[len(f"_ihandle_doc_{clean_name}_"):]
if lang_part:
inline_desc_map[lang_part] = v
info["inline_handlers"].append({
"name": clean_name,
"description": inline_desc_map,
"decorators": cmd["decorators"],
})
module_data = info
break
return module_data
def main():
base_dir = os.getcwd()
modules = {}
blacklisted_modules = load_blacklist("repositories.json")
for root, dirs, files in os.walk(base_dir):
dirs[:] = [d for d in dirs if d not in ("venv", ".venv", "env", ".env", ".git")]
for file in files:
if file.endswith(".py") and not file.startswith("_") and file not in blacklisted_modules.get(os.path.relpath(root, base_dir), []):
path = os.path.join(root, file)
try:
data = get_module_info(path)
if data:
rel = os.path.relpath(path, base_dir).replace("\\", "/")
modules[rel] = data
except Exception as e:
logger.error(f"Error processing {path}: {e}")
output = {
"modules": modules,
"meta": {
"total_modules": len(modules),
"generated_at": __import__("datetime").datetime.now().isoformat(),
}
}
with open("modules.json", "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
modules_data = {}
base_dir = os.getcwd()
print(f"modules.json written ({len(modules)} modules)")
for root, _, files in os.walk(base_dir):
for file in files:
if file.endswith(".py"):
file_path = os.path.join(root, file)
try:
module_info = get_module_info(file_path)
if module_info:
relative_path = os.path.relpath(file_path, base_dir)
modules_data[relative_path] = module_info
except Exception as e:
print(f"Ошибка при парсинге файла {file_path}: {e}")
developers = parse_developers(base_dir)
with open("modules.json", "w", encoding="utf-8") as json_file:
json.dump(modules_data, json_file, ensure_ascii=False, indent=2)
print("Файл modules.json создан!")
with open("developers.json", "w", encoding="utf-8") as json_file:
json.dump(developers, json_file, ensure_ascii=False, indent=2)
print("Файл developers.json создан!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,214 @@
# =======================================
# _ __ __ __ _
# | |/ /___ | \/ | ___ __| |___
# | ' // _ \ | |\/| |/ _ \ / _` / __|
# | . \ __/ | | | | (_) | (_| \__ \
# |_|\_\___| |_| |_|\___/ \__,_|___/
# @ke_mods
# =======================================
#
# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
# --------------------------------------
# https://creativecommons.org/licenses/by-nd/4.0/legalcode
# =======================================
# meta developer: @ke_mods
from .. import loader, utils
import requests
import io
import textwrap
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont, ImageOps
class Banners:
def __init__(
self,
title: str,
artists: list,
track_cover: bytes,
font
):
self.title = title
self.artists = ", ".join(artists) if isinstance(artists, list) else artists
self.track_cover = track_cover
self.font_url = font
def _get_font(self, size, font_bytes):
return ImageFont.truetype(io.BytesIO(font_bytes), size)
def _prepare_cover(self, size, radius):
cover = Image.open(io.BytesIO(self.track_cover)).convert("RGBA")
cover = cover.resize((size, size), Image.Resampling.LANCZOS)
mask = Image.new("L", (size, size), 0)
draw = ImageDraw.Draw(mask)
draw.rounded_rectangle((0, 0, size, size), radius=radius, fill=255)
output = Image.new("RGBA", (size, size), (0, 0, 0, 0))
output.paste(cover, (0, 0), mask=mask)
return output
def _prepare_background(self, w, h):
bg = Image.open(io.BytesIO(self.track_cover)).convert("RGBA")
bg = bg.resize((w, h), Image.Resampling.BICUBIC)
bg = bg.filter(ImageFilter.GaussianBlur(radius=20))
bg = ImageEnhance.Brightness(bg).enhance(0.4)
return bg
def horizontal(self):
W, H = 1500, 600
padding = 60
cover_size = 480
font_bytes = requests.get(self.font_url).content
title_font = self._get_font(55, font_bytes)
artist_font = self._get_font(45, font_bytes)
lfm_font = self._get_font(35, font_bytes)
img = self._prepare_background(W, H)
draw = ImageDraw.Draw(img)
cover = self._prepare_cover(cover_size, 30)
img.paste(cover, (padding, (H - cover_size) // 2), cover)
text_x = padding + cover_size + 60
text_y_start = 100
text_width_limit = W - text_x - padding
display_title = self.title
while title_font.getlength(display_title) > text_width_limit and len(display_title) > 0:
display_title = display_title[:-1]
if len(display_title) < len(self.title): display_title += ""
display_artist = self.artists
while artist_font.getlength(display_artist) > text_width_limit and len(display_artist) > 0:
display_artist = display_artist[:-1]
if len(display_artist) < len(self.artists): display_artist += ""
draw.text((text_x, text_y_start), display_title, font=title_font, fill="white")
draw.text((text_x, text_y_start + 70), display_artist, font=artist_font, fill="#B3B3B3")
bar_y = 480
draw.text((text_x, bar_y), "last.fm", font=lfm_font, fill="white")
by = io.BytesIO()
img.save(by, format="PNG")
by.seek(0)
by.name = "banner.png"
return by
def vertical(self):
W, H = 1000, 1500
padding = 80
cover_size = 800
font_bytes = requests.get(self.font_url).content
title_font = self._get_font(60, font_bytes)
artist_font = self._get_font(45, font_bytes)
lfm_font = self._get_font(35, font_bytes)
img = self._prepare_background(W, H)
draw = ImageDraw.Draw(img)
cover = self._prepare_cover(cover_size, 40)
cover_x = (W - cover_size) // 2
cover_y = 120
img.paste(cover, (cover_x, cover_y), cover)
text_area_y = cover_y + cover_size + 120
text_width_limit = W - (padding * 2)
display_title = self.title
while title_font.getlength(display_title) > text_width_limit and len(display_title) > 0:
display_title = display_title[:-1]
if len(display_title) < len(self.title): display_title += ""
display_artist = self.artists
while artist_font.getlength(display_artist) > text_width_limit and len(display_artist) > 0:
display_artist = display_artist[:-1]
if len(display_artist) < len(self.artists): display_artist += ""
title_w = title_font.getlength(display_title)
draw.text(((W - title_w) / 2, text_area_y), display_title, font=title_font, fill="white")
artist_w = artist_font.getlength(display_artist)
draw.text(((W - artist_w) / 2, text_area_y + 75), display_artist, font=artist_font, fill="#B3B3B3")
bar_y = text_area_y + 260
lfm_w = lfm_font.getlength("last.fm")
draw.text(((W - lfm_w) / 2, bar_y), "last.fm", font=lfm_font, fill="white")
by = io.BytesIO()
img.save(by, format="PNG")
by.seek(0)
by.name = "banner.png"
return by
@loader.tds
class lastfmmod(loader.Module):
"""Module for music from different services"""
strings = {
"name": "LastFm",
"no_track": "<emoji document_id=5465665476971471368>❌</emoji> <b>No track is currently playing</b>",
"_doc_text": "The text that will be written next to the file",
"_doc_username": "Your username from last.fm",
"nick_error": "<emoji document_id=5465665476971471368>❌</emoji> <b>Put your nickname from last.fm</b>",
"uploading": "<emoji document_id=5841359499146825803>🕔</emoji> <i>Uploading banner...</i>",
}
strings_ru = {
"name": "LastFm",
"no_track": "<emoji document_id=5465665476971471368>❌</emoji> <b>Сейчас ничего не играет</b>",
"_doc_text": "Текст, который будет написан рядом с файлом",
"_doc_username": "Ваш username с last.fm",
"nick_error": "<emoji document_id=5465665476971471368>❌</emoji> <b>Укажите ваш никнейм с last.fm</b>",
"uploading": "<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка баннера...</i>",
}
strings_jp = {
"name": "LastFm",
"no_track": "<emoji document_id=5465665476971471368>❌</emoji> <b>現在再生中のトラックはありません</b>",
"_doc_text": "ファイルの横に表示されるテキスト",
"_doc_username": "Last.fmのユーザー名",
"nick_error": "<emoji document_id=5465665476971471368>❌</emoji> <b>Last.fmのニックネームを入力してください</b>",
"uploading": "<emoji document_id=5841359499146825803>🕔</emoji> <i>バナーをアップロード中...</i>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue("username", None, lambda: self.strings["_doc_username"]),
loader.ConfigValue("custom_text", "<emoji document_id=5413612466208799435>🤩</emoji> <b>{song_name}</b> — <b>{song_artist}</b>", lambda: self.strings["_doc_text"]),
loader.ConfigValue("font", "https://raw.githubusercontent.com/kamekuro/assets/master/fonts/Onest-Bold.ttf", "Custom font URL (ttf)"),
loader.ConfigValue("banner_version", "horizontal", lambda: "Banner version", validator=loader.validators.Choice(["horizontal", "vertical"])),
)
@loader.command(alias="np")
async def nowplay(self, message):
"""| send playing track info"""
user = self.config["username"]
if not user:
await self.invoke("config", "lastfm", message=message)
return await utils.answer(message, self.strings["nick_error"])
try:
url = f'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&nowplaying=true&user={user}&api_key=460cda35be2fbf4f28e8ea7a38580730&format=json'
data = requests.get(url).json()
track = next((t for t in data.get('recenttracks', {}).get('track', []) if t.get('@attr', {}).get('nowplaying')), None)
if not track:
return await utils.answer(message, self.strings["no_track"])
name = track.get('name', 'Unknown')
artist = track.get('artist', {}).get('#text', 'Unknown')
caption = self.config["custom_text"].format(song_artist=artist, song_name=name)
imgs = track.get('image', [])
cov_url = next((i['#text'] for i in imgs if i['size'] == 'extralarge'), imgs[-1]['#text'] if imgs else None)
if not cov_url:
return await utils.answer(message, caption)
msg = await utils.answer(message, self.strings["uploading"])
cov_bytes = await utils.run_sync(requests.get, cov_url)
banners = Banners(name, artist, cov_bytes.content, self.config["font"])
file = await utils.run_sync(getattr(banners, self.config["banner_version"]))
await utils.answer(msg, caption, file=file)
except Exception as e:
await utils.answer(message, f"<pre><code class='language-python'>{e}</code></pre>")

View File

@@ -0,0 +1,48 @@
# =======================================
# _ __ __ __ _
# | |/ /___ | \/ | ___ __| |___
# | ' // _ \ | |\/| |/ _ \ / _` / __|
# | . \ __/ | | | | (_) | (_| \__ \
# |_|\_\___| |_| |_|\___/ \__,_|___/
# @ke_mods
# =======================================
#
# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
# --------------------------------------
# https://creativecommons.org/licenses/by-nd/4.0/legalcode
# =======================================
# meta developer: @ke_mods
import subprocess
from .. import loader, utils
@loader.tds
class NeofetchMod(loader.Module):
strings = {
"name": "Neofetch",
"not_installed": "<b>Please, install</b> <i>Neofetch</i> <b>package</b>",
}
strings_ru = {
"not_installed": "<b>Пожалуйста, установите пакет</b> <i>Neofetch</i>",
}
strings_ua = {
"not_installed": "<b>Будь ласка, встановіть пакет<b> <i>Neofetch</i>",
}
@loader.command(
ru_doc="- запустить команду neofetch",
ua_doc="- запустити команду neofetch",
)
async def neofetchcmd(self, message):
"""- run neofetch command"""
try:
result = subprocess.run(["neofetch", "--stdout"], capture_output=True, text=True)
output = result.stdout
await utils.answer(message, f"<pre>{utils.escape_html(output)}</pre>")
except FileNotFoundError:
await utils.answer(message, self.strings("not_installed"))

View File

@@ -0,0 +1,211 @@
# =======================================
# _ __ __ __ _
# | |/ /___ | \/ | ___ __| |___
# | ' // _ \ | |\/| |/ _ \ / _` / __|
# | . \ __/ | | | | (_) | (_| \__ \
# |_|\_\___| |_| |_|\___/ \__,_|___/
# @ke_mods
# =======================================
#
# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
# --------------------------------------
# https://creativecommons.org/licenses/by-nd/4.0/legalcode
# =======================================
# meta developer: @ke_mods
# requires: pillow
import io
import asyncio
from telethon import functions, types
from PIL import Image
from .. import loader, utils
@loader.tds
class PicToStoriesMod(loader.Module):
"""Grid for stories"""
strings = {
"name": "PicToStories",
"no_rep": (
"<emoji document_id=5879813604068298387>❗️</emoji> "
"<b>Reply to photo!</b>"
),
"work": (
"<emoji document_id=5841359499146825803>🕔</emoji> "
"<b>Processing...</b>"
),
"done": (
"<emoji document_id=5776375003280838798>✅</emoji> "
"<b>Done! Check your profile.</b>"
),
"err": (
"<emoji document_id=5778527486270770928>❌</emoji> "
"<b>Error:</b> {}"
),
}
strings_ru = {
"no_rep": (
"<emoji document_id=5879813604068298387>❗️</emoji> "
"<b>Реплай на фото!</b>"
),
"work": (
"<emoji document_id=5841359499146825803>🕔</emoji> "
"<b>Обрабатываю...</b>"
),
"done": (
"<emoji document_id=5776375003280838798>✅</emoji> "
"<b>Готово! Проверяй профиль.</b>"
),
"err": (
"<emoji document_id=5778527486270770928>❌</emoji> "
"<b>Ошибка:</b> {}"
),
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"period",
48,
lambda: "Visibility period in hours",
validator=loader.validators.Integer(),
),
loader.ConfigValue(
"blacklist",
[],
lambda: "Blacklisted user IDs",
validator=loader.validators.Series(loader.validators.Integer()),
),
loader.ConfigValue(
"cooldown",
0,
lambda: "Cooldown between stories in seconds",
validator=loader.validators.Integer(minimum=0),
),
)
@loader.command(ru_doc="<реплай на фото> [название альбома] - сделать сетку")
async def ptscmd(self, message):
"""<reply to photo> [album name] - make grid"""
args = utils.get_args_raw(message)
reply = await message.get_reply_message()
if not reply or not reply.media:
await utils.answer(message, self.strings("no_rep"))
return
try:
image_bytes = await reply.download_media(file=bytes)
img = Image.open(io.BytesIO(image_bytes))
except Exception as e:
await utils.answer(message, self.strings("err").format(e))
return
await utils.answer(message, self.strings("work"))
w, h = img.size
curr_ratio = w / h
variants = [
(5 / 4, 2),
(4 / 5, 3),
(3 / 5, 4),
(9 / 16, 5)
]
best_ratio, rows = min(variants, key=lambda x: abs(curr_ratio - x[0]))
new_h = int(w / best_ratio)
img = img.resize((w, new_h), Image.LANCZOS)
w, h = img.size
parts = []
pw, ph = w // 3, h // rows
for r in range(rows):
for c in range(3):
x, y = c * pw, r * ph
parts.append(img.crop((x, y, x + pw, y + ph)))
parts.reverse()
privacy = [types.InputPrivacyValueAllowAll()]
if self.config["blacklist"]:
entities = []
for uid in self.config["blacklist"]:
try:
entities.append(await self.client.get_input_entity(uid))
except Exception:
continue
if entities:
privacy.append(types.InputPrivacyValueDisallowUsers(users=entities))
story_ids = []
for i, p in enumerate(parts):
out = io.BytesIO()
p.save(out, "JPEG", quality=95)
out.seek(0)
uploaded_file = await self.client.upload_file(out, file_name="s.jpg")
res = await self.client(
functions.stories.SendStoryRequest(
peer=types.InputPeerSelf(),
media=types.InputMediaUploadedPhoto(uploaded_file),
privacy_rules=privacy,
period=self.config["period"] * 3600,
)
)
sid = next(
(
u.story_id if hasattr(u, "story_id") else u.id
for u in res.updates
if hasattr(u, "story_id") or hasattr(u, "id")
),
None,
)
if sid:
story_ids.append(sid)
if self.config["cooldown"] > 0 and i < len(parts) - 1:
await asyncio.sleep(self.config["cooldown"])
if not story_ids:
return
if args:
all_albums = await self.client(
functions.stories.GetAlbumsRequest(peer=types.InputPeerSelf(), hash=0)
)
target = next(
(a for a in all_albums.albums if getattr(a, 'title', '') == args),
None
)
if target:
await self.client(
functions.stories.UpdateAlbumRequest(
peer=types.InputPeerSelf(),
album_id=target.album_id,
add_stories=story_ids,
)
)
else:
await self.client(
functions.stories.CreateAlbumRequest(
peer=types.InputPeerSelf(),
stories=story_ids,
title=args,
)
)
else:
await self.client(
functions.stories.TogglePinnedRequest(
peer=types.InputPeerSelf(), id=story_ids, pinned=True
)
)
await utils.answer(message, self.strings("done"))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
# =======================================
# _ __ __ __ _
# | |/ /___ | \/ | ___ __| |___
# | ' // _ \ | |\/| |/ _ \ / _` / __|
# | . \ __/ | | | | (_) | (_| \__ \
# |_|\_\___| |_| |_|\___/ \__,_|___/
# @ke_mods
# =======================================
#
# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
# --------------------------------------
# https://creativecommons.org/licenses/by-nd/4.0/legalcode
# =======================================
# meta developer: @ke_mods
from .. import loader, utils
from telethon.tl.types import ChatBannedRights
from telethon.tl.functions.channels import EditBannedRequest
from telethon.tl.types import ChannelParticipantsKicked
@loader.tds
class UnbanAllMod(loader.Module):
strings = {
"name": "UnbanAll",
"no_rights": "<b>❌ I don't have administrator rights to remove restrictions.</b>",
"success": "<b>✅ All banned chat members have been unbanned.</b>",
"unban_in_process": "<b>👀 Unbanning users...</b>",
"no_banned": "<b> There are no banned members in this chat.</b>",
"error_occured": "<b>💢 An error occurred while unbanning user <code>{}</code>:</b>\n<code>{}</code>",
}
strings_ru = {
"no_rights": "<b>❌ У меня нет прав администратора для снятия ограничений.</b>",
"success": "<b>✅ Все забаненные участники чата были разблокированы.</b>",
"unban_in_process": "<b>👀 Разбаниваю пользователей...</b>",
"no_banned": "<b> В этом чате нет забаненных участников.</b>",
"error_occured": "<b>💢 Произошла ошибка при разблокировке пользователя <code>{}</code>:</b>\n<code>{}</code>",
}
@loader.command(ru_doc="- Разбанить всех забаненных пользователей")
async def unbanallcmd(self, message):
"""- Unban all banned users"""
chat = await message.get_chat()
if not chat.admin_rights and not chat.creator:
await utils.answer(message, self.strings("no_rights"))
return
await utils.answer(message, self.strings("unban_in_process"))
no_banned = True
async for user in self.client.iter_participants(
message.chat_id, filter=ChannelParticipantsKicked
):
no_banned = False
try:
await self.client(EditBannedRequest(
message.chat_id,
user.id,
ChatBannedRights(until_date=0)
))
except Exception as e:
await utils.answer(message, self.strings("error_occured").format(user.id, e))
pass
if no_banned:
await utils.answer(message, self.strings("no_banned"))
return
await utils.answer(message, self.strings("success"))

View File

@@ -0,0 +1,7 @@
Neofetch
randomanimepic
SpotifyMod
UnbanAll
voicetotext
LastFm
PicToStories

View File

@@ -0,0 +1,65 @@
# =======================================
# _ __ __ __ _
# | |/ /___ | \/ | ___ __| |___
# | ' // _ \ | |\/| |/ _ \ / _` / __|
# | . \ __/ | | | | (_) | (_| \__ \
# |_|\_\___| |_| |_|\___/ \__,_|___/
# @ke_mods
# =======================================
#
# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
# --------------------------------------
# https://creativecommons.org/licenses/by-nd/4.0/legalcode
# =======================================
# meta developer: @ke_mods
import requests
import asyncio
import logging
import traceback
from logging import basicConfig
from .. import loader, utils
basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@loader.tds
class RandomAnimePicMod(loader.Module):
strings = {
"name": "RandomAnimePic",
"img": "<emoji document_id=4916036072560919511>✅</emoji> <b>Your anime pic</b>\n<emoji document_id=5877465816030515018>🔗</emoji> <b>URL:</b> {}",
"loading": "<emoji document_id=4911241630633165627>✨</emoji> <b>Loading image...</b>",
"error": "<emoji document_id=5116151848855667552>🚫</emoji> <b>An unexpected error occurred...</b>",
}
strings_ru = {
"img": "<emoji document_id=4916036072560919511>✅</emoji> <b>Ваша аниме-картинка</b>\n<emoji document_id=5877465816030515018>🔗</emoji> <b>Ссылка:</b> {}",
"loading": "<emoji document_id=4911241630633165627>✨</emoji> <b>Загрузка изображения...</b>",
"error": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Произошла непредвиденная ошибка...</b>",
}
@loader.command(
ru_doc="- получить рандомную аниме-картинку 👀"
)
async def rapiccmd(self, message):
"""- fetch random anime-pic 👀"""
await utils.answer(message, self.strings("loading"))
try:
res = requests.get("https://api.nekosia.cat/api/v1/images/cute?count=1")
res.raise_for_status()
data = res.json()
image_url = data['image']['original']['url']
await asyncio.sleep(2)
await utils.answer(message, self.strings("img").format(image_url), file=image_url, reply_to=message.reply_to_msg_id)
except Exception:
logger.error("Error fetching random anime pic: %s", traceback.format_exc())
await utils.answer(message, self.strings("error"))
await asyncio.sleep(5)

View File

@@ -0,0 +1,77 @@
# =======================================
# _ __ __ __ _
# | |/ /___ | \/ | ___ __| |___
# | ' // _ \ | |\/| |/ _ \ / _` / __|
# | . \ __/ | | | | (_) | (_| \__ \
# |_|\_\___| |_| |_|\___/ \__,_|___/
# @ke_mods
# =======================================
#
# LICENSE: CC BY-ND 4.0 (Attribution-NoDerivatives 4.0 International)
# --------------------------------------
# https://creativecommons.org/licenses/by-nd/4.0/legalcode
# =======================================
# meta developer: @ke_mods
# scope: ffmpeg
# requires: pydub SpeechRecognition
from .. import loader, utils
import os
import speech_recognition as sr
from pydub import AudioSegment
@loader.tds
class VoiceToTextMod(loader.Module):
strings = {
"name": "VoiceToText",
"process_text": "<emoji document_id=4911241630633165627>✨</emoji> <b>Recognizing the message text...</b>",
"vtt_success": "<emoji document_id=5116110535565247270>🔥</emoji> <b>Recognized text:</b>\n<blockquote expandable>{}</blockquote>",
"vtt_failure": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Failed to recognize the message.</b>",
"vtt_request_error": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Error when contacting the recognition service:</b>\n<code>{}</code>",
"vtt_invalid": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Please reply to a voice or video message with the command</b> <code>{}vtt</code>",
"vtt_successful": "<emoji document_id=4916036072560919511>✅</emoji> <b>Text recognized successfully</b>",
}
strings_ru = {
"process_text": "<emoji document_id=4911241630633165627>✨</emoji> <b>Распознаю текст сообщения...</b>",
"vtt_success": "<emoji document_id=5116110535565247270>🔥</emoji> <b>Распознанный текст:</b>\n<blockquote expandable>{}</blockquote>",
"vtt_failure": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Не удалось распознать сообщение.</b>",
"vtt_request_error": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Ошибка при обращении к сервису распознавания:</b>\n<code>{}</code>",
"vtt_invalid": "<emoji document_id=5116151848855667552>🚫</emoji> <b>Пожалуйста, ответьте на голосовое или видеосообщение командой</b> <code>{}vtt</code>",
"vtt_successful": "<emoji document_id=4916036072560919511>✅</emoji> <b>Текст успешно распознан</b>",
}
@loader.command(
ru_doc="- распознает текст из голосового или видеосообщения.",
)
async def vttcmd(self, message):
"""- recognizes text from voice or video messages."""
reply = await message.get_reply_message()
if not reply or not (reply.voice or reply.video_note):
await utils.answer(message, self.strings["vtt_invalid"].format(self.get_prefix()))
return
msg = await utils.answer(
message, self.strings["process_text"], reply_to=message.id
)
media_file = await reply.download_media()
wav_file = media_file.replace('.mp4', '.wav') if reply.video_note else media_file.replace('.oga', '.wav')
try:
AudioSegment.from_file(media_file).export(wav_file, format='wav')
recognizer = sr.Recognizer()
with sr.AudioFile(wav_file) as source:
audio_data = recognizer.record(source)
try:
text = recognizer.recognize_google(audio_data, language='ru-RU')
await utils.answer(msg, self.strings["vtt_success"].format(text))
except sr.UnknownValueError:
await utils.answer(msg, self.strings["vtt_failure"])
except sr.RequestError as e:
await utils.answer(msg, self.strings["vtt_request_error"].format(e))
finally:
os.remove(media_file)
os.remove(wav_file)

View File

@@ -2,191 +2,243 @@
"repositories": [
{
"url": "https://github.com/DziruModules/hikkamods",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/kamolgks/Hikkamods",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/thomasmod/hikkamods",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/SkillsAngels/Modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/Sad0ff/modules-ftg",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/Yahikoro/Modules-for-FTG",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/KeyZenD/modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/AlpacaGang/ftg-modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/trololo65/Modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/Ijidishurka/modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/Fl1yd/FTG-Modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/D4n13l3k00/FTG-Modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/iamnalinor/FTG-modules",
"tags": ["hikkatrusted", "nonactive"]
"tags": ["hikkatrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/SekaiYoneya/modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/GeekTG/FTG-Modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/Den4ikSuperOstryyPer4ik/Astro-modules",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/vsecoder/hikka_modules",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/sqlmerr/hikka_mods",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/N3rcy/modules",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/KorenbZla/HikkaModules",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/MuRuLOSE/HikkaModulesRepo",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/coddrago/modules",
"tags": ["herokutrusted", "hikkatrusted"]
"tags": ["herokutrusted", "hikkatrusted"],
"blacklist": []
},
{
"url": "https://github.com/1jpshiro/hikka-modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/MoriSummerz/ftg-mods",
"tags": ["hikkatrusted", "nonactive"]
"tags": ["hikkatrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/anon97945/hikka-mods",
"tags": ["hikkatrusted", "nonactive"]
"tags": ["hikkatrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/dorotorothequickend/DorotoroModules",
"tags": ["hikkatrusted", "nonlongermaintained"]
"tags": ["hikkatrusted", "nonlongermaintained"],
"blacklist": []
},
{
"url": "https://github.com/AmoreForever/amoremods",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/idiotcoders/idiotmodules",
"tags": ["hikkatrusted", "herokutrusted", "nonactive"]
"tags": ["hikkatrusted", "herokutrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/CakesTwix/Hikka-Modules",
"tags": ["hikkatrusted", "nonactive"]
"tags": ["hikkatrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/archquise/H.Modules",
"tags": ["hikkatrusted", "nonactive"]
"tags": ["hikkatrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/GD-alt/mm-hikka-mods",
"tags": ["hikkatrusted", "herokutrusted", "nonactive"]
"tags": ["hikkatrusted", "herokutrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/HitaloSama/FTG-modules-repo",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/SekaiYoneya/Friendly-telegram",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/blazedzn/ftg-modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/hikariatama/ftg",
"tags": ["hikkatrusted", "nonactive"]
"tags": ["hikkatrusted", "nonactive"],
"blacklist": []
},
{
"url": "https://github.com/m4xx1m/FTG",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/skillzmeow/skillzmods_hikka",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/fajox1/famods",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/TheKsenon/MyHikkaModules",
"tags": ["hikkatrusted", "herokutrusted"]
"tags": ["hikkatrusted", "herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/cryptexctl/modules-mirror",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/Ruslan-Isaev/modules",
"tags": ["herokutrusted"]
"tags": ["herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/shadowhikka/sh.modules",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/fiksofficial/python-modules",
"tags": ["herokutrusted"]
"tags": ["herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/mead0wsss/mead0wsMods",
"tags": ["herokutrusted"]
"tags": ["herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/SenkoGuardian/SenModules",
"tags": ["herokutrusted"]
"tags": ["herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/ZetGoHack/nullmod",
"tags": ["herokutrusted"]
"tags": ["herokutrusted"],
"blacklist": []
},
{
"url": "https://github.com/yummy1gay/limoka",
"tags": []
"tags": [],
"blacklist": []
},
{
"url": "https://github.com/radiocycle/Modules",
"tags": ["newbie"],
"blacklist": []
}
]
}

195
update_diffs.py Normal file
View File

@@ -0,0 +1,195 @@
import asyncio
import aiohttp
import argparse
import subprocess
import os
import tempfile
from pathlib import Path
parser = argparse.ArgumentParser(description="Update Diffs Script")
parser.add_argument(
"--token",
type=str,
required=True,
help="Token of Telegram bot",
)
parser.add_argument(
"--api_url",
type=str,
default="https://api.telegram.org",
help="API URL of Telegram API",
)
parser.add_argument(
"--chat_id",
type=str,
required=True,
help="Chat ID to send updates to",
)
parser.add_argument(
"--base_commit",
type=str,
default="HEAD~1",
help="Base commit to compare against",
)
arguments = parser.parse_args()
async def send_message(session, text):
"""Send a text message to the channel"""
url = f"{arguments.api_url}/bot{arguments.token}/sendMessage"
data = {
'chat_id': arguments.chat_id,
'text': text,
'parse_mode': 'Markdown',
}
async with session.post(url, data=data) as response:
return await response.json()
async def send_document(session, file_path, caption=None):
"""Send a document to the channel"""
url = f"{arguments.api_url}/bot{arguments.token}/sendDocument"
with open(file_path, 'rb') as f:
data = aiohttp.FormData()
data.add_field('chat_id', arguments.chat_id)
data.add_field('document', f, filename=os.path.basename(file_path))
data.add_field('parse_mode', 'HTML')
if caption:
data.add_field('caption', caption)
data.add_field('parse_mode', 'Markdown')
async with session.post(url, data=data) as response:
return await response.json()
def get_changed_files(base_commit):
"""Get list of changed files between commits"""
try:
result = subprocess.check_output(
['git', 'diff', '--name-only', base_commit, 'HEAD'],
cwd=os.getcwd()
).decode().strip().split('\n')
return [f for f in result if f]
except subprocess.CalledProcessError:
return []
def get_file_diff(file_path, base_commit):
"""Get diff for a specific file"""
try:
diff = subprocess.check_output(
['git', 'diff', base_commit, 'HEAD', '--', file_path],
cwd=os.getcwd()
).decode()
return diff
except subprocess.CalledProcessError:
return ""
def is_module_file(file_path):
"""Check if file is a Python module in a modules directory"""
# Check if it's a .py file and in a modules-like directory
return file_path.endswith('.py') and any(
part in file_path.lower() for part in [
'modules', 'mods', 'ftg', 'hikka'
]
)
def extract_module_name(file_path):
"""Extract module name from file path"""
return Path(file_path).stem
async def main():
changed_files = get_changed_files(arguments.base_commit)
if not changed_files:
print("No changes detected")
return
# Filter for module files only
module_files = [f for f in changed_files if is_module_file(f)]
if not module_files:
print("No module changes detected")
return
async with aiohttp.ClientSession() as session:
for file_path in module_files:
try:
module_name = extract_module_name(file_path)
# Create message with raw GitHub URL
github_url = f"https://raw.githubusercontent.com/MuRuLOSE/limoka/refs/heads/main/{file_path}"
try:
new_hash = subprocess.check_output(
['git', 'rev-list', '-n', '1', 'HEAD', '--', file_path],
cwd=os.getcwd()
).decode().strip()
except Exception:
new_hash = 'HEAD'
try:
old_hash = subprocess.check_output(
['git', 'rev-list', '-n', '1', arguments.base_commit, '--', file_path],
cwd=os.getcwd()
).decode().strip()
except Exception:
old_hash = arguments.base_commit
diff_url = f"https://github.com/MuRuLOSE/limoka/compare/{old_hash}...{new_hash}.diff"
message = (
f"🪼 <b>Module <code>{module_name}</code> changes approved</b>\n\n"
f"<b><a href=\"{github_url}\">File URL</a></b> | "
f"<b><a href=\"{diff_url}\">Diff URL</a></b>"
)
# Get diff
diff = get_file_diff(file_path, arguments.base_commit)
if not diff:
print(f"Skipping {file_path} - no diff content")
continue
# Create temporary file with diff using only module name
diff_filename = f"{module_name}.diff"
with tempfile.NamedTemporaryFile(
mode='w',
suffix='',
prefix='',
delete=False,
encoding='utf-8',
dir=tempfile.gettempdir()
) as tmp_file:
tmp_file.write(diff)
tmp_file_path = tmp_file.name
try:
# Rename temp file to have proper name
final_path = os.path.join(tempfile.gettempdir(), diff_filename)
os.rename(tmp_file_path, final_path)
# Send diff as document with full message as caption
doc_result = await send_document(
session,
final_path,
caption=message
)
print(f"Sent diff for {module_name}: {doc_result}")
except Exception as e:
print(f"Error sending {module_name}: {e}")
finally:
# Cleanup temp files
if os.path.exists(tmp_file_path):
try:
os.remove(tmp_file_path)
except:
pass
final_path = os.path.join(tempfile.gettempdir(), diff_filename)
if os.path.exists(final_path):
try:
os.remove(final_path)
except:
pass
except Exception as e:
print(f"Error processing {file_path}: {e}")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,4 +1,4 @@
__version__ = (1, 1, 1, 1)
__version__ = (1, 2, 0, 0)
# This file is a part of Hikka Userbot!
# This product includes software developed by t.me/Fl1yd and t.me/spypm.
@@ -15,6 +15,10 @@ __version__ = (1, 1, 1, 1)
# scope: hikka_only
# scope: hikka_min 1.6.3
# Changelog v1.2:
# - Added: Proxy for users from RF
# - Fixed: Correct reply author resolving for forwarded messages
# █▄█ █░█ █▀▄▀█ █▀▄▀█ █▄█   █▀▄▀█ █▀█ █▀▄ █▀
# ░█░ █▄█ █░▀░█ █░▀░█ ░█░   █░▀░█ █▄█ █▄▀ ▄█
@@ -194,6 +198,12 @@ class Quotes(loader.Module):
validator=loader.validators.Integer(minimum=1,maximum=50)),
loader.ConfigValue("endpoint","https://kok.gay/gayotes/generate",
lambda:"URL API-эндпоинта (можешь поднять локально - github.com/yummy1gay/quote-api)",
validator=loader.validators.Link()),
loader.ConfigValue("use_rf_proxy", False,
lambda:'Включает прокси для РФ, если основной эндпоинт возвращает ошибку "Нетворк еррорь", и при этом сервер с юзерботом находится в России или ты сам сидишь в России с ограниченным доступом к зарубежным ресурсам (Termux / UserLAnd)',
validator=loader.validators.Boolean()),
loader.ConfigValue("rf_endpoint", "https://ru.kok.gay/gayotes/generate",
lambda:"URL API-эндпоинта для РФ",
validator=loader.validators.Link()))
async def client_ready(self, client, db):
@@ -226,10 +236,11 @@ class Quotes(loader.Module):
"format": "webp" if not doc else "png", "type": self.config["type"]}
await utils.answer(st,self.strings["api_processing"])
r=await Dick.post(f"{self.config['endpoint']}.webp",pay)
endpoint=self.config['rf_endpoint'] if self.config['use_rf_proxy'] else self.config['endpoint']
r=await Dick.post(f"{endpoint}.webp",pay)
if not r or r.status_code!=200:
try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь"
except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь"
try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
return await utils.answer(st,self.strings["api_error"].format(err))
buf=io.BytesIO(r.content); buf.name="YgQuote"+(".png" if doc else ".webp")
@@ -259,10 +270,11 @@ class Quotes(loader.Module):
"format": "webp","type":self.config["type"]}
await utils.answer(st,self.strings["api_processing"])
r=await Dick.post(f"{self.config['endpoint']}.webp",dickk)
endpoint=self.config['rf_endpoint'] if self.config['use_rf_proxy'] else self.config['endpoint']
r=await Dick.post(f"{endpoint}.webp",dickk)
if not r or r.status_code!=200:
try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь"
except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь"
try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
return await utils.answer(st,self.strings["api_error"].format(err))
buf=io.BytesIO(r.content); buf.name="YgQuote.webp"
@@ -289,7 +301,8 @@ class Quotes(loader.Module):
try:
r=await mm.get_reply_message()
if r:
rname=telethon.utils.get_display_name(r.sender)
ruser = await self.who(r)
rname=telethon.utils.get_display_name(ruser)
rtxt=Dick.desc(r,True)
if r.raw_text: rtxt=(rtxt+". "+r.raw_text) if rtxt else r.raw_text
rb={"name":rname,"text":rtxt or "","entities":Dick.ents(r.entities),