mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 06:24:18 +02:00
Added and updated repositories 2025-11-21 01:04:46
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
|
||||
# meta banner: https://te.legra.ph/file/7612b5506856c1eb34c56.jpg
|
||||
|
||||
version = (1, 0, 0)
|
||||
__version__ = (1, 1, 0)
|
||||
|
||||
import json
|
||||
import aiohttp
|
||||
@@ -42,7 +42,7 @@ class AuroraBullMod(loader.Module):
|
||||
"error_decoding": "<b><i>Error: The JSON could not be decoded.</i></b>",
|
||||
"error_uploading_data": "<b><i>Error loading data</i></b>",
|
||||
"error_valid_args": "<b><i>Please enter valid arguments!</i></b>",
|
||||
"launched": "<b><i>AuroraBull launched!</i></b>\n\n<b><i>Use <code>.abulloff</code> to stop the attack.</i></b>",
|
||||
"launched": "<b><i>AuroraBull launched!</i></b>\n\n<b><i>Use <code>{prefix}abulloff</code> to stop the attack.</i></b>",
|
||||
"stopped": "<b><i>AuroraBull has stopped.</i></b>",
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class AuroraBullMod(loader.Module):
|
||||
"error_decoding": "<b><i>Error: не удалось декодировать JSON.</i></b>",
|
||||
"error_uploading_data": "<b><i>Ошибка при загрузке данных</i></b>",
|
||||
"error_valid_args": "<b><i>Введите корректные аргументы!</i></b>",
|
||||
"launched": "<b><i>AuroraBull запущен!</i></b>\n\n<b><i>Используйте <code>.abulloff</code>, чтобы остановить атаку.</i></b>",
|
||||
"launched": "<b><i>AuroraBull запущен!</i></b>\n\n<b><i>Используйте <code>{prefix}abulloff</code>, чтобы остановить атаку.</i></b>",
|
||||
"stopped": "<b><i>AuroraBull остановлен.</i></b>",
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class AuroraBullMod(loader.Module):
|
||||
"error_decoding": "<b><i>Error: JSON декодлаш муваффақиятли амалга ошмади.</i></b>",
|
||||
"error_uploading_data": "<b><i>Маълумотлар юклаб олинмади</i></b>",
|
||||
"error_valid_args": "<b><i>Iltimos, to'g'ri dalillarni kiriting!</i></b>",
|
||||
"launched": "<b><i>AuroraBull ishga tushirildi!</i></b>\n\n<b><i>Hujumni toʻxtatish uchun <code>.abulloff</code> dan foydalaning.</i></b>",
|
||||
"launched": "<b><i>AuroraBull ishga tushirildi!</i></b>\n\n<b><i>Hujumni toʻxtatish uchun <code>{prefix}abulloff</code> dan foydalaning.</i></b>",
|
||||
"stopped": "<b><i>AuroraBull to'xtadi.</i></b>",
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class AuroraBullMod(loader.Module):
|
||||
"error_decoding": "<b><i>Error: JSON konnte nicht decodiert werden.</i></b>",
|
||||
"error_uploading_data": "<b><i>Fehler beim Hochladen der Daten</i></b>",
|
||||
"error_valid_args": "<b><i>Bitte geben Sie gültige Argumente ein!</i></b>",
|
||||
"launched": "<b><i>AuroraBull gestartet!</i></b>\n\n<b><i>Verwenden Sie <code>.abulloff</code>, um den Angriff zu stoppen.</i></b>",
|
||||
"launched": "<b><i>AuroraBull gestartet!</i></b>\n\n<b><i>Verwenden Sie <code>{prefix}abulloff</code>, um den Angriff zu stoppen.</i></b>",
|
||||
"stopped": "<b><i>AuroraBull hat angehalten.</i></b>",
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class AuroraBullMod(loader.Module):
|
||||
"error_decoding": "<b><i>Error: No se pudo decodificar JSON.</i></b>",
|
||||
"error_uploading_data": "<b><i>Error al cargar los datos</i></b>",
|
||||
"error_valid_args": "<b><i>¡Por favor ingrese argumentos válidos!</i></b>",
|
||||
"launched": "<b><i>¡AuroraBull lanzado!</i></b>\n\n<b><i>Utiliza <code>.abulloff</code> para detener el ataque.</i></b>",
|
||||
"launched": "<b><i>¡AuroraBull lanzado!</i></b>\n\n<b><i>Utiliza <code>{prefix}abulloff</code> para detener el ataque.</i></b>",
|
||||
"stopped": "<b><i>AuroraBull se ha detenido.</i></b>",
|
||||
|
||||
}
|
||||
@@ -133,7 +133,7 @@ class AuroraBullMod(loader.Module):
|
||||
await utils.answer(message, self.strings("error_valid_args"))
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings("launched"))
|
||||
await utils.answer(message, self.strings("launched").format(prefix=self.get_prefix()))
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
@@ -146,10 +146,11 @@ class AuroraBullMod(loader.Module):
|
||||
bull_text = choice(data["BullText"])
|
||||
await message.respond(text + bull_text)
|
||||
await asyncio.sleep(time)
|
||||
else:
|
||||
await utils.answer(message, self.strings("error_key"))
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, f"{self.strings('error_uploading_data')}: {response.status}")
|
||||
return await utils.answer(message, self.strings("error_key"))
|
||||
else:
|
||||
return await utils.answer(message, f"{self.strings('error_uploading_data')}: {response.status}")
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Остановить оскорбления",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
|
||||
# meta banner: https://te.legra.ph/file/1d547b05f967c9681b90a.jpg
|
||||
|
||||
__version__ = (3, 1, 1)
|
||||
__version__ = (3, 2, 0)
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
@@ -97,7 +97,7 @@ class IrisFarmMod(loader.Module):
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"random_interval",
|
||||
False,
|
||||
True,
|
||||
lambda: self.strings["cfg_random_interval"],
|
||||
validator=loader.validators.Boolean()
|
||||
)
|
||||
@@ -112,8 +112,8 @@ class IrisFarmMod(loader.Module):
|
||||
"""{on/off} - turn auto farm on or off"""
|
||||
args = utils.get_args_raw(message).lower()
|
||||
|
||||
status_result_True = self.db.get("AuroraIrisFarm", "status", True)
|
||||
if status_result_True:
|
||||
status_result = self.db.get("AuroraIrisFarm", "status")
|
||||
if status_result is True:
|
||||
status_result = self.strings("s_1")
|
||||
else:
|
||||
status_result = self.strings("s_0")
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
version = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from telethon import TelegramClient, events
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
import os
|
||||
from .. import loader, utils
|
||||
|
||||
import paramiko
|
||||
|
||||
# requires: paramiko
|
||||
|
||||
def upload_file_sftp(host, port, username, password, local_file, remote_file):
|
||||
try:
|
||||
# Создаем экземпляр SSHClient
|
||||
client = paramiko.SSHClient()
|
||||
|
||||
# Загружаем параметры по умолчанию
|
||||
client.load_system_host_keys()
|
||||
|
||||
# Разрешаем соединение с сервером, если ключа нет в системе
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
# Подключаемся к серверу
|
||||
client.connect(hostname=host, port=port, username=username, password=password)
|
||||
|
||||
# Открываем SFTP сессию
|
||||
sftp = client.open_sftp()
|
||||
|
||||
try:
|
||||
sftp.listdir("SFTP_files")
|
||||
except IOError:
|
||||
sftp.mkdir("SFTP_files")
|
||||
|
||||
# Загружаем файл
|
||||
sftp.put(local_file, remote_file)
|
||||
|
||||
print(f'Файл {local_file} успешно загружен на {remote_file}')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Произошла ошибка: {e}')
|
||||
finally:
|
||||
# Закрываем SFTP сессию и SSH соединение
|
||||
if 'sftp' in locals():
|
||||
sftp.close()
|
||||
client.close()
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SFTPUploaderMod(loader.Module):
|
||||
"""Загрузка файлов на SFTP"""
|
||||
|
||||
strings = {
|
||||
"name": "SFTPUploader",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"host",
|
||||
"None",
|
||||
"IP address or domain",
|
||||
validator=loader.validators.String()
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"username",
|
||||
"None",
|
||||
"SFTP username",
|
||||
validator=loader.validators.String()
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"password",
|
||||
"None",
|
||||
"SFTP password",
|
||||
validator=loader.validators.Hidden()
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"Port",
|
||||
22,
|
||||
"SFTP port",
|
||||
validator=loader.validators.String()
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def sftp(self, message):
|
||||
"""<reply> - загружает файл на SFPT"""
|
||||
host = self.config["host"] or "None"
|
||||
username = self.config["username"] or "None"
|
||||
password = self.config["password"] or "None"
|
||||
port = self.config["Port"] or "None"
|
||||
if host == "None" or username == "None" or password == "None" or port == "None":
|
||||
await utils.answer(message, "<b>Значения не указаны. Укажите их через команду:</b>\n<code>.config SFTPUploader</code>")
|
||||
return
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
if reply.media:
|
||||
await utils.answer(message, f"<b>Начинаю загрузку....</b>")
|
||||
file_path = await message.client.download_media(reply.media)
|
||||
sftp_path = f"SFTP_files/{file_path}"
|
||||
upld = upload_file_sftp(host, port, username, password, file_path, sftp_path)
|
||||
os.remove(file_path)
|
||||
await utils.answer(message, f"<b>Файл загружен на SFTP сервер(не факт), расположение файла:</b> <code>~/SFTP_files/{file_path}</code>")
|
||||
else:
|
||||
await utils.answer(message, "<b>В сообщении не найдены файлы!</b>")
|
||||
else:
|
||||
await utils.answer(message, "<b>Команда должна быть ответом на сообщение!</b>")
|
||||
return
|
||||
|
||||
@@ -1,207 +1,480 @@
|
||||
version = (1, 0, 0)
|
||||
version = (2, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
# requires: paramiko
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from telethon import TelegramClient, events
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
import asyncio
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
from .. import loader, utils
|
||||
|
||||
import paramiko
|
||||
|
||||
def upload_file_sftp(host, port, username, password, local_file, remote_file):
|
||||
try:
|
||||
# Создаем экземпляр SSHClient
|
||||
client = paramiko.SSHClient()
|
||||
|
||||
# Загружаем параметры по умолчанию
|
||||
client.load_system_host_keys()
|
||||
|
||||
# Разрешаем соединение с сервером, если ключа нет в системе
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
# Подключаемся к серверу
|
||||
client.connect(hostname=host, port=port, username=username, password=password)
|
||||
|
||||
# Открываем SFTP сессию
|
||||
sftp = client.open_sftp()
|
||||
|
||||
try:
|
||||
sftp.listdir("sshmod")
|
||||
except IOError:
|
||||
sftp.mkdir("sshmod")
|
||||
|
||||
# Загружаем файл
|
||||
sftp.put(local_file, remote_file)
|
||||
|
||||
print(f'Файл {local_file} успешно загружен на {remote_file}')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Произошла ошибка: {e}')
|
||||
finally:
|
||||
# Закрываем SFTP сессию и SSH соединение
|
||||
if 'sftp' in locals():
|
||||
sftp.close()
|
||||
client.close()
|
||||
|
||||
def execute_ssh_command(host, port, username, password, command):
|
||||
try:
|
||||
# Создаем экземпляр SSHClient
|
||||
client = paramiko.SSHClient()
|
||||
|
||||
# Загружаем параметры по умолчанию
|
||||
client.load_system_host_keys()
|
||||
|
||||
# Разрешаем соединение с сервером, если ключа нет в системе
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
# Подключаемся к серверу
|
||||
client.connect(hostname=host, port=port, username=username, password=password)
|
||||
|
||||
# Выполняем команду
|
||||
stdin, stdout, stderr = client.exec_command(command)
|
||||
|
||||
# Получаем вывод и ошибки
|
||||
output = stdout.read().decode()
|
||||
error = stderr.read().decode()
|
||||
exit_code = stdout.channel.recv_exit_status()
|
||||
|
||||
return exit_code, output, error
|
||||
|
||||
except Exception as e:
|
||||
print(f'Произошла ошибка: {e}')
|
||||
return None, None, str(e)
|
||||
finally:
|
||||
# Закрываем SSH соединение
|
||||
client.close()
|
||||
class SSHConnection:
|
||||
|
||||
def __init__(self, host, port, username, password=None, key_path=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.key_path = key_path
|
||||
self.client = None
|
||||
|
||||
async def connect(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, self._connect_sync)
|
||||
|
||||
def _connect_sync(self):
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.load_system_host_keys()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
connect_kwargs = {
|
||||
'hostname': self.host,
|
||||
'port': self.port,
|
||||
'username': self.username
|
||||
}
|
||||
|
||||
if self.key_path and self.key_path != "None":
|
||||
try:
|
||||
private_key = paramiko.RSAKey.from_private_key_file(self.key_path)
|
||||
connect_kwargs['pkey'] = private_key
|
||||
except:
|
||||
try:
|
||||
private_key = paramiko.Ed25519Key.from_private_key_file(self.key_path)
|
||||
connect_kwargs['pkey'] = private_key
|
||||
except:
|
||||
private_key = paramiko.ECDSAKey.from_private_key_file(self.key_path)
|
||||
connect_kwargs['pkey'] = private_key
|
||||
else:
|
||||
connect_kwargs['password'] = self.password
|
||||
|
||||
self.client.connect(**connect_kwargs)
|
||||
|
||||
async def upload_file(self, local_file, remote_file):
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, self._upload_file_sync, local_file, remote_file)
|
||||
|
||||
def _upload_file_sync(self, local_file, remote_file):
|
||||
sftp = self.client.open_sftp()
|
||||
|
||||
remote_dir = os.path.dirname(remote_file)
|
||||
if remote_dir:
|
||||
self._create_remote_dir(sftp, remote_dir)
|
||||
|
||||
sftp.put(local_file, remote_file)
|
||||
sftp.close()
|
||||
|
||||
def _create_remote_dir(self, sftp, path):
|
||||
dirs = []
|
||||
while path and path != '/':
|
||||
try:
|
||||
sftp.stat(path)
|
||||
break
|
||||
except IOError:
|
||||
dirs.append(path)
|
||||
path = os.path.dirname(path)
|
||||
|
||||
while dirs:
|
||||
dir_path = dirs.pop()
|
||||
try:
|
||||
sftp.mkdir(dir_path)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
async def download_file(self, remote_file, local_file):
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, self._download_file_sync, remote_file, local_file)
|
||||
|
||||
def _download_file_sync(self, remote_file, local_file):
|
||||
sftp = self.client.open_sftp()
|
||||
sftp.get(remote_file, local_file)
|
||||
sftp.close()
|
||||
|
||||
async def execute_command_stream(self, command, callback):
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(
|
||||
None,
|
||||
self._execute_command_stream_sync,
|
||||
command,
|
||||
callback
|
||||
)
|
||||
|
||||
def _execute_command_stream_sync(self, command, callback):
|
||||
stdin, stdout, stderr = self.client.exec_command(command, get_pty=True)
|
||||
|
||||
channel = stdout.channel
|
||||
pid = channel.get_id()
|
||||
|
||||
output_lines = []
|
||||
error_lines = []
|
||||
|
||||
while not stdout.channel.exit_status_ready() or stdout.channel.recv_ready():
|
||||
if stdout.channel.recv_ready():
|
||||
line = stdout.readline()
|
||||
if line:
|
||||
output_lines.append(line)
|
||||
callback('stdout', line, pid)
|
||||
|
||||
remaining = stdout.read().decode()
|
||||
if remaining:
|
||||
output_lines.append(remaining)
|
||||
callback('stdout', remaining, pid)
|
||||
|
||||
error_output = stderr.read().decode()
|
||||
if error_output:
|
||||
error_lines.append(error_output)
|
||||
callback('stderr', error_output, pid)
|
||||
|
||||
exit_code = stdout.channel.recv_exit_status()
|
||||
|
||||
return exit_code, ''.join(output_lines), ''.join(error_lines), pid
|
||||
|
||||
def close(self):
|
||||
if self.client:
|
||||
self.client.close()
|
||||
|
||||
|
||||
@loader.tds
|
||||
class SSHMod(loader.Module):
|
||||
"""SSH module for uploading files and executing commands"""
|
||||
"""Модуль для работы с SSH"""
|
||||
|
||||
strings = {
|
||||
"name": "SSHMod",
|
||||
"cfg_host": "IP address or domain",
|
||||
"cfg_username": "SSH username",
|
||||
"cfg_password": "SSH password",
|
||||
"cfg_port": "SSH port",
|
||||
"save_description": "<reply> - saves the file to the ~/sshmod directory",
|
||||
"save_uploading": "<b>Starting upload....</b>",
|
||||
"save_success": "<b>File uploaded to SSH server, file location:</b> <code>~/sshmod/{}</code>",
|
||||
"save_no_file": "<b>No files found in the message!</b>",
|
||||
"save_reply_required": "<b>The command must be a reply to a message!</b>",
|
||||
"sterminal_description": "<command> - executes a command on the SSH server",
|
||||
"sterminal_no_command": "<b>No command specified!</b>",
|
||||
"sterminal_output": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>Exit code:</b> <code>{}</code>\n<b>📼 Output:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"sterminal_error": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>Exit code:</b> <code>{}</code>\n<b>🚫 Errors:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"sterminal_output_and_error": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>Exit code:</b> <code>{}</code>\n<b>📼 Output:</b>\n<pre><code class='language-stdout'>{}</code></pre>\n<b>🚫 Errors:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"config_not_set": "<b>Values are not set. Set them using the command:</b>\n<code>.config SSHMod</code>",
|
||||
}
|
||||
strings = {
|
||||
"name": "SSHMod",
|
||||
"cfg_host": "IP address or domain",
|
||||
"cfg_username": "SSH username",
|
||||
"cfg_password": "SSH password (leave None if using key)",
|
||||
"cfg_key": "Path to private SSH key (leave None if using password)",
|
||||
"cfg_port": "SSH port",
|
||||
"cfg_default_dir": "Default directory for saving files",
|
||||
"sftpsave_description": "<reply> [directory] - saves the file to the specified directory",
|
||||
"sftpsave_uploading": "<b>Starting upload....</b>",
|
||||
"sftpsave_success": "<b>File uploaded to SSH server, file location:</b> <code>{}</code>",
|
||||
"sftpsave_no_file": "<b>No files found in the message!</b>",
|
||||
"sftpsave_reply_required": "<b>The command must be a reply to a message!</b>",
|
||||
"sftpupload_description": "<file_path> - downloads file from SSH server",
|
||||
"sftpupload_no_path": "<b>No file path specified!</b>",
|
||||
"sftpupload_downloading": "<b>Downloading file from SSH server...</b>",
|
||||
"sftpupload_error": "<b>Error downloading file:</b> <code>{}</code>",
|
||||
"sterminal_description": "<command> - executes a command on the SSH server",
|
||||
"sterminal_no_command": "<b>No command specified!</b>",
|
||||
"sterminal_starting": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Status:</b> Running...\n<b>📼 Output:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"sterminal_output": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Exit code:</b> <code>{}</code>\n<b>📼 Output:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"sterminal_error": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Exit code:</b> <code>{}</code>\n<b>🚫 Errors:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"sterminal_output_and_error": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Exit code:</b> <code>{}</code>\n<b>📼 Output:</b>\n<pre><code class='language-stdout'>{}</code></pre>\n<b>🚫 Errors:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"sterminal_stopped": "⌨️<b> System command</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Status:</b> ⛔ Stopped by user\n<b>📼 Output:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"addkey_description": "<key_content> - saves SSH private key to .ssh directory",
|
||||
"addkey_no_key": "<b>No key content provided!</b>",
|
||||
"addkey_success": "<b>Key saved successfully!</b>\n<b>Key file:</b> <code>{}</code>\n<b>Full path:</b> <code>{}</code>\n\nYou can now set it in config:\n<code>.fcfg SSHMod key_path {}</code>",
|
||||
"addkey_error": "<b>Error saving key:</b> <code>{}</code>",
|
||||
"config_not_set": "<b>Values are not set. Set them using the command:</b>\n<code>.config SSHMod</code>",
|
||||
"stop_button": "⛔ Stop",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "SSHMod",
|
||||
"cfg_host": "IP-адрес или домен",
|
||||
"cfg_username": "Имя пользователя SSH",
|
||||
"cfg_password": "Пароль SSH",
|
||||
"cfg_port": "Порт SSH",
|
||||
"save_description": "<reply> - сохраняет файл в директорию ~/sshmod",
|
||||
"save_uploading": "<b>Начинаю загрузку....</b>",
|
||||
"save_success": "<b>Файл загружен на SSH сервер, расположение файла:</b> <code>~/sshmod/{}</code>",
|
||||
"save_no_file": "<b>В сообщении не найдены файлы!</b>",
|
||||
"save_reply_required": "<b>Команда должна быть ответом на сообщение!</b>",
|
||||
"sterminal_description": "<command> - выполняет команду на SSH сервере",
|
||||
"sterminal_no_command": "<b>Не указана команда для выполнения!</b>",
|
||||
"sterminal_output": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>Код выхода:</b> <code>{}</code>\n<b>📼 Вывод:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"sterminal_error": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>Код выхода:</b> <code>{}</code>\n<b>🚫 Ошибки:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"sterminal_output_and_error": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>Код выхода:</b> <code>{}</code>\n<b>📼 Вывод:</b>\n<pre><code class='language-stdout'>{}</code></pre>\n<b>🚫 Ошибки:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"config_not_set": "<b>Значения не указаны. Укажите их через команду:</b>\n<code>.config SSHMod</code>",
|
||||
}
|
||||
strings_ru = {
|
||||
"name": "SSHMod",
|
||||
"cfg_host": "IP-адрес или домен",
|
||||
"cfg_username": "Имя пользователя SSH",
|
||||
"cfg_password": "Пароль SSH (оставьте None при использовании ключа)",
|
||||
"cfg_key": "Путь к закрытому SSH ключу (оставьте None при использовании пароля)",
|
||||
"cfg_port": "Порт SSH",
|
||||
"cfg_default_dir": "Директория по умолчанию для сохранения файлов",
|
||||
"sftpsave_description": "<reply> [директория] - сохраняет файл в указанную директорию",
|
||||
"sftpsave_uploading": "<b>Начинаю загрузку....</b>",
|
||||
"sftpsave_success": "<b>Файл загружен на SSH сервер, расположение файла:</b> <code>{}</code>",
|
||||
"sftpsave_no_file": "<b>В сообщении не найдены файлы!</b>",
|
||||
"sftpsave_reply_required": "<b>Команда должна быть ответом на сообщение!</b>",
|
||||
"sftpupload_description": "<путь_к_файлу> - скачивает файл с SSH сервера",
|
||||
"sftpupload_no_path": "<b>Не указан путь к файлу!</b>",
|
||||
"sftpupload_downloading": "<b>Скачиваю файл с SSH сервера...</b>",
|
||||
"sftpupload_error": "<b>Ошибка при скачивании файла:</b> <code>{}</code>",
|
||||
"sterminal_description": "<command> - выполняет команду на SSH сервере",
|
||||
"sterminal_no_command": "<b>Не указана команда для выполнения!</b>",
|
||||
"sterminal_starting": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Статус:</b> Выполняется...\n<b>📼 Вывод:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"sterminal_output": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Код выхода:</b> <code>{}</code>\n<b>📼 Вывод:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"sterminal_error": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Код выхода:</b> <code>{}</code>\n<b>🚫 Ошибки:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"sterminal_output_and_error": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Код выхода:</b> <code>{}</code>\n<b>📼 Вывод:</b>\n<pre><code class='language-stdout'>{}</code></pre>\n<b>🚫 Ошибки:</b>\n<pre><code class='language-stderr'>{}</code></pre>",
|
||||
"sterminal_stopped": "⌨️<b> Системная команда</b>\n<pre><code class='language-bash'>{}</code></pre>\n<b>PID:</b> <code>{}</code>\n<b>Статус:</b> ⛔ Остановлено пользователем\n<b>📼 Вывод:</b>\n<pre><code class='language-stdout'>{}</code></pre>",
|
||||
"addkey_description": "<содержимое_ключа> - сохраняет SSH ключ в директорию .ssh",
|
||||
"addkey_no_key": "<b>Не указано содержимое ключа!</b>",
|
||||
"addkey_success": "<b>Ключ успешно сохранён!</b>\n<b>Имя файла:</b> <code>{}</code>\n<b>Полный путь:</b> <code>{}</code>\n\nДля применения напишите:\n<code>.fcfg SSHMod key_path {}</code>",
|
||||
"addkey_error": "<b>Ошибка при сохранении ключа:</b> <code>{}</code>",
|
||||
"config_not_set": "<b>Значения не указаны. Укажите их через команду:</b>\n<code>.config SSHMod</code>",
|
||||
"stop_button": "⛔ Остановить",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"host",
|
||||
"None",
|
||||
lambda: self.strings["cfg_host"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"username",
|
||||
"None",
|
||||
lambda: self.strings["cfg_username"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"password",
|
||||
"None",
|
||||
lambda: self.strings["cfg_password"],
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"Port",
|
||||
22,
|
||||
lambda: self.strings["cfg_port"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
)
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"host",
|
||||
"None",
|
||||
lambda: self.strings["cfg_host"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"username",
|
||||
"None",
|
||||
lambda: self.strings["cfg_username"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"password",
|
||||
"None",
|
||||
lambda: self.strings["cfg_password"],
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"key_path",
|
||||
"None",
|
||||
lambda: self.strings["cfg_key"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"Port",
|
||||
22,
|
||||
lambda: self.strings["cfg_port"],
|
||||
validator=loader.validators.Integer(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"default_directory",
|
||||
"sshmod",
|
||||
lambda: self.strings["cfg_default_dir"],
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
)
|
||||
self.active_tasks = {}
|
||||
|
||||
@loader.command(alias="save")
|
||||
async def save(self, message):
|
||||
"""<reply> - saves the file to the ~/sshmod directory"""
|
||||
host = self.config["host"] or "None"
|
||||
username = self.config["username"] or "None"
|
||||
password = self.config["password"] or "None"
|
||||
port = self.config["Port"] or "None"
|
||||
if host == "None" or username == "None" or password == "None" or port == "None":
|
||||
await utils.answer(message, self.strings["config_not_set"])
|
||||
return
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
if reply.media:
|
||||
await utils.answer(message, self.strings["save_uploading"])
|
||||
file_path = await message.client.download_media(reply.media)
|
||||
sftp_path = f"sshmod/{os.path.basename(file_path)}"
|
||||
upload_file_sftp(host, port, username, password, file_path, sftp_path)
|
||||
os.remove(file_path)
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["save_success"].format(os.path.basename(file_path)),
|
||||
)
|
||||
else:
|
||||
await utils.answer(message, self.strings["save_no_file"])
|
||||
else:
|
||||
await utils.answer(message, self.strings["save_reply_required"])
|
||||
@loader.command()
|
||||
async def sftpsave(self, message):
|
||||
"""<reply> [dir] - сохраняет указанных файл на сервер"""
|
||||
host = self.config["host"]
|
||||
username = self.config["username"]
|
||||
password = self.config["password"]
|
||||
key_path = self.config["key_path"]
|
||||
port = self.config["Port"]
|
||||
|
||||
if host == "None" or username == "None" or (password == "None" and key_path == "None"):
|
||||
await utils.answer(message, self.strings["config_not_set"])
|
||||
return
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
if not reply:
|
||||
await utils.answer(message, self.strings["sftpsave_reply_required"])
|
||||
return
|
||||
|
||||
if not reply.media:
|
||||
await utils.answer(message, self.strings["sftpsave_no_file"])
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
remote_dir = args if args else self.config["default_directory"]
|
||||
|
||||
await utils.answer(message, self.strings["sftpsave_uploading"])
|
||||
|
||||
file_path = await message.client.download_media(reply.media)
|
||||
file_name = os.path.basename(file_path)
|
||||
sftp_path = f"{remote_dir}/{file_name}"
|
||||
|
||||
conn = SSHConnection(host, port, username, password, key_path)
|
||||
await conn.connect()
|
||||
await conn.upload_file(file_path, sftp_path)
|
||||
conn.close()
|
||||
|
||||
os.remove(file_path)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["sftpsave_success"].format(sftp_path),
|
||||
)
|
||||
|
||||
@loader.command(alias="sterminal")
|
||||
async def sterminal(self, message):
|
||||
"""<command> - executes a command on the SSH server"""
|
||||
host = self.config["host"] or "None"
|
||||
username = self.config["username"] or "None"
|
||||
password = self.config["password"] or "None"
|
||||
port = self.config["Port"] or "None"
|
||||
if host == "None" or username == "None" or password == "None" or port == "None":
|
||||
await utils.answer(message, self.strings["config_not_set"])
|
||||
return
|
||||
command = utils.get_args_raw(message)
|
||||
if not command:
|
||||
await utils.answer(message, self.strings["sterminal_no_command"])
|
||||
return
|
||||
@loader.command()
|
||||
async def sftpdownload(self, message):
|
||||
"""<path> - скачивает указанных файл с сервера"""
|
||||
host = self.config["host"]
|
||||
username = self.config["username"]
|
||||
password = self.config["password"]
|
||||
key_path = self.config["key_path"]
|
||||
port = self.config["Port"]
|
||||
|
||||
if host == "None" or username == "None" or (password == "None" and key_path == "None"):
|
||||
await utils.answer(message, self.strings["config_not_set"])
|
||||
return
|
||||
|
||||
remote_path = utils.get_args_raw(message)
|
||||
if not remote_path:
|
||||
await utils.answer(message, self.strings["sftpupload_no_path"])
|
||||
return
|
||||
|
||||
await utils.answer(message, self.strings["sftpupload_downloading"])
|
||||
|
||||
local_file = f"/tmp/sftp_download_{random.randint(1000, 9999)}_{os.path.basename(remote_path)}"
|
||||
|
||||
try:
|
||||
conn = SSHConnection(host, port, username, password, key_path)
|
||||
await conn.connect()
|
||||
await conn.download_file(remote_path, local_file)
|
||||
conn.close()
|
||||
|
||||
await utils.answer_file(
|
||||
message,
|
||||
local_file,
|
||||
f"📥 File from SSH server: <code>{remote_path}</code>"
|
||||
)
|
||||
|
||||
if os.path.exists(local_file):
|
||||
os.remove(local_file)
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, self.strings["sftpupload_error"].format(str(e)))
|
||||
if os.path.exists(local_file):
|
||||
os.remove(local_file)
|
||||
|
||||
# Выполняем команду на SSH сервере
|
||||
exit_code, output, error = execute_ssh_command(host, port, username, password, command)
|
||||
@loader.command()
|
||||
async def addkey(self, message):
|
||||
"""<ключ> - сохраняет указанный ssh ключ"""
|
||||
key_content = utils.get_args_raw(message)
|
||||
|
||||
if not key_content:
|
||||
await utils.answer(message, self.strings["addkey_no_key"])
|
||||
return
|
||||
|
||||
try:
|
||||
ssh_dir = os.path.expanduser("~/.ssh")
|
||||
os.makedirs(ssh_dir, exist_ok=True)
|
||||
|
||||
random_name = ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
||||
key_filename = f"ssh_key_{random_name}"
|
||||
key_path = os.path.join(ssh_dir, key_filename)
|
||||
|
||||
with open(key_path, 'w') as f:
|
||||
f.write(key_content)
|
||||
|
||||
os.chmod(key_path, 0o600)
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["addkey_success"].format(key_filename, key_path, key_path)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, self.strings["addkey_error"].format(str(e)))
|
||||
|
||||
# Формируем ответ в зависимости от наличия вывода и ошибок
|
||||
if output and not error:
|
||||
response = self.strings["sterminal_output"].format(command, exit_code, output)
|
||||
elif error and not output:
|
||||
response = self.strings["sterminal_error"].format(command, exit_code, error)
|
||||
elif output and error:
|
||||
response = self.strings["sterminal_output_and_error"].format(command, exit_code, output, error)
|
||||
else:
|
||||
response = f"⌨️<b> System command</b>\n<pre><code class='language-bash'>{command}</code></pre>\n<b>Exit code:</b> <code>{exit_code}</code>"
|
||||
@loader.command(alias="ssh")
|
||||
async def sterminal(self, message):
|
||||
"""<команда> - выполняет команду на ssh сервере"""
|
||||
host = self.config["host"]
|
||||
username = self.config["username"]
|
||||
password = self.config["password"]
|
||||
key_path = self.config["key_path"]
|
||||
port = self.config["Port"]
|
||||
|
||||
if host == "None" or username == "None" or (password == "None" and key_path == "None"):
|
||||
await utils.answer(message, self.strings["config_not_set"])
|
||||
return
|
||||
|
||||
command = utils.get_args_raw(message)
|
||||
if not command:
|
||||
await utils.answer(message, self.strings["sterminal_no_command"])
|
||||
return
|
||||
|
||||
await utils.answer(message, response)
|
||||
conn = SSHConnection(host, port, username, password, key_path)
|
||||
await conn.connect()
|
||||
|
||||
output_buffer = []
|
||||
error_buffer = []
|
||||
current_pid = None
|
||||
stop_flag = False
|
||||
|
||||
def stream_callback(stream_type, data, pid):
|
||||
nonlocal current_pid
|
||||
if current_pid is None:
|
||||
current_pid = pid
|
||||
if stream_type == 'stdout':
|
||||
output_buffer.append(data)
|
||||
else:
|
||||
error_buffer.append(data)
|
||||
|
||||
stop_button = {
|
||||
"text": self.strings["stop_button"],
|
||||
"callback": self._stop_callback,
|
||||
"args": (message.chat_id, message.id),
|
||||
}
|
||||
|
||||
task_id = f"{message.chat_id}_{message.id}"
|
||||
self.active_tasks[task_id] = {'stop': False, 'conn': conn}
|
||||
|
||||
msg = await utils.answer(
|
||||
message,
|
||||
self.strings["sterminal_starting"].format(command, "...", ""),
|
||||
reply_markup=[[stop_button]]
|
||||
)
|
||||
|
||||
async def execute_task():
|
||||
try:
|
||||
exit_code, output, error, pid = await conn.execute_command_stream(
|
||||
command,
|
||||
stream_callback
|
||||
)
|
||||
|
||||
if self.active_tasks.get(task_id, {}).get('stop'):
|
||||
current_output = ''.join(output_buffer)
|
||||
await utils.answer(
|
||||
msg,
|
||||
self.strings["sterminal_stopped"].format(
|
||||
command,
|
||||
pid if pid else "N/A",
|
||||
current_output if current_output else "No output"
|
||||
)
|
||||
)
|
||||
else:
|
||||
if output and not error:
|
||||
response = self.strings["sterminal_output"].format(command, pid, exit_code, output)
|
||||
elif error and not output:
|
||||
response = self.strings["sterminal_error"].format(command, pid, exit_code, error)
|
||||
elif output and error:
|
||||
response = self.strings["sterminal_output_and_error"].format(command, pid, exit_code, output, error)
|
||||
else:
|
||||
response = f"⌨️<b> System command</b>\n<pre><code class='language-bash'>{command}</code></pre>\n<b>PID:</b> <code>{pid}</code>\n<b>Exit code:</b> <code>{exit_code}</code>"
|
||||
|
||||
await utils.answer(msg, response)
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(msg, f"<b>Error:</b> {str(e)}")
|
||||
finally:
|
||||
conn.close()
|
||||
if task_id in self.active_tasks:
|
||||
del self.active_tasks[task_id]
|
||||
|
||||
asyncio.create_task(execute_task())
|
||||
|
||||
for _ in range(60):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
if task_id not in self.active_tasks:
|
||||
break
|
||||
|
||||
if self.active_tasks[task_id].get('stop'):
|
||||
break
|
||||
|
||||
current_output = ''.join(output_buffer[-20:])
|
||||
if current_output:
|
||||
await msg.edit(
|
||||
self.strings["sterminal_starting"].format(
|
||||
command,
|
||||
current_pid if current_pid else "...",
|
||||
current_output[-1500:] # Ограничение длины
|
||||
),
|
||||
reply_markup=[[stop_button]]
|
||||
)
|
||||
|
||||
async def _stop_callback(self, call, chat_id, msg_id):
|
||||
"""Callback для кнопки остановки"""
|
||||
task_id = f"{chat_id}_{msg_id}"
|
||||
if task_id in self.active_tasks:
|
||||
self.active_tasks[task_id]['stop'] = True
|
||||
try:
|
||||
self.active_tasks[task_id]['conn'].close()
|
||||
except:
|
||||
pass
|
||||
await call.answer("Stopping...")
|
||||
@@ -49,34 +49,4 @@ class TTFMod(loader.Module):
|
||||
|
||||
# Удаление файла
|
||||
os.remove(file_path)
|
||||
|
||||
@loader.command()
|
||||
async def ttf_noreply(self, message):
|
||||
"""
|
||||
Создает текстовый файл с заданным именем и расширением,
|
||||
записывает в него текст, отправляет его в Telegram и удаляет с диска.
|
||||
|
||||
Пример:
|
||||
.ttf название.txt
|
||||
Текст для файла
|
||||
|
||||
"""
|
||||
args = utils.get_args_raw(message).split("\n")
|
||||
if len(args) < 1:
|
||||
await message.edit("Недостаточно аргументов. Используйте: .ttf название.txt\nТекст для файла")
|
||||
return
|
||||
|
||||
filename = args[0].strip()
|
||||
text = "\n".join(args[1:])
|
||||
|
||||
# Создание файла
|
||||
file_path = os.path.join(os.getcwd(), filename)
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(text)
|
||||
await message.client.delete_messages(message.chat_id, message.id)
|
||||
# Отправка файла
|
||||
await message.client.send_file(message.chat_id, file_path)
|
||||
|
||||
# Удаление файла
|
||||
os.remove(file_path)
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
# requires: opencv-python pillow
|
||||
|
||||
import os, shutil, cv2
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from telethon.tl.functions.stickers import CreateStickerSetRequest
|
||||
from telethon.tl.types import InputStickerSetItem, InputDocument
|
||||
from telethon.errors.rpcerrorlist import PackShortNameOccupiedError
|
||||
from .. import loader
|
||||
from telethon.tl.functions.photos import GetUserPhotosRequest
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import string
|
||||
|
||||
try:
|
||||
resample = Image.Resampling.LANCZOS
|
||||
except:
|
||||
resample = Image.LANCZOS
|
||||
|
||||
@loader.tds
|
||||
class CreateAvatarsPack(loader.Module):
|
||||
"""Creates a sticker pack from photos and video avatars of participants"""
|
||||
strings = {
|
||||
"name": "CreateAvatarsPack",
|
||||
"processing": "📥 I'm collecting avatars of participants...",
|
||||
"no_avatars": "❌ No members with avatars",
|
||||
"no_valid": "❌ Could not process any avatars",
|
||||
"done": "✅ The sticker pack is ready:\n👉 <a href='https://t.me/addstickers/{}'>Open</a>",
|
||||
"already": "⚠️ A sticker pack with this name already exists.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"processing": "📥 Собираю аватарки участников...",
|
||||
"no_avatars": "❌ Нет участников с аватарками",
|
||||
"no_valid": "❌ Не удалось обработать ни одну аватарку",
|
||||
"done": "✅ Стикерпак готов:\n👉 <a href='https://t.me/addstickers/{}'>Открыть</a>",
|
||||
"already": "⚠️ Стикерпак с таким именем уже существует",
|
||||
}
|
||||
|
||||
@loader.command(doc="- Create a sticker pack from the avatars of users in the group", ru_doc="- Создать стикерпак из аватаров пользователей группы", only_groups=True)
|
||||
async def createavatars(self, message):
|
||||
"""- Create a sticker pack from the avatars of users in the group"""
|
||||
chat = await message.get_chat()
|
||||
cid = abs(message.chat_id)
|
||||
await message.edit(self.strings["processing"])
|
||||
|
||||
users = []
|
||||
async for u in self._client.iter_participants(chat.id):
|
||||
if u.photo:
|
||||
users.append(u)
|
||||
if len(users) >= 100:
|
||||
break
|
||||
|
||||
if not users:
|
||||
return await message.edit(self.strings["no_avatars"])
|
||||
|
||||
tmp_dir = f"/tmp/avatars_{cid}"
|
||||
os.makedirs(tmp_dir, exist_ok=True)
|
||||
sticker_files = []
|
||||
|
||||
for u in users:
|
||||
try:
|
||||
photos = await self._client(GetUserPhotosRequest(u.id, 0, 0, 1))
|
||||
if not photos.photos:
|
||||
continue
|
||||
|
||||
raw = await self._client.download_media(photos.photos[0])
|
||||
data = raw if isinstance(raw, (bytes, bytearray)) else open(raw, "rb").read()
|
||||
|
||||
path_raw = os.path.join(tmp_dir, f"{u.id}_raw")
|
||||
with open(path_raw, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
if b"ftyp" in data[:32] or path_raw.endswith((".mp4", ".webm", ".mov")):
|
||||
cap = cv2.VideoCapture(path_raw)
|
||||
success, frame = cap.read()
|
||||
cap.release()
|
||||
if not success:
|
||||
continue
|
||||
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA))
|
||||
else:
|
||||
try:
|
||||
img = Image.open(path_raw).convert("RGBA")
|
||||
except UnidentifiedImageError:
|
||||
continue
|
||||
|
||||
img.thumbnail((512, 512), resample)
|
||||
w, h = img.size
|
||||
final = Image.new("RGBA", (512, 512), (0, 0, 0, 0))
|
||||
final.paste(img, ((512 - w)//2, (512 - h)//2))
|
||||
|
||||
out = os.path.join(tmp_dir, f"{u.id}.webp")
|
||||
final.save(out, "WEBP")
|
||||
sticker_files.append(out)
|
||||
|
||||
except:
|
||||
continue
|
||||
|
||||
if not sticker_files:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return await message.edit(self.strings["no_valid"])
|
||||
|
||||
tag = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
|
||||
short = f"f{cid}_{tag}_by_fcreateavatars"
|
||||
title = f"AvaPack {tag}"
|
||||
|
||||
stickers = []
|
||||
for p in sticker_files:
|
||||
await asyncio.sleep(0.3)
|
||||
file = await self._client.upload_file(p)
|
||||
msg = await self._client.send_file("me", file, force_document=True)
|
||||
doc = msg.document
|
||||
await self._client.delete_messages("me", msg.id)
|
||||
stickers.append(InputStickerSetItem(
|
||||
document=InputDocument(doc.id, doc.access_hash, doc.file_reference),
|
||||
emoji="🖼️"
|
||||
))
|
||||
|
||||
try:
|
||||
await self._client(CreateStickerSetRequest(
|
||||
user_id="me",
|
||||
title=title,
|
||||
short_name=short,
|
||||
stickers=stickers
|
||||
))
|
||||
except PackShortNameOccupiedError:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return await message.edit(self.strings["already"])
|
||||
except Exception as e:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return await message.edit(f"❌ Error: {e}")
|
||||
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
await message.edit(self.strings["done"].format(short))
|
||||
353
fiksofficial/python-modules/createpacks.py
Normal file
353
fiksofficial/python-modules/createpacks.py
Normal file
@@ -0,0 +1,353 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
# requires: opencv-python pillow
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import cv2
|
||||
import random
|
||||
import string
|
||||
import asyncio
|
||||
import logging
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
|
||||
from telethon.tl.functions.stickers import CreateStickerSetRequest
|
||||
from telethon.tl.types import InputStickerSetItem, InputDocument
|
||||
from telethon.errors.rpcerrorlist import PackShortNameOccupiedError
|
||||
|
||||
from .. import loader, utils
|
||||
from telethon.tl.functions.photos import GetUserPhotosRequest
|
||||
|
||||
|
||||
try:
|
||||
resample = Image.Resampling.LANCZOS
|
||||
except AttributeError:
|
||||
resample = Image.LANCZOS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
async def process_to_webp(input_path: str, output_path: str, size: int = 512) -> bool:
|
||||
try:
|
||||
is_video = input_path.lower().endswith(('.mp4', '.webm', '.mov')) or b'ftyp' in open(input_path, 'rb').read(32)
|
||||
if is_video:
|
||||
cap = cv2.VideoCapture(input_path)
|
||||
success, frame = cap.read()
|
||||
cap.release()
|
||||
if not success:
|
||||
logger.warning(f"Video: Unable to read frame {input_path}")
|
||||
return False
|
||||
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA))
|
||||
else:
|
||||
try:
|
||||
img = Image.open(input_path).convert("RGBA")
|
||||
except UnidentifiedImageError:
|
||||
logger.warning(f"Image: incorrect {input_path}")
|
||||
return False
|
||||
|
||||
img.thumbnail((size, size), resample)
|
||||
final = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
w, h = img.size
|
||||
final.paste(img, ((size - w) // 2, (size - h) // 2))
|
||||
|
||||
final.save(output_path, "WEBP", quality=95, method=6)
|
||||
|
||||
try:
|
||||
check = Image.open(output_path)
|
||||
if check.size != (size, size):
|
||||
logger.warning(f"WEBP: size not {size}x{size}: {check.size}")
|
||||
return False
|
||||
if os.path.getsize(output_path) > 512 * 1024:
|
||||
final.save(output_path, "WEBP", quality=80, method=6)
|
||||
if os.path.getsize(output_path) > 512 * 1024:
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"WEBP: verification error {output_path}: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"WEBP: processing error {input_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
async def process_to_png(input_path: str, output_path: str, size: int = 100) -> bool:
|
||||
try:
|
||||
is_video = input_path.lower().endswith(('.mp4', '.webm', '.mov')) or b'ftyp' in open(input_path, 'rb').read(32)
|
||||
if is_video:
|
||||
cap = cv2.VideoCapture(input_path)
|
||||
success, frame = cap.read()
|
||||
cap.release()
|
||||
if not success:
|
||||
logger.warning(f"Video: Unable to read frame {input_path}")
|
||||
return False
|
||||
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA))
|
||||
else:
|
||||
try:
|
||||
img = Image.open(input_path).convert("RGBA")
|
||||
except UnidentifiedImageError:
|
||||
logger.warning(f"Image: incorrect {input_path}")
|
||||
return False
|
||||
|
||||
img.thumbnail((size, size), resample)
|
||||
final = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
w, h = img.size
|
||||
final.paste(img, ((size - w) // 2, (size - h) // 2))
|
||||
|
||||
final.save(output_path, "PNG")
|
||||
|
||||
try:
|
||||
check = Image.open(output_path)
|
||||
if check.size != (size, size):
|
||||
logger.warning(f"PNG: size not {size}x{size}: {check.size}")
|
||||
return False
|
||||
if os.path.getsize(output_path) > 512 * 1024:
|
||||
logger.warning(f"PNG: file >512KB: {os.path.getsize(output_path)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"PNG: verification error {output_path}: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"PNG: processing error {input_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CreatePacks(loader.Module):
|
||||
"""Creates sticker packs and emoji packs from the avatars of chat participants"""
|
||||
|
||||
strings = {
|
||||
"name": "CreatePacks",
|
||||
"processing": "<b>[CreatePacks]</b> Collecting avatars of participants...",
|
||||
"no_avatars": "<b>[CreatePacks]</b> No members with avatars",
|
||||
"no_valid": "<b>[CreatePacks]</b> Could not process any avatars",
|
||||
"done_pack": "<b>[CreatePacks]</b> Sticker pack is ready:\n<b>[CreatePacks]</b> Open: <a href='https://t.me/addstickers/{}'>here</a>",
|
||||
"done_emoji_pack": "<b>[CreatePacks]</b> Emoji pack is ready:\n<b>[CreatePacks]</b> Open: <a href='https://t.me/addstickers/{}'>here</a>",
|
||||
"already": "<b>[CreatePacks]</b> A sticker pack with this name already exists.",
|
||||
"emoji_processing": "<b>[CreatePacks]</b> Creating emoji pack from avatars...",
|
||||
"emoji_no_emoji": "<b>[CreatePacks]</b> No emoji specified — using",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": "Создаёт стикерпаки и эмодзи-паки из аватаров участников чата",
|
||||
"processing": "<b>[CreatePacks]</b> Собираю аватарки участников...",
|
||||
"no_avatars": "<b>[CreatePacks]</b> Нет участников с аватарками",
|
||||
"no_valid": "<b>[CreatePacks]</b> Не удалось обработать ни одну аватарку",
|
||||
"done_pack": "<b>[CreatePacks]</b> Стикерпак готов:\n<b>[CreatePacks]</b> Открыть: <a href='https://t.me/addstickers/{}'>здесь</a>",
|
||||
"done_emoji_pack": "<b>[CreatePacks]</b> Эмодзи-пак готов:\n<b>[CreatePacks]</b> Открыть: <a href='https://t.me/addstickers/{}'>здесь</a>",
|
||||
"already": "<b>[CreatePacks]</b> Стикерпак с таким именем уже существует",
|
||||
"emoji_processing": "<b>[CreatePacks]</b> Создаю эмодзи-пак из аватаров...",
|
||||
"emoji_no_emoji": "<b>[CreatePacks]</b> Эмодзи не указан — используется",
|
||||
}
|
||||
|
||||
async def _get_avatar_files(self, message, format: str = "webp", size: int = 512) -> tuple[list[str], str]:
|
||||
chat = await message.get_chat()
|
||||
cid = abs(message.chat_id)
|
||||
tmp_dir = f"/tmp/avatars_{cid}_{random.randint(1000, 9999)}"
|
||||
os.makedirs(tmp_dir, exist_ok=True)
|
||||
|
||||
users = []
|
||||
async for u in self._client.iter_participants(chat.id):
|
||||
if u.photo:
|
||||
users.append(u)
|
||||
if len(users) >= 100:
|
||||
break
|
||||
|
||||
if not users:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return [], tmp_dir
|
||||
|
||||
processed = []
|
||||
process_func = process_to_webp if format == "webp" else process_to_png
|
||||
|
||||
for u in users:
|
||||
try:
|
||||
photos = await self._client(GetUserPhotosRequest(u.id, 0, 0, 1))
|
||||
if not photos.photos:
|
||||
continue
|
||||
|
||||
raw_path = os.path.join(tmp_dir, f"{u.id}_raw")
|
||||
raw = await self._client.download_media(photos.photos[0], file=raw_path)
|
||||
|
||||
ext = ".webp" if format == "webp" else ".png"
|
||||
output_path = os.path.join(tmp_dir, f"{u.id}{ext}")
|
||||
success = False
|
||||
|
||||
if isinstance(raw, str):
|
||||
success = await process_func(raw, output_path, size=size)
|
||||
if os.path.exists(raw):
|
||||
os.unlink(raw)
|
||||
else:
|
||||
temp_raw = os.path.join(tmp_dir, f"{u.id}_temp_raw")
|
||||
with open(temp_raw, "wb") as f:
|
||||
f.write(raw)
|
||||
success = await process_func(temp_raw, output_path, size=size)
|
||||
if os.path.exists(temp_raw):
|
||||
os.unlink(temp_raw)
|
||||
|
||||
if success:
|
||||
try:
|
||||
img_size = Image.open(output_path).size
|
||||
if img_size != (size, size):
|
||||
logger.warning(f"{format.upper()}: size not {size}x{size}: {img_size}")
|
||||
os.unlink(output_path)
|
||||
continue
|
||||
if os.path.getsize(output_path) > 512 * 1024:
|
||||
logger.warning(f"{format.upper()}: file >512KB: {os.path.getsize(output_path)}")
|
||||
os.unlink(output_path)
|
||||
continue
|
||||
processed.append(output_path)
|
||||
except Exception as e:
|
||||
logger.error(f"{format.upper()}: verification error {output_path}: {e}")
|
||||
else:
|
||||
logger.warning(f"{format.upper()}: Failed to process avatar {u.id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"User processing error {u.id}: {e}")
|
||||
continue
|
||||
|
||||
return processed, tmp_dir
|
||||
|
||||
@loader.command(
|
||||
ru_doc="- Создать стикерпак из аватаров в группе",
|
||||
only_groups=True
|
||||
)
|
||||
async def createavatars(self, message):
|
||||
"""- Create a sticker pack from avatars in a group"""
|
||||
await message.edit(self.strings("processing"))
|
||||
|
||||
files, tmp_dir = await self._get_avatar_files(message, format="webp", size=512)
|
||||
if not files:
|
||||
return await message.edit(self.strings("no_avatars"))
|
||||
|
||||
tag = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
|
||||
short_name = f"f{abs(message.chat_id)}_{tag}_by_fcreateavatars"
|
||||
title = f"AvaPack {tag}"
|
||||
|
||||
stickers = []
|
||||
for path in files:
|
||||
try:
|
||||
await asyncio.sleep(0.3)
|
||||
file = await self._client.upload_file(path)
|
||||
msg = await self._client.send_file("me", file, force_document=True)
|
||||
doc = msg.document
|
||||
await self._client.delete_messages("me", msg.id)
|
||||
stickers.append(InputStickerSetItem(
|
||||
document=InputDocument(doc.id, doc.access_hash, doc.file_reference),
|
||||
emoji="🖼️"
|
||||
))
|
||||
except Exception as e:
|
||||
logger.error(f"Sticker loading error {path}: {e}")
|
||||
continue
|
||||
|
||||
if not stickers:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return await message.edit(self.strings("no_valid"))
|
||||
|
||||
try:
|
||||
await self._client(CreateStickerSetRequest(
|
||||
user_id="me",
|
||||
title=title,
|
||||
short_name=short_name,
|
||||
stickers=stickers
|
||||
))
|
||||
await message.edit(self.strings("done_pack").format(short_name))
|
||||
except PackShortNameOccupiedError:
|
||||
await message.edit(self.strings("already"))
|
||||
except Exception as e:
|
||||
error_details = f"❌ Ошибка создания стикерпака:\n<code>{type(e).__name__}: {e}</code>\n"
|
||||
error_details += f"Пак: {short_name}\nСтикеров: {len(stickers)}\n"
|
||||
if files:
|
||||
error_details += f"Последний файл: {files[-1]}\n"
|
||||
try:
|
||||
error_details += f"Размер: {Image.open(files[-1]).size}\n"
|
||||
error_details += f"Вес: {os.path.getsize(files[-1])} байт"
|
||||
except:
|
||||
pass
|
||||
await message.edit(error_details)
|
||||
logger.exception("Error creating sticker pack")
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="[эмодзи] - Создать эмодзи-пак из всех аватаров",
|
||||
only_groups=True
|
||||
)
|
||||
async def createemojis(self, message):
|
||||
"""[emoji] - Create an emoji pack from all avatars"""
|
||||
args = utils.get_args_raw(message)
|
||||
emoji = args.strip() if args else "🖼️"
|
||||
if not args:
|
||||
await message.edit(self.strings("emoji_no_emoji") + f" `{emoji}`")
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
await message.edit(self.strings("emoji_processing"))
|
||||
|
||||
files, tmp_dir = await self._get_avatar_files(message, format="png", size=100)
|
||||
if not files:
|
||||
return await message.edit(self.strings("no_avatars"))
|
||||
|
||||
tag = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
|
||||
short_name = f"f{abs(message.chat_id)}_{tag}_by_fcreateemojis"
|
||||
title = f"EmojiPack {tag}"
|
||||
|
||||
stickers = []
|
||||
for path in files:
|
||||
try:
|
||||
await asyncio.sleep(0.3)
|
||||
file = await self._client.upload_file(path)
|
||||
msg = await self._client.send_file("me", file, force_document=True)
|
||||
doc = msg.document
|
||||
await self._client.delete_messages("me", msg.id)
|
||||
stickers.append(InputStickerSetItem(
|
||||
document=InputDocument(doc.id, doc.access_hash, doc.file_reference),
|
||||
emoji=emoji
|
||||
))
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading emoji {path}: {e}")
|
||||
continue
|
||||
|
||||
if not stickers:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return await message.edit(self.strings("no_valid"))
|
||||
|
||||
try:
|
||||
await self._client(CreateStickerSetRequest(
|
||||
user_id="me",
|
||||
title=title,
|
||||
short_name=short_name,
|
||||
stickers=stickers,
|
||||
emojis=True
|
||||
))
|
||||
await message.edit(self.strings("done_emoji_pack").format(short_name))
|
||||
except PackShortNameOccupiedError:
|
||||
await message.edit(self.strings("already"))
|
||||
except Exception as e:
|
||||
error_details = f"❌ Ошибка создания эмодзи-пака:\n<code>{type(e).__name__}: {e}</code>\n"
|
||||
error_details += f"Пак: {short_name}\nСмайликов: {len(stickers)}\n"
|
||||
if files:
|
||||
error_details += f"Последний файл: {files[-1]}\n"
|
||||
try:
|
||||
error_details += f"Размер: {Image.open(files[-1]).size}\n"
|
||||
error_details += f"Вес: {os.path.getsize(files[-1])} байт"
|
||||
except:
|
||||
pass
|
||||
await message.edit(error_details)
|
||||
logger.exception("Error creating emoji pack")
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
@@ -449,7 +449,7 @@ class DeviceInfo(loader.Module):
|
||||
await call.edit(
|
||||
text=self.strings["no_results"].format(query),
|
||||
reply_markup=[],
|
||||
photo=None, # Explicitly remove any existing photo
|
||||
photo=None,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
except Exception as edit_error:
|
||||
@@ -471,7 +471,7 @@ class DeviceInfo(loader.Module):
|
||||
await call.edit(
|
||||
text=list_text,
|
||||
reply_markup=button_rows,
|
||||
photo=None, # Explicitly remove any existing photo
|
||||
photo=None,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
except Exception as edit_error:
|
||||
@@ -491,8 +491,8 @@ class DeviceInfo(loader.Module):
|
||||
await call.edit(
|
||||
text=self.strings["error"].format(str(e)),
|
||||
reply_markup=[],
|
||||
photo=None, # Explicitly remove any existing photo
|
||||
disable_web_page_preview=True
|
||||
photo=None,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
except Exception as edit_error:
|
||||
logger.warning(f"DeviceInfo: Failed to edit error message: {edit_error}")
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
@@ -5,30 +14,147 @@
|
||||
# requires: speedtest-cli
|
||||
|
||||
import speedtest
|
||||
from .. import loader
|
||||
from .. import loader, utils
|
||||
|
||||
@loader.tds
|
||||
class SpeedTestMod(loader.Module):
|
||||
"""Модуль для проверки скорости интернета"""
|
||||
"""Checking your internet speed"""
|
||||
strings = {
|
||||
"name": "SpeedTest",
|
||||
"starting": "Running Speedtest…",
|
||||
"ping": "Ping: <i>{:.2f} ms</i>",
|
||||
"download": "Download: <i>{:.2f} Mbps</i>",
|
||||
"upload": "Upload: <i>{:.2f} Mbps</i>",
|
||||
"finished": "<b>Speedtest completed!</b>",
|
||||
"error": "Speedtest error: <code>{}</code>",
|
||||
"progress_ping": "Testing \"Ping\"...",
|
||||
"progress_download": "Testing \"Download\"...",
|
||||
"progress_upload": "Testing \"Upload\"...",
|
||||
"cfg_timeout": "Server request timeout (sec)",
|
||||
"cfg_retries": "Number of retry attempts",
|
||||
"quality_website": "Websites: {}",
|
||||
"quality_video": "Video: {}",
|
||||
"quality_gaming": "Gaming: {}",
|
||||
"quality_calls": "Video calls: {}",
|
||||
}
|
||||
|
||||
strings = {"name": "SpeedTest"}
|
||||
strings_ru = {
|
||||
"_cls_doc": "Проверка скорости интернета",
|
||||
"starting": "Запускаем Speedtest…",
|
||||
"ping": "Ping: <i>{:.2f} мс</i>",
|
||||
"download": "Загрузка: <i>{:.2f} Мбит/с</i>",
|
||||
"upload": "Отдача: <i>{:.2f} Мбит/с</i>",
|
||||
"finished": "<b>Speedtest завершён!</b>",
|
||||
"error": "Ошибка при выполнении Speedtest: <code>{}</code>",
|
||||
"progress_ping": "Тестируем пинг...",
|
||||
"progress_download": "Тестируем скачивание...",
|
||||
"progress_upload": "Тестируем загрузку...",
|
||||
"cfg_timeout": "Таймаут запросов к серверу (сек)",
|
||||
"cfg_retries": "Кол‑во попыток при неудаче",
|
||||
"quality_website": "Сайты: {}",
|
||||
"quality_video": "Видео: {}",
|
||||
"quality_gaming": "Игры: {}",
|
||||
"quality_calls": "Видеосвязь: {}",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"timeout",
|
||||
30,
|
||||
lambda: self.strings("cfg_timeout"),
|
||||
validator=loader.validators.Integer(minimum=10, maximum=120),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"retries",
|
||||
2,
|
||||
lambda: self.strings("cfg_retries"),
|
||||
validator=loader.validators.Integer(minimum=0, maximum=5),
|
||||
),
|
||||
)
|
||||
|
||||
def _get_quality_rating(self, category: str, ping: float, download: float, upload: float) -> str:
|
||||
if category == "website":
|
||||
if ping < 50 and download > 5:
|
||||
return "🟢🟢🟢🟢🟢"
|
||||
elif ping < 100 and download > 3:
|
||||
return "🟠🟠🟠🟠"
|
||||
elif ping < 200 and download > 1:
|
||||
return "🟡🟡🟡"
|
||||
elif ping < 300 and download > 0.5:
|
||||
return "🔴🔴"
|
||||
else:
|
||||
return "⚫"
|
||||
elif category == "video":
|
||||
if ping < 50 and download > 25:
|
||||
return "🟢🟢🟢🟢🟢"
|
||||
elif ping < 75 and download > 5:
|
||||
return "🟠🟠🟠🟠"
|
||||
elif ping < 100 and download > 3:
|
||||
return "🟡🟡🟡"
|
||||
elif ping < 150 and download > 1:
|
||||
return "🔴🔴"
|
||||
else:
|
||||
return "⚫"
|
||||
elif category == "gaming":
|
||||
if ping < 50 and download > 5 and upload > 3:
|
||||
return "🟢🟢🟢🟢🟢"
|
||||
elif ping < 100 and download > 3 and upload > 1:
|
||||
return "🟠🟠🟠🟠"
|
||||
elif ping < 150 and download > 1 and upload > 0.5:
|
||||
return "🟡🟡🟡"
|
||||
elif ping < 200 and download > 0.5:
|
||||
return "🔴🔴"
|
||||
else:
|
||||
return "⚫"
|
||||
elif category == "calls":
|
||||
if ping < 50 and download > 4 and upload > 4:
|
||||
return "🟢🟢🟢🟢🟢"
|
||||
elif ping < 100 and download > 1.5 and upload > 1.5:
|
||||
return "🟡🟡🟡🟡"
|
||||
elif ping < 150 and download > 1 and upload > 1:
|
||||
return "🟠🟠🟠"
|
||||
elif ping < 200 and download > 0.5:
|
||||
return "🔴🔴"
|
||||
else:
|
||||
return "⚫"
|
||||
return "⚫"
|
||||
|
||||
@loader.command(
|
||||
ru_doc="(.st) - Запускает тест скорости интернета",
|
||||
en_doc="(.st) - Runs an internet speed test",
|
||||
alias="st",
|
||||
)
|
||||
async def speedcmd(self, message):
|
||||
"""Запускает тест скорости интернета"""
|
||||
msg = await message.edit("Запускаем Speedtest... 🏁")
|
||||
msg = await utils.answer(message, self.strings("starting"))
|
||||
|
||||
try:
|
||||
st = speedtest.Speedtest()
|
||||
st.get_best_server()
|
||||
download = st.download() / 1_000_000 # Мбит/с
|
||||
upload = st.upload() / 1_000_000 # Мбит/с
|
||||
ping = st.results.ping
|
||||
s = speedtest.Speedtest()
|
||||
s.get_best_server()
|
||||
await utils.answer(msg, self.strings("progress_ping"))
|
||||
|
||||
await msg.edit(
|
||||
f"<emoji document_id=5325547803936572038>✨</emoji> <b>Speedtest завершён!</b> <emoji document_id=5325547803936572038>✨</emoji>\n\n"
|
||||
f"<b>Ping:</b> <i>{ping:.2f} ms</i>\n"
|
||||
f"<emoji document_id=6041730074376410123>📥</emoji> <b>Загрузка:</b> <i>{download:.2f} Mbps</i>\n"
|
||||
f"<emoji document_id=6041730074376410123>📤</emoji> <b>Отдача:</b> <i>{upload:.2f} Mbps</i>",
|
||||
parse_mode="HTML"
|
||||
ping = s.results.ping
|
||||
|
||||
await utils.answer(msg, self.strings("progress_download"))
|
||||
download = s.download() / 1_000_000
|
||||
|
||||
await utils.answer(msg, self.strings("progress_upload"))
|
||||
upload = s.upload() / 1_000_000
|
||||
|
||||
text = (
|
||||
f"{self.strings('finished')}\n\n"
|
||||
f"{self.strings('ping').format(ping)}\n"
|
||||
f"{self.strings('download').format(download)}\n"
|
||||
f"{self.strings('upload').format(upload)}\n\n"
|
||||
f"{self.strings('quality_website').format(self._get_quality_rating('website', ping, download, upload))}\n"
|
||||
f"{self.strings('quality_video').format(self._get_quality_rating('video', ping, download, upload))}\n"
|
||||
f"{self.strings('quality_gaming').format(self._get_quality_rating('gaming', ping, download, upload))}\n"
|
||||
f"{self.strings('quality_calls').format(self._get_quality_rating('calls', ping, download, upload))}"
|
||||
)
|
||||
|
||||
await utils.answer(msg, text)
|
||||
except Exception as exc:
|
||||
await utils.answer(
|
||||
msg,
|
||||
self.strings("error").format(utils.escape_html(str(exc))),
|
||||
)
|
||||
except Exception as e:
|
||||
await msg.edit(f"<b>Ошибка при выполнении Speedtest:</b>\n<code>{e}</code>")
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
en:
|
||||
guide: "<emoji document_id=5956561916573782596>📜</emoji> <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Guide for obtaining access token for Yandex.Music</a></b>"
|
||||
iguide: "📜 <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Guide for obtaining access token for Yandex.Music</a></b>"
|
||||
no_token: "<emoji document_id=5778527486270770928>❌</emoji> <b>You didn't specify the access token in the config!</b>"
|
||||
search: "<emoji document_id=5474304919651491706>🎧</emoji> <b>{performer} — {title}</b>\n<emoji document_id=5242574232688298747>🎵</emoji> <b><a href=\"https://music.yandex.ru/track/{track_id}\">Yandex.Music</a> | <a href=\"https://song.link/ya/{track_id}\">song.link</a></b>"
|
||||
downloading_track: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Downloading audio…</i>"
|
||||
uploading_banner: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Uploading banner…</i>"
|
||||
lyrics: "<emoji document_id=5956561916573782596>📜</emoji> <b>Lyrics of the <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> track:</b>\n<blockquote expandable>{text}</blockquote>\n\n<emoji document_id=5776287149724798198>©️</emoji> <b>Writers:</b> {writers}"
|
||||
no_lyrics: "<emoji document_id=5872829476143894491>🚫</emoji> <b>Track <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> has no lyrics!</b>"
|
||||
errors:
|
||||
no_query: "<emoji document_id=5872829476143894491>🚫</emoji> <b>Specify the search query first!</b>"
|
||||
no_token_or_invalid: "<emoji document_id=5872829476143894491>🚫</emoji> <b>You specified an invalid access token or didn't specified it at all!</b>"
|
||||
not_found: "<emoji document_id=5872829476143894491>🚫</emoji> <b>No results found.</b>"
|
||||
no_playing: "<emoji document_id=5872829476143894491>🚫</emoji> <b>You don't listening to anything right now.</b>"
|
||||
autobio:
|
||||
d: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Autobio is off now</b>"
|
||||
e: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Autobio is on now</b>"
|
||||
there_is_no_playing: "<emoji document_id=5474140048741901455>❌</emoji> <b>You don't listening to anything right now</b>"
|
||||
queue_types:
|
||||
enabled: "<emoji document_id=5242574232688298747>🎧</emoji> <b>Autobio was enabled.</b>"
|
||||
disabled: "<emoji document_id=5242574232688298747>🎧</emoji> <b>Autobio was disabled.</b>"
|
||||
likes:
|
||||
liked: "<emoji document_id=5899833370052923106>❤️</emoji> <b>Track <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> was liked.</b>"
|
||||
unliked: "<emoji document_id=5992453811510186287>🖤</emoji> <b>Track <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> was unliked.</b>"
|
||||
disliked: "<emoji document_id=5952055319059239589>💔</emoji> <b>Track <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> was disliked.</b>"
|
||||
_entity_types:
|
||||
VARIOUS: "Your queue"
|
||||
RADIO: "«My Wave»"
|
||||
RADIO: "«My Vibe»"
|
||||
PLAYLIST: "Playlist «{}»"
|
||||
ALBUM: "«{}»"
|
||||
ARTIST: "Popular tracks by {}"
|
||||
downloading: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Downloading audio…</i>"
|
||||
uploading_banner: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Uploading banner…</i>"
|
||||
likes:
|
||||
liked: "<emoji document_id=6037533152593842454>❤️</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> was liked</b>"
|
||||
unliked: "<emoji document_id=5992453811510186287>❤️</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> was unliked</b>"
|
||||
disliked: "<emoji document_id=5222400230133081714>💔</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> was disliked</b>"
|
||||
lyrics: "<emoji document_id=5956561916573782596>📜</emoji> <b>Lyrics of the <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> track:</b>\n<blockquote expandable>{text}</blockquote>\n\n<emoji document_id=5247213725080890199>©️</emoji> <b>Writers:</b> {writers}"
|
||||
no_lyrics: "<emoji document_id=5886285363869126932>❌</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> has no lyrics!</b>"
|
||||
args: "<emoji document_id=5778527486270770928>❌</emoji> <b>Specify search query</b>"
|
||||
searching: "<emoji document_id=5258274739041883702>🔍</emoji> <b>Searching…</b>"
|
||||
404: "<emoji document_id=5778527486270770928>❌</emoji> <b>No results found</b>"
|
||||
search: "<emoji document_id=5474304919651491706>🎧</emoji> <b>{performer} — {title}</b>\n<emoji document_id=5429189857324841688>🎵</emoji> <b><a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">Yandex.Music</a> | <a href=\"https://song.link/ya/{track_id}\">song.link</a></b>"
|
||||
_cfg:
|
||||
token: "Your access token for Yandex.Music"
|
||||
now_playing_text: "The text that is used in commands to get now playing track. May contain {performer}, {title}, {device}, {volume}, {playing_from}, {link}, {track_id}, {album_id} keywords"
|
||||
autobio: "Automatic bio template (may contain {artist} and {title} keywords)"
|
||||
no_playing_bio: "Bio that is set when nothing is playing"
|
||||
token: "The access token for Yandex.Music."
|
||||
now_playing_text: "The caption for .ynow and .ynowt commands. May contain {performer}, {title}, {device}, {volume}, {playing_from}, {link}, {track_id}, {album_id} keywords."
|
||||
autobio_text: "The text for automatically changing «Bio». May contains {performer} and {title}."
|
||||
no_playing_bio_text: "The text for changing «Bio» when there is no playing tracks."
|
||||
banner_version: "Banner version (old / new with lyrics / new without lyrics)."
|
||||
|
||||
ru:
|
||||
guide: "<emoji document_id=5956561916573782596>📜</emoji> <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Гайд по получению токена Яндекс.Музыки</a></b>"
|
||||
iguide: "📜 <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Гайд по получению токена Яндекс.Музыки</a></b>"
|
||||
no_token: "<emoji document_id=5312526098750252863>❌</emoji> <b>Вы не указали токен Яндекс.Музыки в конфиге!</b>"
|
||||
search: "<emoji document_id=5474304919651491706>🎧</emoji> <b>{performer} — {title}</b>\n<emoji document_id=5242574232688298747>🎵</emoji> <b><a href=\"https://music.yandex.ru/track/{track_id}\">Яндекс.Музыка</a> | <a href=\"https://song.link/ya/{track_id}\">song.link</a></b>"
|
||||
downloading_track: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка трека…</i>"
|
||||
uploading_banner: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка баннера…</i>"
|
||||
lyrics: "<emoji document_id=5956561916573782596>📜</emoji> <b>Текст трека <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a>:</b>\n<blockquote expandable>{text}</blockquote>\n\n<emoji document_id=5776287149724798198>©️</emoji> <b>Авторы:</b> {writers}"
|
||||
no_lyrics: "<emoji document_id=5872829476143894491>🚫</emoji> <b>У трека <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> нет текста!</b>"
|
||||
errors:
|
||||
no_query: "<emoji document_id=5872829476143894491>🚫</emoji> <b>Укажите поисковый запрос!</b>"
|
||||
no_token_or_invalid: "<emoji document_id=5872829476143894491>🚫</emoji> <b>Вы указали невалидный токен или не указали его вообще!</b>"
|
||||
not_found: "<emoji document_id=5872829476143894491>🚫</emoji> <b>Результаты не найдены.</b>"
|
||||
no_playing: "<emoji document_id=5872829476143894491>🚫</emoji> <b>Вы ничего не слушаете сейчас.</b>"
|
||||
autobio:
|
||||
d: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Автобио выключено</b>"
|
||||
e: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Автобио включено</b>"
|
||||
there_is_no_playing: "<emoji document_id=5474140048741901455>❌</emoji> <b>Вы ничего не слушаете сейчас</b>"
|
||||
queue_types:
|
||||
enabled: "<emoji document_id=5242574232688298747>🎧</emoji> <b>Автобио теперь включено.</b>"
|
||||
disabled: "<emoji document_id=5242574232688298747>🎧</emoji> <b>Автобио теперь выключено.</b>"
|
||||
likes:
|
||||
liked: "<emoji document_id=5899833370052923106>❤️</emoji> <b>Трек <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> был лайкнут.</b>"
|
||||
unliked: "<emoji document_id=5992453811510186287>🖤</emoji> <b>С трека <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> был снят лайк.</b>"
|
||||
disliked: "<emoji document_id=5952055319059239589>💔</emoji> <b>Трек <a href=\"https://music.yandex.ru/track/{track_id}\">{track}</a> был дизлайкнут.</b>"
|
||||
_entity_types:
|
||||
VARIOUS: "Ваша очередь"
|
||||
RADIO: "«Моя Волна»"
|
||||
RADIO: "«Моя волна»"
|
||||
PLAYLIST: "Плейлист «{}»"
|
||||
ALBUM: "«{}»"
|
||||
ARTIST: "Популярные треки {}"
|
||||
downloading: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка трека…</i>"
|
||||
uploading_banner: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка баннера…</i>"
|
||||
likes:
|
||||
liked: "<emoji document_id=6037533152593842454>❤️</emoji> <b>Трек <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> лайкнут</b>"
|
||||
unliked: "<emoji document_id=5992453811510186287>❤️</emoji> <b>С трека <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> снят лайк</b>"
|
||||
disliked: "<emoji document_id=5222400230133081714>💔</emoji> <b>Трек <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> дизлайкнут</b>"
|
||||
lyrics: "<emoji document_id=5956561916573782596>📜</emoji> <b>Текст трека <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a>:</b>\n<blockquote expandable>{text}</blockquote>\n\n<emoji document_id=5247213725080890199>©️</emoji> <b>Авторы:</b> {writers}"
|
||||
no_lyrics: "<emoji document_id=5886285363869126932>❌</emoji> <b>У трека <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> нет текста!</b>"
|
||||
args: "<emoji document_id=5312526098750252863>❌</emoji> <b>Укажите поисковый запрос</b>"
|
||||
searching: "<emoji document_id=5258274739041883702>🔍</emoji> <b>Ищем…</b>"
|
||||
404: "<emoji document_id=5312526098750252863>❌</emoji> <b>Ничего не найдено</b>"
|
||||
search: "<emoji document_id=5474304919651491706>🎧</emoji> <b>{performer} — {title}</b>\n<emoji document_id=5429189857324841688>🎵</emoji> <b><a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">Яндекс.Музыка</a> | <a href=\"https://song.link/ya/{track_id}\">song.link</a></b>"
|
||||
_cfg:
|
||||
token: "Ваш токен от Яндекс.Музыки"
|
||||
now_playing_text: "Текст, использующийся в командах для получения прослушиваемого трека. Может содержать ключевые слова {performer}, {title}, {device}, {volume}, {playing_from}, {link}, {track_id}, {album_id}"
|
||||
autobio: "Шаблон автоматического био (может содержать ключевые слова {artist} и {title})"
|
||||
no_playing_bio: "Био, которое ставится, когда ничего не играет"
|
||||
token: "Токен для Яндекс.Музыки."
|
||||
now_playing_text: "Текст, использующийся в подписи к файлу в командах .ynow и .ynowt. Может содержать {performer}, {title}, {device}, {volume}, {playing_from}, {link}, {track_id} и {album_id}"
|
||||
autobio_text: "Текст, использующийся при автоматическом изменении «О себе». Может содержать {performer} и {title}."
|
||||
no_playing_bio_text: "Текст, использующийся при изменении «О себе», когда ничего не играет."
|
||||
banner_version: "Версия баннера (старый / новый с текстом трека / новый без текста трека)."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user