mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Commited backup
This commit is contained in:
105
Ruslan-Isaev/modules/Amnesty.py
Normal file
105
Ruslan-Isaev/modules/Amnesty.py
Normal file
@@ -0,0 +1,105 @@
|
||||
version = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
from telethon import functions, TelegramClient
|
||||
from telethon.tl.types import Message, ChannelParticipantsKicked, ChatBannedRights
|
||||
import time
|
||||
from .. import loader, utils
|
||||
import typing
|
||||
from telethon.errors import ChatAdminRequiredError, UserAdminInvalidError
|
||||
from telethon.tl.functions.contacts import GetBlockedRequest, UnblockRequest
|
||||
|
||||
def seq_rights(sequence: str, inv: bool = False) -> typing.Union[dict, None]:
|
||||
if not sequence:
|
||||
return None
|
||||
|
||||
result = {}
|
||||
|
||||
for right in sequence:
|
||||
if right == '0':
|
||||
result['view_messages'] = not inv
|
||||
elif right == '1':
|
||||
result['send_messages'] = not inv
|
||||
elif right == '2':
|
||||
result['send_media'] = not inv
|
||||
elif right == '3':
|
||||
result['send_stickers'] = not inv
|
||||
elif right == '4':
|
||||
result['send_gifs'] = not inv
|
||||
elif right == '5':
|
||||
result['send_games'] = not inv
|
||||
elif right == '6':
|
||||
result['send_inline'] = not inv
|
||||
elif right == '7':
|
||||
result['embed_link_previews'] = not inv
|
||||
elif right == '8':
|
||||
result['send_polls'] = not inv
|
||||
elif right == '9':
|
||||
result['change_info'] = not inv
|
||||
elif right == 'a':
|
||||
result['invite_users'] = not inv
|
||||
elif right == 'b':
|
||||
result['pin_messages'] = not inv
|
||||
else:
|
||||
raise ValueError(f'Unknown right: {right}')
|
||||
|
||||
return result
|
||||
|
||||
async def unblock_user(message, user_id, i, ids):
|
||||
try:
|
||||
await message.client(UnblockRequest(id=user_id))
|
||||
await utils.answer(message, f"♻️ <b>Разбанено пользователей: {i + 1}/{int(len(ids))}</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"🚫 <b> Ошибка! </b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.tds
|
||||
class AmnestyMod(loader.Module):
|
||||
"""Модуль для разбана всех пользователей в чате или в лс (амнистия)"""
|
||||
|
||||
strings = {
|
||||
"name": "Amnesty",
|
||||
}
|
||||
|
||||
@loader.command()
|
||||
async def amnestycmd(self, message):
|
||||
""" - разблокирует всех в чате"""
|
||||
try:
|
||||
chat_id = message.chat.id
|
||||
except:
|
||||
await utils.answer(message, "🚫 <b>Команда доступна только в супергруппах и каналах!</b>")
|
||||
return
|
||||
chat = await message.client.get_participants(chat_id, filter=ChannelParticipantsKicked)
|
||||
ids = [user.id for user in chat]
|
||||
i = 0
|
||||
if len(ids) == 0:
|
||||
await utils.answer(message, "<b>Черный список чата уже пустой!</b>")
|
||||
return
|
||||
for id in ids:
|
||||
try:
|
||||
await self.client.edit_permissions(chat_id, id, None, **seq_rights('0'),)
|
||||
except ChatAdminRequiredError:
|
||||
return await utils.answer(message, "🚫 <b> Недостаточно прав! </b>")
|
||||
except UserAdminInvalidError:
|
||||
return await utils.answer(message, "🚫 <b> Недостаточно прав! </b>")
|
||||
except Exception as e:
|
||||
return await utils.answer(message, "🚫 <b> Ошибка! </b>\n\n<code>{e}</code>")
|
||||
await utils.answer(message, f"♻️ <b>Разбанено пользователей: {i + 1}/{int(len(ids))}</b>")
|
||||
i += 1
|
||||
time.sleep(1)
|
||||
await utils.answer(message, f"✅ <b>Успешно! {int(len(ids))} пользователей разблокировано!</b>")
|
||||
|
||||
@loader.command()
|
||||
async def amnistiacmd(self, message):
|
||||
""" - разблокирует всех в лс"""
|
||||
chat = await message.client(GetBlockedRequest(offset=0, limit=500))
|
||||
i = 0
|
||||
ids = [user.id for user in chat.users]
|
||||
if len(ids) == 0:
|
||||
await utils.answer(message, "<b>Черный список пустой!</b>")
|
||||
return
|
||||
for id in ids:
|
||||
await unblock_user(message, id, i, ids)
|
||||
i += 1
|
||||
time.sleep(1)
|
||||
await utils.answer(message, f"✅ <b>Успешно! {int(len(ids))} пользователей разблокировано!</b>")
|
||||
45
Ruslan-Isaev/modules/DogPic.py
Normal file
45
Ruslan-Isaev/modules/DogPic.py
Normal file
@@ -0,0 +1,45 @@
|
||||
version = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import asyncio
|
||||
from .. import loader, utils
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
API_KEY = 'live_AQldtIr1OR2HIwnXKIONXtGRhEvVd0ZDKBthbAwlC3UgFbxwYFwsEDm4fCcWgSfP'
|
||||
URL = 'https://api.thedogapi.com/v1/images/search'
|
||||
|
||||
async def get_image():
|
||||
headers = {'x-api-key': API_KEY}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(URL, headers=headers) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
image_url = data[0]['url']
|
||||
return image_url, 0
|
||||
else:
|
||||
return response.status, 1
|
||||
|
||||
@loader.tds
|
||||
class DogPicMod(loader.Module):
|
||||
"""Модуль для фотографий с милыми собачками"""
|
||||
|
||||
strings = {
|
||||
"name": "DogPic",
|
||||
}
|
||||
|
||||
|
||||
@loader.command()
|
||||
async def dogpic(self, message):
|
||||
"""картинка с собачкой"""
|
||||
await utils.answer(message, "🔎 <b>Ищу лучшую картинку</b>")
|
||||
try:
|
||||
link, exitcode = await get_image()
|
||||
if exitcode == 0:
|
||||
await message.delete()
|
||||
await utils.answer_file(message, link)
|
||||
else:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{link}</code>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
29
Ruslan-Isaev/modules/Figlet.py
Normal file
29
Ruslan-Isaev/modules/Figlet.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# meta developer: @RUIS_VlP
|
||||
# requires: pyfiglet
|
||||
|
||||
import random
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
from .. import loader, utils
|
||||
import pyfiglet
|
||||
|
||||
@loader.tds
|
||||
class FigletMod(loader.Module):
|
||||
"""Длинные слова лучше переносить на другую строчку. Пример:
|
||||
`.figlet Hello
|
||||
World!`
|
||||
Если написать в одну строчку, то слово не уместится в одно сообщение """
|
||||
|
||||
strings = {
|
||||
"name": "Figlet",
|
||||
}
|
||||
|
||||
def init(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
@loader.command()
|
||||
async def figlet(self, message: Message):
|
||||
"""<text> - делает текст большим"""
|
||||
mtext = message.text[8:]
|
||||
ftext = pyfiglet.figlet_format(mtext)
|
||||
await utils.answer(message, f"<code>{ftext}</code>")
|
||||
190
Ruslan-Isaev/modules/GenNick.py
Normal file
190
Ruslan-Isaev/modules/GenNick.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""gennick module for hikka userbot
|
||||
Copyright (C) 2025 Ruslan Isaev
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/."""
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
# при поддержке @hikka_mods
|
||||
|
||||
__version__ = (1, 0, 0)
|
||||
|
||||
from .. import loader, utils
|
||||
import random
|
||||
import string
|
||||
from typing import List, Optional, Literal
|
||||
import re
|
||||
|
||||
class NicknameGenerator:
|
||||
"""Генератор произносимых и стильных никнеймов."""
|
||||
|
||||
DEFAULT_SYLLABLES = [
|
||||
'ka', 'shi', 'mi', 'zo', 'ren', 'ti', 'ne', 'su', 'vo', 'dai',
|
||||
'xan', 'ri', 'fu', 'pu', 'ko', 'me', 'el', 'tra', 'qua', 'gen',
|
||||
'lor', 'vik', 'nyx', 'zel', 'thor', 'syn', 'try', 'pho', 'lux', 'ry',
|
||||
'kor', 'vex', 'ji', 'al', 'bis', 'war', 'tex', 'yon', 'ga', 'dra',
|
||||
'fi', 'na', 'to', 'va', 'qu', 'ex', 'ja', 'ki', 'lu', 'ma'
|
||||
]
|
||||
|
||||
SPECIAL_CHARS = ['_', '-', '.', '!', '~']
|
||||
|
||||
def __init__(self):
|
||||
self.syllables = self.DEFAULT_SYLLABLES.copy()
|
||||
|
||||
def generate(
|
||||
self,
|
||||
length: int = 8,
|
||||
*,
|
||||
phonemic_alternation: bool = True,
|
||||
add_number: bool = False,
|
||||
add_special_char: bool = False,
|
||||
syllables: Optional[List[str]] = None,
|
||||
capital_style: Literal['first', 'all', 'random', 'camel'] = 'first',
|
||||
min_syllable_length: int = 1,
|
||||
max_syllable_length: int = 3
|
||||
) -> str:
|
||||
"""
|
||||
Генерирует произносимый никнейм с заданными параметрами.
|
||||
|
||||
Параметры:
|
||||
length: Длина никнейма
|
||||
phonemic_alternation: Чередовать гласные/согласные для лучшей произносимости
|
||||
add_number: Добавить случайное число в конец
|
||||
add_special_char: Добавить специальный символ
|
||||
syllables: Кастомный список слогов
|
||||
capital_style: Стиль капитализации
|
||||
min_syllable_length: Минимальная длина слога
|
||||
max_syllable_length: Максимальная длина слога
|
||||
"""
|
||||
# Инициализация слогов
|
||||
syllables = syllables or self.syllables
|
||||
syllables = [s for s in syllables if min_syllable_length <= len(s) <= max_syllable_length]
|
||||
|
||||
# Проверка параметров
|
||||
if not syllables:
|
||||
raise ValueError("Нет подходящих слогов для генерации")
|
||||
|
||||
# Выделение места для дополнительных символов
|
||||
extra_length = 0
|
||||
if add_number:
|
||||
extra_length += random.randint(1, 2)
|
||||
if add_special_char:
|
||||
extra_length += 1
|
||||
|
||||
if extra_length >= length:
|
||||
raise ValueError("Запрошенная длина слишком мала для добавления дополнительных символов")
|
||||
|
||||
nickname = []
|
||||
remaining = length - extra_length
|
||||
last_type = None
|
||||
|
||||
# Генерация основной части
|
||||
while remaining > 0:
|
||||
# Фильтрация слогов по длине
|
||||
possible = [s for s in syllables if len(s) <= remaining]
|
||||
|
||||
# Фильтрация по фонетическому чередованию
|
||||
if phonemic_alternation and last_type is not None and len(possible) > 1:
|
||||
possible = self._filter_by_phonetics(possible, last_type)
|
||||
|
||||
if not possible:
|
||||
# Если нет подходящих слогов, добавляем случайную букву
|
||||
char = random.choice(string.ascii_lowercase)
|
||||
nickname.append(char)
|
||||
remaining -= 1
|
||||
last_type = self._get_char_type(char)
|
||||
continue
|
||||
|
||||
syllable = random.choice(possible)
|
||||
nickname.append(syllable)
|
||||
remaining -= len(syllable)
|
||||
last_type = self._get_char_type(syllable[0])
|
||||
|
||||
# Добавление дополнительных символов
|
||||
if add_number:
|
||||
num_length = min(2, length - len(''.join(nickname)))
|
||||
if num_length > 0:
|
||||
min_num = 10 ** (num_length - 1)
|
||||
max_num = (10 ** num_length) - 1
|
||||
nickname.append(str(random.randint(min_num, max_num)))
|
||||
|
||||
if add_special_char:
|
||||
special_char = random.choice(self.SPECIAL_CHARS)
|
||||
insert_pos = random.choice([len(nickname) - 1, # Перед числом
|
||||
random.randint(1, len(nickname) - 1), # В середине
|
||||
0 # В начале
|
||||
])
|
||||
nickname.insert(insert_pos, special_char)
|
||||
|
||||
# Сборка финальной строки
|
||||
nickname_str = ''.join(nickname)[:length]
|
||||
|
||||
# Применение стиля капитализации
|
||||
nickname_str = self._apply_capital_style(nickname_str, capital_style)
|
||||
|
||||
return nickname_str
|
||||
|
||||
def _filter_by_phonetics(self, syllables: List[str], last_type: str) -> List[str]:
|
||||
"""Фильтрует слоги по фонетическому чередованию."""
|
||||
filtered = []
|
||||
for s in syllables:
|
||||
first_char = s[0].lower()
|
||||
current_type = self._get_char_type(first_char)
|
||||
if last_type != current_type:
|
||||
filtered.append(s)
|
||||
return filtered or syllables
|
||||
|
||||
@staticmethod
|
||||
def _get_char_type(char: str) -> str:
|
||||
"""Определяет тип символа (гласный/согласный)."""
|
||||
vowels = {'a', 'e', 'i', 'o', 'u', 'y'}
|
||||
return 'vowel' if char.lower() in vowels else 'consonant'
|
||||
|
||||
@staticmethod
|
||||
def _apply_capital_style(nickname: str, style: str) -> str:
|
||||
"""Применяет выбранный стиль капитализации."""
|
||||
if style == 'first':
|
||||
return nickname.capitalize()
|
||||
elif style == 'all':
|
||||
return nickname.upper()
|
||||
elif style == 'random':
|
||||
return ''.join(random.choice([c.upper(), c.lower()]) for c in nickname)
|
||||
elif style == 'camel':
|
||||
parts = []
|
||||
for i, part in enumerate(re.split('([^a-zA-Z0-9]+)', nickname)):
|
||||
if i % 2 == 0 and part:
|
||||
parts.append(part.capitalize())
|
||||
else:
|
||||
parts.append(part)
|
||||
return ''.join(parts)
|
||||
return nickname
|
||||
|
||||
@loader.tds
|
||||
class GenNickMod(loader.Module):
|
||||
"""Простой генератор ников"""
|
||||
|
||||
strings = {
|
||||
"name": "GenNick",
|
||||
}
|
||||
|
||||
@loader.command()
|
||||
async def GenNick(self, message):
|
||||
"""Генерирует стандартный ник"""
|
||||
generator = NicknameGenerator()
|
||||
await utils.answer(message, f"<b>Ваш новый ник</b>: <code>{generator.generate()}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def GenIntNick(self, message):
|
||||
"""Генерирует ник с цифрами"""
|
||||
generator = NicknameGenerator()
|
||||
await utils.answer(message, f"<b>Ваш новый ник</b>: <code>{generator.generate(add_number=True)}</code>")
|
||||
63
Ruslan-Isaev/modules/IrisAutoFarm.py
Normal file
63
Ruslan-Isaev/modules/IrisAutoFarm.py
Normal file
@@ -0,0 +1,63 @@
|
||||
version = (2, 2, 8)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class IrisAutoFarm(loader.Module):
|
||||
"""Автофарм в ирисе"""
|
||||
|
||||
strings = {
|
||||
"name": "IrisAutoFarm",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.myid = (await client.get_me()).id
|
||||
self.iris = 5443619563
|
||||
|
||||
async def message_q(
|
||||
self,
|
||||
text: str,
|
||||
user_id: int,
|
||||
mark_read: bool = False,
|
||||
delete: bool = False,
|
||||
):
|
||||
"""Отправляет сообщение и возращает ответ"""
|
||||
async with self.client.conversation(user_id) as conv:
|
||||
msg = await conv.send_message(text)
|
||||
response = await conv.get_response()
|
||||
if mark_read:
|
||||
await conv.mark_read()
|
||||
|
||||
if delete:
|
||||
await msg.delete()
|
||||
await response.delete()
|
||||
|
||||
return response
|
||||
|
||||
@loader.command()
|
||||
async def блэкстарт(self, message):
|
||||
"""Завести таймеры в Iris Black Diamond"""
|
||||
await utils.answer(message, "Начинаю установку таймеров...")
|
||||
for i in range(100):
|
||||
timee = datetime.now()
|
||||
hours_to_add = 4.1 * (i + 1)
|
||||
schedule_time = timee + timedelta(hours=hours_to_add, minutes=5)
|
||||
await self.client.send_message('@iris_black_bot', "Ферма", schedule=schedule_time)
|
||||
await utils.answer(message, "Таймеры успешно установлены!")
|
||||
|
||||
|
||||
|
||||
|
||||
674
Ruslan-Isaev/modules/LICENSE
Normal file
674
Ruslan-Isaev/modules/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
240
Ruslan-Isaev/modules/NewMlMod.py
Normal file
240
Ruslan-Isaev/modules/NewMlMod.py
Normal file
@@ -0,0 +1,240 @@
|
||||
# ©️ Dan Gazizullin, 2021-2023
|
||||
# This file is a part of Hikka Userbot
|
||||
# 🌐 https://github.com/hikariatama/Hikka
|
||||
# You can redistribute it and/or modify it under the terms of the GNU AGPLv3
|
||||
# 🔑 https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
import difflib
|
||||
import inspect
|
||||
import io
|
||||
from hikkatl.tl.types import Message
|
||||
from .. import loader, utils
|
||||
from ..version import __version__
|
||||
|
||||
|
||||
@loader.tds
|
||||
class NewMlMod(loader.Module):
|
||||
"""A module for uploading modules as a file. Let's just say it's a heavily stripped-down UnitHeta."""
|
||||
|
||||
strings = {
|
||||
"name": "NewMlMod",
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Module not found</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>No exact match has been found, so the closest result is shown instead</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Link</a> of</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>File of</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>in reply to this message to install</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>You must specify arguments</b>",
|
||||
"_cmd_doc_ml": "<module name> - Send link to module"
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Модуль не найден</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Точного совпадения не найдено, поэтому показан ближайший результат</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Ссылка</a> на</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Файл</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>в ответ на это сообщение, чтобы установить</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Вы должны указать аргументы</b>",
|
||||
"_cmd_doc_ml": "<имя модуля> - Отправить ссылку на модуль"
|
||||
}
|
||||
strings_uz = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Modul topilmadi</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>To'g'ri mos keladigan natija topilmadi, shuning uchun eng yaqin natija ko'rsatiladi</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Havola</a> bo'yicha</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Fayl</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>bu habarga javob qilib, uni o'rnatish uchun</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Siz argumentlarni belgilamadingiz</b>",
|
||||
"_cmd_doc_ml": "<modul nomi> - Modulga havola yuborish"
|
||||
}
|
||||
|
||||
strings_tt = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Модуль табылмады</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Тулы тапкыр килгән нәтиҗәләр табылмады, сондыктан ең яңа нәтиҗә күрсәтелә</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Сылтама</a> өчен</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Файл</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>осы хәбәрне кабул килгәндә</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Аргументларны күрсәтмәгәнсез</b>",
|
||||
"_cmd_doc_ml": "<модуль исеме> - Модульга сылтама җибәрү"
|
||||
}
|
||||
|
||||
strings_tr = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Modül bulunamadı</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Herhangi bir tam eşleşme bulunamadığından, en yakın sonuç gösteriliyor</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Bağlantı</a> için</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Dosya</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>bu mesaja yanıt olarak yüklemek için</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Argümanlar belirtmelisiniz</b>",
|
||||
"_cmd_doc_ml": "<modül adı> - Modül bağlantısını gönder"
|
||||
}
|
||||
|
||||
strings_kk = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Модуль табылмады</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Толық сәйкес келетін нәтижелер табылмады, сондықтан ең жақын нәтиже көрсетіледі</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Сілтеме</a> үшін</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Файл</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>осы хабарламаны жауап болар енгізу үшін</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Аргументтерді көрсетуіңіз керек</b>",
|
||||
"_cmd_doc_ml": "<модуль атауы> - Модульге сілтеме жіберу"
|
||||
}
|
||||
|
||||
strings_it = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Modulo non trovato</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Nessuna corrispondenza esatta trovata, quindi viene visualizzato il risultato più vicino</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Collegamento</a> per</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>File</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>questo messaggio come risposta per installarlo</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>È necessario specificare gli argomenti</b>",
|
||||
"_cmd_doc_ml": "<nome del modulo> - Invia il link al modulo"
|
||||
}
|
||||
|
||||
strings_fr = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Module introuvable</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Aucune correspondance exacte n'a été trouvée, le résultat le plus proche est donc affiché</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Lien</a> vers</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Fichier</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>en réponse à ce message pour l'installer</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Vous devez spécifier des arguments</b>",
|
||||
"_cmd_doc_ml": "<nom du module> - Envoyer le lien vers le module"
|
||||
}
|
||||
|
||||
strings_es = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Módulo no encontrado</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>No se ha encontrado una coincidencia exacta, por lo que se muestra el resultado más cercano</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Enlace</a> de</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Archivo de</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>en respuesta a este mensaje para instalar</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Debes especificar argumentos</b>",
|
||||
"_cmd_doc_ml": "<nombre del módulo> - Enviar enlace al módulo"
|
||||
}
|
||||
|
||||
strings_de = {
|
||||
"404": "<emoji document_id=5210952531676504517>❌</emoji> <b>Modul nicht gefunden</b>",
|
||||
"not_exact": "<emoji document_id=5312383351217201533>⚠️</emoji> <b>Es wurde keine exakte Übereinstimmung gefunden, daher wird stattdessen das nächstgelegene Ergebnis angezeigt</b>",
|
||||
"link": "<emoji document_id=5280658777148760247>🌐</emoji> <b><a href=\"{url}\">Link</a> zu</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}dlm {url}</code>\n\n{not_exact}",
|
||||
"file": "<emoji document_id=5433653135799228968>📁</emoji> <b>Datei</b> <code>{class_name}</code>\n\n<emoji document_id=5188377234380954537>🌘</emoji> <code>{prefix}lm</code> <b>in Antwort auf diese Nachricht, um sie zu installieren</b>\n\n{not_exact}",
|
||||
"args": "<emoji document_id=5210952531676504517>❌</emoji> <b>Du musst Argumente angeben</b>",
|
||||
"_cmd_doc_ml": "<Modulname> - Send link to module"
|
||||
}
|
||||
|
||||
@loader.command()
|
||||
async def nmlcmd(self, message: Message):
|
||||
"""send module via file"""
|
||||
if not (args := utils.get_args_raw(message)):
|
||||
await utils.answer(message, self.strings("args"))
|
||||
return
|
||||
|
||||
exact = True
|
||||
if not (
|
||||
class_name := next(
|
||||
(
|
||||
module.strings("name")
|
||||
for module in self.allmodules.modules
|
||||
if args.lower()
|
||||
in {
|
||||
module.strings("name").lower(),
|
||||
module.__class__.__name__.lower(),
|
||||
}
|
||||
),
|
||||
None,
|
||||
)
|
||||
):
|
||||
if not (
|
||||
class_name := next(
|
||||
reversed(
|
||||
sorted(
|
||||
[
|
||||
module.strings["name"].lower()
|
||||
for module in self.allmodules.modules
|
||||
]
|
||||
+ [
|
||||
module.__class__.__name__.lower()
|
||||
for module in self.allmodules.modules
|
||||
],
|
||||
key=lambda x: difflib.SequenceMatcher(
|
||||
None,
|
||||
args.lower(),
|
||||
x,
|
||||
).ratio(),
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
):
|
||||
await utils.answer(message, self.strings("404"))
|
||||
return
|
||||
|
||||
exact = False
|
||||
|
||||
try:
|
||||
module = self.lookup(class_name)
|
||||
sys_module = inspect.getmodule(module)
|
||||
except Exception:
|
||||
await utils.answer(message, self.strings("404"))
|
||||
return
|
||||
|
||||
link = module.__origin__
|
||||
|
||||
text = (
|
||||
f"<b>🧳 {utils.escape_html(class_name)}</b>"
|
||||
if not utils.check_url(link)
|
||||
else (
|
||||
f'📼 <b><a href="{link}">Link</a> for'
|
||||
f" {utils.escape_html(class_name)}:</b>"
|
||||
f' <code>{link}</code>\n\n{self.strings("not_exact") if not exact else ""}'
|
||||
)
|
||||
)
|
||||
|
||||
text = (
|
||||
self.strings("link").format(
|
||||
class_name=utils.escape_html(class_name),
|
||||
url=link,
|
||||
not_exact=self.strings("not_exact") if not exact else "",
|
||||
prefix=utils.escape_html(self.get_prefix()),
|
||||
)
|
||||
if utils.check_url(link)
|
||||
else self.strings("file").format(
|
||||
class_name=utils.escape_html(class_name),
|
||||
not_exact=self.strings("not_exact") if not exact else "",
|
||||
prefix=utils.escape_html(self.get_prefix()),
|
||||
)
|
||||
)
|
||||
|
||||
file = io.BytesIO(sys_module.__loader__.data)
|
||||
file.name = f"{class_name}.py"
|
||||
file.seek(0)
|
||||
|
||||
await utils.answer_file(
|
||||
message,
|
||||
file,
|
||||
caption=text,
|
||||
)
|
||||
|
||||
def _format_result(
|
||||
self,
|
||||
result: dict,
|
||||
query: str,
|
||||
no_translate: bool = False,
|
||||
) -> str:
|
||||
commands = "\n".join(
|
||||
[
|
||||
f"▫️ <code>{utils.escape_html(self.get_prefix())}{utils.escape_html(cmd)}</code>:"
|
||||
f" <b>{utils.escape_html(cmd_doc)}</b>"
|
||||
for cmd, cmd_doc in result["module"]["commands"].items()
|
||||
]
|
||||
)
|
||||
|
||||
kwargs = {
|
||||
"name": utils.escape_html(result["module"]["name"]),
|
||||
"dev": utils.escape_html(result["module"]["dev"]),
|
||||
"commands": commands,
|
||||
"cls_doc": utils.escape_html(result["module"]["cls_doc"]),
|
||||
"mhash": result["module"]["hash"],
|
||||
"query": utils.escape_html(query),
|
||||
"prefix": utils.escape_html(self.get_prefix()),
|
||||
}
|
||||
|
||||
strings = (
|
||||
self.strings.get("result", "en")
|
||||
if self.config["translate"] and not no_translate
|
||||
else self.strings("result")
|
||||
)
|
||||
|
||||
text = strings.format(**kwargs)
|
||||
|
||||
if len(text) > 2048:
|
||||
kwargs["commands"] = "..."
|
||||
text = strings.format(**kwargs)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
38
Ruslan-Isaev/modules/Quotly.py
Normal file
38
Ruslan-Isaev/modules/Quotly.py
Normal file
@@ -0,0 +1,38 @@
|
||||
version = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import telethon
|
||||
from .. import loader, utils
|
||||
|
||||
@loader.tds
|
||||
class QuotlyMod(loader.Module):
|
||||
"""Модуль для создания стикеров по сообщению через @QuotLyBot"""
|
||||
|
||||
strings = {
|
||||
"name": "Quotly",
|
||||
}
|
||||
|
||||
async def on_dlmod(self):
|
||||
await self.client.send_message("@QuotLyBot", "/start")
|
||||
|
||||
bot = ["@QuotLyBot", 1031952739]
|
||||
|
||||
@loader.command()
|
||||
async def quotly(self, message):
|
||||
"""<reply> - создать стикер по сообщению"""
|
||||
reply = await message.get_reply_message()
|
||||
try: chat_id = message.chat_id
|
||||
except: chat_id = (await utils.get_user(message)).id
|
||||
if not reply:
|
||||
await utils.answer(message, "❌ <b>Команда должна быть ответом на сообщение!</b>")
|
||||
return
|
||||
try:
|
||||
async with message.client.conversation(self.bot[0]) as conv:
|
||||
forward = await reply.forward_to(self.bot[0])
|
||||
answer = await conv.wait_event(telethon.events.NewMessage(incoming=True, from_users=self.bot[1]))
|
||||
await utils.answer_file(message, answer.message)
|
||||
await forward.delete()
|
||||
await answer.delete()
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
1
Ruslan-Isaev/modules/README.md
Normal file
1
Ruslan-Isaev/modules/README.md
Normal file
@@ -0,0 +1 @@
|
||||

|
||||
520
Ruslan-Isaev/modules/S3.py
Normal file
520
Ruslan-Isaev/modules/S3.py
Normal file
@@ -0,0 +1,520 @@
|
||||
__version__ = (1, 2, 0)
|
||||
|
||||
# changelog 1.1.0: убрана проверка хэш-суммы, сделано для избежания ошибок
|
||||
|
||||
# changelog 1.1.1: изменен способ передачи файла, что бы избежать перерасход оперативной памяти
|
||||
|
||||
# changelog 1.2.0: добавлены команды для переименования, вырезания, копирования файлов, просмотра занятого места, отмены незавершенных загрузок и полной очистки S3 хранилища
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
# requires: aioboto3 aiofiles
|
||||
|
||||
import aioboto3
|
||||
import aiofiles
|
||||
import os
|
||||
from .. import loader, utils
|
||||
import mimetypes
|
||||
import botocore
|
||||
import asyncio
|
||||
|
||||
CHUNK_SIZE = 50 * 1024 * 1024 # 50MB
|
||||
|
||||
#полная очистка S3 хранилища
|
||||
async def s3_purge(url, bucket, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client("s3", endpoint_url=url, aws_access_key_id=access_key, aws_secret_access_key=secret_key) as s3:
|
||||
response = await s3.list_objects_v2(Bucket=bucket, Prefix="")
|
||||
files = [obj["Key"] for obj in response.get("Contents", [])]
|
||||
async for file in files:
|
||||
await s3.delete_object(Bucket=bucket, Key=file)
|
||||
await s3_clear(url, bucket, access_key, secret_key)
|
||||
|
||||
|
||||
#удаление незавершенных загрузок
|
||||
async def s3_clear(url, bucket, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client(
|
||||
"s3",
|
||||
endpoint_url=url,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key
|
||||
) as s3:
|
||||
deleted_count = 0
|
||||
|
||||
# Удаляем незавершённые загрузки
|
||||
paginator = s3.get_paginator("list_multipart_uploads")
|
||||
async for page in paginator.paginate(Bucket=bucket):
|
||||
if "Uploads" in page:
|
||||
for upload in page["Uploads"]:
|
||||
upload_id = upload["UploadId"]
|
||||
key = upload["Key"]
|
||||
|
||||
# Прерываем незавершённые загрузки
|
||||
await s3.abort_multipart_upload(
|
||||
Bucket=bucket,
|
||||
Key=key,
|
||||
UploadId=upload_id
|
||||
)
|
||||
deleted_count += 1
|
||||
|
||||
# Проверка объектов, которые могут быть частично загружены
|
||||
paginator = s3.get_paginator("list_objects_v2")
|
||||
async for page in paginator.paginate(Bucket=bucket):
|
||||
if "Contents" in page:
|
||||
for obj in page["Contents"]:
|
||||
try:
|
||||
# Получаем размер объекта
|
||||
head_response = await s3.head_object(Bucket=bucket, Key=obj["Key"])
|
||||
# Если размер объекта на сервере меньше ожидаемого (ошибочная загрузка), удаляем его
|
||||
if head_response["ContentLength"] < obj["Size"]:
|
||||
await s3.delete_objects(
|
||||
Bucket=bucket,
|
||||
Delete={"Objects": [{"Key": obj["Key"]}]}
|
||||
)
|
||||
deleted_count += 1
|
||||
except Exception as e:
|
||||
pass # Игнорируем ошибки, если они возникнут, например, из-за отсутствия доступа
|
||||
|
||||
return deleted_count
|
||||
|
||||
#сколько памяти занято
|
||||
async def s3_usage(url, bucket, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client(
|
||||
"s3",
|
||||
endpoint_url=url,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key
|
||||
) as s3:
|
||||
total_size = 0
|
||||
|
||||
paginator = s3.get_paginator("list_objects_v2")
|
||||
async for page in paginator.paginate(Bucket=bucket):
|
||||
if "Contents" in page:
|
||||
total_size += sum(obj["Size"] for obj in page["Contents"])
|
||||
|
||||
return total_size / (1024**3) # Размер в ГБ
|
||||
|
||||
#вырезать
|
||||
async def s3_cut(url, bucket, newkey, oldkey, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client(
|
||||
"s3",
|
||||
endpoint_url=url,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key
|
||||
) as s3:
|
||||
await s3.copy_object(
|
||||
Bucket=bucket,
|
||||
CopySource={'Bucket': bucket, 'Key': oldkey},
|
||||
Key=newkey
|
||||
)
|
||||
await s3.delete_object(Bucket=bucket, Key=oldkey)
|
||||
|
||||
async def s3_copy(url, bucket, newkey, oldkey, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client(
|
||||
"s3",
|
||||
endpoint_url=url,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key
|
||||
) as s3:
|
||||
await s3.copy_object(
|
||||
Bucket=bucket,
|
||||
CopySource={'Bucket': bucket, 'Key': oldkey},
|
||||
Key=newkey
|
||||
)
|
||||
|
||||
async def s3_upload(url, bucket, filename, filepath, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
|
||||
mime_type, _ = mimetypes.guess_type(filename)
|
||||
if mime_type is None:
|
||||
mime_type = "binary/octet-stream"
|
||||
|
||||
async with session.client(
|
||||
"s3",
|
||||
endpoint_url=url,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
config=botocore.config.Config(
|
||||
request_checksum_calculation="when_required",
|
||||
response_checksum_validation="when_required",
|
||||
),
|
||||
) as s3:
|
||||
async with aiofiles.open(filename, "rb") as file:
|
||||
upload_id = None
|
||||
parts = []
|
||||
part_number = 1
|
||||
|
||||
# Инициализируем многокомпонентную загрузку
|
||||
response = await s3.create_multipart_upload(
|
||||
Bucket=bucket,
|
||||
Key=f"{filepath}/{filename}".replace(" ", "_"),
|
||||
ContentType=mime_type
|
||||
)
|
||||
upload_id = response["UploadId"]
|
||||
|
||||
try:
|
||||
while True:
|
||||
chunk = await file.read(CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
response = await s3.upload_part(
|
||||
Bucket=bucket,
|
||||
Key=f"{filepath}/{filename}".replace(" ", "_"),
|
||||
PartNumber=part_number,
|
||||
UploadId=upload_id,
|
||||
Body=chunk
|
||||
)
|
||||
|
||||
parts.append({"PartNumber": part_number, "ETag": response["ETag"]})
|
||||
part_number += 1
|
||||
|
||||
# Завершаем многокомпонентную загрузку
|
||||
await s3.complete_multipart_upload(
|
||||
Bucket=bucket,
|
||||
Key=f"{filepath}/{filename}".replace(" ", "_"),
|
||||
UploadId=upload_id,
|
||||
MultipartUpload={"Parts": parts},
|
||||
)
|
||||
except Exception as e:
|
||||
await s3.abort_multipart_upload(
|
||||
Bucket=bucket,
|
||||
Key=f"{filepath}/{filename}".replace(" ", "_"),
|
||||
UploadId=upload_id,
|
||||
)
|
||||
raise e
|
||||
|
||||
async def s3_download(url, bucket, filename, filepath, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client("s3", endpoint_url=url, aws_access_key_id=access_key, aws_secret_access_key=secret_key) as s3:
|
||||
await s3.download_file(bucket, filename, f"{filepath}/{filename.split('/')[-1]}")
|
||||
return f"{filepath}/{filename.split('/')[-1]}"
|
||||
|
||||
async def s3_delete(url, bucket, filename, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client("s3", endpoint_url=url, aws_access_key_id=access_key, aws_secret_access_key=secret_key) as s3:
|
||||
await s3.delete_object(Bucket=bucket, Key=filename)
|
||||
|
||||
async def s3_ls(url, bucket, filepath, access_key, secret_key):
|
||||
session = aioboto3.Session()
|
||||
async with session.client("s3", endpoint_url=url, aws_access_key_id=access_key, aws_secret_access_key=secret_key) as s3:
|
||||
response = await s3.list_objects_v2(Bucket=bucket, Prefix=filepath)
|
||||
return [obj["Key"] for obj in response.get("Contents", [])] #я сам не ебу что это, мне это ChatGPT сделал
|
||||
|
||||
|
||||
@loader.tds
|
||||
class S3Mod(loader.Module):
|
||||
"""Модуль для работы с S3 хранилищами"""
|
||||
|
||||
strings = {
|
||||
"name": "S3",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"url",
|
||||
"None",
|
||||
lambda: "Ссылка на ваше S3 хранилище",
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"bucketname",
|
||||
"None",
|
||||
lambda: "Имя bucket'а",
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"access_key",
|
||||
"None",
|
||||
lambda: "Ключ авторизации",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"secret_key",
|
||||
"None",
|
||||
lambda: "Секретный ключ",
|
||||
validator=loader.validators.Hidden(),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def S3upload(self, message):
|
||||
"""<path> <reply> - сохраняет файл в S3 хранилище"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
filepath = args.split(" ")[0]
|
||||
filepath = filepath[1:] if filepath.startswith('/') else filepath #удаление / из переменной, если она идет первым символом
|
||||
else:
|
||||
filepath = "FromHikka"
|
||||
reply = await message.get_reply_message()
|
||||
if reply and reply.media:
|
||||
await utils.answer(message, "💿 <b>Сохраняю файл на сервер...</b>")
|
||||
try:
|
||||
filename = await message.client.download_media(reply.media)
|
||||
await utils.answer(message, "☁️ <b>Сохраняю файл в S3 хранилище...</b>")
|
||||
await s3_upload(url, bucket, filename, filepath, access, secret)
|
||||
await utils.answer(message, "💿 <b>Удаляю файл с сервера...</b>")
|
||||
os.remove(filename)
|
||||
await utils.answer(message, f"✅ <b>Успешно! Файл сохранен в директорию</b> <code>/{filepath}</code> <b>на вашем S3 хранилище</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
os.remove(filename)
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Ошибка! Не найден ответ на сообщение или в ответном сообщении отсутствуют файлы.</b>")
|
||||
|
||||
|
||||
@loader.command()
|
||||
async def S3LS(self, message):
|
||||
"""<path> - список файлов в S3 хранилище"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
filepath = args
|
||||
filepath = filepath[1:] if filepath.startswith('/') else filepath
|
||||
else:
|
||||
filepath = ""
|
||||
try:
|
||||
ls = await s3_ls(url, bucket, filepath, access, secret)
|
||||
output = '\n'.join(['▪️<code>' + item + '</code>' for item in ls]) #превращение списка в человекочитаемый текст
|
||||
await utils.answer(message, f"🗂 <b>Список файлов и директорий в</b> <code>/{filepath}</code><b>:</b>\n\n{output}")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3delete(self, message):
|
||||
"""<path> - удаляет файл из S3 хрпнилища"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
filepath = args
|
||||
filepath = filepath[1:] if filepath.startswith('/') else filepath #удаление / из переменной, если она идет первым символом
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы не указали файл для удаления!</b>")
|
||||
return
|
||||
try:
|
||||
await s3_delete(url, bucket, filepath, access, secret)
|
||||
await utils.answer(message, f"✅ <b>Файл</b> <code>{filepath}</code> <b>успешно удален!</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3rename(self, message):
|
||||
"""<folder> <old_filename> <new_filename> - переименовывает файл. Пробелы в адресе заменяйте на %20"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
keys = args.split(" ")
|
||||
if len(keys) == 3 or len(keys) > 3:
|
||||
key0 = keys[0]
|
||||
key0 = key0[1:] if key0.startswith('/') else key0
|
||||
key0 = key0.replace("%20", " ")
|
||||
|
||||
key1 = keys[1]
|
||||
key1 = key1[1:] if key1.startswith('/') else key1
|
||||
key1 = key1.replace("%20", " ")
|
||||
|
||||
key2 = keys[2]
|
||||
key2 = key2[1:] if key2.startswith('/') else key2
|
||||
key2 = key2.replace("%20", " ")
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы указали недостаточно аргументов!</b>")
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы не указали файл для переименования!</b>")
|
||||
return
|
||||
try:
|
||||
oldfilename = f"{key0}/{key1}"
|
||||
newfilename = f"{key0}/{key2}"
|
||||
await s3_cut(url, bucket, newfilename, oldfilename, access, secret)
|
||||
await utils.answer(message, f"✅ <b>Файл</b> <code>{oldfilename}</code> <b>успешно переименован в</b> <code>{newfilename}</code>!</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3cut(self, message):
|
||||
"""<file> <old_folder> <new_folder> - вырезает файл. Пробелы в адресе заменяйте на %20"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
keys = args.split(" ")
|
||||
if len(keys) == 3 or len(keys) > 3:
|
||||
key0 = keys[0]
|
||||
key0 = key0[1:] if key0.startswith('/') else key0
|
||||
key0 = key0.replace("%20", " ")
|
||||
|
||||
key1 = keys[1]
|
||||
key1 = key1[1:] if key1.startswith('/') else key1
|
||||
key1 = key1.replace("%20", " ")
|
||||
|
||||
key2 = keys[2]
|
||||
key2 = key2[1:] if key2.startswith('/') else key2
|
||||
key2 = key2.replace("%20", " ")
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы указали недостаточно аргументов!</b>")
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы не указали файл для вырезания!</b>")
|
||||
return
|
||||
try:
|
||||
oldfilename = f"{key1}/{key0}"
|
||||
newfilename = f"{key2}/{key0}"
|
||||
await s3_cut(url, bucket, newfilename, oldfilename, access, secret)
|
||||
await utils.answer(message, f"✅ <b>Файл</b> <code>{oldfilename}</code> <b>успешно вырезан в</b> <code>{newfilename}</code>!</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3copy(self, message):
|
||||
"""<file> <old_folder> <new_folder> - копирует файл. Пробелы в адресе заменяйте на %20"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
keys = args.split(" ")
|
||||
if len(keys) == 3 or len(keys) > 3:
|
||||
key0 = keys[0]
|
||||
key0 = key0[1:] if key0.startswith('/') else key0
|
||||
key0 = key0.replace("%20", " ")
|
||||
|
||||
key1 = keys[1]
|
||||
key1 = key1[1:] if key1.startswith('/') else key1
|
||||
key1 = key1.replace("%20", " ")
|
||||
|
||||
key2 = keys[2]
|
||||
key2 = key2[1:] if key2.startswith('/') else key2
|
||||
key2 = key2.replace("%20", " ")
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы указали недостаточно аргументов!</b>")
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы не указали файл для копирования!</b>")
|
||||
return
|
||||
try:
|
||||
oldfilename = f"{key1}/{key0}"
|
||||
newfilename = f"{key2}/{key0}"
|
||||
await s3_copy(url, bucket, newfilename, oldfilename, access, secret)
|
||||
await utils.answer(message, f"✅ <b>Файл</b> <code>{oldfilename}</code> <b>успешно копирован в</b> <code>{newfilename}</code>!</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3download(self, message):
|
||||
"""<path> - скачивает файл из S3 хрпнилища и отправляет в Telegram"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
filename = args
|
||||
filename = filename[1:] if filename.startswith('/') else filename #удаление / из переменной, если она идет первым символом
|
||||
else:
|
||||
await utils.answer(message, "❌ <b>Вы не указали файл для сохранения!</b>")
|
||||
return
|
||||
try:
|
||||
dl = await s3_download(url, bucket, filename, utils.get_base_dir(), access, secret)
|
||||
await utils.answer_file(message, dl, caption=f"✅ <b>Вот ваш файл</b> <code>/{filename}</code><b>!</b>")
|
||||
os.remove(dl)
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def s3config(self, message):
|
||||
"""- открыть конфигурацию модуля"""
|
||||
name = "S3"
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def S3usage(self, message):
|
||||
"""- сколько занято памяти на S3"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
try:
|
||||
usage = await s3_usage(url, bucket, access, secret)
|
||||
await utils.answer(message, f"🗂 <b>Использовано</b> <code>{round(usage, 2)}</code> <b>ГБ памяти.</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3clear(self, message):
|
||||
"""- удаление незавершенных загрузок"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
try:
|
||||
await utils.answer(message, "🔎 <b>Ищу незавершенные загрузки...</b>")
|
||||
clear = await s3_clear(url, bucket, access, secret)
|
||||
await utils.answer(message, f"🗂 <b>Удалено</b> <code>{clear}</code> <b>неудавшихся загрузок.</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def S3purge(self, message):
|
||||
"""- ПОЛНАЯ ОЧИСТКА ХРАНИЛИЩА S3. Будьте осторожны с этой командой"""
|
||||
url = self.config["url"] or "None"
|
||||
bucket = self.config["bucketname"] or "None"
|
||||
access = self.config["access_key"] or "None"
|
||||
secret = self.config["secret_key"] or "None"
|
||||
if url == "None" or bucket == "None" or secret == "None" or access == "None":
|
||||
await utils.answer(message, f"❌ <b>Вы не настроили модуль! Укажите необходимые данные в config. Команда: </b><code>{self.get_prefix()}config S3</code>")
|
||||
return
|
||||
try:
|
||||
await utils.answer(message, "🗂 <b>Начинаю очистку...</b>")
|
||||
clear = await s3_purge(url, bucket, access, secret)
|
||||
await utils.answer(message, f"🗂 <b>Ваше S3 хранилище полностью очищено.</b>")
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
114
Ruslan-Isaev/modules/SFTPUploader.py
Normal file
114
Ruslan-Isaev/modules/SFTPUploader.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# -*- 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
|
||||
|
||||
39
Ruslan-Isaev/modules/ThreadLink.py
Normal file
39
Ruslan-Isaev/modules/ThreadLink.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# meta developer: @RUIS_VlP, @matubuntu
|
||||
|
||||
import re
|
||||
from telethon import TelegramClient, events, sync, utils
|
||||
from telethon.tl.types import Channel, Chat
|
||||
from .. import loader, utils
|
||||
from ..inline.types import (
|
||||
BotInlineCall,
|
||||
BotInlineMessage,
|
||||
BotMessage,
|
||||
InlineCall,
|
||||
InlineMessage,
|
||||
InlineQuery,
|
||||
InlineUnit,
|
||||
)
|
||||
|
||||
@loader.tds
|
||||
class ThreadMod(loader.Module):
|
||||
"""Модуль для получения ветки"""
|
||||
|
||||
strings = {"name": "Thread"}
|
||||
|
||||
@loader.command()
|
||||
async def threadlink(self, message):
|
||||
"""Получает ссылку на ветку сообщений.
|
||||
"""
|
||||
reply = await message.get_reply_message()
|
||||
if not reply:
|
||||
await utils.answer(message, "❌ <b>Команда должна быть ответом на сообщение!</b>")
|
||||
return
|
||||
try:
|
||||
cid = message.chat.id
|
||||
except:
|
||||
await utils.answer(message, "❌ <b>Команда доступна только в чатах и каналах!</b>")
|
||||
return
|
||||
chat = await message.get_chat()
|
||||
url = f'https://t.me/{chat.username}' if chat.username else f'https://t.me/c/{chat.id}'
|
||||
msg_link = f"{url}/{reply.id}?thread={reply.id}"
|
||||
await utils.answer(message, "<b>🪵 Ветка сообщений</b>", reply_markup={"text": "Перейти", "url": msg_link})
|
||||
38
Ruslan-Isaev/modules/barcode.py
Normal file
38
Ruslan-Isaev/modules/barcode.py
Normal file
@@ -0,0 +1,38 @@
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
# requires: python-barcode[images]
|
||||
import barcode
|
||||
from barcode.writer import ImageWriter
|
||||
from .. import loader, utils
|
||||
import uuid
|
||||
import os
|
||||
|
||||
async def generate_barcode(data, filename):
|
||||
options = {
|
||||
'write_text': False,
|
||||
'quiet_zone': 2,
|
||||
'module_height': 15.0
|
||||
}
|
||||
code128 = barcode.get('code128', data, writer=ImageWriter())
|
||||
code128.save(filename, options)
|
||||
|
||||
@loader.tds
|
||||
class BarcodeGeneratorMod(loader.Module):
|
||||
"""Генерирует штрих код (code128) """
|
||||
|
||||
strings = {
|
||||
"name": "BarcodeGenerator",
|
||||
}
|
||||
|
||||
@loader.command()
|
||||
async def barcodecmd(self, message):
|
||||
"""<код> - генерирует штрих-код"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
args = " "
|
||||
randuuid = str(uuid.uuid4())
|
||||
filename = f"{randuuid}.png"
|
||||
await generate_barcode(args, randuuid)
|
||||
await utils.answer_file(message, filename, caption=args)
|
||||
os.remove(filename)
|
||||
99
Ruslan-Isaev/modules/checkmodule.py
Normal file
99
Ruslan-Isaev/modules/checkmodule.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import requests
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
from .. import loader, utils
|
||||
|
||||
# meta developer: @matubuntu
|
||||
@loader.tds
|
||||
class CheckModulesMod(loader.Module):
|
||||
"""Модуль для проверки модулей"""
|
||||
|
||||
strings = {
|
||||
"name": "Check module",
|
||||
"answer": (
|
||||
"<pre>Found: ❌️ {0} | ⚠️ {1} | ✅ {2}\n\n"
|
||||
"🔍 Module check completed:\n\n"
|
||||
"❌️ Criticals ({3}):\n{4}\n\n"
|
||||
"⚠️ Warnings ({5}):\n{6}\n\n"
|
||||
"🔰 Advices ({7}):\n{8}</pre>"
|
||||
),
|
||||
"error": "Error!\n\n.checkmod <module_link> or reply to a file",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"answer": (
|
||||
"<pre>Найдено: ❌️ {0} | ⚠️ {1} | ✅ {2}\n\n"
|
||||
"🔍 Проверка модуля завершена:\n\n"
|
||||
"❌️ Критические ({3}):\n{4}\n\n"
|
||||
"⚠️ Предупреждения ({5}):\n{6}\n\n"
|
||||
"🔰 Советы ({7}):\n{8}</pre>"
|
||||
),
|
||||
"error": "Ошибка!\n\n.checkmod <ссылка_на_модуль> или ответ на файл",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
async def send_request(self, url, code=None):
|
||||
try:
|
||||
if code:
|
||||
response = requests.post(url, json={"code": code})
|
||||
else:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status() # Проверяем, нет ли ошибок в запросе
|
||||
return json.loads(response.text)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
async def format_response(self, response):
|
||||
if "error" in response:
|
||||
return f"<b>Error:</b> {response['error']}"
|
||||
|
||||
critical = "\n".join([f" {item}" for item in response.get("critical_details", [])]) or " ▪️ ➖"
|
||||
warn = "\n".join([f" {item}" for item in response.get("warn_details", [])]) or " ▪️ ➖"
|
||||
council = "\n".join([f" {item}" for item in response.get("council_details", [])]) or " ▪️ ➖"
|
||||
|
||||
return self.strings["answer"].format(
|
||||
response.get("critical_count", 0),
|
||||
response.get("warn_count", 0),
|
||||
response.get("council_count", 0),
|
||||
response.get("critical_count", 0),
|
||||
critical,
|
||||
response.get("warn_count", 0),
|
||||
warn,
|
||||
response.get("council_count", 0),
|
||||
council
|
||||
)
|
||||
|
||||
@loader.unrestricted
|
||||
@loader.ratelimit
|
||||
async def checkmodcmd(self, message):
|
||||
"""
|
||||
<url/reply file> - проверяет модули
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
url = f"http://ruisblyat.serv00.net/checkmod.php?url={quote(args)}"
|
||||
response = await self.send_request(url)
|
||||
await utils.answer(message, await self.format_response(response))
|
||||
return
|
||||
|
||||
try:
|
||||
code_from_message = (await self._client.download_file(message.media, bytes)).decode("utf-8")
|
||||
except Exception:
|
||||
code_from_message = ""
|
||||
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
code_from_reply = (await self._client.download_file(reply.media, bytes)).decode("utf-8")
|
||||
except Exception:
|
||||
code_from_reply = ""
|
||||
|
||||
code = code_from_message or code_from_reply
|
||||
if code:
|
||||
url = "http://ruisblyat.serv00.net/checkmod.php"
|
||||
response = await self.send_request(url, code)
|
||||
await utils.answer(message, await self.format_response(response))
|
||||
else:
|
||||
await utils.answer(message, self.strings["error"])
|
||||
65
Ruslan-Isaev/modules/clck.py
Normal file
65
Ruslan-Isaev/modules/clck.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""clckru module for hikka userbot
|
||||
Copyright (C) 2025 Ruslan Isaev
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/."""
|
||||
|
||||
version = (1, 0, 0)
|
||||
|
||||
# with the support of: @hikka_mods
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import aiohttp
|
||||
from .. import loader, utils
|
||||
|
||||
@loader.tds
|
||||
class ClckMod(loader.Module):
|
||||
"""Помогает сократить ссылку в clck.ru или расшифровать укороченную ссылку."""
|
||||
|
||||
strings = {
|
||||
"name": "ClckRu",
|
||||
}
|
||||
|
||||
async def short_url(self, url: str) -> str:
|
||||
endpoint = 'https://clck.ru/--'
|
||||
params = {'url': url}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(endpoint, params=params) as response:
|
||||
return await response.text()
|
||||
|
||||
@loader.command()
|
||||
async def schortcmd(self, message):
|
||||
"""<url> - сократит ссылку."""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, "❌ <b>Вы не указали ссылку для сокращения!</b>")
|
||||
return
|
||||
url = args.split(" ")[0]
|
||||
slink = await self.short_url(url)
|
||||
await utils.answer(message, f"🔗 <b>Ваша ссылка:</b>\n<code>{slink}</code>")
|
||||
|
||||
@loader.command()
|
||||
async def deschortcmd(self, message):
|
||||
"""<url> - расшифрует ссылку."""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, "❌ <b>Вы не указали ссылку для расшифровки!</b>")
|
||||
return
|
||||
url = args.split(" ")[0]
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
final_url = str(response.url)
|
||||
if final_url.startswith("https://clck.ru/showcaptcha?"):
|
||||
await utils.answer(message, "⛔️ <b>На</b> <code>clck.ru<code> <b>сработала капча! Попробуйте позже.</b>")
|
||||
else:
|
||||
await utils.answer(message, f"🔗 <b>Ваша ссылка:</b>\n<code>{final_url}</code>")
|
||||
300
Ruslan-Isaev/modules/financemod.py
Normal file
300
Ruslan-Isaev/modules/financemod.py
Normal file
@@ -0,0 +1,300 @@
|
||||
#meta developer: @matubuntu
|
||||
import requests, bs4
|
||||
from datetime import datetime
|
||||
from .. import loader, utils
|
||||
import lxml
|
||||
|
||||
# requires: lxml requests bs4
|
||||
|
||||
_FLAGS = {
|
||||
"AUD": "🇦🇺",
|
||||
"AZN": "🇦🇿",
|
||||
"GBP": "🇬🇧",
|
||||
"AMD": "🇦🇲",
|
||||
"BYN": "🇧🇾",
|
||||
"BGN": "🇧🇬",
|
||||
"BRL": "🇧🇷",
|
||||
"HUF": "🇭🇺",
|
||||
"VND": "🇻🇳",
|
||||
"HKD": "🇭🇰",
|
||||
"GEL": "🇬🇪",
|
||||
"DKK": "🇩🇰",
|
||||
"AED": "🇦🇪",
|
||||
"USD": "🇺🇸",
|
||||
"EUR": "🇪🇺",
|
||||
"EGP": "🇪🇬",
|
||||
"INR": "🇮🇳",
|
||||
"IDR": "🇮🇩",
|
||||
"KZT": "🇰🇿",
|
||||
"CAD": "🇨🇦",
|
||||
"QAR": "🇶🇦",
|
||||
"KGS": "🇰🇬",
|
||||
"CNY": "🇨🇳",
|
||||
"MDL": "🇲🇩",
|
||||
"NZD": "🇳🇿",
|
||||
"NOK": "🇳🇴",
|
||||
"PLN": "🇵🇱",
|
||||
"RON": "🇷🇴",
|
||||
"SGD": "🇸🇬",
|
||||
"TJS": "🇹🇯",
|
||||
"THB": "🇹🇭",
|
||||
"TRY": "🇹🇷",
|
||||
"TMT": "🇹🇲",
|
||||
"UZS": "🇺🇿",
|
||||
"UAH": "🇺🇦",
|
||||
"CZK": "🇨🇿",
|
||||
"SEK": "🇸🇪",
|
||||
"CHF": "🇨🇭",
|
||||
"RSD": "🇷🇸",
|
||||
"ZAR": "🇿🇦",
|
||||
"KRW": "🇰🇷",
|
||||
"JPY": "🇯🇵",
|
||||
}
|
||||
|
||||
_CRYPTO_EMOJIS = {
|
||||
"BTC": "<emoji document_id=5289519973285257969>💰</emoji>",
|
||||
"ETH": "<emoji document_id=5287735049301550386>💰</emoji>",
|
||||
"SOL": "<emoji document_id=5251712673258697260>💰</emoji>",
|
||||
"TON": "<emoji document_id=5289648693455119919>💰</emoji>",
|
||||
"USDT": "<emoji document_id=5289904548951911168>💰</emoji>",
|
||||
"XRP": "<emoji document_id=5373312921214401986>💰</emoji>",
|
||||
"USDC": "<emoji document_id=5372958453268497353>💰</emoji>",
|
||||
"ADA": "<emoji document_id=5373076801092338046>💰</emoji>",
|
||||
"DOGE": "<emoji document_id=5375192042420842380>💰</emoji>",
|
||||
"TRX": "<emoji document_id=5375187081733616165>💰</emoji>",
|
||||
"AVAX": "<emoji document_id=5375311275007947936>💰</emoji>",
|
||||
"LTC": "<emoji document_id=5373035462032113888>💰</emoji>",
|
||||
"BCH": "<emoji document_id=5375596920397903962>💰</emoji>",
|
||||
"ATOM": "<emoji document_id=5375468745688889977>💰</emoji>",
|
||||
"XLM": "<emoji document_id=5372823290647690288>💰</emoji>",
|
||||
"SHIB": "<emoji document_id=5375231036428924778>💰</emoji>",
|
||||
"UNI": "<emoji document_id=5372953110329180525>💰</emoji>",
|
||||
"XMR": "<emoji document_id=5375507073977038661>💰</emoji>",
|
||||
"LINK": "<emoji document_id=5375149651093633217>💰</emoji>",
|
||||
"ETC": "<emoji document_id=5375543306321146693>💰</emoji>",
|
||||
"SUI": "<emoji document_id=5391002164929772708>💰</emoji>",
|
||||
"NEAR": "<emoji document_id=5391181990915487346>💰</emoji>",
|
||||
"VET": "<emoji document_id=5391091302681033446>💰</emoji>",
|
||||
"FIL": "<emoji document_id=5373117173784919811>💰</emoji>",
|
||||
"XTZ": "<emoji document_id=5390985478981829698>💰</emoji>",
|
||||
"ALGO": "<emoji document_id=5391337713544738420>💰</emoji>",
|
||||
"THETA": "<emoji document_id=5391256014676833736>💰</emoji>",
|
||||
"FTM": "<emoji document_id=5393179395521263785>💰</emoji>",
|
||||
"XDAI": "<emoji document_id=5391325992578988886>💰</emoji>",
|
||||
"RUNE": "<emoji document_id=5391347570494684983>💰</emoji>",
|
||||
"DOT": "<emoji document_id=5375224568208177973>💰</emoji>",
|
||||
}
|
||||
|
||||
_CRYPTO_LIST = {
|
||||
"BTC": "Bitcoin",
|
||||
"ETH": "Ethereum",
|
||||
"XMR": "Monero",
|
||||
"LTC": "Litecoin",
|
||||
"XRP": "XRP",
|
||||
"ADA": "Cardano",
|
||||
"DOGE": "Dogecoin",
|
||||
"SOL": "Solana",
|
||||
"DOT": "Polkadot",
|
||||
"USDT": "Tether",
|
||||
"TON": "Toncoin",
|
||||
"USDC": "USD Coin",
|
||||
"TRX": "TRON",
|
||||
"AVAX": "Avalanche",
|
||||
"BCH": "Bitcoin Cash",
|
||||
"ATOM": "Cosmos",
|
||||
"XLM": "Stellar",
|
||||
"SHIB": "Shiba Inu",
|
||||
"UNI": "Uniswap",
|
||||
"LINK": "Chainlink",
|
||||
"ETC": "Ethereum Classic",
|
||||
"SUI": "Sui",
|
||||
"NEAR": "NEAR Protocol",
|
||||
"VET": "VeChain",
|
||||
"FIL": "Filecoin",
|
||||
"XTZ": "Tezos",
|
||||
"ALGO": "Algorand",
|
||||
"THETA": "Theta Network",
|
||||
"FTM": "Fantom",
|
||||
"XDAI": "xDai",
|
||||
"RUNE": "THORChain",
|
||||
}
|
||||
|
||||
def _fmt_num(v, d=3):
|
||||
p = f"{v:,.{d}f}".replace(",", " ").split(".")
|
||||
i = p[0]
|
||||
d = p[1].rstrip("0") if len(p) > 1 else ""
|
||||
return f"{i},{d}" if d else i
|
||||
|
||||
@loader.tds
|
||||
class FinanceMod(loader.Module):
|
||||
strings = {
|
||||
"name": "FinanceMod",
|
||||
"valute_description": "<кол-во> <код> - курс валюты\n<кол-во> - список",
|
||||
"valute_no_args": (
|
||||
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
|
||||
"<b>Актуально на</b> <i>{}</i>\n\n{}"
|
||||
),
|
||||
"valute_specific": (
|
||||
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
|
||||
"<b>Актуально на</b> <i>{}</i>\n\n{}"
|
||||
),
|
||||
"valute_not_found": "🚫 Валюта {} не найдена",
|
||||
"crypto_description": "<кол-во> <код> - курс крипты\n<кол-во> - список",
|
||||
"crypto_no_args": "💎 <b>Курсы криптовалют</b>\n\n{}",
|
||||
"crypto_specific": "💎 <b>Курс криптовалюты</b>\n\n{}",
|
||||
"crypto_not_found": "🚫 Криптовалюта {} не найдена",
|
||||
"error": "🚫 Ошибка получения данных",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"crypto_currency",
|
||||
"USD",
|
||||
lambda: "Валюта для отображения крипты (USD, RUB, EUR)",
|
||||
validator=loader.validators.Choice(["USD", "RUB", "EUR"])
|
||||
)
|
||||
)
|
||||
|
||||
async def _get_curr_data(self):
|
||||
try:
|
||||
r = requests.get("https://www.cbr.ru/scripts/XML_daily.asp")
|
||||
s = bs4.BeautifulSoup(r.content, 'xml')
|
||||
d = datetime.strptime(s.ValCurs['Date'], "%d.%m.%Y").strftime("%d.%m.%Y")
|
||||
return d, s.find_all('Valute')
|
||||
except:
|
||||
return None, None
|
||||
|
||||
async def _get_rates(self):
|
||||
try:
|
||||
r = requests.get("https://www.cbr.ru/scripts/XML_daily.asp")
|
||||
s = bs4.BeautifulSoup(r.content, 'xml')
|
||||
rt = {'USD': None, 'EUR': None}
|
||||
for v in s.find_all('Valute'):
|
||||
if v.CharCode.text in ['USD', 'EUR']:
|
||||
n = float(v.Nominal.text.replace(',', '.'))
|
||||
vl = float(v.Value.text.replace(',', '.'))
|
||||
rt[v.CharCode.text] = vl / n
|
||||
if rt['USD'] and rt['EUR']:
|
||||
rt['EUR_USD'] = rt['USD'] / rt['EUR']
|
||||
else:
|
||||
rt['EUR_USD'] = None
|
||||
return rt
|
||||
except:
|
||||
return None
|
||||
|
||||
async def _fmt_curr(self, v, a=1):
|
||||
if v.CharCode.text == "XDR":
|
||||
return None
|
||||
c = v.CharCode.text
|
||||
n = v.Name.text
|
||||
v = float(v.Value.text.replace(',', '.')) / float(v.Nominal.text.replace(',', '.'))
|
||||
t = v * a
|
||||
ts = _fmt_num(t, 3)
|
||||
return f"{_FLAGS.get(c, '🏳')} [{a}] {n} ({c}) - {ts} руб."
|
||||
|
||||
async def _get_crypto(self):
|
||||
try:
|
||||
return requests.get("https://api.coinlore.net/api/tickers/").json().get('data', [])
|
||||
except:
|
||||
return None
|
||||
|
||||
async def _fmt_crypto(self, c, a=1):
|
||||
r = await self._get_rates()
|
||||
if not r:
|
||||
return "🚫 Ошибка получения курсов валют"
|
||||
cr = self.config["crypto_currency"]
|
||||
try:
|
||||
p = float(c['price_usd'])
|
||||
except:
|
||||
return "🚫 Ошибка данных криптовалюты"
|
||||
if cr == "RUB":
|
||||
if not r['USD']:
|
||||
return "🚫 Курс USD не найден"
|
||||
p *= r['USD']
|
||||
elif cr == "EUR":
|
||||
if not r['EUR_USD']:
|
||||
return "🚫 Курс EUR/USD не рассчитан"
|
||||
p *= r['EUR_USD']
|
||||
t = p * a
|
||||
ts = _fmt_num(t)
|
||||
s = c['symbol'].upper()
|
||||
e = _CRYPTO_EMOJIS.get(s, "💠")
|
||||
n = _CRYPTO_LIST.get(s, c['name'])
|
||||
cs = {"USD": "$", "RUB": "₽", "EUR": "€"}.get(cr, "$")
|
||||
return f"{e} [{a}] {n} ({s}) - {ts}{cs}"
|
||||
|
||||
@loader.command()
|
||||
async def valutecmd(self, m):
|
||||
"""[count] [usd, eur, ...]"""
|
||||
a = utils.get_args(m)
|
||||
d, v = await self._get_curr_data()
|
||||
if not d or not v:
|
||||
return await utils.answer(m, self.strings["error"])
|
||||
if len(a) == 0:
|
||||
l = []
|
||||
for x in v:
|
||||
if (n := await self._fmt_curr(x)):
|
||||
l.append(n)
|
||||
await utils.answer(m, self.strings["valute_no_args"].format(d, "\n".join(l)))
|
||||
elif len(a) == 1:
|
||||
try:
|
||||
am = float(a[0])
|
||||
l = []
|
||||
for x in v:
|
||||
if (n := await self._fmt_curr(x, am)):
|
||||
l.append(n)
|
||||
await utils.answer(m, self.strings["valute_no_args"].format(d, "\n".join(l)))
|
||||
except:
|
||||
await utils.answer(m, "🚫 Некорректное число")
|
||||
elif len(a) == 2:
|
||||
try:
|
||||
am = float(a[0])
|
||||
c = a[1].upper()
|
||||
for x in v:
|
||||
if x.CharCode.text == c:
|
||||
if (n := await self._fmt_curr(x, am)):
|
||||
return await utils.answer(m, self.strings["valute_specific"].format(d, n))
|
||||
await utils.answer(m, self.strings["valute_not_found"].format(c))
|
||||
except:
|
||||
await utils.answer(m, "🚫 Некорректное число")
|
||||
|
||||
@loader.command()
|
||||
async def cryptocmd(self, m):
|
||||
"""[count] [ton, btc, ...]"""
|
||||
a = utils.get_args(m)
|
||||
c = await self._get_crypto()
|
||||
if not c:
|
||||
return await utils.answer(m, self.strings["error"])
|
||||
try:
|
||||
if len(a) == 0:
|
||||
f = [x for x in c if x['symbol'].upper() in _CRYPTO_LIST]
|
||||
l = []
|
||||
for x in f:
|
||||
if (n := await self._fmt_crypto(x)):
|
||||
l.append(n)
|
||||
await utils.answer(m, self.strings["crypto_no_args"].format("\n".join(l)))
|
||||
elif len(a) == 1:
|
||||
am = float(a[0])
|
||||
f = [x for x in c if x['symbol'].upper() in _CRYPTO_LIST]
|
||||
l = []
|
||||
for x in f:
|
||||
if (n := await self._fmt_crypto(x, am)):
|
||||
l.append(n)
|
||||
await utils.answer(m, self.strings["crypto_no_args"].format("\n".join(l)))
|
||||
elif len(a) == 2:
|
||||
am = float(a[0])
|
||||
t = a[1].upper()
|
||||
f = False
|
||||
for x in c:
|
||||
if x['symbol'].upper() == t:
|
||||
if (n := await self._fmt_crypto(x, am)):
|
||||
f = True
|
||||
await utils.answer(m, self.strings["crypto_specific"].format(n))
|
||||
break
|
||||
if not f:
|
||||
await utils.answer(m, self.strings["crypto_not_found"].format(t))
|
||||
except ValueError:
|
||||
await utils.answer(m, "🚫 Некорректное число")
|
||||
except Exception as e:
|
||||
await utils.answer(m, f"🚫 Ошибка: {str(e)}")
|
||||
14
Ruslan-Isaev/modules/full.txt
Normal file
14
Ruslan-Isaev/modules/full.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
Amnesty
|
||||
DogPic
|
||||
Figlet
|
||||
IrisAutoFarm
|
||||
SFTPUploader
|
||||
ThreadLink
|
||||
GigaGPT
|
||||
GitRepo
|
||||
IrisSup
|
||||
Search
|
||||
TTF
|
||||
youtube-loader
|
||||
Надстрочка
|
||||
TorNodes
|
||||
91
Ruslan-Isaev/modules/gigagpt.py
Normal file
91
Ruslan-Isaev/modules/gigagpt.py
Normal file
@@ -0,0 +1,91 @@
|
||||
version = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP, @matubuntu
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from telethon import events
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
from .. import loader, utils
|
||||
|
||||
bot = ["@GPTChatRBot", 5989217330]
|
||||
bot1 = ["@gigachat_bot", 6218783903]
|
||||
@loader.tds
|
||||
class RUISChatGPTMod(loader.Module):
|
||||
"""ChatGPT 3, Gigachat без API ключа и с контекстом. Бот, который используется для запросов: @Gigachat_bot и @GPTChatRBot. Модуль распространяется по лицензии MIT."""
|
||||
|
||||
strings = {
|
||||
"name": "RUIS-GigaGpt",
|
||||
}
|
||||
|
||||
@loader.command()
|
||||
async def gptdelcmd(self, message):
|
||||
"""- очищает историю переписки с нейросетью(контекст)"""
|
||||
chat = bot[1]
|
||||
text = "/clear"
|
||||
async with message.client.conversation(bot[0]) as conv:
|
||||
response = await conv.send_message(text)
|
||||
response1 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
await utils.answer(message, "✅<b>Контекст успешно очищен!</b>")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
|
||||
@loader.command()
|
||||
async def giga(self, message):
|
||||
"""<текст> - запрос к нейросети GigaChat"""
|
||||
chat = bot1[1]
|
||||
reply = await message.get_reply_message()
|
||||
text = reply.raw_text if reply else message.text[5:]
|
||||
if len(text) < 3:
|
||||
await utils.answer(message, "🚫<b>Ошибка!\nСлишком маленький запрос.</b>")
|
||||
return
|
||||
await utils.answer(message, "🤖<b>Нейросеть обрабатывает ваш запрос...</b>")
|
||||
async with message.client.conversation(bot1[0]) as conv:
|
||||
|
||||
response = await conv.send_message(text)
|
||||
|
||||
response1 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
|
||||
if "💭Ещё чуть-чуть, готовлю ответ" in response1.text:
|
||||
response2 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
await utils.answer(message, f"❓<b>Вопрос:</b> \n{text}\n\n🤖 <b>Ответ нейросети:</b>\n{response2.text}")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
await response2.delete()
|
||||
return
|
||||
else:
|
||||
await utils.answer(message, f"❓<b>Вопрос:</b> \n{text}\n\n🤖 <b>Ответ нейросети:</b>\n{response1.text}")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
|
||||
|
||||
|
||||
@loader.command()
|
||||
async def gigadelcmd(self, message):
|
||||
"""- очищает историю переписки с нейросетью(контекст)"""
|
||||
chat = bot1[1]
|
||||
text = "🆕 Перезапустить диалог"
|
||||
async with message.client.conversation(bot1[0]) as conv:
|
||||
response = await conv.send_message(text)
|
||||
response1 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
await utils.answer(message, "✅<b>Контекст успешно очищен!</b>")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
|
||||
@loader.command()
|
||||
async def gptcmd(self, message):
|
||||
"""<текст> - запрос к нейросети ChatGPT"""
|
||||
chat = bot[1]
|
||||
reply = await message.get_reply_message()
|
||||
text = reply.raw_text if reply else message.text[5:]
|
||||
if len(text) < 3:
|
||||
await utils.answer(message, "🚫<b>Ошибка!\nСлишком маленький запрос.</b>")
|
||||
return
|
||||
await utils.answer(message, "🤖<b>Нейросеть обрабатывает ваш запрос...</b>")
|
||||
async with message.client.conversation(bot[0]) as conv:
|
||||
response = await conv.send_message(text)
|
||||
response1 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
await utils.answer(message, f"❓<b>Вопрос:</b> \n{text}\n\n🤖 <b>Ответ нейросети:</b>\n{response1.text}")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
109
Ruslan-Isaev/modules/gitrepo.py
Normal file
109
Ruslan-Isaev/modules/gitrepo.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# meta developer: @qShad0_bio
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
import aiohttp
|
||||
import asyncio
|
||||
from .. import loader, utils
|
||||
|
||||
async def run_subprocess(command):
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
return process.returncode, stdout.decode(), stderr.decode()
|
||||
|
||||
async def clonerepo(url: str, dir: str):
|
||||
command = ['git', 'clone', url, dir]
|
||||
returncode, stdout, stderr = await run_subprocess(command)
|
||||
return returncode, stderr
|
||||
|
||||
class GitRepoMod(loader.Module):
|
||||
"""Клонирует git репозиторий и отправляет его в виде zip-архива"""
|
||||
|
||||
strings = {'name': 'GitRepo'}
|
||||
|
||||
@loader.command()
|
||||
async def git(self, message):
|
||||
"""Клонирует git репозиторий и отправляет его в виде zip-архива"""
|
||||
if message.reply_to_msg_id:
|
||||
replied_message = await message.get_reply_message()
|
||||
url = replied_message.message.strip()
|
||||
else:
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, "<b>Укажите URL git репозитория.</b>")
|
||||
return
|
||||
url = args.strip()
|
||||
|
||||
await utils.answer(message, "<b>Начинаю загрузку....</b>")
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
repo_dir = os.path.join(temp_dir, "repo")
|
||||
try:
|
||||
repocode, stderr = await clonerepo(url, repo_dir)
|
||||
if repocode != 0:
|
||||
await utils.answer(message, f"<b>Ошибка при клонировании репозитория: {str(stderr)}</b>")
|
||||
return
|
||||
repo_name = os.path.basename(url.split("/").pop().rstrip(".git"))
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"<b>Ошибка при клонировании репозитория: {str(e)}</b>")
|
||||
return
|
||||
|
||||
zip_file = os.path.join(temp_dir, f"{repo_name}.zip")
|
||||
try:
|
||||
with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
for root, _, files in os.walk(repo_dir):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
arcname = os.path.relpath(file_path, repo_dir)
|
||||
zipf.write(file_path, arcname)
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"<b>Ошибка при архивации репозитория: {str(e)}</b>")
|
||||
return
|
||||
|
||||
await utils.answer_file(message, zip_file, f"<b>Репозиторий {repo_name} в виде zip-архива.</b>")
|
||||
await message.delete()
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"<b>Произошла ошибка: {str(e)}</b>")
|
||||
|
||||
@loader.command()
|
||||
async def wget(self, message):
|
||||
"""Сохраняет файл из интернета"""
|
||||
if message.reply_to_msg_id:
|
||||
replied_message = await message.get_reply_message()
|
||||
url = replied_message.message.strip()
|
||||
else:
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, "<b>Укажите URL с файлом</b>")
|
||||
return
|
||||
url = args.strip()
|
||||
|
||||
await utils.answer(message, "<b>Начинаю загрузку....</b>")
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
downloaded_file_path = os.path.join(temp_dir, os.path.basename(url))
|
||||
|
||||
# Скачивание файла
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
if resp.status == 200:
|
||||
with open(downloaded_file_path, 'wb') as f:
|
||||
f.write(await resp.read())
|
||||
else:
|
||||
await utils.answer(message, "<b>Ошибка при скачивании файла.</b>")
|
||||
return
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"<b>Ошибка сохранения: {str(e)}</b>")
|
||||
return
|
||||
|
||||
await utils.answer_file(message, downloaded_file_path, f"<b>Файл {url} успешно сохранен</b>")
|
||||
await message.delete()
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"<b>Произошла ошибка: {str(e)}</b>")
|
||||
51
Ruslan-Isaev/modules/grok.py
Normal file
51
Ruslan-Isaev/modules/grok.py
Normal file
@@ -0,0 +1,51 @@
|
||||
version = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from telethon import events
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
from .. import loader, utils
|
||||
|
||||
bot = "@GrokAI"
|
||||
bot_id = 7828964235
|
||||
|
||||
@loader.tds
|
||||
class GrokAIMod(loader.Module):
|
||||
"""Модуль для нейросети Grok через бота @GrokAI"""
|
||||
|
||||
strings = {
|
||||
"name": "GrokAI",
|
||||
}
|
||||
|
||||
@loader.command()
|
||||
async def grokdelcmd(self, message):
|
||||
"""- очищает историю переписки с нейросетью (контекст)"""
|
||||
chat = bot_id
|
||||
text = "/newchat"
|
||||
async with message.client.conversation(bot) as conv:
|
||||
response = await conv.send_message(text)
|
||||
response1 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
await utils.answer(message, "✅ <b>Контекст успешно очищен!</b>")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
|
||||
|
||||
@loader.command()
|
||||
async def grokcmd(self, message):
|
||||
"""<текст> - запрос к нейросети Grok"""
|
||||
chat = bot_id
|
||||
reply = await message.get_reply_message()
|
||||
text = reply.raw_text if reply else utils.get_args_raw(message)
|
||||
if len(text) < 3:
|
||||
await utils.answer(message, "🚫<b>Ошибка!\nСлишком маленький запрос.</b>")
|
||||
return
|
||||
await utils.answer(message, "🤖<b>Нейросеть обрабатывает ваш запрос...</b>")
|
||||
async with message.client.conversation(bot) as conv:
|
||||
response = await conv.send_message(text)
|
||||
response1 = await conv.wait_event(events.NewMessage(incoming=True, from_users=chat))
|
||||
await utils.answer(message, f"❓<b>Вопрос:</b> \n{text}\n\n🤖 <b>Ответ нейросети:</b>\n{response1.text}")
|
||||
await response.delete()
|
||||
await response1.delete()
|
||||
168
Ruslan-Isaev/modules/irissup
Normal file
168
Ruslan-Isaev/modules/irissup
Normal file
@@ -0,0 +1,168 @@
|
||||
version = (2, 2, 8)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
@loader.tds
|
||||
class IrisSupMod(loader.Module):
|
||||
"""Саппорт для лс"""
|
||||
|
||||
strings = {
|
||||
"name": "irissup",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
self.myid = (await client.get_me()).id
|
||||
self.iris = 5443619563
|
||||
|
||||
async def message_q(
|
||||
self,
|
||||
text: str,
|
||||
user_id: int,
|
||||
mark_read: bool = False,
|
||||
delete: bool = False,
|
||||
):
|
||||
"""Отправляет сообщение и возращает ответ"""
|
||||
async with self.client.conversation(user_id) as conv:
|
||||
msg = await conv.send_message(text)
|
||||
response = await conv.get_response()
|
||||
if mark_read:
|
||||
await conv.mark_read()
|
||||
|
||||
if delete:
|
||||
await msg.delete()
|
||||
await response.delete()
|
||||
|
||||
return response
|
||||
|
||||
@loader.command()
|
||||
async def команды(self, message):
|
||||
"""Команды Iris Support Bot"""
|
||||
ihelp = (
|
||||
"Команды Iris Support Bot: https://teletype.in/@iris_cm/isb_commands"
|
||||
)
|
||||
await utils.answer(message, ihelp)
|
||||
|
||||
|
||||
@loader.command()
|
||||
async def перевод(self, message):
|
||||
"""перевод текста с помощью Iris Support Bot"""
|
||||
bot = "@IrisSupportBot"
|
||||
if len(message.text) < 11:
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
text = reply.raw_text
|
||||
text = f".переведи \n{text}"
|
||||
givs = await self.message_q(text, bot, mark_read=True, delete=True)
|
||||
await utils.answer(message, givs)
|
||||
return
|
||||
except:
|
||||
await utils.answer(message, "Где текст?")
|
||||
return
|
||||
text = f".переведи {message.text[9:]}"
|
||||
givs = await self.message_q(
|
||||
text,
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True,
|
||||
)
|
||||
await utils.answer(message, givs.text)
|
||||
|
||||
@loader.command()
|
||||
async def раскладка(self, message):
|
||||
"""меняет раскладку текста с помощью Iris Support Bot"""
|
||||
bot = "@IrisSupportBot"
|
||||
if len(message.text) < 15:
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
text = reply.raw_text
|
||||
text = f".раскладка {text}"
|
||||
givs = await self.message_q(text, bot, mark_read=True, delete=True)
|
||||
await utils.answer(message, givs)
|
||||
return
|
||||
except:
|
||||
await utils.answer(message, "Где текст?")
|
||||
return
|
||||
text = f".раскладка {message.text[11:]}"
|
||||
givs = await self.message_q(
|
||||
text,
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True,
|
||||
)
|
||||
await utils.answer(message, givs.text)
|
||||
|
||||
@loader.command()
|
||||
async def длина(self, message):
|
||||
"""перевод текста с помощью Iris Support Bot"""
|
||||
bot = "@IrisSupportBot"
|
||||
if len(message.text) < 10:
|
||||
try:
|
||||
reply = await message.get_reply_message()
|
||||
text = reply.raw_text
|
||||
text = f".длина {text}"
|
||||
givs = await self.message_q(text, bot, mark_read=True, delete=True)
|
||||
await utils.answer(message, givs)
|
||||
return
|
||||
except:
|
||||
await utils.answer(message, "Где текст?")
|
||||
return
|
||||
text = f".длина {message.text[7:]}"
|
||||
givs = await self.message_q(
|
||||
text,
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True,
|
||||
)
|
||||
await utils.answer(message, givs.text)
|
||||
|
||||
@loader.command()
|
||||
async def сап(self, message):
|
||||
"""передает введенную команду в Iris Support Bot"""
|
||||
bot = "@IrisSupportBot"
|
||||
if len(message.text) < 6:
|
||||
await utils.answer(message, "Где текст?")
|
||||
return
|
||||
text = f".{message.text[4:]}"
|
||||
offtoptext = """⚠️ <b>Внимание! В этой беседе запрещён оффтоп.</b>
|
||||
<i>Если вы хотите поболтать или обсудить что-то, то переходите в </i><a href="https://t.me/iris_talk"><i>оффтоп-чатик</i></a><i>!</i>
|
||||
|
||||
ℹ️ <b>Оффтоп</b> — <u>сообщения не по теме чата</u>. Этот чат только по вопросам <a href="https://t.me/iris_cm">Iris | Чат-менеджера</a>.
|
||||
|
||||
💬 Если вы проигнорируете это сообщение, то модераторы в полном праве могут выдать вам наказание или удалить из чата!
|
||||
"""
|
||||
if message.text[4:] == " оффтоп" or message.text[4:] == "оффтоп":
|
||||
await self.inline.form(
|
||||
text=offtoptext,
|
||||
message=message,
|
||||
reply_markup = [
|
||||
[
|
||||
{"text": "💬 В оффтоп-чат", "url": f"https://t.me/iris_talk"}, {"text": "🧠 Стать умнее", "url": f"https://teletype.in/@iris_cm/rules"}
|
||||
],
|
||||
])
|
||||
return
|
||||
givs = await self.message_q(
|
||||
text,
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True,
|
||||
)
|
||||
await utils.answer(message, givs.text)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
Ruslan-Isaev/modules/photos/banner.jpg
Normal file
BIN
Ruslan-Isaev/modules/photos/banner.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 489 KiB |
181
Ruslan-Isaev/modules/search.py
Normal file
181
Ruslan-Isaev/modules/search.py
Normal file
@@ -0,0 +1,181 @@
|
||||
# meta developer: @RUIS_VlP
|
||||
# requires: requests pillow
|
||||
|
||||
from telethon import TelegramClient, events
|
||||
from .. import loader, utils
|
||||
from urllib.parse import quote
|
||||
import requests
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from ..inline.types import InlineQuery
|
||||
import json
|
||||
|
||||
@loader.tds
|
||||
class SearchMod(loader.Module):
|
||||
"""🌐 Internet search module"""
|
||||
|
||||
strings = {
|
||||
"name": "search",
|
||||
"picsearch": "🔍 Image search",
|
||||
"no_photo": "🚫 Reply to an image to search",
|
||||
"upload_error": "❌ Image upload error",
|
||||
"search_title": "🌐 Search Internet",
|
||||
"search_description": "Search the web using different engines",
|
||||
"failed_download": "❌ Failed to download image: {}",
|
||||
"image_processing_error": "❌ Image processing error",
|
||||
"google_images": "Google Images",
|
||||
"yandex_images": "Yandex Images",
|
||||
"loading" : "⚙️ Loading...",
|
||||
"search_query_prompt": "🔍 Please specify search query"
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"picsearch": "🔍 Поиск по изображению",
|
||||
"no_photo": "🚫 Ответьте на изображение для поиска",
|
||||
"upload_error": "❌ Ошибка загрузки изображения",
|
||||
"search_title": "🌐 Поиск в интернете",
|
||||
"loading" : "⚙️ Загрузка...",
|
||||
"search_description": "Поиск в сети с использованием разных систем",
|
||||
"failed_download": "❌ Не удалось загрузить изображение: {}",
|
||||
"image_processing_error": "❌ Ошибка обработки изображения",
|
||||
"google_images": "Google Картинки",
|
||||
"yandex_images": "Яндекс.Картинки",
|
||||
"search_query_prompt": "🔍 Укажите запрос для поиска"
|
||||
}
|
||||
|
||||
async def upload_to_x0(self, image_bytes: bytes) -> str:
|
||||
"""Upload image to x0.at"""
|
||||
try:
|
||||
files = {'file': image_bytes}
|
||||
response = requests.post('https://x0.at/', files=files)
|
||||
response.raise_for_status()
|
||||
return response.text.strip() # x0.at возвращает прямую ссылку
|
||||
except Exception as e:
|
||||
print(f"x0.at upload error: {e}")
|
||||
return None
|
||||
|
||||
async def yandex_image_search(self, image_bytes: bytes) -> str:
|
||||
"""Search image via Yandex"""
|
||||
try:
|
||||
searchUrl = "https://yandex.ru/images/search"
|
||||
files = {"upfile": ("blob", image_bytes, "image/jpeg")}
|
||||
params = {
|
||||
"rpt": "imageview",
|
||||
"format": "json",
|
||||
"request": '{"blocks":[{"block":"b-page_type_search-by-image__link"}]}',
|
||||
}
|
||||
response = requests.post(searchUrl, params=params, files=files)
|
||||
if response.ok:
|
||||
query_string = json.loads(response.content)["blocks"][0]["params"]["url"]
|
||||
return searchUrl + "?" + query_string
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Yandex search error: {e}")
|
||||
return None
|
||||
|
||||
@loader.command()
|
||||
async def picsearchcmd(self, message):
|
||||
"""<reply to image> - 🔍 Reverse image search"""
|
||||
if not message.is_reply:
|
||||
await utils.answer(message, self.strings["no_photo"])
|
||||
return
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
if not (reply.photo or (reply.document and reply.document.mime_type.startswith('image/'))):
|
||||
await utils.answer(message, self.strings["no_photo"])
|
||||
return
|
||||
|
||||
# Download image
|
||||
try:
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
image = await reply.download_media(bytes)
|
||||
except Exception as e:
|
||||
await utils.answer(message, self.strings["failed_download"].format(e))
|
||||
return
|
||||
|
||||
# Convert to JPEG
|
||||
try:
|
||||
with BytesIO(image) as img_buffer:
|
||||
with Image.open(img_buffer) as img:
|
||||
jpeg_buffer = BytesIO()
|
||||
img.convert('RGB').save(jpeg_buffer, 'JPEG')
|
||||
image_bytes = jpeg_buffer.getvalue()
|
||||
except Exception as e:
|
||||
print(f"Image processing error: {e}")
|
||||
await utils.answer(message, self.strings["image_processing_error"])
|
||||
return
|
||||
|
||||
# Upload to x0.at
|
||||
url = await self.upload_to_x0(image_bytes)
|
||||
if not url:
|
||||
await utils.answer(message, self.strings["upload_error"])
|
||||
return
|
||||
|
||||
# Create search buttons
|
||||
encoded_url = quote(url, safe='')
|
||||
yandex_url = await self.yandex_image_search(image_bytes)
|
||||
|
||||
await self.inline.form(
|
||||
text=self.strings["picsearch"],
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{"text": self.strings["google_images"], "url": f"https://lens.google.com/uploadbyurl?url={encoded_url}"},
|
||||
{"text": self.strings["yandex_images"], "url": yandex_url if yandex_url else f"https://yandex.ru/images/search?rpt=imageview&url={encoded_url}"}
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def searchcmd(self, message):
|
||||
"""<text> / <reply> - 🌐 Search Internet"""
|
||||
if not message.is_reply:
|
||||
if len(message.text) < 8:
|
||||
await utils.answer(message, self.strings["search_query_prompt"])
|
||||
return
|
||||
reply_text = utils.get_args_raw(message)
|
||||
else:
|
||||
replied_message = await message.get_reply_message()
|
||||
reply_text = replied_message.text
|
||||
|
||||
encoded_query = quote(reply_text)
|
||||
await self.inline.form(
|
||||
text=self.strings["search_title"],
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{"text": "Google", "url": f"https://www.google.com/search?q={encoded_query}"},
|
||||
{"text": "Yandex", "url": f"https://yandex.ru/search/?text={encoded_query}"}
|
||||
],
|
||||
[
|
||||
{"text": "Duckduckgo", "url": f"https://duckduckgo.com/?q={encoded_query}"}
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
@loader.inline_handler()
|
||||
async def search(self, query: InlineQuery):
|
||||
"""<text> - 🌐 Search Internet"""
|
||||
search_text = query.query.strip()
|
||||
if not search_text:
|
||||
return
|
||||
|
||||
encoded_query = quote(search_text)
|
||||
buttons = [
|
||||
[
|
||||
{"text": "Google", "url": f"https://www.google.com/search?q={encoded_query}"},
|
||||
{"text": "Yandex", "url": f"https://yandex.ru/search/?text={encoded_query}"}
|
||||
],
|
||||
[
|
||||
{"text": "Duckduckgo", "url": f"https://duckduckgo.com/?q={encoded_query}"}
|
||||
]
|
||||
]
|
||||
|
||||
return {
|
||||
"title": self.strings["search_title"],
|
||||
"description": self.strings["search_description"],
|
||||
"thumb": "https://0x0.st/XlHF.png",
|
||||
"message": self.strings["search_title"],
|
||||
"reply_markup": buttons
|
||||
}
|
||||
71
Ruslan-Isaev/modules/spellchecker.py
Normal file
71
Ruslan-Isaev/modules/spellchecker.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# meta developer: @RUIS_VlP
|
||||
from .. import loader, utils
|
||||
from telethon.tl.types import Message
|
||||
import aiohttp
|
||||
|
||||
@loader.tds
|
||||
class SpellerMod(loader.Module):
|
||||
strings = {
|
||||
"name": "Speller",
|
||||
"no_text": "❌ <b>Укажите текст для проверки или используйте реплай.</b>",
|
||||
"no_reply": "❌ <b>Это не реплай на сообщение.</b>",
|
||||
"api_error": "❌ <b>Ошибка при обращении к API Яндекс.Спеллера.</b>"
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.db = db
|
||||
self._client = client
|
||||
|
||||
async def spellcheckcmd(self, message: Message):
|
||||
"""
|
||||
Проверить орфографию текста.
|
||||
|
||||
Использование:
|
||||
.spellcheck [текст] - проверка указанного текста.
|
||||
.spellcheck -r - проверка текста из реплая.
|
||||
"""
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
# Если используется флаг -r, проверяем текст из реплая
|
||||
if "-r" in args or "-r" in message.text:
|
||||
if not reply or not reply.text:
|
||||
return await utils.answer(message, self.strings["no_reply"])
|
||||
text_to_check = reply.text
|
||||
else:
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings["no_text"])
|
||||
text_to_check = args
|
||||
|
||||
corrected_text = await self.correct_text(text_to_check)
|
||||
await utils.answer(message, corrected_text)
|
||||
|
||||
async def correct_text(self, text: str) -> str:
|
||||
"""Исправление текста через API Яндекс.Спеллера"""
|
||||
url = "https://speller.yandex.net/services/spellservice.json/checkText"
|
||||
params = {
|
||||
"text": text,
|
||||
"lang": "ru",
|
||||
"options": 518
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, data=params) as response:
|
||||
if response.status != 200:
|
||||
return text
|
||||
|
||||
data = await response.json()
|
||||
if not data:
|
||||
return text
|
||||
|
||||
corrected_text = text
|
||||
for error in reversed(data):
|
||||
start = error["pos"]
|
||||
end = start + error["len"]
|
||||
corrected_text = corrected_text[:start] + error["s"][0] + corrected_text[end:]
|
||||
|
||||
return corrected_text
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обращении к API: {e}")
|
||||
return text
|
||||
207
Ruslan-Isaev/modules/ssh.py
Normal file
207
Ruslan-Isaev/modules/ssh.py
Normal file
@@ -0,0 +1,207 @@
|
||||
version = (1, 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 os
|
||||
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()
|
||||
|
||||
@loader.tds
|
||||
class SSHMod(loader.Module):
|
||||
"""SSH module for uploading files and executing commands"""
|
||||
|
||||
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_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>",
|
||||
}
|
||||
|
||||
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(),
|
||||
),
|
||||
)
|
||||
|
||||
@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(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
|
||||
|
||||
# Выполняем команду на SSH сервере
|
||||
exit_code, output, error = execute_ssh_command(host, port, username, password, command)
|
||||
|
||||
# Формируем ответ в зависимости от наличия вывода и ошибок
|
||||
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>"
|
||||
|
||||
await utils.answer(message, response)
|
||||
110
Ruslan-Isaev/modules/tornodes.py
Normal file
110
Ruslan-Isaev/modules/tornodes.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# meta developer: @matubuntu
|
||||
# requires: qrcode httpx
|
||||
import os
|
||||
import re
|
||||
import qrcode
|
||||
import httpx
|
||||
from .. import loader, utils
|
||||
|
||||
async def fetch_bridges(brigde, url):
|
||||
url = f"{url}?transport={brigde}"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return [item['bridge'] for item in data]
|
||||
|
||||
async def create_qr(data, filename="qrcode.png", version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4):
|
||||
qr = qrcode.QRCode(
|
||||
version=version,
|
||||
error_correction=error_correction,
|
||||
box_size=box_size,
|
||||
border=border,
|
||||
)
|
||||
qr.add_data(f"{str(data)}")
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
img.save(filename)
|
||||
return None
|
||||
|
||||
@loader.tds
|
||||
class TorNodes(loader.Module):
|
||||
"""Получает список мостов для сети Tor"""
|
||||
|
||||
strings = {"name": "TorNodes"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"EnabledSubAPI",
|
||||
True,
|
||||
lambda: "Что то вроде Proxy, на случай блокировки torproject.org. Подробнее: https://github.com/Neotele-Studio/GetTorBridgesSubAPI",
|
||||
validator=loader.validators.Boolean()
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"LinkSubAPI",
|
||||
"https://ruishikka.serv00.net/tor.php",
|
||||
lambda: "Ссылка на ваш SubAPI",
|
||||
validator=loader.validators.String()
|
||||
)
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def bridge(self, message):
|
||||
"""obfs4 / webtunnel - получить мосты для сети Tor"""
|
||||
transport_type = None
|
||||
if message.is_reply:
|
||||
reply_message = await message.get_reply_message()
|
||||
transport_type = reply_message.raw_text.lower().strip() # Получаем транспорт из ответа
|
||||
else:
|
||||
args = utils.get_args_raw(message)
|
||||
if args:
|
||||
transport_type = args.lower().strip() # Получаем транспорт из аргументов команды
|
||||
|
||||
if transport_type == "obfs4":
|
||||
url = 'https://bridges.torproject.org/bridges?transport=obfs4'
|
||||
pattern = r'obfs4.*?cert=[a-zA-Z0-9+/]+={0,2}.*?iat-mode=\d+'
|
||||
elif transport_type == "webtunnel":
|
||||
url = 'https://bridges.torproject.org/bridges?transport=webtunnel'
|
||||
pattern = r'webtunnel \[.*?\]:\d+ \S+ url=\S+ ver=\d+\.\d+' # Новое регулярное выражение
|
||||
else:
|
||||
await utils.answer(message, "Неправильный тип транспорта. Используйте 'obfs4' или 'webtunnel'.")
|
||||
return
|
||||
|
||||
try:
|
||||
if self.config["EnabledSubAPI"] == False:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
bridges = re.findall(pattern, response.text, flags=re.MULTILINE)
|
||||
|
||||
if not bridges:
|
||||
raise ValueError(f"Не найдено ни одной строки, содержащей '{transport_type}'.")
|
||||
|
||||
await create_qr(bridges)
|
||||
|
||||
await utils.answer_file(message, "qrcode.png", caption=f"<code>{bridges[0]}\n{bridges[1]}</code>")
|
||||
|
||||
os.remove("qrcode.png")
|
||||
|
||||
else:
|
||||
bridges = await fetch_bridges(transport_type, self.config["LinkSubAPI"])
|
||||
if not bridges:
|
||||
raise ValueError(f"Не найдено ни одной строки, содержащей '{transport_type}'.")
|
||||
|
||||
await create_qr(bridges)
|
||||
|
||||
await utils.answer_file(message, "qrcode.png", caption=f"<code>{bridges[0]}\n{bridges[1]}</code>")
|
||||
|
||||
os.remove("qrcode.png")
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, f'Произошла ошибка при получении мостов: {e}')
|
||||
|
||||
@loader.command()
|
||||
async def tncfg(self, message):
|
||||
"""- открыть конфигурацию модуля"""
|
||||
name = self.strings("name")
|
||||
await self.allmodules.commands["config"](
|
||||
await utils.answer(message, f"{self.get_prefix()}config {name}")
|
||||
)
|
||||
82
Ruslan-Isaev/modules/ttf.py
Normal file
82
Ruslan-Isaev/modules/ttf.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# meta developer: @RUIS_VlP, @matubuntu
|
||||
import os
|
||||
from telethon import TelegramClient, events, sync, utils
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
@loader.tds
|
||||
class TTFMod(loader.Module):
|
||||
"""Создает текстовый файл, отправляет его в Telegram, а затем удаляет."""
|
||||
|
||||
strings = {
|
||||
"name": "TTF",
|
||||
}
|
||||
|
||||
def init(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
@loader.command()
|
||||
async def ttf(self, message):
|
||||
"""
|
||||
Создает текстовый файл с заданным именем и расширением,
|
||||
записывает в него текст, отправляет его в Telegram и удаляет с диска.
|
||||
|
||||
Пример:
|
||||
.ttf название.txt
|
||||
Текст для файла/<reply>
|
||||
"""
|
||||
args = utils.get_args_raw(message).split("\n")
|
||||
if len(args) < 1:
|
||||
await message.edit("Недостаточно аргументов. Используйте: .ttf название.txt\nТекст для файла")
|
||||
return
|
||||
|
||||
filename = args[0].strip()
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
txt = reply.raw_text
|
||||
if txt:
|
||||
text = txt
|
||||
else:
|
||||
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)
|
||||
|
||||
@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)
|
||||
|
||||
201
Ruslan-Isaev/modules/whois.py
Normal file
201
Ruslan-Isaev/modules/whois.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""whois module for hikka userbot
|
||||
Copyright (C) 2025 Ruslan Isaev
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/."""
|
||||
|
||||
__version__ = (2, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
# при поддержке @hikka_mods
|
||||
|
||||
import json
|
||||
import aiohttp
|
||||
from .. import loader, utils
|
||||
import asyncio
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
async def clean_domain(value: str) -> List[str]:
|
||||
# Убираем протокол, порт, путь
|
||||
value = re.sub(r'^(https?://)?', '', value)
|
||||
value = value.split('/')[0]
|
||||
value = value.split(':')[0]
|
||||
|
||||
return value
|
||||
|
||||
async def ipcheck(value: str) -> str:
|
||||
# Проверка IPv4
|
||||
parts = value.split('.')
|
||||
if len(parts) == 4 and all(part.isdigit() and 0 <= int(part) <= 255 for part in parts):
|
||||
return "ip"
|
||||
|
||||
# Проверка IPv6
|
||||
ipv6_pattern = re.compile(r'^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$')
|
||||
if ipv6_pattern.match(value):
|
||||
return "ip"
|
||||
|
||||
return "domain"
|
||||
|
||||
async def get_whois(identifier, API_KEY: str) -> dict:
|
||||
url = "https://api.jsonwhoisapi.com/v1/whois"
|
||||
headers = {
|
||||
"Authorization": API_KEY
|
||||
}
|
||||
params = {
|
||||
"identifier": identifier
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers, params=params) as resp:
|
||||
resp.raise_for_status()
|
||||
response = await resp.json()
|
||||
return response
|
||||
|
||||
async def fetch_dns_record(session, domain, record_type):
|
||||
url = "https://unfiltered.adguard-dns.com/resolve"
|
||||
headers = {"accept": "application/dns-json"}
|
||||
params = {"name": domain, "type": record_type}
|
||||
|
||||
async with session.get(url, headers=headers, params=params) as resp:
|
||||
text = await resp.text()
|
||||
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
if not isinstance(data, dict):
|
||||
return []
|
||||
|
||||
answers = data.get("Answer")
|
||||
if not answers:
|
||||
return []
|
||||
|
||||
return [
|
||||
ans["data"]
|
||||
for ans in answers
|
||||
if ans.get("type") == (1 if record_type == "A" else 28)
|
||||
]
|
||||
|
||||
async def get_ips(domain):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
ipv4_task = fetch_dns_record(session, domain, "A")
|
||||
ipv6_task = fetch_dns_record(session, domain, "AAAA")
|
||||
ipv4, ipv6 = await asyncio.gather(ipv4_task, ipv6_task)
|
||||
return [ipv4, ipv6]
|
||||
|
||||
async def json2text(data: dict, ips, check) -> str:
|
||||
def get(value):
|
||||
return str(value) if value not in (None, '', [], {}) else 'Неизвестно'
|
||||
|
||||
status = data.get("status", [])
|
||||
status_str = ', '.join(status) if isinstance(status, list) else get(status)
|
||||
|
||||
nameservers = data.get("nameservers", []) or ['Неизвестнo']
|
||||
registered = 'Да' if data.get('registered') else 'Нет'
|
||||
if registered == "Нет":
|
||||
return f"<emoji document_id=5224450179368767019>🌎</emoji><b>Домен:</b> <code>{(get(data.get('name'))).encode('ascii').decode('idna')}</code>\n\n<emoji document_id=4985637404867036136>🖥</emoji> <b>Домен свободен</b>"
|
||||
|
||||
admin = (data.get("contacts", {}).get("admin") or [{}])[0]
|
||||
registrar = data.get("registrar", {})
|
||||
|
||||
lines = [
|
||||
f"<emoji document_id=5224450179368767019>🌎</emoji><b>Домен:</b> <code>{(get(data.get('name'))).encode('ascii').decode('idna')}</code>",]
|
||||
if len(ips) > 0:
|
||||
lines += ["<emoji document_id=4992466832364405778>🖥</emoji> <b>IP адреса:</b>"]
|
||||
lines += [f" • <code>{ip}</code>" for ip in ips[0]]
|
||||
lines += [f" • <code>{ip}</code>" for ip in ips[1]]
|
||||
else:
|
||||
pass
|
||||
|
||||
lines += [
|
||||
"",
|
||||
f"<emoji document_id=5274055917766202507>🗓</emoji> <b>Дата регистрации:</b> <code>{get(data.get('created'))}</code>",
|
||||
f"♻️ <b>Изменено:</b> <code>{get(data.get('changed'))}</code>",
|
||||
f"<emoji document_id=5325583469344989152>⏳</emoji><b>Истекает:</b> <code>{get(data.get('expires'))}</code>",
|
||||
f"<emoji document_id=5206607081334906820>✔️</emoji> <b>Зарегистрирован:</b> <code>{registered}</code>",
|
||||
f"<emoji document_id=5231200819986047254>📊</emoji> <b>Статус:</b> <code>{status_str}</code>",
|
||||
"",]
|
||||
|
||||
if check == "domain":
|
||||
lines += [
|
||||
f"<emoji document_id=4985545282113503960>🖥</emoji> <b>DNS-серверы:</b>",
|
||||
]
|
||||
lines += [f" • <code>{ns}</code>" for ns in nameservers]
|
||||
|
||||
lines += [
|
||||
"",
|
||||
"<emoji document_id=5936110055404342764>👤</emoji> <b>Админ-контакт:</b>",
|
||||
f" • Имя: <code>{get(admin.get('name'))}</code>",
|
||||
f" • Email: <code>{get(admin.get('email'))}</code>",
|
||||
f" • Организация: <code>{get(admin.get('organization'))}</code>",
|
||||
f" • Страна: <code>{get(admin.get('country'))}</code>",
|
||||
"",]
|
||||
|
||||
if check == "domain":
|
||||
lines += [
|
||||
"<emoji document_id=5445353829304387411>💳</emoji> <b>Регистратор:</b>",
|
||||
f" • ID: <code>{get(registrar.get('id'))}</code>",
|
||||
f" • Название: <code>{get(registrar.get('name'))}</code>",
|
||||
f" • Email: <code>{get(registrar.get('email'))}</code>",
|
||||
f" • Сайт: <code>{get(registrar.get('url'))}</code>",
|
||||
f" • Телефон: <code>{get(registrar.get('phone'))}</code>",
|
||||
]
|
||||
|
||||
return '\n'.join(line for line in lines if '<code>Неизвестно</code>' not in line)
|
||||
|
||||
@loader.tds
|
||||
class WhoisMod(loader.Module):
|
||||
"""Модуль для получения информации о домене или ip адресе"""
|
||||
|
||||
strings = {"name": "Whois"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"api_key",
|
||||
"None",
|
||||
lambda: "API ключ с сайта https://jsonwhoisapi.com/",
|
||||
validator=loader.validators.String(),
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def whois(self, message):
|
||||
"""<домен> - получить информацию о домене или IP"""
|
||||
api_key = self.config["api_key"]
|
||||
if api_key == "None":
|
||||
await utils.answer(message, '❌ <b>Не указан API ключ! Получите его на</b> jsonwhoisapi.com <b>и вставьте в config</b> (<code>.config Whois</code>)')
|
||||
return
|
||||
|
||||
domain = ((utils.get_args_raw(message)).split()[0]).encode('idna').decode('ascii')
|
||||
if not domain:
|
||||
await utils.answer(message, "❌ <b>Вы не указали домен!</b>")
|
||||
return
|
||||
|
||||
try:
|
||||
check = await ipcheck(domain)
|
||||
clean = await clean_domain(domain)
|
||||
if check == "ip":
|
||||
info = await get_whois(clean, api_key)
|
||||
text = await json2text(info, [], "ip")
|
||||
await utils.answer(message, text)
|
||||
return
|
||||
whois = get_whois(clean, api_key)
|
||||
ips = get_ips(clean)
|
||||
info, ips = await asyncio.gather(whois, ips)
|
||||
text = await json2text(info, ips, "domain")
|
||||
await utils.answer(message, text)
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"❌ <b>Ошибка!</b>\n\n<code>{e}</code>")
|
||||
128
Ruslan-Isaev/modules/youtube-loader.py
Normal file
128
Ruslan-Isaev/modules/youtube-loader.py
Normal file
@@ -0,0 +1,128 @@
|
||||
__version__ = (1, 1, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP, @RoKrz
|
||||
# requires: yt_dlp
|
||||
|
||||
import yt_dlp
|
||||
import uuid
|
||||
import os
|
||||
import re
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
def extract_youtube_link(text):
|
||||
if not text:
|
||||
return None
|
||||
match = re.search(r"(https?://)?(www\.)?(youtube\.com|youtu\.be)/[^\s]+", text)
|
||||
return match.group(0) if match else None
|
||||
|
||||
|
||||
async def download_video(url):
|
||||
output_dir = utils.get_base_dir()
|
||||
random_uuid = str(uuid.uuid4())
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
ydl_opts = {
|
||||
'format': 'best',
|
||||
'outtmpl': os.path.join(output_dir, f'{random_uuid}.%(ext)s'),
|
||||
'noplaylist': True,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info_dict = ydl.extract_info(url, download=True)
|
||||
video_ext = info_dict.get('ext', None)
|
||||
file_path = os.path.join(output_dir, f"{random_uuid}.{video_ext}")
|
||||
title = info_dict.get('title', None)
|
||||
|
||||
return file_path, title
|
||||
|
||||
|
||||
def convert_markdown_to_html(template: str, link: str) -> str:
|
||||
return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', template).replace("{link}", link)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class YouTube_DLDMod(loader.Module):
|
||||
"""Помогает скачивать видео с YouTube"""
|
||||
|
||||
strings = {
|
||||
"name": "YouTube-DLD",
|
||||
"no_link": "❌ <b>Пожалуйста, укажите ссылку на YouTube либо ответьте на сообщение с ней.</b>",
|
||||
"default_downloading": "📥 <b>Начинаю загрузку видео.</b>\n\nℹ️ <code>Это может занять до 5 минут, в зависимости от длины и качества видео.</code>",
|
||||
"default_error": "❌ <b>Ошибка!</b>\n\n<code>{}</code>",
|
||||
"default_response": "🎥 Вот [ваше видео]({link})!\n\n<code>{title}</code>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"show_link",
|
||||
True,
|
||||
"Показывать ссылку в сообщении?",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"downloading_text",
|
||||
self.strings["default_downloading"],
|
||||
"Текст во время загрузки"
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"error_text",
|
||||
self.strings["default_error"],
|
||||
"Текст ошибки. (используй {} для ошибки)"
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"response_text",
|
||||
self.strings["default_response"],
|
||||
"Ответ после загрузки. (используй {link} для ссылки и {title} для названия видео)"
|
||||
),
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def dlvideo(self, message):
|
||||
"""<ссылка> или ответ на сообщение со ссылкой — скачивает видео с YouTube"""
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
reply = await message.get_reply_message()
|
||||
|
||||
link = extract_youtube_link(args) if args else None
|
||||
if not link and reply:
|
||||
link = extract_youtube_link(reply.raw_text)
|
||||
|
||||
if not link:
|
||||
await utils.answer(message, self.strings["no_link"])
|
||||
return
|
||||
|
||||
await utils.answer(message, self.config["downloading_text"])
|
||||
|
||||
try:
|
||||
video, title = await download_video(link)
|
||||
|
||||
if self.config["show_link"]:
|
||||
caption_template = self.config["response_text"]
|
||||
caption = convert_markdown_to_html(caption_template, link)
|
||||
caption = caption.replace("{title}", title or "")
|
||||
else:
|
||||
caption = title or "Готово!"
|
||||
|
||||
await utils.answer_file(
|
||||
message,
|
||||
video,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
try:
|
||||
await message.delete()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.remove(video)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
error_msg = self.config["error_text"].format(e)
|
||||
await utils.answer(message, error_msg)
|
||||
try:
|
||||
os.remove(video)
|
||||
except:
|
||||
pass
|
||||
85
Ruslan-Isaev/modules/Надстрочка.py
Normal file
85
Ruslan-Isaev/modules/Надстрочка.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import random
|
||||
from telethon import functions
|
||||
from telethon.tl.types import Message
|
||||
from .. import loader, utils
|
||||
|
||||
def replace_text(input_text):
|
||||
# Задаем соответствия для замен
|
||||
upper_mapping = {
|
||||
'Q': 'ᵠ', 'W': 'ᵂ', 'E': 'ᴱ', 'R': 'ᴿ', 'T': 'ᵀ',
|
||||
'Y': 'ʸ', 'U': 'ᵁ', 'I': 'ᴵ', 'O': 'ᴼ', 'P': 'ᴾ',
|
||||
'A': 'ᴬ', 'S': 'ˢ', 'D': 'ᴰ', 'F': 'ᶠ', 'G': 'ᴳ',
|
||||
'H': 'ᴴ', 'J': 'ᴶ', 'K': 'ᴷ', 'L': 'ᴸ', 'Z': 'ᶻ',
|
||||
'X': 'ˣ', 'C': 'ᶜ', 'V': 'ⱽ', 'B': 'ᴮ', 'N': 'ᴺ',
|
||||
'M': 'ᴹ'
|
||||
}
|
||||
|
||||
lower_mapping = {
|
||||
'q': 'ᵠ', 'w': 'ʷ', 'e': 'ᵉ', 'r': 'ʳ', 't': 'ᵗ',
|
||||
'y': 'ʸ', 'u': 'ᵘ', 'i': 'ᶦ', 'o': 'ᵒ', 'p': 'ᵖ',
|
||||
'a': 'ᵃ', 's': 'ˢ', 'd': 'ᵈ', 'f': 'ᶠ', 'g': 'ᵍ',
|
||||
'h': 'ʰ', 'j': 'ʲ', 'k': 'ᵏ', 'l': 'ˡ', 'z': 'ᶻ',
|
||||
'x': 'ˣ', 'c': 'ᶜ', 'v': 'ᵛ', 'b': 'ᵇ', 'n': 'ⁿ',
|
||||
'm': 'ᵐ'
|
||||
}
|
||||
|
||||
digit_mapping = {
|
||||
'0': '⁰',
|
||||
'1': '¹',
|
||||
'2': '²',
|
||||
'3': '³',
|
||||
'4': '⁴',
|
||||
'5': '⁵',
|
||||
'6': '⁶',
|
||||
'7': '⁷',
|
||||
'8': '⁸',
|
||||
'9': '⁹'
|
||||
}
|
||||
|
||||
special_mapping = {
|
||||
'+': '⁺',
|
||||
'=': '⁼',
|
||||
'!': 'ᵎ',
|
||||
'(' : '⁽',
|
||||
')' : '⁾',
|
||||
'-' : '⁻',
|
||||
' ' : ' '
|
||||
}
|
||||
|
||||
# Инициализируем переменную для результата
|
||||
result = ""
|
||||
|
||||
# Проходим по каждому символу входного текста
|
||||
for char in input_text:
|
||||
if char in upper_mapping:
|
||||
result += upper_mapping[char]
|
||||
elif char in lower_mapping:
|
||||
result += lower_mapping[char]
|
||||
elif char in digit_mapping:
|
||||
result += digit_mapping[char]
|
||||
elif char in special_mapping:
|
||||
result += special_mapping[char]
|
||||
|
||||
# Проверяем, не пустой ли результат
|
||||
if not result:
|
||||
return "Ошибка!"
|
||||
|
||||
return result
|
||||
|
||||
@loader.tds
|
||||
class НадстрочкаMod(loader.Module):
|
||||
"""Делает надстрочный текст"""
|
||||
|
||||
strings = {
|
||||
"name": "Надстрочка",
|
||||
}
|
||||
|
||||
def init(self):
|
||||
self.name = self.strings["name"]
|
||||
|
||||
@loader.command()
|
||||
async def upcmd(self, message: Message):
|
||||
"""<text> - сделать верхний шрифт"""
|
||||
mt = message.text[4:]
|
||||
mt = replace_text(mt)
|
||||
await utils.answer(message, f"<code>{mt}</code>")
|
||||
Reference in New Issue
Block a user