mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-18 15:14:18 +02:00
Compare commits
2 Commits
update-sub
...
update-sub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
086ad660ba | ||
|
|
e6f45bd09e |
88
.github/workflows/ci.yml
vendored
88
.github/workflows/ci.yml
vendored
@@ -10,10 +10,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
workflow_dispatch: # Allows manual triggering from GitHub UI
|
workflow_dispatch: # Allows manual triggering from GitHub UI
|
||||||
|
|
||||||
|
|
||||||
# Environment variables available to all jobs
|
# Environment variables available to all jobs
|
||||||
env:
|
env:
|
||||||
BRANCH_NAME: "update-submodules_${{ github.sha }}"
|
BRANCH_NAME: "update-submodules_${{ github.sha }}"
|
||||||
@@ -139,8 +137,9 @@ jobs:
|
|||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install requests
|
pip install requests scikit-learn tqdm
|
||||||
python3 parse.py
|
python3 parse.py
|
||||||
|
python3 categories.py
|
||||||
git add modules.json
|
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 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}"
|
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${REPO_URL}"
|
||||||
@@ -207,54 +206,61 @@ jobs:
|
|||||||
echo "Branch ${{ env.BRANCH_NAME }} does not exist in remote repository, skipping PR creation."
|
echo "Branch ${{ env.BRANCH_NAME }} does not exist in remote repository, skipping PR creation."
|
||||||
fi
|
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:
|
backup:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||||
needs: parse
|
needs: parse
|
||||||
steps:
|
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
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: ${{ env.GIT_DEPTH }}
|
fetch-depth: ${{ env.GIT_DEPTH }}
|
||||||
- name: Set up Python 3.9
|
- name: Create and send backup to Telegram
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.9'
|
|
||||||
- name: Install Python dependencies
|
|
||||||
run: pip install aiohttp
|
|
||||||
- name: Run backup script
|
|
||||||
env:
|
env:
|
||||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
run: |
|
run: |
|
||||||
python backup.py --token ${{ secrets.TELEGRAM_BOT_TOKEN }} --chat_id ${{ secrets.TELEGRAM_CHAT_ID }}
|
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!"
|
||||||
|
|||||||
1420
LimokaLegacy.py
1420
LimokaLegacy.py
File diff suppressed because it is too large
Load Diff
@@ -23,4 +23,4 @@ class K(loader.Module):
|
|||||||
"""K"""
|
"""K"""
|
||||||
raise Exception("Testing error handling")
|
raise Exception("Testing error handling")
|
||||||
await utils.answer(message, "K")
|
await utils.answer(message, "K")
|
||||||
# why FUCK YOU BILL GATES ЛЛЛЛ
|
# why
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,267 +0,0 @@
|
|||||||
#░░░███░███░███░███░███
|
|
||||||
#░░░░░█░█░░░░█░░█░░░█░█
|
|
||||||
#░░░░█░░███░░█░░█░█░█░█
|
|
||||||
#░░░█░░░█░░░░█░░█░█░█░█
|
|
||||||
#░░░███░███░░█░░███░███
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
Chess
|
Chess
|
||||||
HaremManager
|
HaremManager
|
||||||
SchedulePlus
|
SchedulePlus
|
||||||
Gradientor
|
|
||||||
|
|||||||
74
backup.py
74
backup.py
@@ -1,74 +0,0 @@
|
|||||||
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())
|
|
||||||
126
categories.py
Normal file
126
categories.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
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.")
|
||||||
@@ -3,18 +3,58 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import json
|
|
||||||
|
|
||||||
def parse_repos(file_path: str) -> list:
|
repos = [
|
||||||
"""Parse repository URLs from a given json file."""
|
"https://github.com/DziruModules/hikkamods",
|
||||||
repos = []
|
"https://github.com/kamolgks/Hikkamods",
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
"https://github.com/thomasmod/hikkamods",
|
||||||
data = json.load(f)
|
"https://github.com/SkillsAngels/Modules",
|
||||||
for repo in data.get("repositories", []):
|
"https://github.com/Sad0ff/modules-ftg",
|
||||||
repos.append(repo["url"])
|
"https://github.com/Yahikoro/Modules-for-FTG",
|
||||||
return repos
|
"https://github.com/KeyZenD/modules",
|
||||||
|
"https://github.com/AlpacaGang/ftg-modules",
|
||||||
|
"https://github.com/trololo65/Modules",
|
||||||
|
"https://github.com/Ijidishurka/modules",
|
||||||
|
"https://github.com/Fl1yd/FTG-Modules",
|
||||||
|
"https://github.com/D4n13l3k00/FTG-Modules",
|
||||||
|
"https://github.com/iamnalinor/FTG-modules",
|
||||||
|
"https://github.com/SekaiYoneya/modules",
|
||||||
|
"https://github.com/GeekTG/FTG-Modules",
|
||||||
|
"https://github.com/Den4ikSuperOstryyPer4ik/Astro-modules",
|
||||||
|
"https://github.com/vsecoder/hikka_modules",
|
||||||
|
"https://github.com/sqlmerr/hikka_mods",
|
||||||
|
"https://github.com/N3rcy/modules",
|
||||||
|
"https://github.com/KorenbZla/HikkaModules",
|
||||||
|
"https://github.com/MuRuLOSE/HikkaModulesRepo",
|
||||||
|
"https://github.com/coddrago/modules",
|
||||||
|
"https://github.com/1jpshiro/hikka-modules",
|
||||||
|
"https://github.com/MoriSummerz/ftg-mods",
|
||||||
|
"https://github.com/anon97945/hikka-mods",
|
||||||
|
"https://github.com/dorotorothequickend/DorotoroModules",
|
||||||
|
"https://github.com/AmoreForever/amoremods",
|
||||||
|
"https://github.com/idiotcoders/idiotmodules",
|
||||||
|
"https://github.com/CakesTwix/Hikka-Modules",
|
||||||
|
"https://github.com/archquise/H.Modules",
|
||||||
|
"https://github.com/GD-alt/mm-hikka-mods",
|
||||||
|
"https://github.com/HitaloSama/FTG-modules-repo",
|
||||||
|
"https://github.com/SekaiYoneya/Friendly-telegram",
|
||||||
|
"https://github.com/blazedzn/ftg-modules",
|
||||||
|
"https://github.com/hikariatama/ftg",
|
||||||
|
"https://github.com/m4xx1m/FTG",
|
||||||
|
"https://github.com/skillzmeow/skillzmods_hikka",
|
||||||
|
"https://github.com/fajox1/famods",
|
||||||
|
"https://github.com/unneyon/hikka-mods",
|
||||||
|
"https://github.com/TheKsenon/MyHikkaModules",
|
||||||
|
"https://github.com/cryptexctl/modules-mirror",
|
||||||
|
"https://github.com/Ruslan-Isaev/modules",
|
||||||
|
"https://github.com/shadowhikka/sh.modules",
|
||||||
|
"https://github.com/fiksofficial/python-modules",
|
||||||
|
"https://github.com/mead0wsss/mead0wsMods",
|
||||||
|
"https://github.com/SenkoGuardian/SenModules",
|
||||||
|
"https://github.com/ZetGoHack/nullmod",
|
||||||
|
"https://github.com/yummy1gay/limoka"
|
||||||
|
|
||||||
repos = parse_repos("repositories.json")
|
]
|
||||||
|
|
||||||
def configure_git():
|
def configure_git():
|
||||||
"""Configure Git to ignore file mode changes."""
|
"""Configure Git to ignore file mode changes."""
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,9 +17,9 @@ figlet
|
|||||||
promoclaimer
|
promoclaimer
|
||||||
passwordgen
|
passwordgen
|
||||||
send
|
send
|
||||||
|
lastfm
|
||||||
dbmod
|
dbmod
|
||||||
chatmodule
|
chatmodule
|
||||||
stats
|
stats
|
||||||
tagwatcher
|
tagwatcher
|
||||||
hardspam
|
hardspam
|
||||||
YaMusic
|
|
||||||
|
|||||||
124
coddrago/modules/lastfm.py
Normal file
124
coddrago/modules/lastfm.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# ---------------------------------------------------------------------------------
|
||||||
|
#░█▀▄░▄▀▀▄░█▀▄░█▀▀▄░█▀▀▄░█▀▀▀░▄▀▀▄░░░█▀▄▀█
|
||||||
|
#░█░░░█░░█░█░█░█▄▄▀░█▄▄█░█░▀▄░█░░█░░░█░▀░█
|
||||||
|
#░▀▀▀░░▀▀░░▀▀░░▀░▀▀░▀░░▀░▀▀▀▀░░▀▀░░░░▀░░▒▀
|
||||||
|
# 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}"))
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
# meta developer: @codrago_m
|
# meta developer: @codrago_m
|
||||||
# scope: heroku_min 2.0.0
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from .. import utils, loader, main
|
||||||
from telethon.tl.functions.messages import MarkDialogUnreadRequest
|
from telethon.tl.functions.messages import MarkDialogUnreadRequest
|
||||||
|
|
||||||
from .. import loader, main, utils
|
|
||||||
|
|
||||||
logger = logging.getLogger("TagWatcher")
|
logger = logging.getLogger("TagWatcher")
|
||||||
|
|
||||||
|
|
||||||
@@ -117,8 +113,15 @@ class TagWatcher(loader.Module):
|
|||||||
validator=loader.validators.Boolean(),
|
validator=loader.validators.Boolean(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def client_ready(self):
|
async def client_ready(self):
|
||||||
self.asset_channel = self._db.get("heroku.forums", "channel_id", 0)
|
await self.request_join("@xdesai_modules", self.strings["request_join_reason"])
|
||||||
|
self.xdlib = await self.import_lib(
|
||||||
|
"https://raw.githubusercontent.com/xdesai96/modules/refs/heads/main/libs/xdlib.py",
|
||||||
|
suspend_on_error=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.asset_channel = self._db.get("legacy.forums", "channel_id", 0)
|
||||||
self._notif_topic = await utils.asset_forum_topic(
|
self._notif_topic = await utils.asset_forum_topic(
|
||||||
self._client,
|
self._client,
|
||||||
self._db,
|
self._db,
|
||||||
@@ -128,11 +131,6 @@ class TagWatcher(loader.Module):
|
|||||||
icon_emoji_id=5409025823388741707,
|
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def render_text(self, m):
|
async def render_text(self, m):
|
||||||
if self.config["custom_notif_text"]:
|
if self.config["custom_notif_text"]:
|
||||||
text = self.config["custom_notif_text"]
|
text = self.config["custom_notif_text"]
|
||||||
@@ -176,7 +174,7 @@ class TagWatcher(loader.Module):
|
|||||||
user_id=id,
|
user_id=id,
|
||||||
msg_content=msg_content,
|
msg_content=msg_content,
|
||||||
reply_content=reply_content,
|
reply_content=reply_content,
|
||||||
link=await m.link(),
|
link=await m.link,
|
||||||
)
|
)
|
||||||
|
|
||||||
@loader.command(
|
@loader.command(
|
||||||
|
|||||||
BIN
fajox1/famods/.DS_Store
vendored
Normal file
BIN
fajox1/famods/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
fajox1/famods/assets/.DS_Store
vendored
Normal file
BIN
fajox1/famods/assets/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
fajox1/famods/assets/banners/.DS_Store
vendored
Normal file
BIN
fajox1/famods/assets/banners/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
fajox1/famods/assets/birds/.DS_Store
vendored
Normal file
BIN
fajox1/famods/assets/birds/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, ai, assistant
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, ai, username
|
|
||||||
# requires: aiohttp
|
# requires: aiohttp
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, user, profile
|
|
||||||
|
|
||||||
from hikkatl.types import Message
|
from hikkatl.types import Message
|
||||||
from telethon.tl.functions.account import UpdateProfileRequest
|
from telethon.tl.functions.account import UpdateProfileRequest
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, calculator, calc
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
import math
|
import math
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# На модуль распространяется лицензия "GNU General Public License v3.0
|
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @PyModule
|
# meta developer: @PyModule
|
||||||
# meta fhsdesc: tool, tools, channel, admintools, admin, admintool
|
|
||||||
from telethon.tl.types import Message
|
from telethon.tl.types import Message
|
||||||
from .. import loader
|
from .. import loader
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# meta fhsdesc: tool, tools, server, admin
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, fun, packs
|
|
||||||
# requires: opencv-python pillow
|
# requires: opencv-python pillow
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: fun, cute, message, love
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, phone, info
|
|
||||||
# requires: aiohttp cachetools
|
# requires: aiohttp cachetools
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -106,7 +105,7 @@ class DeviceInfo(loader.Module):
|
|||||||
self.config = loader.ModuleConfig(
|
self.config = loader.ModuleConfig(
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"api_base_url",
|
"api_base_url",
|
||||||
"https://gmsarena.vercel.app/",
|
"https://mobilespecs.fiksofficial.fun",
|
||||||
lambda: "API Url",
|
lambda: "API Url",
|
||||||
validator=loader.validators.String()
|
validator=loader.validators.String()
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# scope: hikka_only
|
# scope: hikka_only
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, scanner, domain
|
|
||||||
# requires: python-whois dnspython requests
|
# requires: python-whois dnspython requests
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @PyModule
|
# meta developer: @PyModule
|
||||||
# meta fhsdesc: tool, tools, user, id
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
|
|||||||
@@ -11,459 +11,183 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, github, info, inline
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
from ..inline import InlineCall
|
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
|
||||||
import urllib.request
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class GitHubInfoMod(loader.Module):
|
class GitHubInfoMod(loader.Module):
|
||||||
"""GitHub user information"""
|
"""GitHub user info, recent activity and contribution graph"""
|
||||||
strings = {
|
strings = {
|
||||||
"name": "GitHubInfo",
|
"name": "GitHubInfo",
|
||||||
|
|
||||||
"no_username": "❗ Provide a GitHub username.",
|
"no_username": "❗ Provide a GitHub username.",
|
||||||
"user_not_found": "🚫 User not found: <b>{}</b>",
|
"user_not_found": "🚫 User not found: <b>{}</b>",
|
||||||
"api_error": "⚠ GitHub API error: <b>{msg}</b>",
|
"profile": "Profile",
|
||||||
"no_activity": "🕸 No recent activity from <b>{}</b>",
|
"no_activity": "🕸 No recent activity from <b>{}</b>",
|
||||||
"no_contrib": "📭 No contribution data.",
|
"no_contrib": "📭 No contribution data for <b>{}</b>",
|
||||||
"no_repos": "📭 No public repositories.",
|
"info_text": (
|
||||||
"no_orgs": "📭 Not a member of any organizations.",
|
"👤 <b>{name}</b> | <a href=\"{url}\">{profile}</a>\n"
|
||||||
"no_title": "No title",
|
"🏢 {company} | 📍 {location}\n"
|
||||||
"no_desc": "No description",
|
"📝 {bio}\n\n"
|
||||||
"not_specified": "Not specified",
|
"📦 Repos: <b>{repos}</b> | "
|
||||||
"more_commits": " ... and {} more\n",
|
"👥 Followers: <b>{followers}</b> | "
|
||||||
"hireable_yes": "Yes",
|
|
||||||
"hireable_no": "No",
|
|
||||||
|
|
||||||
"menu_text": "Choose a section:",
|
|
||||||
|
|
||||||
"btn_activity": "🔥 Activity",
|
|
||||||
"btn_contrib": "📊 Contributions",
|
|
||||||
"btn_repos": "📦 Repositories",
|
|
||||||
"btn_orgs": "🏛 Organizations",
|
|
||||||
"btn_back": "← Back to profile",
|
|
||||||
|
|
||||||
"profile_header": "<b>Profile</b> <a href=\"{url}\">{username}</a>\n\n",
|
|
||||||
"profile_text": (
|
|
||||||
"👤 Name: <b>{name}</b>\n"
|
|
||||||
"🏷 Login: <code>{login}</code>\n"
|
|
||||||
"📝 Bio: {bio}\n"
|
|
||||||
"🏢 Company: {company}\n"
|
|
||||||
"📍 Location: {location}\n"
|
|
||||||
"📧 Email: {email}\n"
|
|
||||||
"🔗 Website: {blog}\n"
|
|
||||||
"🐦 Twitter: {twitter}\n"
|
|
||||||
"💼 Hireable: {hireable}\n"
|
|
||||||
"📊 Type: {type}\n"
|
|
||||||
"📦 Public repos: <b>{repos}</b>\n"
|
|
||||||
"⭐ Public gists: <b>{gists}</b>\n"
|
|
||||||
"👥 Followers: <b>{followers}</b>\n"
|
|
||||||
"👣 Following: <b>{following}</b>\n"
|
"👣 Following: <b>{following}</b>\n"
|
||||||
"🕐 Created: <code>{created}</code>\n"
|
"🕒 Created: <code>{created}</code>"
|
||||||
"🕐 Updated: <code>{updated}</code>"
|
|
||||||
),
|
),
|
||||||
|
"activity_header": "<b>Recent activity:</b>\n",
|
||||||
"activity_header": "<b>Recent activity</b> <a href=\"https://github.com/{username}\">{username}</a>\n\n",
|
"activity_commit": "🔨 {count} commit(s) → <code>{branch}</code> in {repo}",
|
||||||
|
"activity_create": "✨ Created {ref_type} in {repo}",
|
||||||
"push_header": "🔨 Pushed to <code>{branch}</code> → <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"activity_pr": "🔄 {action} PR: {title}",
|
||||||
"push_no_commits": "🔨 Pushed (no details) to <code>{branch}</code> → <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"activity_issue": "❗ {action} issue: {title}",
|
||||||
"commit_line": "• <a href=\"{url}\"><code>{sha}</code></a>: {message}\n",
|
"activity_star": "⭐ Starred {repo}",
|
||||||
|
"activity_fork": "⑂ Forked to {fork}",
|
||||||
"create_branch": "✨ Created branch <code>{ref}</code> in <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"activity_other": "⚡ {event} in {repo}",
|
||||||
"create_tag": "✨ Created tag <code>{ref}</code> in <a href=\"https://github.com/{repo}/releases/tag/{ref}\">{repo}</a>\n",
|
"contrib_header": "<b>Contribution graph</b> for <a href=\"https://github.com/{username}\">{username}</a>:\n",
|
||||||
"create_repo": "✨ Created repository <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"contrib_footer": "⬛ = 0, 🟩 = 1+ contributions",
|
||||||
|
|
||||||
"pr_opened": "🔄 Opened PR <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
"pr_closed": "🔄 Closed PR <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
"pr_merged": "🔄 Merged PR <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
|
|
||||||
"issue_opened": "❗ Opened issue <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
"issue_closed": "❗ Closed issue <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
|
|
||||||
"star": "⭐ Starred <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
|
||||||
"fork": "⑂ Forked <a href=\"https://github.com/{fork}\">{fork}</a>\n",
|
|
||||||
|
|
||||||
"other": "⚡ {event} in <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
|
||||||
|
|
||||||
"repos_header": "<b>Top repositories by stars</b> <a href=\"https://github.com/{username}\">{username}</a>\n\n",
|
|
||||||
"repo_line": "⭐ <b>{stars}</b> | <a href=\"{url}\">{name}</a> — {desc}\nLanguage: {lang} | Forks: {forks}\n\n",
|
|
||||||
|
|
||||||
"orgs_header": "<b>Organizations</b> <a href=\"https://github.com/{username}\">{username}</a>\n\n",
|
|
||||||
"org_line": "• <a href=\"{url}\">{login}</a> — {desc}\n",
|
|
||||||
|
|
||||||
"contrib_header": "<b>Contribution graph (last year)</b> <a href=\"https://github.com/{username}\">{username}</a>\n",
|
|
||||||
"contrib_footer": "\n⬛ = 0, 🟩 = 1+ contributions",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"_cls_doc": "Информация о GitHub-пользователе",
|
"no_username": "❗ Укажи имя пользователя GitHub.",
|
||||||
|
|
||||||
"no_username": "❗ Укажи GitHub username.",
|
|
||||||
"user_not_found": "🚫 Пользователь не найден: <b>{}</b>",
|
"user_not_found": "🚫 Пользователь не найден: <b>{}</b>",
|
||||||
"api_error": "⚠ Ошибка GitHub API: <b>{msg}</b>",
|
"profile": "Профиль",
|
||||||
"no_activity": "🕸 Нет недавней активности у <b>{}</b>",
|
"no_activity": "🕸 Нет активности у <b>{}</b>",
|
||||||
"no_contrib": "📭 Нет данных о контрибуциях.",
|
"no_contrib": "📭 Нет данных о вкладах <b>{}</b>",
|
||||||
"no_repos": "📭 Нет публичных репозиториев.",
|
"info_text": (
|
||||||
"no_orgs": "📭 Не состоит в организациях.",
|
"👤 <b>{name}</b> | <a href=\"{url}\">{profile}</a>\n"
|
||||||
"no_title": "Без названия",
|
"🏢 {company} | 📍 {location}\n"
|
||||||
"no_desc": "Без описания",
|
"📝 {bio}\n\n"
|
||||||
"not_specified": "Не указано",
|
"📦 Репозитории: <b>{repos}</b> | "
|
||||||
"more_commits": " ... и ещё {}\n",
|
"👥 Подписчики: <b>{followers}</b> | "
|
||||||
"hireable_yes": "Да",
|
|
||||||
"hireable_no": "Нет",
|
|
||||||
|
|
||||||
"menu_text": "Выбери раздел:",
|
|
||||||
|
|
||||||
"btn_activity": "🔥 Активность",
|
|
||||||
"btn_contrib": "📊 Контрибы",
|
|
||||||
"btn_repos": "📦 Репозитории",
|
|
||||||
"btn_orgs": "🏛 Организации",
|
|
||||||
"btn_back": "← Назад к профилю",
|
|
||||||
|
|
||||||
"profile_header": "<b>Профиль</b> <a href=\"{url}\">{username}</a>\n\n",
|
|
||||||
"profile_text": (
|
|
||||||
"👤 Имя: <b>{name}</b>\n"
|
|
||||||
"🏷 Логин: <code>{login}</code>\n"
|
|
||||||
"📝 Био: {bio}\n"
|
|
||||||
"🏢 Компания: {company}\n"
|
|
||||||
"📍 Локация: {location}\n"
|
|
||||||
"📧 Email: {email}\n"
|
|
||||||
"🔗 Сайт: {blog}\n"
|
|
||||||
"🐦 Twitter: {twitter}\n"
|
|
||||||
"💼 Доступен для найма: {hireable}\n"
|
|
||||||
"📊 Тип аккаунта: {type}\n"
|
|
||||||
"📦 Публичные репозитории: <b>{repos}</b>\n"
|
|
||||||
"⭐ Публичные гисты: <b>{gists}</b>\n"
|
|
||||||
"👥 Подписчики: <b>{followers}</b>\n"
|
|
||||||
"👣 Подписки: <b>{following}</b>\n"
|
"👣 Подписки: <b>{following}</b>\n"
|
||||||
"🕐 Создан: <code>{created}</code>\n"
|
"🕒 Создан: <code>{created}</code>"
|
||||||
"🕐 Обновлён: <code>{updated}</code>"
|
|
||||||
),
|
),
|
||||||
|
"activity_header": "<b>Последняя активность:</b>\n",
|
||||||
"activity_header": "<b>Последняя активность</b> <a href=\"https://github.com/{username}\">{username}</a>\n\n",
|
"activity_commit": "🔨 {count} коммит(ов) → <code>{branch}</code> в {repo}",
|
||||||
|
"activity_create": "✨ Создан {ref_type} в {repo}",
|
||||||
"push_header": "🔨 Запушил в <code>{branch}</code> → <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"activity_pr": "🔄 {action} PR: {title}",
|
||||||
"push_no_commits": "🔨 Запушил (без деталей) в <code>{branch}</code> → <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"activity_issue": "❗ {action} issue: {title}",
|
||||||
"commit_line": "• <a href=\"{url}\"><code>{sha}</code></a>: {message}\n",
|
"activity_star": "⭐ В избранное {repo}",
|
||||||
|
"activity_fork": "⑂ Форк в {fork}",
|
||||||
"create_branch": "✨ Создал ветку <code>{ref}</code> в <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"activity_other": "⚡ {event} в {repo}",
|
||||||
"create_tag": "✨ Создал тег <code>{ref}</code> в <a href=\"https://github.com/{repo}/releases/tag/{ref}\">{repo}</a>\n",
|
"contrib_header": "<b>График активности</b> <a href=\"https://github.com/{username}\">{username}</a>:\n",
|
||||||
"create_repo": "✨ Создал репозиторий <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
"contrib_footer": "⬛ = 0, 🟩 = 1+ контрибуций",
|
||||||
|
|
||||||
"pr_opened": "🔄 Открыл PR <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
"pr_closed": "🔄 Закрыл PR <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
"pr_merged": "🔄 Замержил PR <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
|
|
||||||
"issue_opened": "❗ Открыл issue <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
"issue_closed": "❗ Закрыл issue <a href=\"{url}\">#{} {title}</a>\n",
|
|
||||||
|
|
||||||
"star": "⭐ Добавил в избранное <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
|
||||||
"fork": "⑂ Форкнул <a href=\"https://github.com/{fork}\">{fork}</a>\n",
|
|
||||||
|
|
||||||
"other": "⚡ {event} в <a href=\"https://github.com/{repo}\">{repo}</a>\n",
|
|
||||||
|
|
||||||
"repos_header": "<b>Топ репозитории по звёздам</b> <a href=\"https://github.com/{username}\">{username}</a>\n\n",
|
|
||||||
"repo_line": "⭐ <b>{stars}</b> | <a href=\"{url}\">{name}</a> — {desc}\nЯзык: {lang} | Форков: {forks}\n\n",
|
|
||||||
|
|
||||||
"orgs_header": "<b>Организации</b> <a href=\"https://github.com/{username}\">{username}</a>\n\n",
|
|
||||||
"org_line": "• <a href=\"{url}\">{login}</a> — {desc}\n",
|
|
||||||
|
|
||||||
"contrib_header": "<b>График контрибуций (последний год)</b> <a href=\"https://github.com/{username}\">{username}</a>\n",
|
|
||||||
"contrib_footer": "\n⬛ = 0, 🟩 = 1+ контрибуций",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def github_fetch(self, url, github_api=True):
|
def github_api(self, url):
|
||||||
headers = {
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
|
|
||||||
"Accept": "application/vnd.github+json" if github_api else "application/json",
|
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
}
|
|
||||||
req = urllib.request.Request(url, headers=headers)
|
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
with urllib.request.urlopen(url) as resp:
|
||||||
raw = resp.read().decode("utf-8")
|
return json.loads(resp.read().decode())
|
||||||
return json.loads(raw) if raw else {}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"[GitHub] {e}")
|
self.logger.warning(f"[GitHub API] {e}")
|
||||||
return {"message": str(e)}
|
return None
|
||||||
|
|
||||||
@loader.command(ru_doc="{username без @} — Информация о GitHub пользователе")
|
def get_username(self, message):
|
||||||
async def github(self, message):
|
args = message.text.split(maxsplit=1)
|
||||||
"""{username without @} — GitHub user information"""
|
return args[1] if len(args) > 1 else None
|
||||||
username = utils.get_args_raw(message)
|
|
||||||
|
@loader.command(doc="Show GitHub user info", ru_doc="Информация о пользователе GitHub")
|
||||||
|
async def gh(self, message):
|
||||||
|
"""Show GitHub user info"""
|
||||||
|
username = self.get_username(message)
|
||||||
if not username:
|
if not username:
|
||||||
await utils.answer(message, self.strings("no_username"))
|
return await message.edit(self.strings("no_username"))
|
||||||
return
|
|
||||||
|
|
||||||
user_data = await self.github_fetch(f"https://api.github.com/users/{username}")
|
data = self.github_api(f"https://api.github.com/users/{username}")
|
||||||
if "message" in user_data:
|
if not data:
|
||||||
await utils.answer(message, self.strings("user_not_found").format(username))
|
return await message.edit(self.strings("user_not_found").format(username))
|
||||||
return
|
|
||||||
|
|
||||||
hireable = self.strings("hireable_yes") if user_data.get("hireable") else self.strings("hireable_no")
|
await message.edit(self.strings("info_text").format(
|
||||||
|
name=data.get("name") or username,
|
||||||
profile_text = (
|
url=data["html_url"],
|
||||||
self.strings("profile_header").format(url=user_data["html_url"], username=username)
|
profile=self.strings("profile"),
|
||||||
+ self.strings("profile_text").format(
|
company=data.get("company", "N/A"),
|
||||||
name=user_data.get("name") or self.strings("not_specified"),
|
location=data.get("location", "N/A"),
|
||||||
login=username,
|
bio=data.get("bio", "No bio"),
|
||||||
bio=user_data.get("bio") or self.strings("no_desc"),
|
|
||||||
company=user_data.get("company") or self.strings("not_specified"),
|
|
||||||
location=user_data.get("location") or self.strings("not_specified"),
|
|
||||||
email=user_data.get("email") or self.strings("not_specified"),
|
|
||||||
blog=user_data.get("blog") or self.strings("not_specified"),
|
|
||||||
twitter=user_data.get("twitter_username") or self.strings("not_specified"),
|
|
||||||
hireable=hireable,
|
|
||||||
type=user_data.get("type", "User"),
|
|
||||||
repos=user_data.get("public_repos", 0),
|
|
||||||
gists=user_data.get("public_gists", 0),
|
|
||||||
followers=user_data.get("followers", 0),
|
|
||||||
following=user_data.get("following", 0),
|
|
||||||
created=user_data.get("created_at", "")[:10],
|
|
||||||
updated=user_data.get("updated_at", "")[:10],
|
|
||||||
)
|
|
||||||
+ "\n" + self.strings("menu_text")
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.inline.form(
|
|
||||||
message=message,
|
|
||||||
text=profile_text,
|
|
||||||
reply_markup=[
|
|
||||||
[{"text": self.strings("btn_activity"), "callback": self._activity, "args": (username,)}],
|
|
||||||
[{"text": self.strings("btn_contrib"), "callback": self._contrib, "args": (username,)}, {"text": self.strings("btn_repos"), "callback": self._repos, "args": (username,)}],
|
|
||||||
[{"text": self.strings("btn_orgs"), "callback": self._orgs, "args": (username,)}],
|
|
||||||
],
|
|
||||||
ttl=10 * 60,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _profile(self, call: InlineCall, username: str):
|
|
||||||
# Этот метод теперь используется только для возврата к профилю
|
|
||||||
data = await self.github_fetch(f"https://api.github.com/users/{username}")
|
|
||||||
if "message" in data:
|
|
||||||
await call.edit(self.strings("api_error").format(msg=data["message"]))
|
|
||||||
return
|
|
||||||
|
|
||||||
hireable = self.strings("hireable_yes") if data.get("hireable") else self.strings("hireable_no")
|
|
||||||
|
|
||||||
profile_text = (
|
|
||||||
self.strings("profile_header").format(url=data["html_url"], username=username)
|
|
||||||
+ self.strings("profile_text").format(
|
|
||||||
name=data.get("name") or self.strings("not_specified"),
|
|
||||||
login=username,
|
|
||||||
bio=data.get("bio") or self.strings("no_desc"),
|
|
||||||
company=data.get("company") or self.strings("not_specified"),
|
|
||||||
location=data.get("location") or self.strings("not_specified"),
|
|
||||||
email=data.get("email") or self.strings("not_specified"),
|
|
||||||
blog=data.get("blog") or self.strings("not_specified"),
|
|
||||||
twitter=data.get("twitter_username") or self.strings("not_specified"),
|
|
||||||
hireable=hireable,
|
|
||||||
type=data.get("type", "User"),
|
|
||||||
repos=data.get("public_repos", 0),
|
repos=data.get("public_repos", 0),
|
||||||
gists=data.get("public_gists", 0),
|
|
||||||
followers=data.get("followers", 0),
|
followers=data.get("followers", 0),
|
||||||
following=data.get("following", 0),
|
following=data.get("following", 0),
|
||||||
created=data.get("created_at", "")[:10],
|
created=data.get("created_at", "")[:10]
|
||||||
updated=data.get("updated_at", "")[:10],
|
))
|
||||||
)
|
|
||||||
+ "\n" + self.strings("menu_text")
|
|
||||||
)
|
|
||||||
|
|
||||||
await call.edit(
|
@loader.command(doc="Show recent GitHub activity", ru_doc="Последняя активность GitHub")
|
||||||
text=profile_text,
|
async def gha(self, message):
|
||||||
reply_markup=[
|
"""Show recent GitHub activity"""
|
||||||
[{"text": self.strings("btn_activity"), "callback": self._activity, "args": (username,)}],
|
username = self.get_username(message)
|
||||||
[{"text": self.strings("btn_contrib"), "callback": self._contrib, "args": (username,)}, {"text": self.strings("btn_repos"), "callback": self._repos, "args": (username,)}],
|
if not username:
|
||||||
[{"text": self.strings("btn_orgs"), "callback": self._orgs, "args": (username,)}],
|
return await message.edit(self.strings("no_username"))
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _activity(self, call: InlineCall, username: str):
|
events = self.github_api(f"https://api.github.com/users/{username}/events?per_page=5")
|
||||||
events = await self.github_fetch(f"https://api.github.com/users/{username}/events?per_page=40")
|
|
||||||
if "message" in events:
|
|
||||||
await call.edit(self.strings("api_error").format(msg=events["message"]), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
|
||||||
return
|
|
||||||
if not events:
|
if not events:
|
||||||
await call.edit(self.strings("no_activity").format(username=username), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
return await message.edit(self.strings("no_activity").format(username))
|
||||||
return
|
|
||||||
|
|
||||||
lines = [self.strings("activity_header").format(username=username)]
|
lines = []
|
||||||
seen_repos = set()
|
for event in events:
|
||||||
|
|
||||||
for event in events[:25]:
|
|
||||||
etype = event["type"]
|
etype = event["type"]
|
||||||
repo = event["repo"]["name"]
|
repo = event["repo"]["name"]
|
||||||
if repo in seen_repos and len(lines) > 20:
|
|
||||||
continue
|
|
||||||
|
|
||||||
payload = event.get("payload", {})
|
payload = event.get("payload", {})
|
||||||
|
|
||||||
if etype == "PushEvent":
|
if etype == "PushEvent":
|
||||||
branch = payload.get("ref", "refs/heads/main").replace("refs/heads/", "")
|
branch = re.sub(r"refs/heads/", "", payload.get("ref", "main"))
|
||||||
commits = payload.get("commits", [])
|
count = len(payload.get("commits", []))
|
||||||
|
lines.append(self.strings("activity_commit").format(count=count, branch=branch, repo=repo))
|
||||||
if commits:
|
|
||||||
lines.append(self.strings("push_header").format(branch=branch, repo=repo))
|
|
||||||
for commit in commits[:5]:
|
|
||||||
sha = commit["sha"][:7]
|
|
||||||
message = commit["message"].split("\n")[0][:100]
|
|
||||||
if len(commit["message"].split("\n")[0]) > 100:
|
|
||||||
message += "..."
|
|
||||||
url = f"https://github.com/{repo}/commit/{commit['sha']}"
|
|
||||||
lines.append(self.strings("commit_line").format(url=url, sha=sha, message=message))
|
|
||||||
if len(commits) > 5:
|
|
||||||
lines.append(self.strings("more_commits").format(len(commits)-5))
|
|
||||||
else:
|
|
||||||
lines.append(self.strings("push_no_commits").format(branch=branch, repo=repo))
|
|
||||||
|
|
||||||
seen_repos.add(repo)
|
|
||||||
|
|
||||||
elif etype == "CreateEvent":
|
elif etype == "CreateEvent":
|
||||||
ref_type = payload.get("ref_type")
|
lines.append(self.strings("activity_create").format(ref_type=payload.get("ref_type"), repo=repo))
|
||||||
ref = payload.get("ref") or ""
|
|
||||||
if ref_type == "branch":
|
|
||||||
lines.append(self.strings("create_branch").format(ref=ref, repo=repo))
|
|
||||||
elif ref_type == "tag":
|
|
||||||
lines.append(self.strings("create_tag").format(ref=ref, repo=repo))
|
|
||||||
elif ref_type == "repository":
|
|
||||||
lines.append(self.strings("create_repo").format(repo=repo))
|
|
||||||
|
|
||||||
elif etype == "PullRequestEvent":
|
elif etype == "PullRequestEvent":
|
||||||
pr = payload.get("pull_request", {})
|
pr = payload.get("pull_request", {})
|
||||||
number = pr.get("number", "?")
|
lines.append(self.strings("activity_pr").format(action=payload.get("action"), title=pr.get("title")))
|
||||||
title = pr.get("title") or self.strings("no_title")
|
|
||||||
url = pr.get("html_url") or f"https://github.com/{repo}"
|
|
||||||
action = payload.get("action")
|
|
||||||
if action == "closed" and pr.get("merged"):
|
|
||||||
lines.append(self.strings("pr_merged").format(url=url, number=number, title=title))
|
|
||||||
elif action == "opened":
|
|
||||||
lines.append(self.strings("pr_opened").format(url=url, number=number, title=title))
|
|
||||||
elif action == "closed":
|
|
||||||
lines.append(self.strings("pr_closed").format(url=url, number=number, title=title))
|
|
||||||
|
|
||||||
elif etype == "IssuesEvent":
|
elif etype == "IssuesEvent":
|
||||||
issue = payload.get("issue", {})
|
issue = payload.get("issue", {})
|
||||||
number = issue.get("number", "?")
|
lines.append(self.strings("activity_issue").format(action=payload.get("action"), title=issue.get("title")))
|
||||||
title = issue.get("title") or self.strings("no_title")
|
|
||||||
url = issue.get("html_url") or f"https://github.com/{repo}"
|
|
||||||
action = payload.get("action")
|
|
||||||
if action == "opened":
|
|
||||||
lines.append(self.strings("issue_opened").format(url=url, number=number, title=title))
|
|
||||||
elif action == "closed":
|
|
||||||
lines.append(self.strings("issue_closed").format(url=url, number=number, title=title))
|
|
||||||
|
|
||||||
elif etype == "WatchEvent":
|
elif etype == "WatchEvent":
|
||||||
lines.append(self.strings("star").format(repo=repo))
|
lines.append(self.strings("activity_star").format(repo=repo))
|
||||||
|
|
||||||
elif etype == "ForkEvent":
|
elif etype == "ForkEvent":
|
||||||
fork = payload.get("forkee", {}).get("full_name", "unknown")
|
lines.append(self.strings("activity_fork").format(fork=payload.get("forkee", {}).get("full_name")))
|
||||||
lines.append(self.strings("fork").format(fork=fork))
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
event_name = etype.replace("Event", "")
|
lines.append(self.strings("activity_other").format(event=etype, repo=repo))
|
||||||
lines.append(self.strings("other").format(event=event_name, repo=repo))
|
|
||||||
|
|
||||||
await call.edit(
|
await message.edit(self.strings("activity_header") + "\n".join(lines))
|
||||||
text="".join(lines),
|
|
||||||
reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _repos(self, call: InlineCall, username: str):
|
@loader.command(doc="Show GitHub contribution graph", ru_doc="Показать график контрибов GitHub")
|
||||||
repos = await self.github_fetch(f"https://api.github.com/users/{username}/repos?sort=stars&per_page=10")
|
async def ghc(self, message):
|
||||||
if "message" in repos:
|
"""Show GitHub contribution graph"""
|
||||||
await call.edit(self.strings("api_error").format(msg=repos["message"]), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
username = self.get_username(message)
|
||||||
return
|
if not username:
|
||||||
if not repos:
|
return await message.edit(self.strings("no_username"))
|
||||||
await call.edit(self.strings("no_repos"), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
|
||||||
return
|
|
||||||
|
|
||||||
lines = [self.strings("repos_header").format(username=username)]
|
data = self.github_api(f"https://github-contributions-api.deno.dev/{username}.json")
|
||||||
for repo in repos[:10]:
|
contribs = data.get("contributions") if data else None
|
||||||
lines.append(self.strings("repo_line").format(
|
|
||||||
stars=repo.get("stargazers_count", 0),
|
|
||||||
url=repo["html_url"],
|
|
||||||
name=repo["name"],
|
|
||||||
desc=repo.get("description") or self.strings("no_desc"),
|
|
||||||
lang=repo.get("language") or self.strings("not_specified"),
|
|
||||||
forks=repo.get("forks_count", 0),
|
|
||||||
))
|
|
||||||
|
|
||||||
await call.edit(
|
if not isinstance(contribs, list):
|
||||||
text="".join(lines),
|
return await message.edit(self.strings("no_contrib").format(username))
|
||||||
reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _orgs(self, call: InlineCall, username: str):
|
|
||||||
orgs = await self.github_fetch(f"https://api.github.com/users/{username}/orgs")
|
|
||||||
if "message" in orgs:
|
|
||||||
await call.edit(self.strings("api_error").format(msg=orgs["message"]), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
|
||||||
return
|
|
||||||
if not orgs:
|
|
||||||
await call.edit(self.strings("no_orgs"), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
|
||||||
return
|
|
||||||
|
|
||||||
lines = [self.strings("orgs_header").format(username=username)]
|
|
||||||
for org in orgs:
|
|
||||||
lines.append(self.strings("org_line").format(
|
|
||||||
url=f"https://github.com/{org['login']}",
|
|
||||||
login=org["login"],
|
|
||||||
desc=org.get("description") or self.strings("no_desc"),
|
|
||||||
))
|
|
||||||
|
|
||||||
await call.edit(
|
|
||||||
text="".join(lines),
|
|
||||||
reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _contrib(self, call: InlineCall, username: str):
|
|
||||||
data = await self.github_fetch(f"https://github-contributions-api.deno.dev/{username}.json", github_api=False)
|
|
||||||
if not data or not data.get("contributions"):
|
|
||||||
await call.edit(self.strings("no_contrib"), reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]])
|
|
||||||
return
|
|
||||||
|
|
||||||
raw_days = []
|
|
||||||
for week in data.get("contributions", []):
|
|
||||||
if isinstance(week, list):
|
|
||||||
raw_days.extend([day for day in week if isinstance(day, dict)])
|
|
||||||
|
|
||||||
today = datetime.utcnow().date()
|
today = datetime.utcnow().date()
|
||||||
weeks_count = 53
|
start = today - timedelta(days=90)
|
||||||
days_back = weeks_count * 7 + 7
|
matrix = [["⬛" for _ in range(13)] for _ in range(7)]
|
||||||
start = today - timedelta(days=days_back)
|
|
||||||
|
|
||||||
matrix = [["⬛" for _ in range(weeks_count)] for _ in range(7)]
|
for entry in contribs:
|
||||||
|
|
||||||
for entry in raw_days:
|
|
||||||
date_str = entry.get("date")
|
|
||||||
if not date_str:
|
|
||||||
continue
|
|
||||||
try:
|
try:
|
||||||
date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
date = datetime.strptime(entry["date"], "%Y-%m-%d").date()
|
||||||
if date < start or date > today:
|
if not (start <= date <= today):
|
||||||
continue
|
continue
|
||||||
count = entry.get("contributionCount") or entry.get("count", 0) or 0
|
day = (date.weekday() + 1) % 7 # Sunday=0
|
||||||
if count > 0:
|
week = (date - start).days // 7
|
||||||
day_idx = (date.weekday() + 1) % 7
|
if entry.get("contributionCount", 0) > 0:
|
||||||
week_idx = (date - start).days // 7
|
matrix[day][week] = "🟩"
|
||||||
if week_idx < weeks_count:
|
except:
|
||||||
matrix[day_idx][week_idx] = "🟩"
|
|
||||||
except Exception:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
days_labels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
||||||
graph = "\n".join(f"{days_labels[i]} {''.join(matrix[i])}" for i in range(7))
|
graph = "\n".join(f"{days[i]} {''.join(matrix[i])}" for i in range(7))
|
||||||
|
|
||||||
await call.edit(
|
await message.edit(
|
||||||
text=self.strings("contrib_header").format(username=username)
|
self.strings("contrib_header").format(username=username)
|
||||||
+ f"<pre>{graph}</pre>"
|
+ f"<pre>{graph}</pre>\n"
|
||||||
+ self.strings("contrib_footer"),
|
+ self.strings("contrib_footer")
|
||||||
reply_markup=[[{"text": self.strings("btn_back"), "callback": self._profile, "args": (username,)}]]
|
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, auto, restart, heroku, hikka
|
|
||||||
|
|
||||||
from hikkatl.types import Message
|
from hikkatl.types import Message
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @PyModule
|
# meta developer: @PyModule
|
||||||
# meta fhsdesc: fun, rp, rpgame
|
|
||||||
# requires: toml
|
# requires: toml
|
||||||
import os
|
import os
|
||||||
from hikka import loader, utils
|
from hikka import loader, utils
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @PyModule
|
# meta developer: @PyModule
|
||||||
# meta fhsdesc: tool, tools, lyrics, music
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup, Tag, NavigableString
|
from bs4 import BeautifulSoup, Tag, NavigableString
|
||||||
import re
|
import re
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, minecraft, game
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import base64
|
import base64
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, in heroku
|
|
||||||
# requires: asyncio
|
# requires: asyncio
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, point, auto
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, qr
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
import requests
|
import requests
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# scope: hikka_only
|
# scope: hikka_only
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, random
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
import random
|
import random
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, test, speedtest
|
|
||||||
# requires: speedtest-cli
|
# requires: speedtest-cli
|
||||||
|
|
||||||
import speedtest
|
import speedtest
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, info, sysinfo, system
|
|
||||||
# requires: psutil
|
# requires: psutil
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# meta fhsdesc: tool, tools, admin, tag, alltag, tagall
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
from telethon.tl.types import ChannelParticipantsAdmins, UserStatusRecently, UserStatusOnline, Message
|
from telethon.tl.types import ChannelParticipantsAdmins, UserStatusRecently, UserStatusOnline, Message
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
# Name: UserParser
|
# Name: UserParser
|
||||||
# Description: Данный модуль позволяет копировать ID, Username и Name участников чата при помощи команды .userpars
|
# Description: Данный модуль позволяет копировать ID, Username и Name участников чата при помощи команды .userpars
|
||||||
# meta developer: @PyModule
|
# meta developer: @PyModule
|
||||||
# meta fhsdesc: tool, tools, id, parser, userparser
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
import json
|
import json
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
# meta developer: @pymodule
|
# meta developer: @pymodule
|
||||||
# requires: aiohttp
|
# requires: aiohttp
|
||||||
# meta fhsdesc: tool, tools, wiki, wikipedia, info, wikiinfo
|
|
||||||
|
|
||||||
from .. import loader, utils
|
from .. import loader, utils
|
||||||
from ..inline.types import InlineQuery
|
from ..inline.types import InlineQuery
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -- version --
|
# -- version --
|
||||||
__version__ = (1, 2, 2)
|
__version__ = (1, 2, 1)
|
||||||
# -- version --
|
# -- version --
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ __version__ = (1, 2, 2)
|
|||||||
# ██║╚██╔╝██║██╔══╝░░██╔══██║██║░░██║██║░░██║░░████╔═████║░░╚═══██╗░╚═══██╗
|
# ██║╚██╔╝██║██╔══╝░░██╔══██║██║░░██║██║░░██║░░████╔═████║░░╚═══██╗░╚═══██╗
|
||||||
# ██║░╚═╝░██║███████╗██║░░██║██████╔╝╚█████╔╝░░╚██╔╝░╚██╔╝░██████╔╝██████╔╝
|
# ██║░╚═╝░██║███████╗██║░░██║██████╔╝╚█████╔╝░░╚██╔╝░╚██╔╝░██████╔╝██████╔╝
|
||||||
# ╚═╝░░░░░╚═╝╚══════╝╚═╝░░╚═╝╚═════╝░░╚════╝░░░░╚═╝░░░╚═╝░░╚═════╝░╚═════╝░
|
# ╚═╝░░░░░╚═╝╚══════╝╚═╝░░╚═╝╚═════╝░░╚════╝░░░░╚═╝░░░╚═╝░░╚═════╝░╚═════╝░
|
||||||
# © Copyright 2026
|
# © Copyright 2025
|
||||||
# ✈ https://t.me/mead0wssMods
|
# ✈ https://t.me/mead0wssMods
|
||||||
|
|
||||||
|
|
||||||
@@ -33,17 +33,14 @@ class SenderGifts(loader.Module):
|
|||||||
"checking_user": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка пользователя...",
|
"checking_user": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка пользователя...",
|
||||||
"checking_balance": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка баланса...",
|
"checking_balance": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка баланса...",
|
||||||
"user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден",
|
"user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден",
|
||||||
"gift_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Выберите категорию подарков.\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}\n<tg-emoji emoji-id=5321485469249198987>⭐️</tg-emoji> Баланс: {} звезд",
|
"gift_menu": "<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": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Подарки за {} ⭐\n\n<tg-emoji emoji-id=6048471184461271609>👤</tg-emoji> Пользователь: {}\n<tg-emoji emoji-id=6048762138430803961>📂</tg-emoji> Текст: {}",
|
"category_menu": "<emoji document_id=5931696400982088015>🎁</emoji> Подарки за {} ⭐\n\n<emoji document_id=6032693626394382504>👤</emoji> Пользователь: {}\n<emoji document_id=5873153278023307367>📄</emoji> Текст: {}",
|
||||||
"privacy_menu": "<tg-emoji emoji-id=5370781982886220096>🎁</tg-emoji> Выбран подарок: {}\n\nКак отправить подарок?",
|
|
||||||
"sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...",
|
"sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...",
|
||||||
"gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!",
|
"gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!",
|
||||||
"not_enough_stars": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки подарка {}!",
|
"not_enough_stars": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки подарка {}!",
|
||||||
"min_stars_error": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки минимального подарка!",
|
"min_stars_error": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки минимального подарка!",
|
||||||
"no_available_gifts": "<emoji document_id=4958526153955476488>❌</emoji> Нет доступных подарков для вашего баланса",
|
"no_available_gifts": "<emoji document_id=4958526153955476488>❌</emoji> Нет доступных подарков для вашего баланса",
|
||||||
"balance_error": "<emoji document_id=4958526153955476488>❌</emoji> Ошибка при проверке баланса",
|
"balance_error": "<emoji document_id=4958526153955476488>❌</emoji> Ошибка при проверке баланса",
|
||||||
"btn_public": "📢 Публично",
|
|
||||||
"btn_anon": "🕵️ Анонимно",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gift_categories = {
|
gift_categories = {
|
||||||
@@ -60,7 +57,6 @@ class SenderGifts(loader.Module):
|
|||||||
{"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
|
{"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
|
||||||
{"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"},
|
{"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"},
|
||||||
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка"},
|
{"id": 5922558454332916696, "emoji": "🎄", "name": "Ёлка"},
|
||||||
{"id": 5956217000635139069, "emoji": "🧸", "name": "Новогодний мишка"}
|
|
||||||
],
|
],
|
||||||
100: [
|
100: [
|
||||||
{"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"},
|
{"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"},
|
||||||
@@ -140,10 +136,8 @@ class SenderGifts(loader.Module):
|
|||||||
if row:
|
if row:
|
||||||
buttons.append(row)
|
buttons.append(row)
|
||||||
|
|
||||||
helper_msg = await self.inline.form("🪐", balance_msg)
|
|
||||||
|
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
helper_msg,
|
balance_msg,
|
||||||
self.strings["gift_menu"].format(
|
self.strings["gift_menu"].format(
|
||||||
f"@{user.username}" if user.username else user.first_name,
|
f"@{user.username}" if user.username else user.first_name,
|
||||||
text if text else "-",
|
text if text else "-",
|
||||||
@@ -159,8 +153,8 @@ class SenderGifts(loader.Module):
|
|||||||
for gift in gifts:
|
for gift in gifts:
|
||||||
row.append({
|
row.append({
|
||||||
"text": gift["emoji"],
|
"text": gift["emoji"],
|
||||||
"callback": self._select_privacy,
|
"callback": self._send_gift,
|
||||||
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance, price),
|
"args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance),
|
||||||
})
|
})
|
||||||
if len(row) == 3:
|
if len(row) == 3:
|
||||||
buttons.append(row)
|
buttons.append(row)
|
||||||
@@ -189,34 +183,6 @@ class SenderGifts(loader.Module):
|
|||||||
reply_markup=buttons
|
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):
|
async def _back_to_categories(self, call, user_id, text, balance, msg_id):
|
||||||
try:
|
try:
|
||||||
user = await self.client.get_entity(user_id)
|
user = await self.client.get_entity(user_id)
|
||||||
@@ -250,7 +216,7 @@ class SenderGifts(loader.Module):
|
|||||||
reply_markup=buttons
|
reply_markup=buttons
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance, hide_name):
|
async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance):
|
||||||
try:
|
try:
|
||||||
await call.edit(
|
await call.edit(
|
||||||
self.strings["sending_gift"],
|
self.strings["sending_gift"],
|
||||||
@@ -261,11 +227,11 @@ class SenderGifts(loader.Module):
|
|||||||
self.client.parse_mode,
|
self.client.parse_mode,
|
||||||
)
|
)
|
||||||
text, entities = parse_mode.parse(text)
|
text, entities = parse_mode.parse(text)
|
||||||
|
|
||||||
user = await self.client.get_input_entity(user_id)
|
user = await self.client.get_input_entity(user_id)
|
||||||
inv = InputInvoiceStarGift(
|
inv = InputInvoiceStarGift(
|
||||||
user,
|
user,
|
||||||
gift_id,
|
gift_id,
|
||||||
hide_name=hide_name,
|
|
||||||
message=TextWithEntities(text, entities) if text else TextWithEntities("", [])
|
message=TextWithEntities(text, entities) if text else TextWithEntities("", [])
|
||||||
)
|
)
|
||||||
form = await self.client(GetPaymentFormRequest(inv))
|
form = await self.client(GetPaymentFormRequest(inv))
|
||||||
|
|||||||
118974
modules.json
118974
modules.json
File diff suppressed because one or more lines are too long
477
parse.py
477
parse.py
@@ -1,376 +1,171 @@
|
|||||||
|
import os
|
||||||
import ast
|
import ast
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
from typing import Dict, Any, Optional, List
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING, format="%(message)s")
|
from clone_repos import repos
|
||||||
logger = logging.getLogger(__name__)
|
from typing import Dict
|
||||||
|
|
||||||
def safe_unparse(node: ast.AST) -> str:
|
# TODO: ADD VENV IGNORE
|
||||||
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 = {}
|
|
||||||
|
|
||||||
for i in repositories:
|
def get_module_info(module_path):
|
||||||
path = i.get("path", "")
|
"""Парсит Python-модуль и извлекает информацию о нем."""
|
||||||
blacklist = i.get("blacklist", [])
|
|
||||||
if path and blacklist:
|
|
||||||
blacklisted_modules[path] = blacklist
|
|
||||||
|
|
||||||
return blacklisted_modules
|
|
||||||
|
|
||||||
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 extract_string_value(node: ast.AST) -> Optional[str]:
|
|
||||||
try:
|
|
||||||
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
||||||
return node.value
|
|
||||||
|
|
||||||
if isinstance(node, ast.Str):
|
|
||||||
return node.s
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def get_module_info(module_path: str) -> Optional[Dict[str, Any]]:
|
|
||||||
try:
|
|
||||||
with open(module_path, "r", encoding="utf-8") as f:
|
with open(module_path, "r", encoding="utf-8") as f:
|
||||||
source = f.read()
|
module_content = f.read()
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Skipping {module_path}: read failed — {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
source = source.lstrip('\ufeff')
|
meta_info = {"pic": None, "banner": None}
|
||||||
source = ''.join(c for c in source if ord(c) >= 32 or c in '\n\r\t') if source else source
|
for line in module_content.split("\n"):
|
||||||
|
|
||||||
meta = {"pic": None, "banner": None, "developer": None}
|
|
||||||
for line in source.splitlines():
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith("# meta"):
|
if line.startswith("# meta"):
|
||||||
try:
|
key, value = line.replace("# meta ", "").split(": ")
|
||||||
key, val = line[len("# meta "):].split(":", 1)
|
meta_info[key] = value
|
||||||
meta[key.strip()] = val.strip()
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
tree = ast.parse(module_content)
|
||||||
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": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
module_data = None
|
def get_decorator_names(decorator_list):
|
||||||
|
return [ast.unparse(decorator) for decorator in decorator_list]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
result = {}
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if not isinstance(node, ast.ClassDef):
|
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:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
is_module_class = (
|
class_docstring = ast.get_docstring(node)
|
||||||
"Mod" in node.name or
|
class_info = {
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not is_module_class:
|
|
||||||
continue
|
|
||||||
|
|
||||||
info = {
|
|
||||||
"name": node.name,
|
"name": node.name,
|
||||||
"description": ast.get_docstring(node) or "",
|
"description": class_docstring,
|
||||||
"cls_doc": {},
|
"meta": meta_info,
|
||||||
"meta": meta,
|
|
||||||
"commands": [],
|
"commands": [],
|
||||||
"new_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:
|
for class_body_node in node.body:
|
||||||
if isinstance(item, ast.Assign):
|
if isinstance(class_body_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
for target in item.targets:
|
decorators = get_decorator_names(class_body_node.decorator_list)
|
||||||
if isinstance(target, ast.Name) and (target.id == "strings" or target.id.startswith("strings_")):
|
is_loader_command = [d for d in decorators if "command" in d]
|
||||||
try:
|
if not is_loader_command and "cmd" not in class_body_node.name:
|
||||||
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
|
continue
|
||||||
|
|
||||||
name = func.name
|
method_docstring = ast.get_docstring(class_body_node)
|
||||||
if name == "on_load":
|
command_name = class_body_node.name
|
||||||
info["has_on_load"] = True
|
ru_doc, en_doc = None, None
|
||||||
continue
|
|
||||||
if name == "on_unload":
|
|
||||||
info["has_on_unload"] = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
is_decorated = any(
|
for decorator in class_body_node.decorator_list:
|
||||||
isinstance(d, ast.Call) and hasattr(d.func, 'attr') and
|
ru_doc_tmp, en_doc_tmp = extract_loader_command_args(decorator)
|
||||||
d.func.attr in ("command", "inline_handler", "unrestricted", "owner")
|
if ru_doc_tmp:
|
||||||
for d in func.decorator_list
|
ru_doc = ru_doc_tmp
|
||||||
|
if en_doc_tmp:
|
||||||
|
en_doc = en_doc_tmp
|
||||||
|
|
||||||
|
descriptions = []
|
||||||
|
if method_docstring:
|
||||||
|
descriptions.append(method_docstring)
|
||||||
|
if ru_doc:
|
||||||
|
descriptions.append(ru_doc)
|
||||||
|
if en_doc:
|
||||||
|
descriptions.append(en_doc)
|
||||||
|
|
||||||
|
class_info["commands"].append(
|
||||||
|
{command_name: ' '.join(descriptions)}
|
||||||
)
|
)
|
||||||
|
|
||||||
if name.startswith("_") and not is_decorated:
|
command_name = command_name.replace('cmd', '')
|
||||||
continue
|
|
||||||
|
|
||||||
cmd = {
|
class_info["new_commands"].append(
|
||||||
"name": name,
|
{
|
||||||
"doc": ast.get_docstring(func) or "",
|
command_name: {
|
||||||
"lang_docs": {},
|
"ru_doc": ru_doc,
|
||||||
"aliases": [],
|
"en_doc": en_doc,
|
||||||
"usage": None,
|
"doc": method_docstring,
|
||||||
"inline": False,
|
}
|
||||||
"is_inline_handler": False,
|
}
|
||||||
"decorators": [],
|
)
|
||||||
"cmd_names": {},
|
|
||||||
|
result = class_info
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def parse_developers(base_dir: str) -> Dict[str, list]:
|
||||||
|
developers = {
|
||||||
|
"repo": set(), # используем set внутри функции
|
||||||
|
"channel": set()
|
||||||
}
|
}
|
||||||
|
|
||||||
for dec in func.decorator_list:
|
for repo_url in repos:
|
||||||
if isinstance(dec, ast.Call) and hasattr(dec.func, 'attr'):
|
repo_path = repo_url.replace("https://github.com/", "")
|
||||||
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:
|
try:
|
||||||
data = get_module_info(path)
|
owner, repo_name = repo_path.split("/")
|
||||||
if data:
|
developers["repo"].add(owner)
|
||||||
rel = os.path.relpath(path, base_dir).replace("\\", "/")
|
except ValueError:
|
||||||
modules[rel] = data
|
print(f"Incorrect URL of repository: {repo_url}")
|
||||||
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error processing {path}: {e}")
|
print(f"Ошибка при парсинге файла {file_path}: {e}")
|
||||||
|
|
||||||
output = {
|
# Преобразуем set в list перед возвратом
|
||||||
"modules": modules,
|
return {
|
||||||
"meta": {
|
"repo": list(developers["repo"]),
|
||||||
"total_modules": len(modules),
|
"channel": list(developers["channel"])
|
||||||
"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)
|
|
||||||
|
|
||||||
print(f"modules.json written ({len(modules)} modules)")
|
modules_data = {}
|
||||||
|
base_dir = os.getcwd()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
for root, _, files in os.walk(base_dir):
|
||||||
main()
|
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 создан!")
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
# =======================================
|
|
||||||
# _ __ __ __ _
|
|
||||||
# | |/ /___ | \/ | ___ __| |___
|
|
||||||
# | ' // _ \ | |\/| |/ _ \ / _` / __|
|
|
||||||
# | . \ __/ | | | | (_) | (_| \__ \
|
|
||||||
# |_|\_\___| |_| |_|\___/ \__,_|___/
|
|
||||||
# @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>")
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# =======================================
|
|
||||||
# _ __ __ __ _
|
|
||||||
# | |/ /___ | \/ | ___ __| |___
|
|
||||||
# | ' // _ \ | |\/| |/ _ \ / _` / __|
|
|
||||||
# | . \ __/ | | | | (_) | (_| \__ \
|
|
||||||
# |_|\_\___| |_| |_|\___/ \__,_|___/
|
|
||||||
# @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"))
|
|
||||||
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
# =======================================
|
|
||||||
# _ __ __ __ _
|
|
||||||
# | |/ /___ | \/ | ___ __| |___
|
|
||||||
# | ' // _ \ | |\/| |/ _ \ / _` / __|
|
|
||||||
# | . \ __/ | | | | (_) | (_| \__ \
|
|
||||||
# |_|\_\___| |_| |_|\___/ \__,_|___/
|
|
||||||
# @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
@@ -1,74 +0,0 @@
|
|||||||
# =======================================
|
|
||||||
# _ __ __ __ _
|
|
||||||
# | |/ /___ | \/ | ___ __| |___
|
|
||||||
# | ' // _ \ | |\/| |/ _ \ / _` / __|
|
|
||||||
# | . \ __/ | | | | (_) | (_| \__ \
|
|
||||||
# |_|\_\___| |_| |_|\___/ \__,_|___/
|
|
||||||
# @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"))
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
Neofetch
|
|
||||||
randomanimepic
|
|
||||||
SpotifyMod
|
|
||||||
UnbanAll
|
|
||||||
voicetotext
|
|
||||||
LastFm
|
|
||||||
PicToStories
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# =======================================
|
|
||||||
# _ __ __ __ _
|
|
||||||
# | |/ /___ | \/ | ___ __| |___
|
|
||||||
# | ' // _ \ | |\/| |/ _ \ / _` / __|
|
|
||||||
# | . \ __/ | | | | (_) | (_| \__ \
|
|
||||||
# |_|\_\___| |_| |_|\___/ \__,_|___/
|
|
||||||
# @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)
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
# =======================================
|
|
||||||
# _ __ __ __ _
|
|
||||||
# | |/ /___ | \/ | ___ __| |___
|
|
||||||
# | ' // _ \ | |\/| |/ _ \ / _` / __|
|
|
||||||
# | . \ __/ | | | | (_) | (_| \__ \
|
|
||||||
# |_|\_\___| |_| |_|\___/ \__,_|___/
|
|
||||||
# @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)
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
{
|
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"url": "https://github.com/DziruModules/hikkamods",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/kamolgks/Hikkamods",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/thomasmod/hikkamods",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/SkillsAngels/Modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/Sad0ff/modules-ftg",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/Yahikoro/Modules-for-FTG",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/KeyZenD/modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/AlpacaGang/ftg-modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/trololo65/Modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/Ijidishurka/modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/Fl1yd/FTG-Modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/D4n13l3k00/FTG-Modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/iamnalinor/FTG-modules",
|
|
||||||
"tags": ["hikkatrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/SekaiYoneya/modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/GeekTG/FTG-Modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/Den4ikSuperOstryyPer4ik/Astro-modules",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/vsecoder/hikka_modules",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/sqlmerr/hikka_mods",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/N3rcy/modules",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/KorenbZla/HikkaModules",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/MuRuLOSE/HikkaModulesRepo",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/coddrago/modules",
|
|
||||||
"tags": ["herokutrusted", "hikkatrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/1jpshiro/hikka-modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/MoriSummerz/ftg-mods",
|
|
||||||
"tags": ["hikkatrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/anon97945/hikka-mods",
|
|
||||||
"tags": ["hikkatrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/dorotorothequickend/DorotoroModules",
|
|
||||||
"tags": ["hikkatrusted", "nonlongermaintained"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/AmoreForever/amoremods",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/idiotcoders/idiotmodules",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/CakesTwix/Hikka-Modules",
|
|
||||||
"tags": ["hikkatrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/archquise/H.Modules",
|
|
||||||
"tags": ["hikkatrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/GD-alt/mm-hikka-mods",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/HitaloSama/FTG-modules-repo",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/SekaiYoneya/Friendly-telegram",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/blazedzn/ftg-modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/hikariatama/ftg",
|
|
||||||
"tags": ["hikkatrusted", "nonactive"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/m4xx1m/FTG",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/skillzmeow/skillzmods_hikka",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fajox1/famods",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/TheKsenon/MyHikkaModules",
|
|
||||||
"tags": ["hikkatrusted", "herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/cryptexctl/modules-mirror",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/Ruslan-Isaev/modules",
|
|
||||||
"tags": ["herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/shadowhikka/sh.modules",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fiksofficial/python-modules",
|
|
||||||
"tags": ["herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/mead0wsss/mead0wsMods",
|
|
||||||
"tags": ["herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/SenkoGuardian/SenModules",
|
|
||||||
"tags": ["herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/ZetGoHack/nullmod",
|
|
||||||
"tags": ["herokutrusted"],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/yummy1gay/limoka",
|
|
||||||
"tags": [],
|
|
||||||
"blacklist": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/radiocycle/Modules",
|
|
||||||
"tags": ["newbie"],
|
|
||||||
"blacklist": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
20
trusted.json
Normal file
20
trusted.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"trusted": [
|
||||||
|
"iamnalinor/FTG-modules",
|
||||||
|
"vsecoder/hikka_modules",
|
||||||
|
"sqlmerr/hikka_mods",
|
||||||
|
"N3rcy/modules",
|
||||||
|
"KorenbZla/HikkaModules",
|
||||||
|
"coddrago/modules",
|
||||||
|
"MoriSummerz/ftg-mods",
|
||||||
|
"anon97945/hikka-mods",
|
||||||
|
"dorotorothequickend/DorotoroModules",
|
||||||
|
"idiotcoders/idiotmodules",
|
||||||
|
"C0dwiz/H.Modules",
|
||||||
|
"GD-alt/mm-hikka-mods",
|
||||||
|
"hikariatama/ftg",
|
||||||
|
"fajox1/famods",
|
||||||
|
"TheKsenon/MyHikkaModules",
|
||||||
|
"Den4ikSuperOstryyPer4ik/Astro-modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
195
update_diffs.py
195
update_diffs.py
@@ -1,195 +0,0 @@
|
|||||||
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())
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = (1, 2, 0, 0)
|
__version__ = (1, 1, 1, 1)
|
||||||
|
|
||||||
# This file is a part of Hikka Userbot!
|
# This file is a part of Hikka Userbot!
|
||||||
# This product includes software developed by t.me/Fl1yd and t.me/spypm.
|
# This product includes software developed by t.me/Fl1yd and t.me/spypm.
|
||||||
@@ -15,10 +15,6 @@ __version__ = (1, 2, 0, 0)
|
|||||||
# scope: hikka_only
|
# scope: hikka_only
|
||||||
# scope: hikka_min 1.6.3
|
# scope: hikka_min 1.6.3
|
||||||
|
|
||||||
# Changelog v1.2:
|
|
||||||
# - Added: Proxy for users from RF
|
|
||||||
# - Fixed: Correct reply author resolving for forwarded messages
|
|
||||||
|
|
||||||
# █▄█ █░█ █▀▄▀█ █▀▄▀█ █▄█ █▀▄▀█ █▀█ █▀▄ █▀
|
# █▄█ █░█ █▀▄▀█ █▀▄▀█ █▄█ █▀▄▀█ █▀█ █▀▄ █▀
|
||||||
# ░█░ █▄█ █░▀░█ █░▀░█ ░█░ █░▀░█ █▄█ █▄▀ ▄█
|
# ░█░ █▄█ █░▀░█ █░▀░█ ░█░ █░▀░█ █▄█ █▄▀ ▄█
|
||||||
|
|
||||||
@@ -198,12 +194,6 @@ class Quotes(loader.Module):
|
|||||||
validator=loader.validators.Integer(minimum=1,maximum=50)),
|
validator=loader.validators.Integer(minimum=1,maximum=50)),
|
||||||
loader.ConfigValue("endpoint","https://kok.gay/gayotes/generate",
|
loader.ConfigValue("endpoint","https://kok.gay/gayotes/generate",
|
||||||
lambda:"URL API-эндпоинта (можешь поднять локально - github.com/yummy1gay/quote-api)",
|
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()))
|
validator=loader.validators.Link()))
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
@@ -236,11 +226,10 @@ class Quotes(loader.Module):
|
|||||||
"format": "webp" if not doc else "png", "type": self.config["type"]}
|
"format": "webp" if not doc else "png", "type": self.config["type"]}
|
||||||
|
|
||||||
await utils.answer(st,self.strings["api_processing"])
|
await utils.answer(st,self.strings["api_processing"])
|
||||||
endpoint=self.config['rf_endpoint'] if self.config['use_rf_proxy'] else self.config['endpoint']
|
r=await Dick.post(f"{self.config['endpoint']}.webp",pay)
|
||||||
r=await Dick.post(f"{endpoint}.webp",pay)
|
|
||||||
if not r or r.status_code!=200:
|
if not r or r.status_code!=200:
|
||||||
try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
|
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 "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
|
except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь"
|
||||||
return await utils.answer(st,self.strings["api_error"].format(err))
|
return await utils.answer(st,self.strings["api_error"].format(err))
|
||||||
|
|
||||||
buf=io.BytesIO(r.content); buf.name="YgQuote"+(".png" if doc else ".webp")
|
buf=io.BytesIO(r.content); buf.name="YgQuote"+(".png" if doc else ".webp")
|
||||||
@@ -270,11 +259,10 @@ class Quotes(loader.Module):
|
|||||||
"format": "webp","type":self.config["type"]}
|
"format": "webp","type":self.config["type"]}
|
||||||
|
|
||||||
await utils.answer(st,self.strings["api_processing"])
|
await utils.answer(st,self.strings["api_processing"])
|
||||||
endpoint=self.config['rf_endpoint'] if self.config['use_rf_proxy'] else self.config['endpoint']
|
r=await Dick.post(f"{self.config['endpoint']}.webp",dickk)
|
||||||
r=await Dick.post(f"{endpoint}.webp",dickk)
|
|
||||||
if not r or r.status_code!=200:
|
if not r or r.status_code!=200:
|
||||||
try: err=r.json().get("error",f"HTTP {r.status_code}") if r else "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
|
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 "Нетворк еррорь (попробуй включить <code>use_rf_proxy</code> в конфиге)"
|
except Exception: err=f"HTTP {r.status_code}" if r else "Нетворк еррорь"
|
||||||
return await utils.answer(st,self.strings["api_error"].format(err))
|
return await utils.answer(st,self.strings["api_error"].format(err))
|
||||||
|
|
||||||
buf=io.BytesIO(r.content); buf.name="YgQuote.webp"
|
buf=io.BytesIO(r.content); buf.name="YgQuote.webp"
|
||||||
@@ -301,8 +289,7 @@ class Quotes(loader.Module):
|
|||||||
try:
|
try:
|
||||||
r=await mm.get_reply_message()
|
r=await mm.get_reply_message()
|
||||||
if r:
|
if r:
|
||||||
ruser = await self.who(r)
|
rname=telethon.utils.get_display_name(r.sender)
|
||||||
rname=telethon.utils.get_display_name(ruser)
|
|
||||||
rtxt=Dick.desc(r,True)
|
rtxt=Dick.desc(r,True)
|
||||||
if r.raw_text: rtxt=(rtxt+". "+r.raw_text) if rtxt else r.raw_text
|
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),
|
rb={"name":rname,"text":rtxt or "","entities":Dick.ents(r.entities),
|
||||||
|
|||||||
Reference in New Issue
Block a user