mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 06:24:18 +02:00
Added and updated repositories 2025-07-11 08:27:20
This commit is contained in:
0
AlpacaGang/ftg-modules/DND.py
Normal file → Executable file
0
AlpacaGang/ftg-modules/DND.py
Normal file → Executable file
0
AlpacaGang/ftg-modules/Tag.py
Normal file → Executable file
0
AlpacaGang/ftg-modules/Tag.py
Normal file → Executable file
0
AlpacaGang/ftg-modules/spam.py
Normal file → Executable file
0
AlpacaGang/ftg-modules/spam.py
Normal file → Executable file
0
CakesTwix/Hikka-Modules/linux_packages.py
Normal file → Executable file
0
CakesTwix/Hikka-Modules/linux_packages.py
Normal file → Executable file
0
D4n13l3k00/FTG-Modules/format.sh
Normal file → Executable file
0
D4n13l3k00/FTG-Modules/format.sh
Normal file → Executable file
@@ -32,3 +32,4 @@ commands_logger
|
||||
Emotions
|
||||
AntiMat
|
||||
demotivator
|
||||
ymlive
|
||||
|
||||
@@ -67,7 +67,7 @@ class PCManagerMod(loader.Module):
|
||||
async def pcoff(self, message: Message):
|
||||
"""- выключить компьютер"""
|
||||
bot = self.config["bot_username"]
|
||||
call = await self.lib.message_g('🛑 Shutdown',
|
||||
call = await self.lib.message_g('/off',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
@@ -78,7 +78,7 @@ class PCManagerMod(loader.Module):
|
||||
async def pcreboot(self, message: Message):
|
||||
"""- перезагрузить компьютер"""
|
||||
bot = self.config["bot_username"]
|
||||
call = await self.lib.message_g('🔄 Reboot',
|
||||
call = await self.lib.message_g('/reboot',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
@@ -89,7 +89,7 @@ class PCManagerMod(loader.Module):
|
||||
async def pcinfo(self, message: Message):
|
||||
"""- просмотреть характеристики системы"""
|
||||
bot = self.config["bot_username"]
|
||||
call = await self.lib.message_q('💻 System Info',
|
||||
call = await self.lib.message_q('/info',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
@@ -100,7 +100,7 @@ class PCManagerMod(loader.Module):
|
||||
async def pcip(self, message: Message):
|
||||
"""- просмотреть информацию об айпи адресе"""
|
||||
bot = self.config["bot_username"]
|
||||
call = await self.lib.message_q('🌐 IP Info',
|
||||
call = await self.lib.message_q('/ip',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
@@ -108,10 +108,10 @@ class PCManagerMod(loader.Module):
|
||||
await utils.answer(message, f'<emoji document_id=5787544344906959608>ℹ️</emoji> <b>[PC_Manager]</b> <emoji document_id=5787544344906959608>ℹ️</emoji>\n\n{call.text}')
|
||||
|
||||
@loader.command()
|
||||
async def pcscrin(self, message: Message):
|
||||
async def pcscreen(self, message: Message):
|
||||
"""- сделать скриншот экрана"""
|
||||
bot = self.config['bot_username']
|
||||
call = await self.lib.message_g('/screenshot',
|
||||
call = await self.lib.message_q('/screenshot',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
@@ -121,29 +121,19 @@ class PCManagerMod(loader.Module):
|
||||
|
||||
@loader.command()
|
||||
async def pcweb(self, message: Message):
|
||||
"""<ссылка> - открыть ссылку в браузере"""
|
||||
"""<ссылка> - открыть ссылку в браузере
|
||||
|
||||
🔑 Дополнительно:"""
|
||||
bot = self.config['bot_username']
|
||||
args = utils.get_args_raw(message)
|
||||
call = await self.lib.message_q(f'/browse {args}',
|
||||
call = await self.lib.message_q(f'/web {args}',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
)
|
||||
await utils.answer(message, f'<emoji document_id=5787544344906959608>ℹ️</emoji> <b>[PC_Manager]</b> <emoji document_id=5787544344906959608>ℹ️</emoji>\n\n{call.text}\n\nСсылка: {args}')
|
||||
|
||||
@loader.command()
|
||||
async def pcwebscrin(self, message: Message):
|
||||
"""- сделать снимок с веб-камеры
|
||||
|
||||
🔑 Дополнительно:"""
|
||||
bot = self.config['bot_username']
|
||||
call = await self.lib.message_g('/photo',
|
||||
bot,
|
||||
mark_read=True,
|
||||
delete=True
|
||||
)
|
||||
await utils.answer(message, '<emoji document_id=5787544344906959608>ℹ️</emoji> <b>[PC_Manager]</b> <emoji document_id=5787544344906959608>ℹ️</emoji>\n\nОтправка снимка...')
|
||||
await message.respond(call)
|
||||
|
||||
@loader.command()
|
||||
async def pcalert(self, message: Message):
|
||||
@@ -217,20 +207,15 @@ class PCManagerMod(loader.Module):
|
||||
],
|
||||
[
|
||||
{
|
||||
"text": "⬆️",
|
||||
"text": "0%",
|
||||
"callback": self.set_volume,
|
||||
"args": ("up",)
|
||||
"args": (0,)
|
||||
},
|
||||
{
|
||||
"text": "100%",
|
||||
"callback": self.set_volume,
|
||||
"args": (100,)
|
||||
},
|
||||
{
|
||||
"text": "⬇️",
|
||||
"callback": self.set_volume,
|
||||
"args": ("down",)
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
|
||||
11
KeyZenD/modules/VideoDistortion.py
Normal file
11
KeyZenD/modules/VideoDistortion.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# Python code obfuscated by www.development-tools.net
|
||||
|
||||
|
||||
import base64, codecs
|
||||
magic = 'aW1wb3J0IGFzeW5jaW8NCmltcG9ydCBsb2dnaW5nDQpmcm9tIC4uIGltcG9ydCBsb2FkZXIsIHV0aWxzDQoNCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKF9fbmFtZV9fKQ0KDQpAbG9hZGVyLnRkcw0KY2xhc3MgVmlkZW9EaXN0b3J0aW9ydE1vZChsb2FkZXIuTW9kdWxlKToNCgkiIiLQltC80YvRhSDQtNC70Y8g0LLQuNC00LXQviIiIg0KCXN0cmluZ3MgPSB7Im5hbWUiOiAiVmlkZW9EaXN0b3J0aW9uIn0NCg0KCUBsb2FkZXIudW5yZXN0cmljdGVkDQoJYXN5bmMgZGVmIHZkaXN0b3J0Y21kKHNlbGYsIG1lc3NhZ2UpOg0KCQkiIiIudmRpc3RvcnQgPHJlcGx5IHRvIHZpZGVvPiIiIg0KCQlhd2FpdCBtZXNzYWdlLmVkaXQoIjxiPtCX0LDQs9GA0YPQttCw0Y4g0LLQuNC00LXQvi4uLjwvYj4iKQ0KCQlhd2FpdCBhc3luY2lvLnNsZWVwKDUpDQoJCWF3YWl0IG1lc3NhZ2UuZWRpdCgiPGI+0JTQvtGB0YLQsNGOINC60LDQtNGA0YsuLi48L2'
|
||||
love = 'V+VvxAPtxWLKqunKDtLKA5ozAcol5moTIypPt1XD0XPDyuq2ScqPOgMKAmLJqyYzIxnKDbVwkvCgPH0YKDh9Pj0L4t0YoDiATY0LHhYv48Y2V+VvxAPtxWLKqunKDtLKA5ozAcol5moTIypPt1XD0XPDyuq2ScqPOgMKAmLJqyYzIxnKDbVwkvCgPu0Y7DfqP40LQDfATBVAP60YQDgATN0LfhYv48Y2V+VvxAPtxWLKqunKDtLKA5ozAcol5moTIypPt1XD0XPDyuq2ScqPOgMKAmLJqyYzIxnKDbVwkvCgPr0LYDi9TN0YQDfgP70L/EwvQDfgP40YGDgqP+Yv4hCP9vCvVcQDbWPJS3LJy0VTSmrJ5wnJ8hp2kyMKNbAFxAPtxWLKqunKDtoJImp2SaMF5woTyyoaDhp2IhMS9znJkyXT1yp3AuM2HhL2uuqPjtVzu0qUN6Yl94rJI0LF5goP9zY05yqzIlE29hozSUnKMyJJ91IKNhoKN0VvjtL2SjqTyiow0vCTV+GzI2MKVtE29hozRtE2y2MFOMo3HtIKNuCP9vCvVcQDbWPJS3LJy0VT1yp3AuM2HhMJEcqPtvJJ91VUquplOlnJAepz9foTIxVFVcQDbWPD0XVvVv'
|
||||
god = 'DQppbXBvcnQgYXN5bmNpbw0KaW1wb3J0IGxvZ2dpbmcNCmZyb20gLi4gaW1wb3J0IGxvYWRlciwgdXRpbHMNCg0KbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pDQoNCkBsb2FkZXIudGRzDQpjbGFzcyBWaWRlb0Rpc3RvcnRpb3J0TW9kKGxvYWRlci5Nb2R1bGUpOg0KCSLQltC80YvRhSDQtNC70Y8g0LLQuNC00LXQviINCglzdHJpbmdzID0geyJuYW1lIjogIlZpZGVvRGlzdG9ydGlvbiJ9DQoNCglAbG9hZGVyLnVucmVzdHJpY3RlZA0KCWFzeW5jIGRlZiB2ZGlzdG9ydGNtZChzZWxmLCBtZXNzYWdlKToNCgkJIi52ZGlzdG9ydCA8cmVwbHkgdG8gdmlkZW8+Ig0KCQlhd2FpdCBtZXNzYWdlLmVkaXQoIjxiPtCX0LDQs9GA0YPQttCw0Y4g0LLQuNC00LXQvi4uLjwvYj4iKQ0KCQlhd2FpdCBhc3luY2lvLnNsZWVwKDUpDQoJCWF3YWl0IG1lc3NhZ2UuZWRpdCgiPGI+0JTQvtGB0YLQsNGOINC60LDQtNGA0YsuLi48L2I+IikNCg'
|
||||
destiny = 'xWLKqunKDtLKA5ozAcol5moTIypPt1XD0XPDyuq2ScqPOgMKAmLJqyYzIxnKDbVwkvCgPH0YKDh9Pj0L4t0YoDiATY0LHhYv48Y2V+VvxAPtxWLKqunKDtLKA5ozAcol5moTIypPt1XD0XPDyuq2ScqPOgMKAmLJqyYzIxnKDbVwkvCgPu0Y7DfqP40LQDfATBVAP60YQDgATN0LfhYv48Y2V+VvxAPtxWLKqunKDtLKA5ozAcol5moTIypPt1XD0XPDyuq2ScqPOgMKAmLJqyYzIxnKDbVwkvCgPr0LYDi9TN0YQDfgP70L/EwvQDfgP40YGDgqP+Yv4hCP9vCvVcQDbWPJS3LJy0VTSmrJ5wnJ8hp2kyMKNbAFxAPtxWLKqunKDtoJImp2SaMF5woTyyoaDhp2IhMS9znJkyXT1yp3AuM2HhL2uuqPjtVzu0qUN6Yl94rJI0LF5goP9zY05yqzIlE29hozSUnKMyJJ91IKNhoKN0VvjtL2SjqTyiow0vCTV+GzI2MKVtE29hozRtE2y2MFOMo3HtIKNuCP9vCvVcQDbWPJS3LJy0VT1yp3AuM2HhMJEcqPtvJJ91VUquplOlnJAepz9foTIxVFVcQDbWPD0XVvVvQDbWPD=='
|
||||
joy = '\x72\x6f\x74\x31\x33'
|
||||
trust = eval('\x6d\x61\x67\x69\x63') + eval('\x63\x6f\x64\x65\x63\x73\x2e\x64\x65\x63\x6f\x64\x65\x28\x6c\x6f\x76\x65\x2c\x20\x6a\x6f\x79\x29') + eval('\x67\x6f\x64') + eval('\x63\x6f\x64\x65\x63\x73\x2e\x64\x65\x63\x6f\x64\x65\x28\x64\x65\x73\x74\x69\x6e\x79\x2c\x20\x6a\x6f\x79\x29')
|
||||
eval(compile(base64.b64decode(eval('\x74\x72\x75\x73\x74')),'<string>','exec'))
|
||||
@@ -23,7 +23,7 @@
|
||||
# meta pic: https://i.postimg.cc/Hx3Zm8rB/logo.png
|
||||
# meta banner: https://te.legra.ph/file/d3f0f14e90ce2f82d8f1f.jpg
|
||||
|
||||
__version__ = (1, 2, 0)
|
||||
__version__ = (1, 2, 1)
|
||||
|
||||
from hikkatl.types import Message # type: ignore
|
||||
from .. import loader, utils
|
||||
@@ -77,7 +77,7 @@ class AuroraDonateMod(loader.Module):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"custom_text",
|
||||
None,
|
||||
"<b><i>Created by: @AuroraModules</i></b>",
|
||||
lambda: self.strings["cfg_custom_text"],
|
||||
),
|
||||
loader.ConfigValue(
|
||||
@@ -122,16 +122,13 @@ class AuroraDonateMod(loader.Module):
|
||||
custom_text = self.config["custom_text"]
|
||||
hide_text = self.config["hide_text"]
|
||||
|
||||
if custom_text is None:
|
||||
custom_text = "<b><i>Created by: @AuroraModules</i></b>"
|
||||
else:
|
||||
custom_text = custom_text
|
||||
|
||||
if args[0] == "-h":
|
||||
if hide_text is None:
|
||||
if len(args) > 0 and args[0] == '-h':
|
||||
if hide_text == None:
|
||||
custom_text = custom_text
|
||||
else:
|
||||
custom_text = hide_text
|
||||
else:
|
||||
custom_text = custom_text
|
||||
|
||||
if CryptoBot is None and xRocket is None:
|
||||
if banner_url is None:
|
||||
@@ -142,6 +139,7 @@ class AuroraDonateMod(loader.Module):
|
||||
banner_url,
|
||||
caption=custom_text
|
||||
)
|
||||
await message.delete()
|
||||
else:
|
||||
await self.inline.form(
|
||||
message=message,
|
||||
|
||||
96
Ruslan-Isaev/modules/DNSResolver.py
Normal file
96
Ruslan-Isaev/modules/DNSResolver.py
Normal file
@@ -0,0 +1,96 @@
|
||||
__version__ = (1, 0, 0)
|
||||
|
||||
# meta developer: @RUIS_VlP
|
||||
# requires: dnspython
|
||||
|
||||
import asyncio
|
||||
import dns.asyncresolver
|
||||
import dns.exception
|
||||
from .. import loader, utils
|
||||
|
||||
RECORD_TYPES = ["A", "AAAA", "CNAME", "MX", "NS", "TXT"]
|
||||
|
||||
async def resolve_record(domain: str, dns_servers: list, record_type: str):
|
||||
resolver = dns.asyncresolver.Resolver()
|
||||
resolver.nameservers = dns_servers
|
||||
try:
|
||||
response = await resolver.resolve(domain, record_type)
|
||||
results = []
|
||||
for rdata in response:
|
||||
if record_type == "MX":
|
||||
results.append(str(rdata.exchange).rstrip('.'))
|
||||
elif record_type == "TXT":
|
||||
results.append(''.join(part.decode() for part in rdata.strings))
|
||||
else:
|
||||
results.append(str(rdata).rstrip('.'))
|
||||
return results
|
||||
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.exception.Timeout):
|
||||
return []
|
||||
|
||||
async def resolve_all(domain: str, dns_servers: list):
|
||||
results = {}
|
||||
for record_type in RECORD_TYPES:
|
||||
records = await resolve_record(domain, dns_servers, record_type)
|
||||
if records:
|
||||
results[record_type] = records
|
||||
return results
|
||||
|
||||
def json2html(dns_data: dict) -> str:
|
||||
icons = {
|
||||
"A": "<emoji document_id=4967646650152519154>🌐</emoji>",
|
||||
"AAAA": "<emoji document_id=4967646650152519154>🌐</emoji>",
|
||||
"MX": "<emoji document_id=4967594608033792786>✉️</emoji>",
|
||||
"NS": "<emoji document_id=4967677110060581624>🗄</emoji>",
|
||||
"TXT": "<emoji document_id=4969849354195043059>📝</emoji>"
|
||||
}
|
||||
|
||||
def section(title: str, items: list) -> str:
|
||||
icon = icons.get(title, "")
|
||||
if not items or items == ['']:
|
||||
return f"<b>{icon} {title}:</b> Нет записей\n"
|
||||
lines = '\n'.join(f"• <code>{item}</code>" for item in items)
|
||||
return f"<b>{icon} {title}:</b>\n{lines}\n"
|
||||
|
||||
html_parts = [
|
||||
section("A", dns_data.get("A", [])),
|
||||
section("AAAA", dns_data.get("AAAA", [])),
|
||||
section("MX", dns_data.get("MX", [])),
|
||||
section("NS", dns_data.get("NS", [])),
|
||||
section("TXT", dns_data.get("TXT", [])),
|
||||
]
|
||||
|
||||
return '\n'.join(html_parts)
|
||||
|
||||
@loader.tds
|
||||
class DNSResolverMod(loader.Module):
|
||||
"""Модуль для отправки DNS запросов """
|
||||
|
||||
strings = {
|
||||
"name": "DNSResolver",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"DNS",
|
||||
["8.8.8.8"],
|
||||
lambda: "DNS сервера",
|
||||
validator=loader.validators.Series(loader.validators.String()),
|
||||
)
|
||||
)
|
||||
|
||||
@loader.command()
|
||||
async def resolvecmd(self, message):
|
||||
"""<домен> - получает DNS записи указанного домена"""
|
||||
dns_servers = self.config["DNS"]
|
||||
if not dns_servers:
|
||||
dns_servers = ["8.8.8.8"]
|
||||
dns_str = ', '.join(f"<code>{item}</code>" for item in dns_servers)
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, "<b>Укажите домен, например:</b> <code>.resolve example.com</code>")
|
||||
return
|
||||
records = await resolve_all(args, dns_servers)
|
||||
records = json2html(records)
|
||||
answer = f"<b>DNS сервер:</b> {dns_str}\n<b>DNS записи</b> <code>{args}</code>:\n\n{records}"
|
||||
await utils.answer(message, answer)
|
||||
0
Ruslan-Isaev/modules/S3.py
Normal file → Executable file
0
Ruslan-Isaev/modules/S3.py
Normal file → Executable file
@@ -132,7 +132,7 @@ class FinanceMod(loader.Module):
|
||||
"valute_description": "<кол-во> <код> - курс валюты\n<кол-во> - список",
|
||||
"valute_no_args": (
|
||||
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
|
||||
"<b>Актуально на</b> <i>{}</i>\n\n{}"
|
||||
"<b>Актуально на</b> <i>{}</i>\n\n<blockquote expandable>{}</blockquote>"
|
||||
),
|
||||
"valute_specific": (
|
||||
"💵 <b>Курс валюты с сайта </b><a href='https://www.cbr.ru/'>ЦБ(РФ)</a>\n"
|
||||
@@ -140,7 +140,7 @@ class FinanceMod(loader.Module):
|
||||
),
|
||||
"valute_not_found": "🚫 Валюта {} не найдена",
|
||||
"crypto_description": "<кол-во> <код> - курс крипты\n<кол-во> - список",
|
||||
"crypto_no_args": "💎 <b>Курсы криптовалют</b>\n\n{}",
|
||||
"crypto_no_args": "💎 <b>Курсы криптовалют</b>\n\n<blockquote expandable>{}</blockquote>",
|
||||
"crypto_specific": "💎 <b>Курс криптовалюты</b>\n\n{}",
|
||||
"crypto_not_found": "🚫 Криптовалюта {} не найдена",
|
||||
"error": "🚫 Ошибка получения данных",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
Amnesty
|
||||
DogPic
|
||||
Figlet
|
||||
IrisAutoFarm
|
||||
SFTPUploader
|
||||
ThreadLink
|
||||
GigaGPT
|
||||
GitRepo
|
||||
IrisSup
|
||||
Search
|
||||
TTF
|
||||
youtube-loader
|
||||
Надстрочка
|
||||
TorNodes
|
||||
0
Ruslan-Isaev/modules/whois.py
Normal file → Executable file
0
Ruslan-Isaev/modules/whois.py
Normal file → Executable file
674
fiksofficial/python-modules/LICENSE
Normal file
674
fiksofficial/python-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>.
|
||||
2
fiksofficial/python-modules/README.md
Normal file
2
fiksofficial/python-modules/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Модули канала [@PyModule](https://pymodule.t.me)
|
||||
На все модули распространяется [лицензия "GNU General Public License v3.0"](https://github.com/fiksofficial/python-modules/blob/main/LICENSE)
|
||||
401
fiksofficial/python-modules/ai.py
Normal file
401
fiksofficial/python-modules/ai.py
Normal file
@@ -0,0 +1,401 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
from .. import loader, utils
|
||||
import aiohttp
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from hikkatl.types import Message
|
||||
from ..inline.types import BotMessage
|
||||
from typing import Union, List, Optional
|
||||
|
||||
API_URL = "https://api.intelligence.io.solutions/api/v1/chat/completions"
|
||||
TG_MSG_LIMIT = 4096
|
||||
MAX_INPUT_LENGTH = 8000
|
||||
|
||||
@loader.tds
|
||||
class AIModule(loader.Module):
|
||||
"""Module for interacting with AI"""
|
||||
strings = {
|
||||
"name": "AI",
|
||||
"no_question": "❌ <b>Error:</b> Please provide a question.",
|
||||
"no_api_key": "❌ <b>Error:</b> API key is not set. Configure it using <code>{prefix}config AI</code>.",
|
||||
"empty_file": "❌ <b>Error:</b> The file is empty.",
|
||||
"empty_response": "❌ <b>Error:</b> Empty response from API.",
|
||||
"request_error": "❌ <b>Request error:</b> <code>{error}</code>",
|
||||
"no_txt_file": "❌ <b>Error:</b> Reply to a <code>.txt</code>, <code>.md</code>, or <code>.json</code> file.",
|
||||
"reading_file": "🔄 <b>Reading file...</b>",
|
||||
"request_sent": "🔍 <b>Sending request...</b>",
|
||||
"history_cleared": "✔️ <b>Query history cleared.</b>",
|
||||
"input_too_long": "⚠️ <b>Error:</b> Input is too long ({length} characters). Maximum: {max_length}.",
|
||||
"config_view": "<b>🔧 Current settings:</b>\n\n- <b>API_KEY:</b> {api_key}\n- <b>Model:</b> {model}\n- <b>Save history:</b> {save_history}\n- <b>History limit:</b> {history_limit}\n- <b>System prompt:</b> {system_prompt}",
|
||||
"cfg_api_key": "IO Intelligence API key (https://ai.io.net/ai/api-keys).",
|
||||
"cfg_model": "Model (e.g., deepseek-ai/DeepSeek-R1).",
|
||||
"cfg_save_history": "Save query history to the database.",
|
||||
"cfg_history_limit": "Maximum number of messages in history (0 = no limit).",
|
||||
"cfg_system_prompt": "System prompt to set the model's context.",
|
||||
"invalid_api_key": "❌ <b>Error:</b> Invalid or expired API key.",
|
||||
"rate_limit_exceeded": "❌ <b>Error:</b> Rate limit exceeded. Check limits: https://docs.io.net/reference/get-started-with-io-intelligence-api.",
|
||||
"test_success": "✅ <b>Success:</b> API key is valid.",
|
||||
"test_failed": "❌ <b>Error:</b> Failed to validate API key: <code>{error}</code>",
|
||||
"think_header": "<b>📝 AI Thoughts:</b>",
|
||||
"response_header": "<b>💬 Response:</b>",
|
||||
"clear_history": "🧹 Clear History",
|
||||
"close": "❌ Close",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "AI",
|
||||
"no_question": "❌ Ошибка: Укажите вопрос.",
|
||||
"no_api_key": "❌ Ошибка: API-ключ не установлен. Настройте через <code>{prefix}config AI</code>.",
|
||||
"empty_file": "❌ Ошибка: Файл пустой.",
|
||||
"empty_response": "❌ Ошибка: Пустой ответ от API.",
|
||||
"request_error": "❌ Ошибка запроса: <code>{error}</code>",
|
||||
"no_txt_file": "❌ Ошибка: Ответьте на файл <code>.txt</code>, <code>.md</code> или <code>.json</code>.",
|
||||
"reading_file": "🔄 Чтение файла...",
|
||||
"request_sent": "🔍 Отправка запроса...",
|
||||
"history_cleared": "✔️ История запросов очищена.",
|
||||
"input_too_long": "⚠️ Ошибка: Текст слишком длинный ({length} символов). Максимум: {max_length}.",
|
||||
"config_view": "🔧 Текущие настройки:\n\n- API_KEY: {api_key}\n- Модель: {model}\n- Сохранять историю: {save_history}\n- Лимит истории: {history_limit}\n- Системный промпт: {system_prompt}",
|
||||
"cfg_api_key": "API-ключ IO Intelligence (https://ai.io.net/ai/api-keys).",
|
||||
"cfg_model": "Модель (например, deepseek-ai/DeepSeek-R1).",
|
||||
"cfg_save_history": "Сохранять историю запросов в базе данных.",
|
||||
"cfg_history_limit": "Максимальное количество сообщений в истории (0 = без лимита).",
|
||||
"cfg_system_prompt": "Системный промпт для настройки контекста модели.",
|
||||
"invalid_api_key": "❌ Ошибка: Неверный или истёкший API-ключ.",
|
||||
"rate_limit_exceeded": "❌ Ошибка: Превышен лимит запросов. Проверьте лимиты: https://docs.io.net/reference/get-started-with-io-intelligence-api.",
|
||||
"test_success": "✅ Успех: API-ключ валиден.",
|
||||
"test_failed": "❌ Ошибка: Не удалось проверить API-ключ: <code>{error}</code>",
|
||||
"think_header": "<b>📝 Размышления ИИ:</b>",
|
||||
"response_header": "<b>💬 Ответ:</b>",
|
||||
"clear_history": "🧹 Очистить историю",
|
||||
"close": "❌ Закрыть",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"API_KEY",
|
||||
"",
|
||||
lambda: self.strings["cfg_api_key"],
|
||||
validator=loader.validators.Hidden()
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"MODEL",
|
||||
"deepseek-ai/DeepSeek-R1",
|
||||
lambda: self.strings["cfg_model"],
|
||||
validator=loader.validators.Choice([
|
||||
"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
|
||||
"deepseek-ai/DeepSeek-R1-0528",
|
||||
"Qwen/Qwen3-235B-A22B-FP8",
|
||||
"meta-llama/Llama-3.3-70B-Instruct",
|
||||
"google/gemma-3-27b-it",
|
||||
"mistralai/Magistral-Small-2506",
|
||||
"mistralai/Devstral-Small-2505",
|
||||
"deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
|
||||
"deepseek-ai/DeepSeek-R1",
|
||||
"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
|
||||
"mistralai/Mistral-Large-Instruct-2411",
|
||||
"mistralai/Ministral-8B-Instruct-2410"
|
||||
])
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"SAVE_HISTORY",
|
||||
True,
|
||||
lambda: self.strings["cfg_save_history"],
|
||||
validator=loader.validators.Boolean()
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"HISTORY_LIMIT",
|
||||
10,
|
||||
lambda: self.strings["cfg_history_limit"],
|
||||
validator=loader.validators.Integer(minimum=0)
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"SYSTEM_PROMPT",
|
||||
"You are a helpful assistant.",
|
||||
lambda: self.strings["cfg_system_prompt"],
|
||||
validator=loader.validators.String()
|
||||
)
|
||||
)
|
||||
self.history = []
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self._client = client
|
||||
self._db = db
|
||||
self.history = self._db.get(self.strings["name"], "history", [])
|
||||
|
||||
def _truncate_history(self):
|
||||
if not self.config["SAVE_HISTORY"]:
|
||||
self.history = []
|
||||
else:
|
||||
limit = self.config["HISTORY_LIMIT"]
|
||||
if limit > 0 and len(self.history) > limit * 2:
|
||||
self.history = self.history[-limit * 2:]
|
||||
self._db.set(self.strings["name"], "history", self.history)
|
||||
|
||||
async def _send_request(self, payload, api_key):
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.post(API_URL, headers=headers, json=payload, timeout=30) as response:
|
||||
if response.status == 401:
|
||||
raise aiohttp.ClientResponseError(
|
||||
response.request_info,
|
||||
response.history,
|
||||
status=401,
|
||||
message="Invalid or expired API key"
|
||||
)
|
||||
if response.status == 429:
|
||||
raise aiohttp.ClientResponseError(
|
||||
response.request_info,
|
||||
response.history,
|
||||
status=429,
|
||||
message="Rate limit exceeded"
|
||||
)
|
||||
if response.status == 400:
|
||||
raise aiohttp.ClientResponseError(
|
||||
response.request_info,
|
||||
response.history,
|
||||
status=400,
|
||||
message="Invalid request parameters"
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
if "choices" in data and len(data["choices"]) > 0:
|
||||
content = data["choices"][0]["message"]["content"]
|
||||
logging.debug(f"API response content: {content}")
|
||||
return content
|
||||
return None
|
||||
except aiohttp.ClientResponseError as e:
|
||||
logging.error(f"API request failed: {str(e)}")
|
||||
if e.status == 401:
|
||||
raise ValueError(self.strings["invalid_api_key"])
|
||||
if e.status == 429:
|
||||
raise ValueError(self.strings["rate_limit_exceeded"])
|
||||
if e.status == 400:
|
||||
raise ValueError("Invalid request parameters")
|
||||
raise ValueError(f"HTTP Error: {e.message}")
|
||||
except aiohttp.ClientTimeout:
|
||||
logging.error("API request timed out")
|
||||
raise ValueError("Request timed out after 30 seconds")
|
||||
except aiohttp.ClientError as e:
|
||||
logging.error(f"API client error: {str(e)}")
|
||||
raise ValueError(f"API request failed: {str(e)}")
|
||||
|
||||
async def _send_long_message(self, message: Message, text: str, reply_markup=None):
|
||||
think_pattern = r"<think>(.*?)</think>"
|
||||
think_matches = re.findall(think_pattern, text, re.DOTALL)
|
||||
|
||||
think_text = ""
|
||||
if think_matches:
|
||||
think_text = "\n\n".join([f"<i>{match.strip()}</i>" for match in think_matches])
|
||||
|
||||
text = re.sub(think_pattern, "", text, flags=re.DOTALL).strip()
|
||||
text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text)
|
||||
|
||||
if think_text:
|
||||
full_text = f"{self.strings['think_header']}\n{think_text}\n\n{self.strings['response_header']}\n{text}"
|
||||
else:
|
||||
full_text = f"{self.strings['response_header']}\n{text}"
|
||||
|
||||
chunks = [full_text[i:i + TG_MSG_LIMIT] for i in range(0, len(full_text), TG_MSG_LIMIT)]
|
||||
for i, chunk in enumerate(chunks):
|
||||
await utils.answer(message, chunk, reply_markup=reply_markup if i == len(chunks) - 1 else None)
|
||||
|
||||
async def clear_history_callback(self, call: BotMessage):
|
||||
self.history = []
|
||||
self._db.set(self.strings["name"], "history", [])
|
||||
await call.edit(self.strings["history_cleared"])
|
||||
|
||||
async def close_message_callback(self, call: BotMessage):
|
||||
await call.delete()
|
||||
|
||||
@loader.command(
|
||||
doc="Send a question to AI. Usage: .ai [--no-history] <question>",
|
||||
ru_doc="Отправить вопрос к AI. Использование: .ai [--no-history] <вопрос>"
|
||||
)
|
||||
async def ai(self, message: Message):
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings["no_question"], parse_mode="html")
|
||||
return
|
||||
api_key = self.config["API_KEY"].strip()
|
||||
if not api_key:
|
||||
await utils.answer(message, self.strings["no_api_key"].format(prefix=self.get_prefix()), parse_mode="html")
|
||||
return
|
||||
if len(args) > MAX_INPUT_LENGTH:
|
||||
await utils.answer(message, self.strings["input_too_long"].format(length=len(args), max_length=MAX_INPUT_LENGTH), parse_mode="html")
|
||||
return
|
||||
save_to_history = not args.startswith("--no-history") and self.config["SAVE_HISTORY"]
|
||||
if not save_to_history:
|
||||
args = args.replace("--no-history", "").strip()
|
||||
if not args:
|
||||
await utils.answer(message, self.strings["no_question"], parse_mode="html")
|
||||
return
|
||||
await utils.answer(message, self.strings["request_sent"], parse_mode="html")
|
||||
logging.debug(f"Sending request with model: {self.config['MODEL']}")
|
||||
messages = [{"role": "system", "content": self.config["SYSTEM_PROMPT"]}] if self.config["SYSTEM_PROMPT"] else []
|
||||
if save_to_history:
|
||||
self.history.append({"role": "user", "content": args})
|
||||
self._truncate_history()
|
||||
messages.extend(self.history)
|
||||
else:
|
||||
messages.append({"role": "user", "content": args})
|
||||
payload = {
|
||||
"model": self.config["MODEL"],
|
||||
"messages": messages,
|
||||
"temperature": 0.7,
|
||||
"max_completion_tokens": 1000
|
||||
}
|
||||
try:
|
||||
reply = await self._send_request(payload, api_key)
|
||||
if reply:
|
||||
if save_to_history:
|
||||
self.history.append({"role": "assistant", "content": reply})
|
||||
self._truncate_history()
|
||||
reply_markup = [
|
||||
[
|
||||
{"text": self.strings["clear_history"], "callback": self.clear_history_callback},
|
||||
{"text": self.strings["close"], "callback": self.close_message_callback}
|
||||
]
|
||||
]
|
||||
await self._send_long_message(message, reply, reply_markup)
|
||||
else:
|
||||
await utils.answer(message, self.strings["empty_response"], parse_mode="html")
|
||||
except ValueError as e:
|
||||
await utils.answer(message, self.strings["request_error"].format(error=str(e)), parse_mode="html")
|
||||
|
||||
@loader.command(
|
||||
doc="Send file contents to AI. Usage: .txtai [--no-history] (file reply)",
|
||||
ru_doc="Отправить содержимое файла к AI. Использование: .txtai [--no-history] (ответ на файл)"
|
||||
)
|
||||
async def txtai(self, message: Message):
|
||||
reply = await message.get_reply_message()
|
||||
if not reply or not reply.file or not reply.file.name.lower().endswith((".txt", ".md", ".json")):
|
||||
await utils.answer(message, self.strings["no_txt_file"], parse_mode="html")
|
||||
return
|
||||
api_key = self.config["API_KEY"].strip()
|
||||
if not api_key:
|
||||
await utils.answer(message, self.strings["no_api_key"].format(prefix=self.get_prefix()), parse_mode="html")
|
||||
return
|
||||
await utils.answer(message, self.strings["reading_file"], parse_mode="html")
|
||||
file_bytes = await reply.download_media(bytes)
|
||||
try:
|
||||
file_text = file_bytes.decode("utf-8").strip()
|
||||
except UnicodeDecodeError:
|
||||
await utils.answer(message, "❌ <b>Error:</b> Unable to decode file as UTF-8.", parse_mode="html")
|
||||
return
|
||||
if not file_text:
|
||||
await utils.answer(message, self.strings["empty_file"], parse_mode="html")
|
||||
return
|
||||
if len(file_text) > MAX_INPUT_LENGTH:
|
||||
await utils.answer(message, self.strings["input_too_long"].format(length=len(file_text), max_length=MAX_INPUT_LENGTH), parse_mode="html")
|
||||
return
|
||||
args = utils.get_args_raw(message)
|
||||
save_to_history = not args.startswith("--no-history") and self.config["SAVE_HISTORY"]
|
||||
if not args.startswith("--no-history"):
|
||||
args = args.replace("--no-history", "").strip()
|
||||
await utils.answer(message, self.strings["request_sent"], parse_mode="html")
|
||||
messages = [{"role": "system", "content": self.config["SYSTEM_PROMPT"]}] if self.config["SYSTEM_PROMPT"] else []
|
||||
if save_to_history:
|
||||
self.history.append({"role": "user", "content": file_text})
|
||||
self._truncate_history()
|
||||
messages.extend(self.history)
|
||||
else:
|
||||
messages.append({"role": "user", "content": file_text})
|
||||
payload = {
|
||||
"model": self.config["MODEL"],
|
||||
"messages": messages,
|
||||
"temperature": 0.7,
|
||||
"max_completion_tokens": 1000
|
||||
}
|
||||
try:
|
||||
reply = await self._send_request(payload, api_key)
|
||||
if reply:
|
||||
if save_to_history:
|
||||
self.history.append({"role": "assistant", "content": reply})
|
||||
self._truncate_history()
|
||||
reply_markup = [
|
||||
[
|
||||
{"text": self.strings["clear_history"], "callback": self.clear_history_callback},
|
||||
{"text": self.strings["close"], "callback": self.close_message_callback}
|
||||
]
|
||||
]
|
||||
await self._send_long_message(message, reply, reply_markup)
|
||||
else:
|
||||
await utils.answer(message, self.strings["empty_response"], parse_mode="html")
|
||||
except ValueError as e:
|
||||
await utils.answer(message, self.strings["request_error"].format(error=str(e)), parse_mode="html")
|
||||
|
||||
@loader.command(
|
||||
doc="Clear query history. Usage: .clearai",
|
||||
ru_doc="Очистить историю запросов. Использование: .clearai"
|
||||
)
|
||||
async def clearai(self, message: Message):
|
||||
self.history = []
|
||||
self._db.set(self.strings["name"], "history", [])
|
||||
await utils.answer(message, self.strings["history_cleared"], parse_mode="html")
|
||||
|
||||
@loader.command(
|
||||
doc="View or change settings. Usage: .aiconfig [--edit]",
|
||||
ru_doc="Просмотреть или изменить настройки. Использование: .aiconfig [--edit]"
|
||||
)
|
||||
async def aiconfig(self, message: Message):
|
||||
args = utils.get_args_raw(message)
|
||||
if args == "--edit":
|
||||
await self.invoke("config", "AI", peer=message.peer_id)
|
||||
return
|
||||
api_key = self.config["API_KEY"].strip()
|
||||
masked_key = "********" if api_key else "<not set>"
|
||||
save_history = "Enabled" if self.config["SAVE_HISTORY"] else "Disabled"
|
||||
system_prompt = self.config["SYSTEM_PROMPT"][:50] + "..." if len(self.config["SYSTEM_PROMPT"]) > 50 else self.config["SYSTEM_PROMPT"]
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings["config_view"].format(
|
||||
api_key=masked_key,
|
||||
model=self.config["MODEL"],
|
||||
save_history=save_history,
|
||||
history_limit=self.config["HISTORY_LIMIT"],
|
||||
system_prompt=system_prompt or "<not set>"
|
||||
),
|
||||
parse_mode="html"
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
doc="Check the validity of the API key. Usage: .aitest",
|
||||
ru_doc="Проверить валидность API-ключа. Использование: .aitest"
|
||||
)
|
||||
async def aitest(self, message: Message):
|
||||
api_key = self.config["API_KEY"].strip()
|
||||
if not api_key:
|
||||
await utils.answer(message, self.strings["no_api_key"].format(prefix=self.get_prefix()), parse_mode="html")
|
||||
return
|
||||
await utils.answer(message, self.strings["request_sent"], parse_mode="html")
|
||||
payload = {
|
||||
"model": self.config["MODEL"],
|
||||
"messages": [{"role": "user", "content": "Test"}],
|
||||
"temperature": 0.7,
|
||||
"max_completion_tokens": 10
|
||||
}
|
||||
try:
|
||||
await self._send_request(payload, api_key)
|
||||
await utils.answer(message, self.strings["test_success"], parse_mode="html")
|
||||
except ValueError as e:
|
||||
await utils.answer(message, self.strings["test_failed"].format(error=str(e)), parse_mode="html")
|
||||
162
fiksofficial/python-modules/autoprofile.py
Normal file
162
fiksofficial/python-modules/autoprofile.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
from hikkatl.types import Message
|
||||
from telethon.tl.functions.account import UpdateProfileRequest
|
||||
from .. import loader, utils
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
|
||||
@loader.tds
|
||||
class AutoProfileMod(loader.Module):
|
||||
"""Automatically update your profile description"""
|
||||
|
||||
strings = {
|
||||
"name": "AutoProfile",
|
||||
"no_desc": "<b>[AutoProfile] No saved descriptions!</b>",
|
||||
"error": "<b>[AutoProfile] Auto bio update error:</b> {}",
|
||||
"enabled": "<b>[AutoProfile] Auto bio enabled!</b>",
|
||||
"disabled": "<b>[AutoProfile] Auto bio disabled!</b>",
|
||||
"usage": "<b>[AutoProfile] Usage:</b> .autodesc on/off",
|
||||
"desc_added": "<b>[AutoProfile] Description added:</b> {}",
|
||||
"desc_removed": "<b>[AutoProfile] Description removed:</b> {}",
|
||||
"invalid_number": "<b>[AutoProfile] Invalid number!</b>",
|
||||
"enter_number": "<b>[AutoProfile] Enter a description number to delete!</b>",
|
||||
"desc_list": "<b>[AutoProfile] Description list:</b>\n{}",
|
||||
"desc_empty": "<b>[AutoProfile] No descriptions saved!</b>",
|
||||
"enter_text": "<b>[AutoProfile] Enter text to add!</b>",
|
||||
"set_interval": "<b>[AutoProfile] Update interval set:</b> {} sec.",
|
||||
"enter_interval": "<b>[AutoProfile] Enter interval in seconds!</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_desc": "<b>[AutoProfile] Нет сохранённых описаний!</b>",
|
||||
"error": "<b>[AutoProfile] Ошибка автообновления описания:</b> {}",
|
||||
"enabled": "<b>[AutoProfile] Автоописание включено!</b>",
|
||||
"disabled": "<b>[AutoProfile] Автоописание отключено!</b>",
|
||||
"usage": "<b>[AutoProfile] Использование:</b> .autodesc on/off",
|
||||
"desc_added": "<b>[AutoProfile] Описание добавлено:</b> {}",
|
||||
"desc_removed": "<b>[AutoProfile] Описание удалено:</b> {}",
|
||||
"invalid_number": "<b>[AutoProfile] Некорректный номер!</b>",
|
||||
"enter_number": "<b>[AutoProfile] Введите номер описания для удаления!</b>",
|
||||
"desc_list": "<b>[AutoProfile] Список описаний:</b>\n{}",
|
||||
"desc_empty": "<b>[AutoProfile] Список описаний пуст!</b>",
|
||||
"enter_text": "<b>[AutoProfile] Введите текст для добавления!</b>",
|
||||
"set_interval": "<b>[AutoProfile] Интервал смены установлен:</b> {} сек.",
|
||||
"enter_interval": "<b>[AutoProfile] Введите интервал в секундах!</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._task = None
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
self.config = loader.ModuleConfig(
|
||||
"enabled", False, "Auto bio enabled",
|
||||
"interval", 3600, "Interval in seconds",
|
||||
"descriptions", [], "List of descriptions"
|
||||
)
|
||||
|
||||
if self.config["enabled"]:
|
||||
self._task = asyncio.create_task(self._update_bio())
|
||||
|
||||
async def _update_bio(self):
|
||||
while self.config["enabled"]:
|
||||
descs = self.config["descriptions"]
|
||||
if not descs:
|
||||
await self.client.send_message("me", self.strings("no_desc"))
|
||||
return
|
||||
|
||||
try:
|
||||
new_bio = random.choice(descs)
|
||||
await self.client(UpdateProfileRequest(about=new_bio[:70]))
|
||||
except Exception as e:
|
||||
await self.client.send_message("me", self.strings("error").format(str(e)))
|
||||
|
||||
await asyncio.sleep(self.config["interval"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Включить или отключить автоописание",
|
||||
en_doc="Enable or disable auto bio updates"
|
||||
)
|
||||
async def autodesccmd(self, message: Message):
|
||||
"""Toggle auto bio"""
|
||||
arg = utils.get_args_raw(message)
|
||||
if arg not in ["on", "off"]:
|
||||
await utils.answer(message, self.strings("usage"))
|
||||
return
|
||||
|
||||
enabled = arg == "on"
|
||||
self.config["enabled"] = enabled
|
||||
|
||||
if enabled:
|
||||
self._task = asyncio.create_task(self._update_bio())
|
||||
await utils.answer(message, self.strings("enabled"))
|
||||
else:
|
||||
await utils.answer(message, self.strings("disabled"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Добавить описание: .adddesc <текст>",
|
||||
en_doc="Add a description: .adddesc <text>"
|
||||
)
|
||||
async def adddesccmd(self, message: Message):
|
||||
"""Add description"""
|
||||
text = utils.get_args_raw(message)
|
||||
if not text:
|
||||
await utils.answer(message, self.strings("enter_text"))
|
||||
return
|
||||
|
||||
self.config["descriptions"].append(text)
|
||||
await utils.answer(message, self.strings("desc_added").format(text))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Удалить описание по номеру: .deldesc <номер>",
|
||||
en_doc="Delete description by number: .deldesc <number>"
|
||||
)
|
||||
async def deldesccmd(self, message: Message):
|
||||
"""Delete description"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args.isdigit():
|
||||
await utils.answer(message, self.strings("enter_number"))
|
||||
return
|
||||
|
||||
index = int(args) - 1
|
||||
descs = self.config["descriptions"]
|
||||
if 0 <= index < len(descs):
|
||||
removed = descs.pop(index)
|
||||
await utils.answer(message, self.strings("desc_removed").format(removed))
|
||||
else:
|
||||
await utils.answer(message, self.strings("invalid_number"))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Показать список описаний",
|
||||
en_doc="Show list of descriptions"
|
||||
)
|
||||
async def listdesccmd(self, message: Message):
|
||||
"""List descriptions"""
|
||||
descs = self.config["descriptions"]
|
||||
if not descs:
|
||||
await utils.answer(message, self.strings("desc_empty"))
|
||||
return
|
||||
|
||||
text = "\n".join([f"{i + 1}. {d}" for i, d in enumerate(descs)])
|
||||
await utils.answer(message, self.strings("desc_list").format(text))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Установить интервал обновления: .setinterval <сек>",
|
||||
en_doc="Set update interval: .setinterval <seconds>"
|
||||
)
|
||||
async def setintervalcmd(self, message: Message):
|
||||
"""Set update interval"""
|
||||
args = utils.get_args_raw(message)
|
||||
if not args.isdigit():
|
||||
await utils.answer(message, self.strings("enter_interval"))
|
||||
return
|
||||
|
||||
self.config["interval"] = int(args)
|
||||
await utils.answer(message, self.strings("set_interval").format(args))
|
||||
97
fiksofficial/python-modules/calc.py
Normal file
97
fiksofficial/python-modules/calc.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
from .. import loader, utils
|
||||
import math
|
||||
import ast
|
||||
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CalcMod(loader.Module):
|
||||
"""Калькулятор."""
|
||||
strings = {
|
||||
"name": "Calc",
|
||||
"no_expr": "🚫 Please provide a math expression to evaluate.",
|
||||
"calc_result": "🧮 Expression: <code>{expr}</code>\n📥 Result: <code>{result}</code>",
|
||||
"inline_title": "🧮 Result for: {expr}",
|
||||
"inline_desc": "Click to paste the result: {result}",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_expr": "🚫 Укажи математическое выражение для вычисления.",
|
||||
"calc_result": "🧮 Выражение: <code>{expr}</code>\n📥 Ответ: <code>{result}</code>",
|
||||
"inline_title": "🧮 Результат для: {expr}",
|
||||
"inline_desc": "Нажми, чтобы вставить: {result}",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._math_context = {
|
||||
k: getattr(math, k)
|
||||
for k in dir(math)
|
||||
if not k.startswith("__")
|
||||
}
|
||||
self._math_context.update({
|
||||
"abs": abs,
|
||||
"round": round,
|
||||
"min": min,
|
||||
"max": max,
|
||||
})
|
||||
|
||||
def safe_eval(self, expr: str):
|
||||
try:
|
||||
tree = ast.parse(expr, mode="eval")
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (
|
||||
ast.Expression, ast.Call, ast.Name, ast.Load,
|
||||
ast.BinOp, ast.UnaryOp, ast.Num, ast.Constant,
|
||||
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod,
|
||||
ast.Pow, ast.USub, ast.UAdd, ast.FloorDiv
|
||||
)):
|
||||
return "🚫 Invalid expression"
|
||||
return eval(compile(tree, "<calc>", "eval"), {"__builtins__": {}}, self._math_context)
|
||||
except Exception as e:
|
||||
return f"🚫 Error: {e}"
|
||||
|
||||
@loader.command(doc="[Math Expression] - Calculate a math expression", ru_doc="[Выражение] - Вычислить выражение")
|
||||
async def calc(self, message):
|
||||
expr = utils.get_args_raw(message)
|
||||
if not expr:
|
||||
return await utils.answer(message, self.strings("no_expr"))
|
||||
|
||||
result = self.safe_eval(expr)
|
||||
await utils.answer(message, self.strings("calc_result").format(expr=expr, result=result))
|
||||
|
||||
@loader.inline_everyone
|
||||
async def calc_inline_handler(self, query: InlineQuery):
|
||||
"""[Math Expression] - Calculate a math expression"""
|
||||
expr = query.args
|
||||
if not expr:
|
||||
return [
|
||||
{
|
||||
"title": "🧮 Calc",
|
||||
"description": "Введите выражение, например: 2+2 или sin(pi/2)",
|
||||
"message": "🔢 Просто введи математическое выражение после @бота",
|
||||
}
|
||||
]
|
||||
|
||||
result = self.safe_eval(expr)
|
||||
return [
|
||||
{
|
||||
"title": self.strings("inline_title").format(expr=expr),
|
||||
"description": self.strings("inline_desc").format(result=result),
|
||||
"message": self.strings("calc_result").format(expr=expr, result=result),
|
||||
}
|
||||
]
|
||||
117
fiksofficial/python-modules/channeladapter.py
Normal file
117
fiksofficial/python-modules/channeladapter.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @PyModule
|
||||
import json
|
||||
import os
|
||||
from telethon.tl.types import Message
|
||||
from .. import loader
|
||||
|
||||
@loader.tds
|
||||
class ChannelAdapterMod(loader.Module):
|
||||
"""Модуль для добавления переходника в сообщения каналов"""
|
||||
strings = {"name": "ChannelAdapter"}
|
||||
|
||||
def __init__(self):
|
||||
self.adapters_file = "adapters.json"
|
||||
self.adapters = self.load_adapters()
|
||||
|
||||
def load_adapters(self):
|
||||
"""Загружает адаптеры из файла, если он существует."""
|
||||
if os.path.exists(self.adapters_file):
|
||||
with open(self.adapters_file, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def save_adapters(self):
|
||||
"""Сохраняет адаптеры в файл."""
|
||||
with open(self.adapters_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self.adapters, f, ensure_ascii=False, indent=4)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
if not self.adapters:
|
||||
self.adapters = {}
|
||||
|
||||
@loader.command()
|
||||
async def addadaptercmd(self, message: Message):
|
||||
"""[CHANNEL ID] [Текст] - Добавить канал и переходник."""
|
||||
args = message.raw_text.split()
|
||||
if len(args) < 2:
|
||||
await message.edit("<emoji document_id=6030563507299160824>❗️</emoji> <b>Укажите ID канала.</b>")
|
||||
return
|
||||
|
||||
chat_id = args[1]
|
||||
adapter_text = " ".join(args[2:])
|
||||
|
||||
if not adapter_text:
|
||||
await message.edit("<emoji document_id=6030563507299160824>❗️</emoji> <b>Укажите текст переходника.</b>")
|
||||
return
|
||||
|
||||
self.adapters[chat_id] = adapter_text
|
||||
self.save_adapters()
|
||||
|
||||
await message.edit(f"<emoji document_id=5774022692642492953>✅</emoji> <b>Переходник добавлен для канала:</b> <code>{chat_id}</code> - {adapter_text}")
|
||||
|
||||
async def deladaptercmd(self, message: Message):
|
||||
"""[CHANNEL ID] - Удалить переходник для канала."""
|
||||
args = message.raw_text.split()
|
||||
if len(args) < 2:
|
||||
await message.edit("<emoji document_id=6030563507299160824>❗️</emoji> <b>Укажите ID канала.</b>")
|
||||
return
|
||||
|
||||
chat_id = args[1]
|
||||
|
||||
if chat_id not in self.adapters:
|
||||
await message.edit("<emoji document_id=5774077015388852135>❌</emoji> <b>Этот канал не найден в списке.</b>")
|
||||
return
|
||||
|
||||
del self.adapters[chat_id]
|
||||
self.save_adapters()
|
||||
|
||||
await message.edit(f"<emoji document_id=5774022692642492953>✅</emoji> <b>Переходник для канала <code>{chat_id}</code> удалён.</b>")
|
||||
|
||||
async def listadapterscmd(self, message: Message):
|
||||
"""- Показать список всех переходников."""
|
||||
if not self.adapters:
|
||||
await message.edit("<emoji document_id=5774077015388852135>❌</emoji> <b>Нет сохранённых переходников.</b>")
|
||||
return
|
||||
|
||||
text = "<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Список сохранённых переходников</b></blockquote>\n\n\n"
|
||||
for chat_id, adapter_text in self.adapters.items():
|
||||
text += f"<emoji document_id=6032924188828767321>➕</emoji> <b><code>{chat_id}</code>:</b> {adapter_text}\n\n"
|
||||
|
||||
await message.edit(text)
|
||||
|
||||
async def clearadapterscmd(self, message: Message):
|
||||
"""- Удалить все переходники."""
|
||||
if not self.adapters:
|
||||
await message.edit("<emoji document_id=5774077015388852135>❌</emoji> <b>Нет переходников для удаления.</b>")
|
||||
return
|
||||
|
||||
self.adapters = {}
|
||||
self.save_adapters()
|
||||
|
||||
await message.edit("<emoji document_id=5774022692642492953>✅</emoji> <b>Все адаптеры были удалены.</b>")
|
||||
|
||||
async def watcher(self, message: Message):
|
||||
"""Автоматически добавляет переходник в сообщения каналов"""
|
||||
if not message or not message.out:
|
||||
return
|
||||
|
||||
adapter_text = self.adapters.get(str(message.chat_id), None)
|
||||
|
||||
if not adapter_text:
|
||||
return
|
||||
|
||||
try:
|
||||
if message.text:
|
||||
modified_text = f"{message.text}\n\n{adapter_text}"
|
||||
await message.edit(modified_text, parse_mode='html')
|
||||
elif message.media:
|
||||
modified_caption = f"{message.text}\n\n{adapter_text}" if message.text else adapter_text
|
||||
await message.edit(text=modified_caption, parse_mode='html')
|
||||
except Exception as e:
|
||||
me = await self.client.get_me()
|
||||
await self.client.send_message(me.id, f"<emoji document_id=6030563507299160824>❗️</emoji> <b>Ошибка в ChannelAdapter:</b>\n`{str(e)}`")
|
||||
122
fiksofficial/python-modules/checkhost.py
Normal file
122
fiksofficial/python-modules/checkhost.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from .. import loader, utils
|
||||
import aiohttp
|
||||
import asyncio
|
||||
from ..inline.types import InlineQuery
|
||||
|
||||
|
||||
@loader.tds
|
||||
class CheckHostMod(loader.Module):
|
||||
"""Check host via check-host.net"""
|
||||
|
||||
strings = {
|
||||
"name": "CheckHost",
|
||||
"no_target": "❗ Please specify a host (IP or domain).",
|
||||
"checking": "🔍 Checking <code>{}</code> using <b>{}</b>...",
|
||||
"result": "📡 <b>{}</b> results for <code>{}</code>:\n\n{}",
|
||||
"error": "❌ Error: <code>{}</code>",
|
||||
"inline_select": "☑️ Choose check type for <b>{}</b>:",
|
||||
"btn_ping": "🏓 Ping",
|
||||
"btn_http": "🌐 HTTP",
|
||||
"btn_tcp": "🔌 TCP",
|
||||
"btn_dns": "🧬 DNS",
|
||||
"no_response": "❌ No response",
|
||||
"ok_response": "✅ {}",
|
||||
"unknown_format": "⚠ Unknown format",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "CheckHost",
|
||||
"no_target": "❗ Укажи хост (IP или домен).",
|
||||
"checking": "🔍 Проверка <code>{}</code> через <b>{}</b>...",
|
||||
"result": "📡 <b>Результаты {}</b> для <code>{}</code>:\n\n{}",
|
||||
"error": "❌ Ошибка: <code>{}</code>",
|
||||
"inline_select": "☑️ Выбери тип проверки для <b>{}</b>:",
|
||||
"btn_ping": "🏓 Пинг",
|
||||
"btn_http": "🌐 HTTP",
|
||||
"btn_tcp": "🔌 TCP",
|
||||
"btn_dns": "🧬 DNS",
|
||||
"no_response": "❌ Нет ответа",
|
||||
"ok_response": "✅ {}",
|
||||
"unknown_format": "⚠ Неизвестный формат",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._last_host = None
|
||||
|
||||
@loader.command(doc="[host] — check host", ru_doc="[хост] — проверить хост")
|
||||
async def checkhost(self, message):
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return await utils.answer(message, self.strings("no_target"))
|
||||
|
||||
host = args.strip()
|
||||
self._last_host = host
|
||||
|
||||
await self.inline.form(
|
||||
text=self.strings("inline_select").format(host),
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{"text": self.strings("btn_ping"), "callback": self._callback("ping", host)},
|
||||
{"text": self.strings("btn_http"), "callback": self._callback("http", host)}
|
||||
],
|
||||
[
|
||||
{"text": self.strings("btn_tcp"), "callback": self._callback("tcp", host)},
|
||||
{"text": self.strings("btn_dns"), "callback": self._callback("dns", host)}
|
||||
]
|
||||
],
|
||||
force_me=True,
|
||||
silent=True
|
||||
)
|
||||
|
||||
def _callback(self, check_type: str, host: str):
|
||||
async def wrapped(call):
|
||||
try:
|
||||
await call.edit(self.strings("checking").format(host, check_type.upper()))
|
||||
result = await self._run_check(check_type, host)
|
||||
await call.edit(self.strings("result").format(check_type.upper(), host, result))
|
||||
except Exception as e:
|
||||
await call.edit(self.strings("error").format(str(e)))
|
||||
return wrapped
|
||||
|
||||
async def _run_check(self, check_type, target):
|
||||
async with aiohttp.ClientSession(headers={"Accept": "application/json"}) as session:
|
||||
params = {"host": target, "max_nodes": 3}
|
||||
start_url = f"https://check-host.net/check-{check_type}"
|
||||
async with session.get(start_url, params=params) as resp:
|
||||
data = await resp.json()
|
||||
|
||||
request_id = data.get("request_id")
|
||||
if not request_id:
|
||||
raise ValueError("API error: no request_id")
|
||||
|
||||
await asyncio.sleep(3)
|
||||
result_url = f"https://check-host.net/check-result/{request_id}"
|
||||
async with session.get(result_url, params={"accept": "application/json"}) as resp:
|
||||
data = await resp.json()
|
||||
|
||||
return self._parse_results(data)
|
||||
|
||||
def _parse_results(self, data):
|
||||
if not isinstance(data, dict):
|
||||
return self.strings("error").format("Invalid API response")
|
||||
|
||||
lines = []
|
||||
for node, result in data.items():
|
||||
location = node.split(".")[0]
|
||||
if result is None:
|
||||
lines.append(f"🌐 {location}: {self.strings('no_response')}")
|
||||
continue
|
||||
|
||||
if isinstance(result, list) and result:
|
||||
first = result[0]
|
||||
if isinstance(first, list) and len(first) > 1:
|
||||
lines.append(f"🌐 {location}: {self.strings('ok_response').format(first[1])}")
|
||||
else:
|
||||
lines.append(f"🌐 {location}: {self.strings('ok_response').format(first)}")
|
||||
elif isinstance(result, str):
|
||||
lines.append(f"🌐 {location}: {self.strings('ok_response').format(result)}")
|
||||
else:
|
||||
lines.append(f"🌐 {location}: {self.strings('unknown_format')}")
|
||||
|
||||
return "\n".join(lines)
|
||||
493
fiksofficial/python-modules/cutemessages.py
Normal file
493
fiksofficial/python-modules/cutemessages.py
Normal file
@@ -0,0 +1,493 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
import random
|
||||
import re
|
||||
import logging
|
||||
from .. import loader, utils
|
||||
from telethon.tl.types import Message
|
||||
|
||||
@loader.tds
|
||||
class CuteMessages(loader.Module):
|
||||
"""Makes your messages extra cute with adorable styles!"""
|
||||
|
||||
strings = {
|
||||
"name": "CuteMessages",
|
||||
"ENABLED": "✅ Cute messages enabled",
|
||||
"DISABLED": "❌ Cute messages disabled",
|
||||
"SETTINGS_UPDATED": "Settings updated! Use .cutemessages settings to view.",
|
||||
"CURRENT_SETTINGS": "Current settings:\n{settings}",
|
||||
"INVALID_SETTING": "Invalid setting or value! Use .cutemessages settings for available options.",
|
||||
"SETTINGS_HEADER": "Cute Messages Settings",
|
||||
"ENABLE_SWITCH": "Enable cute messages",
|
||||
"IGNORE_DOT_COMMANDS_SWITCH": "Ignore dot-commands (.)",
|
||||
"EMOJI_FREQUENCY": "Classic effects frequency",
|
||||
"TEXT_STYLE": "Text style (classic)",
|
||||
"FREQUENCY_VERY_LOW": "Very Low (10%)",
|
||||
"FREQUENCY_LOW": "Low (25%)",
|
||||
"FREQUENCY_MEDIUM": "Medium (50%)",
|
||||
"FREQUENCY_HIGH": "High (75%)",
|
||||
"FREQUENCY_MAX": "Maximum (100%)",
|
||||
"STYLE_EMOJIS": "Emojis only",
|
||||
"STYLE_KAOMOJI": "Kaomoji (◕‿◕)",
|
||||
"STYLE_SPARKLES": "Sparkles ✨",
|
||||
"STYLE_FULL_CLASSIC": "All classic effects",
|
||||
"ENABLE_LOWERCASE_SWITCH": "Convert to lowercase",
|
||||
"ENABLE_UWU_SPEAK_SWITCH": "Enable UwU-speak (r/l → w)",
|
||||
"ENABLE_UWU_SUFFIXES_SWITCH": "Add UwU suffixes (nya, owo)",
|
||||
"UWU_SUFFIXES_FREQUENCY": "UwU suffix frequency",
|
||||
"ENABLE_STUTTERING_SWITCH": "Enable stuttering (h-hello)",
|
||||
"STUTTERING_FREQUENCY": "Stuttering frequency",
|
||||
"ENABLE_VOWEL_STRETCHING_SWITCH": "Stretch vowels (cuuute)",
|
||||
"VOWEL_STRETCHING_FREQUENCY": "Vowel stretching frequency",
|
||||
"VOWEL_STRETCHING_MAX_LENGTH": "Max stretched vowel length",
|
||||
"MAX_LENGTH_2X": "Double (x2)",
|
||||
"MAX_LENGTH_3X": "Triple (x3)",
|
||||
"ENABLE_CUTE_ACTIONS_SWITCH": "Add cute actions (*hugs*)",
|
||||
"CUTE_ACTIONS_FREQUENCY": "Cute actions frequency",
|
||||
"ACTIONS_ON_NEW_LINE": "Actions on new line",
|
||||
"ENABLE_CUTE_PUNCTUATION_SWITCH": "Cute punctuation (. → .~, ? → ?✨)",
|
||||
"CUTE_PUNCTUATION_FREQUENCY": "Cute punctuation frequency",
|
||||
"ENABLE_SOFT_SIGN_SWITCH": "Add soft sign ('ь') to word endings (kotikь)",
|
||||
"SOFT_SIGN_FREQUENCY": "Soft sign frequency",
|
||||
"ENABLE_TEXT_BORDERS": "Enable text borders",
|
||||
"TEXT_BORDERS_FREQUENCY": "Text borders frequency",
|
||||
"THEME_SELECTOR": "Theme selector",
|
||||
"THEME_RANDOM": "Random",
|
||||
"THEME_PASTEL": "Pastel",
|
||||
"THEME_MAGICAL": "Magical",
|
||||
"THEME_NATURE": "Nature",
|
||||
"ERROR_MESSAGE_CUTE": "Oopsie! 🥺 Something went wrong while trying to make the message cute... Here's the original: {original}",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "CuteMessages",
|
||||
"ENABLED": "✅ Милые сообщения включены",
|
||||
"DISABLED": "❌ Милые сообщения отключены",
|
||||
"SETTINGS_UPDATED": "Настройки обновлены! Используйте .cutemessages settings для просмотра.",
|
||||
"CURRENT_SETTINGS": "Текущие настройки:\n{settings}",
|
||||
"INVALID_SETTING": "Неверная настройка или значение! Используйте .cutemessages settings для доступных опций.",
|
||||
"SETTINGS_HEADER": "Настройки милых сообщений",
|
||||
"ENABLE_SWITCH": "Включить милые сообщения",
|
||||
"IGNORE_DOT_COMMANDS_SWITCH": "Игнорировать команды (.)",
|
||||
"EMOJI_FREQUENCY": "Частота классических эффектов",
|
||||
"TEXT_STYLE": "Стиль текста (классика)",
|
||||
"FREQUENCY_VERY_LOW": "Очень низкая (10%)",
|
||||
"FREQUENCY_LOW": "Низкая (25%)",
|
||||
"FREQUENCY_MEDIUM": "Средняя (50%)",
|
||||
"FREQUENCY_HIGH": "Высокая (75%)",
|
||||
"FREQUENCY_MAX": "Максимальная (100%)",
|
||||
"STYLE_EMOJIS": "Только эмодзи",
|
||||
"STYLE_KAOMOJI": "Каомодзи (◕‿◕)",
|
||||
"STYLE_SPARKLES": "Звездочки ✨",
|
||||
"STYLE_FULL_CLASSIC": "Все классические эффекты",
|
||||
"ENABLE_LOWERCASE_SWITCH": "Преобразовать в строчные буквы",
|
||||
"ENABLE_UWU_SPEAK_SWITCH": "Включить UwU-стиль (р/л → в/w)",
|
||||
"ENABLE_UWU_SUFFIXES_SWITCH": "Добавлять UwU-суффиксы (nya, owo)",
|
||||
"UWU_SUFFIXES_FREQUENCY": "Частота UwU-суффиксов",
|
||||
"ENABLE_STUTTERING_SWITCH": "Включить заикание (п-привет)",
|
||||
"STUTTERING_FREQUENCY": "Частота заикания",
|
||||
"ENABLE_VOWEL_STRETCHING_SWITCH": "Растягивать гласные (милооо)",
|
||||
"VOWEL_STRETCHING_FREQUENCY": "Частота растягивания гласных",
|
||||
"VOWEL_STRETCHING_MAX_LENGTH": "Макс. длина растянутой гласной",
|
||||
"MAX_LENGTH_2X": "Двойная (x2)",
|
||||
"MAX_LENGTH_3X": "Тройная (x3)",
|
||||
"ENABLE_CUTE_ACTIONS_SWITCH": "Добавлять милые действия (*обнимает*)",
|
||||
"CUTE_ACTIONS_FREQUENCY": "Частота милых действий",
|
||||
"ACTIONS_ON_NEW_LINE": "Действия на новой строке",
|
||||
"ENABLE_CUTE_PUNCTUATION_SWITCH": "Милая пунктуация (. → .~, ? → ?✨)",
|
||||
"CUTE_PUNCTUATION_FREQUENCY": "Частота милой пунктуации",
|
||||
"ENABLE_SOFT_SIGN_SWITCH": "Добавлять 'ь' в конце слов (котикь)",
|
||||
"SOFT_SIGN_FREQUENCY": "Частота добавления 'ь'",
|
||||
"ENABLE_TEXT_BORDERS": "Включить рамки текста",
|
||||
"TEXT_BORDERS_FREQUENCY": "Частота рамок текста",
|
||||
"THEME_SELECTOR": "Выбор темы",
|
||||
"THEME_RANDOM": "Случайная",
|
||||
"THEME_PASTEL": "Пастельная",
|
||||
"THEME_MAGICAL": "Волшебная",
|
||||
"THEME_NATURE": "Природная",
|
||||
"ERROR_MESSAGE_CUTE": "Ой-ой! 🥺 Что-то пошло не так, когда я пытался сделать сообщение милым... Вот оригинал: {original}",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.emojis = ["🥰", "😊", "💕", "💖", "💗", "🌸", "✨", "🦄", "🌈", "🍭", "🧸", "🌟", "💫", "🌻", "🍬", "🎀", "💝", "💓", "🍨", "🌷", "🦋", "🐇", "🐱", "🐶", "🦊", "🥺", "👉👈", "🍡", "🧁", "🍰", "🌺", "🌹", "💮", "🧚♀️", "💘", "💞", "🩷", "🩵", "🌞", "🫧", "🫶", "🦢", "🐹", "🐰", "🌼", "🧿"]
|
||||
self.kaomojis = ["(◕‿◕)", "♡(˘▽˘)♡", "(つ≧▽≦)つ", "(≧◡≦)", "(*^ω^*)", "(っ˘ω˘ς)", "(´。• ω •。`)", "ʕ•ᴥ•ʔ", "(づ。◕‿‿◕。)づ", "ฅ^•ﻌ•^ฅ", "(*˘︶˘*)", "(*¯︶¯*)", "( ˘ ³˘)♥", "(っ•ᴗ•)っ", "ლ(╹◡╹ლ)", "(๑˃ᴗ˂)ﻭ", "(灬ºωº灬)♡", "૮₍˶ᵔ ᵕ ᵔ˶₎ა", "ฅ^•ﻌ•^ฅ", "(*ฅ́˘ฅ̀*)", "(●'◡'●)", "ฅ(^◕ᴥ◕^)ฅ", "(=^・ω・^=)", "ʕっ•ᴥ•ʔっ", "ʕ ꈍᴥꈍʔ", "ʕ´•ᴥ•`ʔ", "(◡ ω ◡)", "(◕ᴗ◕✿)", "꒰⑅ᵕ༚ᵕ꒱˖♡", "ପ(๑•ᴗ•๑)ଓ ♡", "(⁄ ⁄•⁄ω⁄•⁄ ⁄)"]
|
||||
self.sparkles = ["✨", "⭐", "★", "☆", "₊˚⊹", "˚₊· ͟͟͞͞➳❥", "⋆。°✩", "☆彡", "⊰", "⊱", "✧・゚", "♡", "❀", "❁", "❃", "❋", "✿", "♫", "♪", "✧˖°", "⋆。˚", "⋆⭒˚。⋆", "✧*。", "⁺˚*•̩̩͙✩•̩̩͙*˚⁺", "‧₊˚✧", "ପ♡ଓ", "✩°。⋆⸜", "✮", "✩₊˚.⋆", "☽˚。⋆", "❥", "༘⋆", "⋆⭒˚。⋆", "✮.°:⋆ₓₒ", "✧・゚:✧・゚", "*ੈ✩‧₊˚", "┊͙ ˘͈ᵕ˘͈", "ೃ࿔₊", "˗ˏˋ ★ ˎˊ˗"]
|
||||
self.uwu_suffixes = ["~", " nya~", " uwu", " owo", " >w<", " :3", " nyaaa", "σωσ", "◡ ω ◡", " OwO", " UwU~", " hehe~", " rawr~", " mew", " purr~", " ehehe", " uwu~", " (⁄ ⁄>⁄ω⁄<⁄ ⁄)", " kyaa~", " nyaa", " nyuu~", " mya~", " (◕ᴗ◕✿)", " teehee", " hehehe", " awoo~", " *blushes*", " purrr", " pwease", " nya?"]
|
||||
self.extended_exclamations = ["~!", "!!", "!!!", "!~", "!❤", "!✨", "!💖", "!⭐", "!!!1!", "! nya~", "!?!?", "!!!💕", "~!!~", "! ꒰◍ᐡᐤᐡ◍꒱", "! ❁◕ ‿ ◕❁", "!!♡", "! (*ฅ́˘ฅ̀*)", "~✿!", "! ♡₊˚", "!⋆₊", "! 🎀", "! 🌸", "!🌟", "!☆"]
|
||||
self.vowels_map = {
|
||||
'ru': "аеёиоуыэюяАЕЁИОУЫЭЮЯ",
|
||||
'en': "aeiouyAEIOUY"
|
||||
}
|
||||
self.letter_pattern = re.compile(r'^\w', re.UNICODE)
|
||||
self.cute_actions_map = {
|
||||
"ru": ["*обнимает*", "*нежно обнимает*", "*гладит по голове*", "*хихикает*", "*мурлычет*", "*улыбается*", "*подмигивает*", "*машет лапкой*", "*краснеет*", "*прыгает от радости*", "*делает большие глаза*", "*играет с волосами*", "*качает хвостиком*", "*делает милое личико*", "*танцует от счастья*", "*прячется за лапками*", "*радостно вздыхает*"],
|
||||
"en": ["*hugs*", "*gentle hug*", "*pats head*", "*giggles*", "*purrs*", "*smiles*", "*winks*", "*waves paw*", "*blushes*", "*jumps with joy*", "*makes big eyes*", "*plays with hair*", "*wags tail*", "*makes cute face*", "*happy dance*", "*hides behind paws*", "*happy sigh*"]
|
||||
}
|
||||
self.cute_period_replacements = [".~", ".!", ".✨", ".🌸", ".💖", "~~", " ^^", "₊˚ʚ ᗢ₊˚✧", "✿", ".♡", ".˚ʚ♡ɞ˚", ".ᐟ", ".⋆", ".♬"]
|
||||
self.cute_question_mark_replacements = ["?!!", "???", "❓💖", "?~", "❓✨", " uwu?", "?✧", "?🥺", "??♡", "??🌸", "?☆"]
|
||||
self.consonants_ru = "бвгджзйклмнпрстфхцчшщ"
|
||||
self.text_borders = [
|
||||
["🌸 ", " 🌸"],
|
||||
["✧・゚: ", " :・゚✧"],
|
||||
["— ♡ ", " ♡ —"],
|
||||
["꒩ ", " ꒪"],
|
||||
["1", " 2"],
|
||||
["⋆⠀", " ✩"]
|
||||
]
|
||||
self.themes = {
|
||||
"pastel": {
|
||||
"emojis": ["🌸", "🎀", "💖", "🧸"],
|
||||
"words": ["cute", "sweet"],
|
||||
"prefix": ["✿"],
|
||||
"suffix": ["✧"]
|
||||
},
|
||||
"magical": {
|
||||
"emojis": ["✨", "⭐", "🦄"],
|
||||
"words": ["magic"],
|
||||
"prefix": ["✧"],
|
||||
"suffix": ["☆"]
|
||||
},
|
||||
"nature": {
|
||||
"emojis": ["🌷", "🌱", "🦋"],
|
||||
"words": ["flower", "bloom"],
|
||||
"prefix": ["❀"],
|
||||
"suffix": ["🌿"]
|
||||
}
|
||||
}
|
||||
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"enabled",
|
||||
False,
|
||||
"Enable or disable cute message effects",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"ignore_dot_commands",
|
||||
True,
|
||||
"Ignore messages starting with a dot (e.g., .command)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"emoji_frequency",
|
||||
1,
|
||||
"Frequency of classic effects (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"text_style",
|
||||
0,
|
||||
"Style of classic effects (0: emojis, 1: kaomoji, 2: sparkles, 3: all)",
|
||||
validator=loader.validators.Choice([0, 1, 2, 3]),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_text_borders",
|
||||
False,
|
||||
"Enable text borders around messages",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"text_borders_frequency",
|
||||
2,
|
||||
"Frequency of text borders (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"theme_selector",
|
||||
0,
|
||||
"Theme for messages (0: random, 1: pastel, 2: magical, 3: nature)",
|
||||
validator=loader.validators.Choice([0, 1, 2, 3]),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_lowercase",
|
||||
False,
|
||||
"Convert messages to lowercase",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_uwu_speak",
|
||||
False,
|
||||
"Enable UwU-speak (r/l → w in English, р/л → в in Russian)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_uwu_suffixes",
|
||||
False,
|
||||
"Add UwU suffixes (e.g., nya, owo)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"uwu_suffixes_frequency",
|
||||
2,
|
||||
"Frequency of UwU suffixes (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_stuttering",
|
||||
False,
|
||||
"Enable stuttering effect (e.g., h-hello)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"stuttering_frequency",
|
||||
1,
|
||||
"Frequency of stuttering (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_vowel_stretching",
|
||||
False,
|
||||
"Enable vowel stretching (e.g., cuuute)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"vowel_stretching_frequency",
|
||||
2,
|
||||
"Frequency of vowel stretching (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"vowel_stretching_max_length",
|
||||
0,
|
||||
"Max vowel stretch length (0: double, 1: triple)",
|
||||
validator=loader.validators.Choice([0, 1]),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_cute_actions",
|
||||
False,
|
||||
"Add cute actions (e.g., *hugs*)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"cute_actions_frequency",
|
||||
2,
|
||||
"Frequency of cute actions (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"actions_on_new_line",
|
||||
False,
|
||||
"Place cute actions on a new line",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_cute_punctuation",
|
||||
False,
|
||||
"Use cute punctuation (e.g., . → .~, ? → ?✨)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"cute_punctuation_frequency",
|
||||
1,
|
||||
"Frequency of cute punctuation (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"enable_soft_sign",
|
||||
False,
|
||||
"Add soft sign ('ь') to Russian word endings (e.g., kotikь)",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"soft_sign_frequency",
|
||||
2,
|
||||
"Frequency of soft sign (0: 10%, 1: 25%, 2: 50%, 3: 75%, 4: 100%)",
|
||||
validator=loader.validators.Integer(minimum=0, maximum=4),
|
||||
),
|
||||
)
|
||||
|
||||
def _get_frequency_prob(self, key):
|
||||
frequency_idx = self.config[key]
|
||||
return {0: 0.10, 1: 0.25, 3: 0.75, 4: 1.00}.get(frequency_idx, 0.50)
|
||||
|
||||
def _is_russian(self, text):
|
||||
return bool(re.search(r'[а-яА-Я]', text))
|
||||
|
||||
def _apply_lowercase(self, text):
|
||||
if self.config["enable_lowercase"]:
|
||||
return text.lower()
|
||||
return text
|
||||
|
||||
def _apply_uwu_speak(self, text):
|
||||
if not self.config["enable_uwu_speak"]:
|
||||
return text
|
||||
lang = 'ru' if self._is_russian(text) else 'en'
|
||||
if lang == 'ru':
|
||||
text = text.replace('р', 'в').replace('Р', 'В').replace('л', 'в').replace('Л', 'В')
|
||||
else:
|
||||
text = text.replace('r', 'w').replace('R', 'W').replace('l', 'w').replace('L', 'W')
|
||||
return text
|
||||
|
||||
def _apply_uwu_suffixes(self, text):
|
||||
if not self.config["enable_uwu_suffixes"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("uwu_suffixes_frequency")
|
||||
if random.random() < prob:
|
||||
return text + " " + random.choice(self.uwu_suffixes)
|
||||
return text
|
||||
|
||||
def _apply_stuttering(self, text):
|
||||
if not self.config["enable_stuttering"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("stuttering_frequency")
|
||||
words = text.split()
|
||||
new_words = []
|
||||
for word in words:
|
||||
if random.random() < prob and self.letter_pattern.match(word):
|
||||
new_words.append(word[0] + '-' + word)
|
||||
else:
|
||||
new_words.append(word)
|
||||
return ' '.join(new_words)
|
||||
|
||||
def _apply_vowel_stretching(self, text):
|
||||
if not self.config["enable_vowel_stretching"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("vowel_stretching_frequency")
|
||||
max_length = 3 if self.config["vowel_stretching_max_length"] else 2
|
||||
lang = 'ru' if self._is_russian(text) else 'en'
|
||||
vowels = self.vowels_map[lang]
|
||||
words = text.split()
|
||||
new_words = []
|
||||
for word in words:
|
||||
new_word = word
|
||||
for vowel in vowels:
|
||||
if random.random() < prob and vowel in new_word:
|
||||
count = random.randint(1, max_length)
|
||||
new_word = new_word.replace(vowel, vowel * (count + 1))
|
||||
new_words.append(new_word)
|
||||
return ' '.join(new_words)
|
||||
|
||||
def _apply_cute_actions(self, text):
|
||||
if not self.config["enable_cute_actions"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("cute_actions_frequency")
|
||||
lang = 'ru' if self._is_russian(text) else 'en'
|
||||
if random.random() < prob:
|
||||
action = random.choice(self.cute_actions_map[lang])
|
||||
if self.config["actions_on_new_line"]:
|
||||
return text + "\n" + action
|
||||
else:
|
||||
return text + " " + action
|
||||
return text
|
||||
|
||||
def _apply_cute_punctuation(self, text):
|
||||
if not self.config["enable_cute_punctuation"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("cute_punctuation_frequency")
|
||||
if random.random() < prob:
|
||||
text = text.replace('.', random.choice(self.cute_period_replacements))
|
||||
text = text.replace('?', random.choice(self.cute_question_mark_replacements))
|
||||
return text
|
||||
|
||||
def _apply_soft_sign(self, text):
|
||||
if not self.config["enable_soft_sign"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("soft_sign_frequency")
|
||||
if not self._is_russian(text):
|
||||
return text
|
||||
words = text.split()
|
||||
new_words = []
|
||||
for word in words:
|
||||
if random.random() < prob and word and word[-1] not in self.consonants_ru:
|
||||
new_words.append(word + 'ь')
|
||||
else:
|
||||
new_words.append(word)
|
||||
return ' '.join(new_words)
|
||||
|
||||
def _apply_classic_effects(self, text):
|
||||
if not self.config["enabled"]:
|
||||
return text
|
||||
style = self.config["text_style"]
|
||||
prob = self._get_frequency_prob("emoji_frequency")
|
||||
if style == 0 and random.random() < prob:
|
||||
return text + " " + random.choice(self.emojis)
|
||||
elif style == 1 and random.random() < prob:
|
||||
return text + " " + random.choice(self.kaomojis)
|
||||
elif style == 2 and random.random() < prob:
|
||||
return text + " " + random.choice(self.sparkles)
|
||||
elif style == 3 and random.random() < prob:
|
||||
effect = random.choice(self.emojis + self.kaomojis + self.sparkles)
|
||||
return text + " " + effect
|
||||
return text
|
||||
|
||||
def _apply_text_borders(self, text):
|
||||
if not self.config["enable_text_borders"]:
|
||||
return text
|
||||
prob = self._get_frequency_prob("text_borders_frequency")
|
||||
if random.random() < prob:
|
||||
border = random.choice(self.text_borders)
|
||||
return border[0] + text + border[1]
|
||||
return text
|
||||
|
||||
def _apply_theme(self, text):
|
||||
theme_idx = self.config["theme_selector"]
|
||||
if theme_idx == 0: # Random
|
||||
theme_key = random.choice(["pastel", "magical", "nature"])
|
||||
else:
|
||||
theme_key = ["pastel", "magical", "nature"][theme_idx - 1]
|
||||
theme = self.themes[theme_key]
|
||||
if random.random() < 0.1:
|
||||
return random.choice(theme["prefix"]) + " " + text + " " + random.choice(theme["suffix"])
|
||||
return text
|
||||
|
||||
def make_cute(self, text):
|
||||
try:
|
||||
original = text
|
||||
text = self._apply_lowercase(text)
|
||||
text = self._apply_uwu_speak(text)
|
||||
text = self._apply_uwu_suffixes(text)
|
||||
text = self._apply_stuttering(text)
|
||||
text = self._apply_vowel_stretching(text)
|
||||
text = self._apply_cute_actions(text)
|
||||
text = self._apply_cute_punctuation(text)
|
||||
text = self._apply_soft_sign(text)
|
||||
text = self._apply_classic_effects(text)
|
||||
text = self._apply_text_borders(text)
|
||||
text = self._apply_theme(text)
|
||||
return text
|
||||
except Exception as e:
|
||||
logging.error(f"CuteMessages error: {str(e)}")
|
||||
return self.strings.get("ERROR_MESSAGE_CUTE", "Oopsie! 🥺 Something went wrong... Here's the original: {original}").format(original=original)
|
||||
|
||||
@loader.watcher(out=True)
|
||||
async def watcher(self, message: Message):
|
||||
if not self.config["enabled"]:
|
||||
return
|
||||
if self.config["ignore_dot_commands"] and message.text.startswith(self.get_prefix()):
|
||||
return
|
||||
if not message.text:
|
||||
return
|
||||
try:
|
||||
cute_text = self.make_cute(message.text)
|
||||
await message.edit(cute_text)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to edit message: {e}")
|
||||
|
||||
@loader.command(
|
||||
doc="Toggle CuteMessages on or off.",
|
||||
ru_doc="Включение или выключение CuteMessages.",
|
||||
)
|
||||
@loader.command()
|
||||
async def cutemessages(self, message: Message):
|
||||
"""Toggle CuteMessages on or off."""
|
||||
self.config["enabled"] = not self.config["enabled"]
|
||||
await message.edit(self.strings["ENABLED" if self.config["enabled"] else "DISABLED"])
|
||||
|
||||
@loader.command(
|
||||
doc="View and modify settings for CuteMessages.",
|
||||
ru_doc="Просмотр и изменение настроек CuteMessages.",
|
||||
)
|
||||
async def cutemessages_settings(self, message: Message):
|
||||
await self.invoke("config", "CuteMessages", peer=message.peer_id)
|
||||
190
fiksofficial/python-modules/dscanner.py
Normal file
190
fiksofficial/python-modules/dscanner.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# scope: hikka_only
|
||||
# meta developer: @pymodule
|
||||
# requires: python-whois dnspython requests
|
||||
|
||||
import socket
|
||||
import whois
|
||||
import requests
|
||||
import dns.resolver
|
||||
import asyncio
|
||||
import ssl
|
||||
from .. import loader, utils
|
||||
|
||||
|
||||
class DomainScannerMod(loader.Module):
|
||||
"""Scan a domain / Сканирование домена"""
|
||||
strings = {
|
||||
"name": "DomainScanner",
|
||||
"no_domain": "Specify a domain to scan.",
|
||||
"scanning": "🔍 Scanning <code>{}</code>...",
|
||||
"ip": "🖥 IP: {}",
|
||||
"ip_fail": "⚠️ Failed to get IP.",
|
||||
"whois": "📜 WHOIS:\n{}",
|
||||
"whois_fail": "⚠️ Failed to get WHOIS.",
|
||||
"dns": "🛡 DNS A records:",
|
||||
"dns_fail": "⚠️ Failed to get DNS records.",
|
||||
"mx": "📧 MX records:",
|
||||
"mx_fail": "⚠️ Failed to get MX records.",
|
||||
"txt": "📄 TXT records:",
|
||||
"txt_fail": "⚠️ Failed to get TXT records.",
|
||||
"ssl": "🔒 SSL Certificate:\n - Issued by: {}\n - Expires: {}",
|
||||
"ssl_fail": "⚠️ Failed to get SSL certificate.",
|
||||
"subs": "🌐 Subdomains:",
|
||||
"subs_fail": "⚠️ No subdomains found.",
|
||||
"http": "📶 HTTP Status: {}",
|
||||
"http_fail": "⚠️ Failed to get HTTP status.",
|
||||
"ports": "🚪 Open ports: {}",
|
||||
"ports_fail": "⚠️ No open ports found.",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_domain": "Укажите домен для сканирования.",
|
||||
"scanning": "🔍 Сканирую <code>{}</code>...",
|
||||
"ip": "🖥 IP: {}",
|
||||
"ip_fail": "⚠️ Не удалось получить IP.",
|
||||
"whois": "📜 WHOIS:\n{}",
|
||||
"whois_fail": "⚠️ Не удалось получить WHOIS.",
|
||||
"dns": "🛡 DNS A-записи:",
|
||||
"dns_fail": "⚠️ Не удалось получить DNS-записи.",
|
||||
"mx": "📧 MX-записи:",
|
||||
"mx_fail": "⚠️ Не удалось получить MX-записи.",
|
||||
"txt": "📄 TXT-записи:",
|
||||
"txt_fail": "⚠️ Не удалось получить TXT-записи.",
|
||||
"ssl": "🔒 SSL-сертификат:\n - Выдан: {}\n - Истекает: {}",
|
||||
"ssl_fail": "⚠️ Не удалось получить SSL-сертификат.",
|
||||
"subs": "🌐 Поддомены:",
|
||||
"subs_fail": "⚠️ Поддомены не найдены.",
|
||||
"http": "📶 Статус HTTP: {}",
|
||||
"http_fail": "⚠️ Не удалось получить HTTP-статус.",
|
||||
"ports": "🚪 Открытые порты: {}",
|
||||
"ports_fail": "⚠️ Открытые порты не найдены.",
|
||||
}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
|
||||
@loader.command(
|
||||
doc="Scan domain. Usage: .domscan <domain>",
|
||||
ru_doc="Сканировать домен. Использование: .domscan <домен>"
|
||||
)
|
||||
async def domscancmd(self, message):
|
||||
"""Scan domain / Сканировать домен. Usage: .domscan <domain>"""
|
||||
domain = utils.get_args_raw(message).strip()
|
||||
if not domain:
|
||||
return await utils.answer(message, self.strings("no_domain"))
|
||||
|
||||
await utils.answer(message, self.strings("scanning").format(domain))
|
||||
|
||||
result = []
|
||||
|
||||
async def get_ip():
|
||||
try:
|
||||
return socket.gethostbyname(domain)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_whois():
|
||||
try:
|
||||
return await asyncio.to_thread(whois.whois, domain)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_dns_record(rtype):
|
||||
try:
|
||||
return dns.resolver.resolve(domain, rtype)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_ssl_info():
|
||||
try:
|
||||
ctx = ssl.create_default_context()
|
||||
with ctx.wrap_socket(socket.create_connection((domain, 443), timeout=5), server_hostname=domain) as s:
|
||||
return s.getpeercert()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def check_subdomains(subs):
|
||||
found = []
|
||||
for sub in subs:
|
||||
subdomain = f"{sub}.{domain}"
|
||||
try:
|
||||
ip = socket.gethostbyname(subdomain)
|
||||
found.append(f" - {subdomain} → {ip}")
|
||||
except Exception:
|
||||
continue
|
||||
return found
|
||||
|
||||
async def check_http():
|
||||
try:
|
||||
r = requests.get(f"http://{domain}", timeout=5)
|
||||
return r.status_code
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def check_ports():
|
||||
ports = []
|
||||
for port in [21, 22, 25, 53, 80, 110, 143, 443, 587, 993, 995]:
|
||||
try:
|
||||
with socket.create_connection((domain, port), timeout=1):
|
||||
ports.append(str(port))
|
||||
except Exception:
|
||||
continue
|
||||
return ports
|
||||
|
||||
ip, whois_info, dns_a, dns_mx, dns_txt, ssl_cert, subdomains, http_status, open_ports = await asyncio.gather(
|
||||
get_ip(), get_whois(), get_dns_record("A"), get_dns_record("MX"),
|
||||
get_dns_record("TXT"), get_ssl_info(),
|
||||
check_subdomains(["www", "mail", "ftp", "api", "dev", "blog", "admin", "portal", "shop"]),
|
||||
check_http(), check_ports()
|
||||
)
|
||||
|
||||
result.append(self.strings("ip").format(ip) if ip else self.strings("ip_fail"))
|
||||
|
||||
if whois_info:
|
||||
summary = str(whois_info)
|
||||
result.append(self.strings("whois").format(summary))
|
||||
else:
|
||||
result.append(self.strings("whois_fail"))
|
||||
|
||||
if dns_a:
|
||||
result.append(self.strings("dns"))
|
||||
result.extend([f" - {r.to_text()}" for r in dns_a])
|
||||
else:
|
||||
result.append(self.strings("dns_fail"))
|
||||
|
||||
if dns_mx:
|
||||
result.append(self.strings("mx"))
|
||||
result.extend([f" - {r.to_text()}" for r in dns_mx])
|
||||
else:
|
||||
result.append(self.strings("mx_fail"))
|
||||
|
||||
if dns_txt:
|
||||
result.append(self.strings("txt"))
|
||||
result.extend([f" - {r.to_text()}" for r in dns_txt])
|
||||
else:
|
||||
result.append(self.strings("txt_fail"))
|
||||
|
||||
if ssl_cert:
|
||||
issuer = " ".join(x[0][1] for x in ssl_cert.get("issuer", [])) or "Unknown"
|
||||
expires = ssl_cert.get("notAfter", "Unknown")
|
||||
result.append(self.strings("ssl").format(issuer, expires))
|
||||
else:
|
||||
result.append(self.strings("ssl_fail"))
|
||||
|
||||
if subdomains:
|
||||
result.append(self.strings("subs"))
|
||||
result.extend(subdomains)
|
||||
else:
|
||||
result.append(self.strings("subs_fail"))
|
||||
|
||||
result.append(self.strings("http").format(http_status) if http_status else self.strings("http_fail"))
|
||||
|
||||
if open_ports:
|
||||
result.append(self.strings("ports").format(", ".join(open_ports)))
|
||||
else:
|
||||
result.append(self.strings("ports_fail"))
|
||||
|
||||
await utils.answer(message, "\n".join(result))
|
||||
BIN
fiksofficial/python-modules/favicon.ico
Normal file
BIN
fiksofficial/python-modules/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
18
fiksofficial/python-modules/full.txt
Normal file
18
fiksofficial/python-modules/full.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
ai
|
||||
userparser
|
||||
channeladapter
|
||||
getusername
|
||||
lyrics
|
||||
irisrp
|
||||
speedtest
|
||||
randomizer
|
||||
dscanner
|
||||
autoprofile
|
||||
sysinfo
|
||||
histart
|
||||
cutemessages
|
||||
calc
|
||||
githubinfo
|
||||
qrgen
|
||||
wiki
|
||||
checkhost
|
||||
38
fiksofficial/python-modules/getusername.py
Normal file
38
fiksofficial/python-modules/getusername.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @PyModule
|
||||
from .. import loader, utils
|
||||
|
||||
@loader.tds
|
||||
class GetUserMod(loader.Module):
|
||||
"""Получает username пользователя по его ID"""
|
||||
|
||||
strings = {"name": "GetUser"}
|
||||
|
||||
@loader.command()
|
||||
async def getuser(self, message):
|
||||
"""[ID] - Найти username по ID."""
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
if not args or not args.isdigit():
|
||||
return await message.edit("<emoji document_id=5774077015388852135>❌</emoji> <b>Укажите ID пользователя!</b>")
|
||||
|
||||
user_id = int(args)
|
||||
|
||||
try:
|
||||
user = await self.client.get_entity(user_id)
|
||||
if user.deleted or not user.first_name:
|
||||
return await message.edit(f"<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Пользователь не существует.</b></blockquote>\n\n<emoji document_id=6032850693348399258>🔎</emoji> <b>ID: {user_id}</b>")
|
||||
if user.username:
|
||||
if user.last_name is not None:
|
||||
await message.edit(f"<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Username найден.</b></blockquote>\n\n<emoji document_id=6032850693348399258>🔎</emoji> <b>ID: {user_id}</b>\n<emoji document_id=5771887475421090729>👤</emoji> <b>Username: @{user.username}</b>\n<emoji document_id=6035084557378654059>👤</emoji> <b>First name: {user.first_name}</b>\n<emoji document_id=6035084557378654059>👤</emoji> <b>Last name: {user.last_name}</b>")
|
||||
else:
|
||||
await message.edit(f"<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Username найден.</b></blockquote>\n\n<emoji document_id=6032850693348399258>🔎</emoji> <b>ID: {user_id}</b>\n<emoji document_id=5771887475421090729>👤</emoji> <b>Username: @{user.username}</b>\n<emoji document_id=6035084557378654059>👤</emoji> <b>First name: {user.first_name}</b>")
|
||||
else:
|
||||
if user.last_name is not None:
|
||||
await message.edit(f"<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Username не найден.</b></blockquote>\n\n<emoji document_id=6032850693348399258>🔎</emoji> <b>ID: {user_id}</b>\n<emoji document_id=6035084557378654059>👤</emoji> <b>First name: {user.first_name}</b>\n<emoji document_id=6035084557378654059>👤</emoji> <b>Last name: {user.last_name}</b>")
|
||||
else:
|
||||
await message.edit(f"<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Username не найден.</b></blockquote>\n\n<emoji document_id=6032850693348399258>🔎</emoji> <b>ID: {user_id}</b>\n<emoji document_id=6035084557378654059>👤</emoji> <b>First name: {user.first_name}</b>")
|
||||
except Exception:
|
||||
await message.edit(f"<blockquote><emoji document_id=5253959125838090076>👁</emoji> <b>Ошибка при поиске пользователя.</b></blockquote>\n\n<emoji document_id=6032850693348399258>🔎</emoji> <b>ID: {user_id}</b>")
|
||||
193
fiksofficial/python-modules/githubinfo.py
Normal file
193
fiksofficial/python-modules/githubinfo.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
from .. import loader, utils
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@loader.tds
|
||||
class GitHubInfoMod(loader.Module):
|
||||
"""GitHub user info, recent activity and contribution graph"""
|
||||
strings = {
|
||||
"name": "GitHubInfo",
|
||||
"no_username": "❗ Provide a GitHub username.",
|
||||
"user_not_found": "🚫 User not found: <b>{}</b>",
|
||||
"profile": "Profile",
|
||||
"no_activity": "🕸 No recent activity from <b>{}</b>",
|
||||
"no_contrib": "📭 No contribution data for <b>{}</b>",
|
||||
"info_text": (
|
||||
"👤 <b>{name}</b> | <a href=\"{url}\">{profile}</a>\n"
|
||||
"🏢 {company} | 📍 {location}\n"
|
||||
"📝 {bio}\n\n"
|
||||
"📦 Repos: <b>{repos}</b> | "
|
||||
"👥 Followers: <b>{followers}</b> | "
|
||||
"👣 Following: <b>{following}</b>\n"
|
||||
"🕒 Created: <code>{created}</code>"
|
||||
),
|
||||
"activity_header": "<b>Recent activity:</b>\n",
|
||||
"activity_commit": "🔨 {count} commit(s) → <code>{branch}</code> in {repo}",
|
||||
"activity_create": "✨ Created {ref_type} in {repo}",
|
||||
"activity_pr": "🔄 {action} PR: {title}",
|
||||
"activity_issue": "❗ {action} issue: {title}",
|
||||
"activity_star": "⭐ Starred {repo}",
|
||||
"activity_fork": "⑂ Forked to {fork}",
|
||||
"activity_other": "⚡ {event} in {repo}",
|
||||
"contrib_header": "<b>Contribution graph</b> for <a href=\"https://github.com/{username}\">{username}</a>:\n",
|
||||
"contrib_footer": "⬛ = 0, 🟩 = 1+ contributions",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"no_username": "❗ Укажи имя пользователя GitHub.",
|
||||
"user_not_found": "🚫 Пользователь не найден: <b>{}</b>",
|
||||
"profile": "Профиль",
|
||||
"no_activity": "🕸 Нет активности у <b>{}</b>",
|
||||
"no_contrib": "📭 Нет данных о вкладах <b>{}</b>",
|
||||
"info_text": (
|
||||
"👤 <b>{name}</b> | <a href=\"{url}\">{profile}</a>\n"
|
||||
"🏢 {company} | 📍 {location}\n"
|
||||
"📝 {bio}\n\n"
|
||||
"📦 Репозитории: <b>{repos}</b> | "
|
||||
"👥 Подписчики: <b>{followers}</b> | "
|
||||
"👣 Подписки: <b>{following}</b>\n"
|
||||
"🕒 Создан: <code>{created}</code>"
|
||||
),
|
||||
"activity_header": "<b>Последняя активность:</b>\n",
|
||||
"activity_commit": "🔨 {count} коммит(ов) → <code>{branch}</code> в {repo}",
|
||||
"activity_create": "✨ Создан {ref_type} в {repo}",
|
||||
"activity_pr": "🔄 {action} PR: {title}",
|
||||
"activity_issue": "❗ {action} issue: {title}",
|
||||
"activity_star": "⭐ В избранное {repo}",
|
||||
"activity_fork": "⑂ Форк в {fork}",
|
||||
"activity_other": "⚡ {event} в {repo}",
|
||||
"contrib_header": "<b>График активности</b> <a href=\"https://github.com/{username}\">{username}</a>:\n",
|
||||
"contrib_footer": "⬛ = 0, 🟩 = 1+ контрибуций",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def github_api(self, url):
|
||||
try:
|
||||
with urllib.request.urlopen(url) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[GitHub API] {e}")
|
||||
return None
|
||||
|
||||
def get_username(self, message):
|
||||
args = message.text.split(maxsplit=1)
|
||||
return args[1] if len(args) > 1 else None
|
||||
|
||||
@loader.command(doc="Show GitHub user info", ru_doc="Информация о пользователе GitHub")
|
||||
async def gh(self, message):
|
||||
"""Show GitHub user info"""
|
||||
username = self.get_username(message)
|
||||
if not username:
|
||||
return await message.edit(self.strings("no_username"))
|
||||
|
||||
data = self.github_api(f"https://api.github.com/users/{username}")
|
||||
if not data:
|
||||
return await message.edit(self.strings("user_not_found").format(username))
|
||||
|
||||
await message.edit(self.strings("info_text").format(
|
||||
name=data.get("name") or username,
|
||||
url=data["html_url"],
|
||||
profile=self.strings("profile"),
|
||||
company=data.get("company", "N/A"),
|
||||
location=data.get("location", "N/A"),
|
||||
bio=data.get("bio", "No bio"),
|
||||
repos=data.get("public_repos", 0),
|
||||
followers=data.get("followers", 0),
|
||||
following=data.get("following", 0),
|
||||
created=data.get("created_at", "")[:10]
|
||||
))
|
||||
|
||||
@loader.command(doc="Show recent GitHub activity", ru_doc="Последняя активность GitHub")
|
||||
async def gha(self, message):
|
||||
"""Show recent GitHub activity"""
|
||||
username = self.get_username(message)
|
||||
if not username:
|
||||
return await message.edit(self.strings("no_username"))
|
||||
|
||||
events = self.github_api(f"https://api.github.com/users/{username}/events?per_page=5")
|
||||
if not events:
|
||||
return await message.edit(self.strings("no_activity").format(username))
|
||||
|
||||
lines = []
|
||||
for event in events:
|
||||
etype = event["type"]
|
||||
repo = event["repo"]["name"]
|
||||
payload = event.get("payload", {})
|
||||
|
||||
if etype == "PushEvent":
|
||||
branch = re.sub(r"refs/heads/", "", payload.get("ref", "main"))
|
||||
count = len(payload.get("commits", []))
|
||||
lines.append(self.strings("activity_commit").format(count=count, branch=branch, repo=repo))
|
||||
elif etype == "CreateEvent":
|
||||
lines.append(self.strings("activity_create").format(ref_type=payload.get("ref_type"), repo=repo))
|
||||
elif etype == "PullRequestEvent":
|
||||
pr = payload.get("pull_request", {})
|
||||
lines.append(self.strings("activity_pr").format(action=payload.get("action"), title=pr.get("title")))
|
||||
elif etype == "IssuesEvent":
|
||||
issue = payload.get("issue", {})
|
||||
lines.append(self.strings("activity_issue").format(action=payload.get("action"), title=issue.get("title")))
|
||||
elif etype == "WatchEvent":
|
||||
lines.append(self.strings("activity_star").format(repo=repo))
|
||||
elif etype == "ForkEvent":
|
||||
lines.append(self.strings("activity_fork").format(fork=payload.get("forkee", {}).get("full_name")))
|
||||
else:
|
||||
lines.append(self.strings("activity_other").format(event=etype, repo=repo))
|
||||
|
||||
await message.edit(self.strings("activity_header") + "\n".join(lines))
|
||||
|
||||
@loader.command(doc="Show GitHub contribution graph", ru_doc="Показать график контрибов GitHub")
|
||||
async def ghc(self, message):
|
||||
"""Show GitHub contribution graph"""
|
||||
username = self.get_username(message)
|
||||
if not username:
|
||||
return await message.edit(self.strings("no_username"))
|
||||
|
||||
data = self.github_api(f"https://github-contributions-api.deno.dev/{username}.json")
|
||||
contribs = data.get("contributions") if data else None
|
||||
|
||||
if not isinstance(contribs, list):
|
||||
return await message.edit(self.strings("no_contrib").format(username))
|
||||
|
||||
today = datetime.utcnow().date()
|
||||
start = today - timedelta(days=90)
|
||||
matrix = [["⬛" for _ in range(13)] for _ in range(7)]
|
||||
|
||||
for entry in contribs:
|
||||
try:
|
||||
date = datetime.strptime(entry["date"], "%Y-%m-%d").date()
|
||||
if not (start <= date <= today):
|
||||
continue
|
||||
day = (date.weekday() + 1) % 7 # Sunday=0
|
||||
week = (date - start).days // 7
|
||||
if entry.get("contributionCount", 0) > 0:
|
||||
matrix[day][week] = "🟩"
|
||||
except:
|
||||
continue
|
||||
|
||||
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
||||
graph = "\n".join(f"{days[i]} {''.join(matrix[i])}" for i in range(7))
|
||||
|
||||
await message.edit(
|
||||
self.strings("contrib_header").format(username=username)
|
||||
+ f"<pre>{graph}</pre>\n"
|
||||
+ self.strings("contrib_footer")
|
||||
)
|
||||
146
fiksofficial/python-modules/histart.py
Normal file
146
fiksofficial/python-modules/histart.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
from hikkatl.types import Message
|
||||
from .. import loader, utils
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
|
||||
@loader.tds
|
||||
class HistartMod(loader.Module):
|
||||
"""
|
||||
🔁 Automatically restarts your userbot at set intervals.
|
||||
|
||||
⏱ Use .setrestart <interval> and .histart on/off to enable/disable.
|
||||
"""
|
||||
|
||||
strings = {
|
||||
"name": "Histart",
|
||||
"cfg_interval": "✅ Restart will occur every <b>{}</b>",
|
||||
"enabled_on": "✅ <b>Auto-restart enabled.</b>",
|
||||
"enabled_off": "🛑 <b>Auto-restart disabled.</b>",
|
||||
"invalid_format": "❌ <b>Invalid format.</b> Example: <code>1h30m</code>",
|
||||
"status_enabled": "✅ Auto-restart is currently <b>enabled</b>",
|
||||
"status_disabled": "🛑 Auto-restart is currently <b>disabled</b>",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"cfg_interval": "✅ <b>Рестарт будет каждые {}</b>",
|
||||
"enabled_on": "✅ <b>Авто-рестарт включён.</b>",
|
||||
"enabled_off": "🛑 <b>Авто-рестарт выключен.</b>",
|
||||
"invalid_format": "❌ <b>Неверный формат.</b> Пример: <code>1h30m</code>",
|
||||
"status_enabled": "✅ Авто-рестарт сейчас <b>включён</b>",
|
||||
"status_disabled": "🛑 Авто-рестарт сейчас <b>выключен</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._task = None
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"enabled",
|
||||
False,
|
||||
lambda: "Включить авто-рестарт",
|
||||
validator=loader.validators.Boolean(),
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"interval",
|
||||
10800,
|
||||
lambda: "Интервал между рестартами в секундах (например, 3600 = 1ч)",
|
||||
validator=loader.validators.Integer(minimum=1),
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
|
||||
if self.config["enabled"]:
|
||||
self._start_loop()
|
||||
|
||||
def _start_loop(self):
|
||||
if self._task and not self._task.done():
|
||||
self._task.cancel()
|
||||
self._task = asyncio.create_task(self._auto_restart_loop())
|
||||
|
||||
async def _auto_restart_loop(self):
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(self.config["interval"])
|
||||
await self.invoke("restart", "-f", peer="me")
|
||||
except asyncio.CancelledError:
|
||||
pass # task manually cancelled
|
||||
|
||||
@loader.command(
|
||||
doc="⚙️ Set auto-restart interval. Supports formats like 1h30m, 2d3h.",
|
||||
ru_doc="⚙️ Установить интервал автоперезапуска. Поддерживает 1h30m, 2d3h и т.д.",
|
||||
)
|
||||
async def setrestart(self, message: Message):
|
||||
args = message.raw_text.split(maxsplit=1)
|
||||
if len(args) < 2:
|
||||
return await utils.answer(message, self.strings("invalid_format"))
|
||||
|
||||
seconds = self._parse_interval(args[1].lower())
|
||||
if not seconds:
|
||||
return await utils.answer(message, self.strings("invalid_format"))
|
||||
|
||||
self.config["interval"] = seconds
|
||||
short = self._short_format(seconds)
|
||||
await utils.answer(message, self.strings("cfg_interval").format(short))
|
||||
|
||||
# перезапускаем задачу, если уже включено
|
||||
if self.config["enabled"]:
|
||||
self._start_loop()
|
||||
|
||||
@loader.command(
|
||||
doc="🔁 Enable/disable auto-restart: .histart on | off",
|
||||
ru_doc="🔁 Включить или выключить авто-рестарт: .histart on | off",
|
||||
)
|
||||
async def histart(self, message: Message):
|
||||
args = utils.get_args_raw(message).lower()
|
||||
|
||||
if args == "on":
|
||||
self.config["enabled"] = True
|
||||
self._start_loop()
|
||||
await utils.answer(message, self.strings("enabled_on"))
|
||||
|
||||
elif args == "off":
|
||||
self.config["enabled"] = False
|
||||
if self._task and not self._task.done():
|
||||
self._task.cancel()
|
||||
await utils.answer(message, self.strings("enabled_off"))
|
||||
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("status_enabled") if self.config["enabled"]
|
||||
else self.strings("status_disabled")
|
||||
)
|
||||
|
||||
def _parse_interval(self, text: str) -> int | None:
|
||||
multipliers = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800, "y": 31536000}
|
||||
matches = re.findall(r"(\d+)([smhdwy])", text)
|
||||
if not matches:
|
||||
return None
|
||||
return sum(int(n) * multipliers[u] for n, u in matches)
|
||||
|
||||
def _short_format(self, seconds: int) -> str:
|
||||
units = [("y", 31536000), ("w", 604800), ("d", 86400), ("h", 3600), ("m", 60), ("s", 1)]
|
||||
result = []
|
||||
for key, val in units:
|
||||
count = seconds // val
|
||||
if count:
|
||||
result.append(f"{count}{key}")
|
||||
seconds %= val
|
||||
return "".join(result)
|
||||
3
fiksofficial/python-modules/index.html
Normal file
3
fiksofficial/python-modules/index.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<script>
|
||||
window.location.href = "https://module.newurp.online";
|
||||
</script>
|
||||
468
fiksofficial/python-modules/irisrp.py
Normal file
468
fiksofficial/python-modules/irisrp.py
Normal file
@@ -0,0 +1,468 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @PyModule
|
||||
# requires: toml
|
||||
import os
|
||||
from hikka import loader, utils
|
||||
import pickle
|
||||
from telethon.tl.types import Channel
|
||||
import toml
|
||||
|
||||
|
||||
# noinspection PyCallingNonCallable
|
||||
@loader.tds
|
||||
class IrisRP(loader.Module):
|
||||
"""РП команды как в боте Ирис."""
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"action_decoration",
|
||||
'normal | без стилей',
|
||||
lambda: "Декорация для действия РП-команды",
|
||||
validator=loader.validators.Choice(
|
||||
[
|
||||
"normal | без стилей",
|
||||
"bold | полужирный",
|
||||
"italic | курсив",
|
||||
"underlined | подчёркнутый",
|
||||
"strikethrough | зачёркнутый",
|
||||
"spoiler | скрытый",
|
||||
]
|
||||
),
|
||||
),
|
||||
|
||||
loader.ConfigValue(
|
||||
"replica_decoration",
|
||||
'normal | без стилей',
|
||||
lambda: "Декорация для реплики РП-команды",
|
||||
validator=loader.validators.Choice(
|
||||
[
|
||||
"normal | без стилей",
|
||||
"bold | полужирный",
|
||||
"italic | курсив",
|
||||
"underlined | подчёркнутый",
|
||||
"strikethrough | зачёркнутый",
|
||||
"spoiler | скрытый",
|
||||
]
|
||||
),
|
||||
),
|
||||
|
||||
loader.ConfigValue(
|
||||
"speech_bubble",
|
||||
'💬',
|
||||
lambda: "Эмодзи речевого пузыря для «с репликой»",
|
||||
validator=loader.validators.String()
|
||||
)
|
||||
)
|
||||
|
||||
strings = {'name': 'IrisRP'}
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.db = db
|
||||
|
||||
if not self.db.get("RPMod", "exlist", False):
|
||||
self.db.set("RPMod", "exlist", [])
|
||||
|
||||
if not self.db.get("RPMod", "status", False):
|
||||
self.db.get("RPMod", "status", 1)
|
||||
|
||||
if not self.db.get("RPMod", "rpcomands", False):
|
||||
self.db.set("RPMod", "rpcomands", {})
|
||||
|
||||
if not self.db.get("RPMod", "rpemoji", False):
|
||||
self.db.set("RPMod", "rpemoji", {})
|
||||
|
||||
if not self.db.get("RPMod", "nrpcommands", False):
|
||||
if self.db.get("RPMod", "rpcomands", False):
|
||||
commands_old = self.db.get("RPMod", "rpcomands")
|
||||
emoji_old = self.db.get("RPMod", "rpemoji")
|
||||
commands_new = {}
|
||||
for key in commands_old:
|
||||
try:
|
||||
commands_new[key] = [commands_old[key], emoji_old[key]]
|
||||
except KeyError:
|
||||
commands_new[key] = [commands_old[key], '']
|
||||
self.db.set("RPMod", "nrpcommands", commands_new)
|
||||
|
||||
else:
|
||||
self.db.set("RPMod", "nrpcommands", {})
|
||||
|
||||
if not self.db.get("RPMod", "useraccept", False):
|
||||
self.db.set("RPMod", "useraccept", {"chats": [], "users": []})
|
||||
|
||||
elif isinstance(type(self.db.get("RPMod", "useraccept")), list):
|
||||
self.db.set(
|
||||
"RPMod",
|
||||
"useraccept",
|
||||
{"chats": [], "users": self.db.get("RPMod", "useraccept")},
|
||||
)
|
||||
|
||||
async def addrpcmd(self, message):
|
||||
"""[команда (1-3 слова)] / [действие] / (эмодзи) - Создать РП команду."""
|
||||
args = utils.get_args_raw(message)
|
||||
dict_rp = self.db.get("RPMod", "nrpcommands", {})
|
||||
|
||||
if not args or not args.strip():
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Вы не указали никаких данных.</b>")
|
||||
return
|
||||
|
||||
try:
|
||||
if '/' not in args:
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Неверный формат. Используйте: команда / действие / эмодзи</b>")
|
||||
return
|
||||
|
||||
parts = [part.strip() for part in args.split("/", maxsplit=2)]
|
||||
|
||||
if len(parts) < 2:
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Неверный формат. Используйте: команда / действие / эмодзи</b>")
|
||||
return
|
||||
|
||||
key_rp = parts[0].casefold()
|
||||
words_count = len(key_rp.split())
|
||||
|
||||
if words_count < 1 or words_count > 3:
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Команда должна содержать от 1 до 3 слов.</b>")
|
||||
return
|
||||
|
||||
value_rp = parts[1]
|
||||
if not value_rp.strip():
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Вы не указали действие команды.</b>")
|
||||
return
|
||||
|
||||
if key_rp == "all":
|
||||
await utils.answer(message, '<emoji document_id=5774077015388852135>❌</emoji> <b>РП-команды не могут называться "all"</b>')
|
||||
return
|
||||
|
||||
lenght_args = args.split("/")
|
||||
count_emoji = 0
|
||||
if len(lenght_args) >= 3:
|
||||
emoji_rp = str(message.text.split("/", maxsplit=2)[2]).strip()
|
||||
count_emoji = 1
|
||||
|
||||
if not emoji_rp or not emoji_rp.strip():
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Вы не указали эмодзи.</b>")
|
||||
return
|
||||
|
||||
dict_rp[key_rp] = [value_rp, emoji_rp.strip()]
|
||||
self.db.set("RPMod", "nrpcommands", dict_rp)
|
||||
|
||||
response = f"<emoji document_id=5774022692642492953>✅</emoji> <b>Команда <code>{key_rp}</code> успешно добавлена"
|
||||
if emoji_rp:
|
||||
response += f" с эмодзи {emoji_rp}"
|
||||
response += ".</b>"
|
||||
|
||||
await utils.answer(message, response)
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Неверный формат. Используйте: команда (1-3 слова) / действие / эмодзи</b>")
|
||||
|
||||
async def delrpcmd(self, message):
|
||||
"""[команда / all] - Удалить РП команду."""
|
||||
dict_rp = self.db.get("RPMod", "nrpcommands")
|
||||
args = utils.get_args_raw(message)
|
||||
key_rp = str(args)
|
||||
|
||||
if key_rp == "all":
|
||||
dict_rp.clear()
|
||||
self.db.set("RPMod", "nrpcommands", dict_rp)
|
||||
await utils.answer(message, "<emoji document_id=5774022692642492953>✅</emoji> <b>Все РП команды успешно очищены.</b>")
|
||||
return
|
||||
|
||||
elif not key_rp or not key_rp.strip():
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Вы не указали команду для удаления.</b>")
|
||||
|
||||
else:
|
||||
try:
|
||||
dict_rp.pop(key_rp)
|
||||
self.db.set("RPMod", "nrpcommands", dict_rp)
|
||||
await utils.answer(message, f"<emoji document_id=5774022692642492953>✅</emoji> <b>Команда <code>{key_rp}</code> успешно удалена.</b>")
|
||||
except KeyError:
|
||||
await utils.answer(message, f"<emoji document_id=5774077015388852135>❌</emoji> <b>Команда <code>{key_rp}</code> не найдена.</b>")
|
||||
|
||||
async def rptogglecmd(self, message):
|
||||
"""- Включить/Выключить РП команды."""
|
||||
status = self.db.get("RPMod", "status")
|
||||
if status == 1:
|
||||
self.db.set("RPMod", "status", 2)
|
||||
await utils.answer(message, "<emoji document_id=5253959125838090076>👁</emoji> <b>РП команды теперь выключены.</b>")
|
||||
else:
|
||||
self.db.set("RPMod", "status", 1)
|
||||
await utils.answer(message, "<emoji document_id=5253959125838090076>👁</emoji> <b>РП команды теперь включены.</b>")
|
||||
|
||||
async def rplistcmd(self, message):
|
||||
"""- Список все ваших команд."""
|
||||
com = self.db.get("RPMod", "nrpcommands")
|
||||
|
||||
coms_amount = len(com)
|
||||
com_list = f"<emoji document_id=5253959125838090076>👁</emoji> <b>У вас {coms_amount} команд.</b>"
|
||||
|
||||
if len(com) == 0:
|
||||
await utils.answer(message, f"<emoji document_id=5253959125838090076>👁</emoji> <b>У вас {coms_amount} команд.</b>")
|
||||
return
|
||||
|
||||
for i in com:
|
||||
if com[i][1] != '':
|
||||
com_list += f"\n• <b><code>{i}</code> - {com[i][0]} |</b> {com[i][1]}"
|
||||
else:
|
||||
com_list += f"\n• <b><code>{i}</code> - {com[i][0]}</b>"
|
||||
|
||||
await utils.answer(message, com_list)
|
||||
|
||||
async def rpbackcmd(self, message):
|
||||
"""(all) - Сохранить или загрузить список РП команд. All используется для замены всех команд."""
|
||||
commands = self.db.get("RPMod", "nrpcommands")
|
||||
mes_id = message.to_id
|
||||
me = await self.client.get_me()
|
||||
file_name = f"IrisRP_{me.id}.toml"
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
args = utils.get_args_raw(message)
|
||||
|
||||
replace_commands = "all" in args
|
||||
|
||||
if not reply:
|
||||
try:
|
||||
await message.delete()
|
||||
with open(file_name, "w") as f:
|
||||
toml.dump(commands, f)
|
||||
await message.client.send_file(mes_id, file_name)
|
||||
os.remove(file_name)
|
||||
except Exception as e:
|
||||
await utils.answer(message, f"<emoji document_id=5774077015388852135>❌</emoji> <b>Ошибка при создании бэкапа:</b>\n<code>{e}</code>")
|
||||
else:
|
||||
if not reply.document:
|
||||
await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>В ответе нет файла.</b>")
|
||||
return
|
||||
|
||||
try:
|
||||
await reply.download_media(file_name)
|
||||
|
||||
with open(file_name, "r") as f:
|
||||
try:
|
||||
data = toml.load(f)
|
||||
except toml.TomlDecodeError:
|
||||
os.remove(file_name)
|
||||
return await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Файл поврежден или не является бэкапом.</b>")
|
||||
|
||||
for key in data.keys():
|
||||
if not isinstance(data[key], list) or len(data[key]) != 2:
|
||||
os.remove(file_name)
|
||||
return await utils.answer(message, "<emoji document_id=5774077015388852135>❌</emoji> <b>Неверный формат бэкапа.</b>")
|
||||
|
||||
if replace_commands:
|
||||
self.db.set("RPMod", "nrpcommands", data)
|
||||
os.remove(file_name)
|
||||
await utils.answer(message, "<emoji document_id=5774022692642492953>✅</emoji> <b>РП команды успешно заменены из бэкапа.</b>")
|
||||
else:
|
||||
updated_commands = {**commands, **data}
|
||||
self.db.set("RPMod", "nrpcommands", updated_commands)
|
||||
os.remove(file_name)
|
||||
await utils.answer(message, "<emoji document_id=5774022692642492953>✅</emoji> <b>РП команды успешно добавлены из бэкапа.</b>")
|
||||
|
||||
except Exception as e:
|
||||
if os.path.exists(file_name):
|
||||
os.remove(file_name)
|
||||
await utils.answer(message, f"<emoji document_id=5774077015388852135>❌</emoji> <b>Ошибка при загрузке бэкапа:</b>\n<code>{e}</code>")
|
||||
|
||||
async def rpacmd(self, message):
|
||||
"""(ID/Reply) - Разрешить или запретить доступ к РП командам. Для подробностей напишите .rpa"""
|
||||
|
||||
reply = await message.get_reply_message()
|
||||
args = utils.get_args_raw(message)
|
||||
user_a = self.db.get("RPMod", "useraccept")
|
||||
|
||||
if not reply and not args and message.is_group:
|
||||
chat = message.chat
|
||||
if chat.id not in user_a["chats"]:
|
||||
user_a["chats"].append(chat.id)
|
||||
return await utils.answer(message, f"<emoji document_id=5774022692642492953>✅</emoji> <b>Доступ к РП-командам включен для чата {chat.title}</b>")
|
||||
else:
|
||||
user_a["chats"].remove(chat.id)
|
||||
return await utils.answer(message, f"<emoji document_id=5774022692642492953>✅</emoji> <b>Доступ к РП-командам отключен для чата {chat.title}</b>")
|
||||
|
||||
elif args.lower() in ("-l", "л", "list", "список"):
|
||||
sms = "<b><emoji document_id=5253959125838090076>👁</emoji> Список доступов к РП командам:</b>"
|
||||
|
||||
if not user_a["chats"] and not user_a["users"]:
|
||||
return await utils.answer(message,"<emoji document_id=6028435952299413210>ℹ</emoji> <b>Нет ни пользователей, ни чатов с доступом к РП-командам</b>")
|
||||
|
||||
if user_a["chats"]:
|
||||
sms += "\n\n<emoji document_id=6037421444789440735>💬</emoji> <b>Чаты с доступом:</b>"
|
||||
for chat_id in user_a["chats"]:
|
||||
try:
|
||||
chat = await message.client.get_entity(int(chat_id))
|
||||
sms += f"\n• <b>{chat.title}</b> (<code>{chat_id}</code>)"
|
||||
except:
|
||||
sms += f"\n• <code>{chat_id}</code>"
|
||||
else:
|
||||
sms += "\n\n<emoji document_id=6028435952299413210>ℹ</emoji> <b>Нет чатов с доступом</b>"
|
||||
|
||||
if user_a["users"]:
|
||||
sms += "\n\n<emoji document_id=6035084557378654059>👤</emoji> <b>Пользователи с доступом:</b>"
|
||||
for user_id in user_a["users"]:
|
||||
try:
|
||||
user = await message.client.get_entity(int(user_id))
|
||||
sms += f"\n• <b>{user.first_name}</b> (<code>{user_id}</code>)"
|
||||
except:
|
||||
sms += f"\n• <code>{user_id}</code>"
|
||||
else:
|
||||
sms += "\n\n<emoji document_id=6028435952299413210>ℹ</emoji> <b>Нет пользователей с доступом</b>"
|
||||
|
||||
await utils.answer(message, sms)
|
||||
|
||||
elif args or reply:
|
||||
try:
|
||||
target_id = int(args) if args and args.isdigit() else reply.sender_id
|
||||
entity = await message.client.get_entity(target_id)
|
||||
|
||||
is_channel = isinstance(entity, Channel)
|
||||
target_name = entity.title if is_channel else entity.first_name
|
||||
|
||||
if is_channel:
|
||||
if target_id in user_a["chats"]:
|
||||
user_a["chats"].remove(target_id)
|
||||
msg = f"<emoji document_id=5774022692642492953>✅</emoji> <b>Доступ отключен для чата {target_name}</b>"
|
||||
else:
|
||||
user_a["chats"].append(target_id)
|
||||
msg = f"<emoji document_id=5774022692642492953>✅</emoji> <b>Доступ включен для чата {target_name}</b>"
|
||||
else:
|
||||
if target_id in user_a["users"]:
|
||||
user_a["users"].remove(target_id)
|
||||
msg = f"<emoji document_id=5774022692642492953>✅</emoji> <b>Доступ отключен для пользователя {target_name}</b>"
|
||||
else:
|
||||
user_a["users"].append(target_id)
|
||||
msg = f"<emoji document_id=5774022692642492953>✅</emoji> <b>Доступ включен для пользователя {target_name}</b>"
|
||||
|
||||
self.db.set("RPMod", "useraccept", user_a)
|
||||
await utils.answer(message, msg)
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(
|
||||
message,
|
||||
f"<emoji document_id=5774077015388852135>❌</emoji> <b>Ошибка:</b> {str(e)}"
|
||||
)
|
||||
|
||||
else:
|
||||
await utils.answer(
|
||||
message,
|
||||
"<blockquote><emoji document_id=6028435952299413210>ℹ</emoji> <b>Используйте:</b></blockquote>\n"
|
||||
"<code>.rpa</code> <b>в чате - для управления доступом чата.</b>\n"
|
||||
"<code>.rpa [ID]</code> <b>- для управления по ID.</b>\n"
|
||||
"<code>.rpa</code> <b>в ответ на сообщение - для управления пользователем.</b>\n"
|
||||
"<code>.rpa -l</code> <b>- чтобы показать список доступов.</b>"
|
||||
)
|
||||
|
||||
async def watcher(self, message):
|
||||
try:
|
||||
status = self.db.get("RPMod", "status", 0)
|
||||
commands = self.db.get("RPMod", "nrpcommands", {})
|
||||
users_accept = self.db.get("RPMod", "useraccept", {"users": [], "chats": []})
|
||||
|
||||
if status != 1:
|
||||
return
|
||||
|
||||
me_id = (await message.client.get_me()).id
|
||||
|
||||
if (message.sender_id not in users_accept["users"] and
|
||||
message.sender_id != me_id and
|
||||
message.chat_id not in users_accept["chats"]):
|
||||
return
|
||||
|
||||
me = await message.client.get_entity(message.sender_id)
|
||||
|
||||
text = message.text or ""
|
||||
lines = text.splitlines()
|
||||
if not lines:
|
||||
return
|
||||
|
||||
words = lines[0].split()
|
||||
if not words:
|
||||
return
|
||||
|
||||
found_command = None
|
||||
for i in range(min(3, len(words)), 0, -1):
|
||||
possible_command = " ".join(words[:i]).casefold()
|
||||
if possible_command in commands:
|
||||
found_command = possible_command
|
||||
remaining_text = " ".join(words[i:]) if i < len(words) else ""
|
||||
break
|
||||
|
||||
if not found_command:
|
||||
return
|
||||
|
||||
if len(words) > 1 and words[-1].startswith("@"):
|
||||
target_mention = words[-1][1:]
|
||||
try:
|
||||
if target_mention.isdigit():
|
||||
user = await message.client.get_entity(int(target_mention))
|
||||
else:
|
||||
user = await message.client.get_entity(target_mention)
|
||||
remaining_text = " ".join(words[len(found_command.split()):-1])
|
||||
except:
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
user = await message.client.get_entity(reply.sender_id)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
reply = await message.get_reply_message()
|
||||
if reply:
|
||||
user = await message.client.get_entity(reply.sender_id)
|
||||
else:
|
||||
return
|
||||
|
||||
command = commands[found_command]
|
||||
|
||||
replica = ""
|
||||
if len(lines) > 1:
|
||||
replica = "\n".join(lines[1:])
|
||||
|
||||
action_decoration = self.config.get('action_decoration', '')
|
||||
replica_decoration = self.config.get('replica_decoration', '')
|
||||
bubble = self.config.get('speech_bubble', '💬')
|
||||
|
||||
s1 = {
|
||||
'bold': ("<b>", "</b>"),
|
||||
'italic': ("<i>", "</i>"),
|
||||
'underline': ("<u>", "</u>"),
|
||||
'strikethrough': ("<s>", "</s>"),
|
||||
'spoiler': ("<spoiler>", "</spoiler>")
|
||||
}.get(action_decoration, ("", ""))
|
||||
|
||||
s2 = {
|
||||
'bold': ("<b>", "</b>"),
|
||||
'italic': ("<i>", "</i>"),
|
||||
'underline': ("<u>", "</u>"),
|
||||
'strikethrough': ("<s>", "</s>"),
|
||||
'spoiler': ("<spoiler>", "</spoiler>")
|
||||
}.get(replica_decoration, ("", ""))
|
||||
|
||||
rp_message = []
|
||||
if command[1]:
|
||||
rp_message.append(f"{command[1]} | ")
|
||||
|
||||
rp_message.append(
|
||||
f"<a href='tg://user?id={me.id}'>{me.first_name}</a> "
|
||||
f"{s1[0]}{command[0]}{s1[1]} "
|
||||
f"<a href='tg://user?id={user.id}'>{user.first_name}</a>"
|
||||
)
|
||||
|
||||
if remaining_text:
|
||||
rp_message.append(remaining_text)
|
||||
|
||||
if replica:
|
||||
rp_message.append(f"\n{bubble} <b>С репликой:</b> {s2[0]}{replica}{s2[1]}")
|
||||
|
||||
await utils.answer(message, "".join(rp_message))
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def merge_dict(d1, d2):
|
||||
d_all = {**d1, **d2}
|
||||
for key in d_all:
|
||||
d_all[key] = {**d1[key], **d_all[key]}
|
||||
return d_all
|
||||
62
fiksofficial/python-modules/lyrics.py
Normal file
62
fiksofficial/python-modules/lyrics.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @PyModule
|
||||
from lyricsgenius import Genius
|
||||
from .. import loader, utils
|
||||
|
||||
@loader.tds
|
||||
class LyricsMod(loader.Module):
|
||||
"""Модуль для поиска текста песни через Genius API"""
|
||||
|
||||
strings = {"name": "Lyrics"}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
"GENIUS_TOKEN",
|
||||
None,
|
||||
lambda: "Токен для доступа к Genius API. Получите его на https://genius.com/api-clients",
|
||||
)
|
||||
|
||||
def get_genius(self):
|
||||
token = self.config["GENIUS_TOKEN"]
|
||||
if not token:
|
||||
return None
|
||||
return Genius(token, timeout=10)
|
||||
|
||||
@loader.command()
|
||||
async def lyrics(self, message):
|
||||
"""[запрос] - Найти текст песни по запросу"""
|
||||
genius = self.get_genius()
|
||||
if not genius:
|
||||
return await message.edit(
|
||||
"<emoji document_id=5774077015388852135>❌</emoji> <b>Токен Genius API не установлен. Используйте <code>.cfg Lyrics</code>, чтобы добавить токен.</b>"
|
||||
)
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
return await message.edit("<emoji document_id=5253959125838090076>👁</emoji> <b>Использование:</b> .lyrics [запрос]")
|
||||
|
||||
await message.edit(f"<emoji document_id=5253959125838090076>👁</emoji> <b>Ищу текст песни по запросу:</b> {args}...")
|
||||
|
||||
try:
|
||||
search_results = genius.search_songs(args)
|
||||
if not search_results or not search_results["hits"]:
|
||||
return await message.edit("<emoji document_id=5774077015388852135>❌</emoji> <b>Ничего не найдено.</b>")
|
||||
|
||||
song_info = search_results["hits"][0]["result"]
|
||||
song = genius.search_song(song_info["title"], song_info["primary_artist"]["name"])
|
||||
|
||||
if not song:
|
||||
return await message.edit("<emoji document_id=5774077015388852135>❌</emoji> <b>Не удалось загрузить текст песни.</b>")
|
||||
|
||||
lyrics = song.lyrics
|
||||
if len(lyrics) > 4096:
|
||||
lyrics = lyrics[:4000] + "\n\n<b>Текст обрезан из-за ограничения Telegram.</b>"
|
||||
|
||||
await message.edit(
|
||||
f"<b><emoji document_id=5938473438468378529>🎶</emoji> {song.title} — {song.artist}</b>\n\n"
|
||||
f"<blockquote><b>{lyrics}</b></blockquote>"
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit(f"<b>Ошибка:</b> {str(e)}")
|
||||
1
fiksofficial/python-modules/placeholder.svg
Normal file
1
fiksofficial/python-modules/placeholder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
85
fiksofficial/python-modules/qrgen.py
Normal file
85
fiksofficial/python-modules/qrgen.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
|
||||
from .. import loader, utils
|
||||
import requests
|
||||
import uuid
|
||||
import os
|
||||
|
||||
@loader.tds
|
||||
class QRGenMod(loader.Module):
|
||||
"""Generate QR codes from text or links"""
|
||||
|
||||
strings = {
|
||||
"name": "QRGen",
|
||||
"generating": "📡 Generating QR for:\n<code>{text}</code>",
|
||||
"no_text": "❗ Please provide text or a link to encode",
|
||||
"api_error": "🚫 Error while contacting QR API",
|
||||
"not_image": "⚠️ API did not return an image",
|
||||
"ok": "✅ QR code successfully generated",
|
||||
"error_with_details": "🚫 Error:\n<code>{error}</code>"
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "QRGen",
|
||||
"generating": "📡 Генерация QR для:\n<code>{text}</code>",
|
||||
"no_text": "❗ Укажи текст или ссылку для кодирования",
|
||||
"api_error": "🚫 Ошибка при запросе к QR API",
|
||||
"not_image": "⚠️ API не вернул изображение",
|
||||
"ok": "✅ QR-код успешно сгенерирован",
|
||||
"error_with_details": "🚫 Ошибка:\n<code>{error}</code>"
|
||||
}
|
||||
|
||||
@loader.command(doc="Generate a QR code from text or link", ru_doc="Сгенерировать QR-код из текста или ссылки")
|
||||
async def qr(self, message):
|
||||
"""<text or URL> — generate QR code"""
|
||||
text = utils.get_args_raw(message)
|
||||
if not text:
|
||||
return await utils.answer(message, self.strings("no_text"))
|
||||
|
||||
await utils.answer(message, self.strings("generating").format(text=text))
|
||||
|
||||
try:
|
||||
params = {
|
||||
"data": text,
|
||||
"size": "512x512",
|
||||
"ecc": "M",
|
||||
"format": "png",
|
||||
"margin": 10
|
||||
}
|
||||
|
||||
response = requests.get("https://api.qrserver.com/v1/create-qr-code/", params=params, stream=True, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return await utils.answer(message, self.strings("api_error"))
|
||||
|
||||
if not response.headers.get("Content-Type", "").startswith("image/"):
|
||||
return await utils.answer(message, self.strings("not_image"))
|
||||
|
||||
temp_file = f"/tmp/qr_{uuid.uuid4()}.png"
|
||||
with open(temp_file, "wb") as f:
|
||||
for chunk in response.iter_content(8192):
|
||||
f.write(chunk)
|
||||
|
||||
await message.client.send_file(
|
||||
message.chat_id,
|
||||
temp_file,
|
||||
caption=self.strings("ok"),
|
||||
reply_to=message.id
|
||||
)
|
||||
os.remove(temp_file)
|
||||
|
||||
await message.delete()
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(message, self.strings("error_with_details").format(error=e))
|
||||
43
fiksofficial/python-modules/randomizer.py
Normal file
43
fiksofficial/python-modules/randomizer.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# scope: hikka_only
|
||||
# meta developer: @pymodule
|
||||
|
||||
from .. import loader, utils
|
||||
import random
|
||||
from hikkatl.types import Message
|
||||
|
||||
@loader.tds
|
||||
class RandomizerMod(loader.Module):
|
||||
"""Randomly selects one of the comma-separated values."""
|
||||
|
||||
strings = {
|
||||
"name": "Randomizer",
|
||||
"too_few_values": "Please provide at least two values separated by commas.",
|
||||
"result": "Random choice: {result}"
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "Рандомайзер",
|
||||
"too_few_values": "Укажи хотя бы два значения через запятую.",
|
||||
"result": "Случайный выбор: {result}"
|
||||
}
|
||||
|
||||
@loader.command(
|
||||
doc="Picks a random value from those listed (comma-separated)",
|
||||
ru_doc="Выбирает случайное значение из перечисленных через запятую"
|
||||
)
|
||||
async def randomizecmd(self, message: Message):
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("too_few_values"))
|
||||
return
|
||||
|
||||
items = [item.strip() for item in args.split(",") if item.strip()]
|
||||
if len(items) < 2:
|
||||
await utils.answer(message, self.strings("too_few_values"))
|
||||
return
|
||||
|
||||
result = random.choice(items)
|
||||
await utils.answer(message, self.strings("result").format(result=result))
|
||||
14
fiksofficial/python-modules/robots.txt
Normal file
14
fiksofficial/python-modules/robots.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Twitterbot
|
||||
Allow: /
|
||||
|
||||
User-agent: facebookexternalhit
|
||||
Allow: /
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
34
fiksofficial/python-modules/speedtest.py
Normal file
34
fiksofficial/python-modules/speedtest.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
# requires: speedtest-cli
|
||||
|
||||
import speedtest
|
||||
from .. import loader
|
||||
|
||||
class SpeedTestMod(loader.Module):
|
||||
"""Модуль для проверки скорости интернета"""
|
||||
|
||||
strings = {"name": "SpeedTest"}
|
||||
|
||||
async def speedcmd(self, message):
|
||||
"""Запускает тест скорости интернета"""
|
||||
msg = await message.edit("Запускаем Speedtest... 🏁")
|
||||
|
||||
try:
|
||||
st = speedtest.Speedtest()
|
||||
st.get_best_server()
|
||||
download = st.download() / 1_000_000 # Мбит/с
|
||||
upload = st.upload() / 1_000_000 # Мбит/с
|
||||
ping = st.results.ping
|
||||
|
||||
await msg.edit(
|
||||
f"<emoji document_id=5325547803936572038>✨</emoji> <b>Speedtest завершён!</b> <emoji document_id=5325547803936572038>✨</emoji>\n\n"
|
||||
f"<b>Ping:</b> <i>{ping:.2f} ms</i>\n"
|
||||
f"<emoji document_id=6041730074376410123>📥</emoji> <b>Загрузка:</b> <i>{download:.2f} Mbps</i>\n"
|
||||
f"<emoji document_id=6041730074376410123>📤</emoji> <b>Отдача:</b> <i>{upload:.2f} Mbps</i>",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except Exception as e:
|
||||
await msg.edit(f"<b>Ошибка при выполнении Speedtest:</b>\n<code>{e}</code>")
|
||||
129
fiksofficial/python-modules/sysinfo.py
Normal file
129
fiksofficial/python-modules/sysinfo.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
# requires: psutil
|
||||
|
||||
from .. import loader, utils
|
||||
import platform, psutil, socket, time, getpass, telethon
|
||||
import os
|
||||
|
||||
def bytes2human(n):
|
||||
symbols = ('B','K','M','G','T','P')
|
||||
prefix = {s:1<<(i*10) for i,s in enumerate(symbols[1:],1)}
|
||||
for s in reversed(symbols[1:]):
|
||||
if n >= prefix[s]:
|
||||
return f"{n/prefix[s]:.2f}{s}"
|
||||
return f"{n}B"
|
||||
|
||||
def format_uptime(sec):
|
||||
m, s = divmod(sec, 60); h, m = divmod(m, 60); d, h = divmod(h, 24)
|
||||
return f"{int(d)}d {int(h)}h {int(m)}m"
|
||||
|
||||
def get_distro_info():
|
||||
name = ver = "N/A"
|
||||
try:
|
||||
with open("/etc/os-release") as f:
|
||||
data = dict(line.strip().split("=", 1) for line in f if "=" in line)
|
||||
name = data.get("PRETTY_NAME", data.get("NAME", "Unknown")).strip('"')
|
||||
ver = data.get("VERSION_ID", "").strip('"')
|
||||
except: pass
|
||||
return name, ver
|
||||
|
||||
def get_cpu_model():
|
||||
try:
|
||||
with open("/proc/cpuinfo") as f:
|
||||
for line in f:
|
||||
if "model name" in line:
|
||||
return line.split(":",1)[1].strip()
|
||||
except: pass
|
||||
return platform.processor() or "Unknown"
|
||||
|
||||
@loader.tds
|
||||
class SysInfoMod(loader.Module):
|
||||
"""System information."""
|
||||
strings = {"name": "SysInfo"}
|
||||
|
||||
@loader.command(doc="🔧 Shows information about the system.", ru_doc="🔧 Показывает информацию о системе.")
|
||||
async def sysinfo(self, message):
|
||||
me = await message.client.get_me()
|
||||
is_saved = message.chat_id == me.id
|
||||
|
||||
uname = platform.uname()
|
||||
boot = psutil.boot_time()
|
||||
uptime = time.time() - boot
|
||||
freq = psutil.cpu_freq()
|
||||
load = psutil.cpu_percent(interval=0.5)
|
||||
user = getpass.getuser()
|
||||
vm, sm = psutil.virtual_memory(), psutil.swap_memory()
|
||||
net = psutil.net_io_counters()
|
||||
io = psutil.disk_io_counters()
|
||||
|
||||
distro_name, distro_ver = get_distro_info()
|
||||
cpu_model = get_cpu_model()
|
||||
|
||||
ip_addrs = []
|
||||
mac_addrs = []
|
||||
net_info = []
|
||||
|
||||
for iface, addrs in psutil.net_if_addrs().items():
|
||||
ip = mac = "—"
|
||||
for addr in addrs:
|
||||
if addr.family == socket.AF_INET:
|
||||
ip = addr.address
|
||||
ip_addrs.append(ip)
|
||||
elif hasattr(socket, 'AF_PACKET') and addr.family == socket.AF_PACKET:
|
||||
mac = addr.address
|
||||
mac_addrs.append(mac)
|
||||
net_info.append(f"<b>{iface}</b>: IP <code>{ip}</code>, MAC <code>{mac}</code>")
|
||||
|
||||
freq_str = f"{freq.current:.0f} MHz" if freq else "N/A"
|
||||
|
||||
text = (
|
||||
f"<blockquote><emoji document_id=5776118099812028333>📟</emoji> <b>System Info</b>\n\n"
|
||||
|
||||
f"<emoji document_id=5215186239853964761>🖥️</emoji> <u><b>ОС и система:</b></u>\n"
|
||||
f"<b>OS:</b> <code>{uname.system} {uname.release}</code>\n"
|
||||
f"<b>Distro:</b> <code>{distro_name} {distro_ver}</code>\n"
|
||||
f"<b>Kernel:</b> <code>{uname.version}</code>\n"
|
||||
f"<b>Arch:</b> <code>{uname.machine}</code>\n"
|
||||
f"<b>User:</b> <code>{user}</code>\n\n"
|
||||
|
||||
f"<emoji document_id=5341715473882955310>⚙️</emoji> <u><b>CPU:</b></u>\n"
|
||||
f"<b>Model:</b> <code>{cpu_model}</code>\n"
|
||||
f"<b>Cores:</b> <code>{psutil.cpu_count(logical=False)}/{psutil.cpu_count(logical=True)}</code>\n"
|
||||
f"<b>Freq:</b> <code>{freq_str}</code>\n"
|
||||
f"<b>Load:</b> <code>{load}%</code>\n\n"
|
||||
|
||||
f"<emoji document_id=5237799019329105246>🧠</emoji> <u><b>RAM:</b></u>\n"
|
||||
f"<b>Used:</b> <code>{bytes2human(vm.used)}</code> / <code>{bytes2human(vm.total)}</code>\n"
|
||||
f"<b>Swap:</b> <code>{bytes2human(sm.used)}</code> / <code>{bytes2human(sm.total)}</code>\n\n"
|
||||
|
||||
f"<emoji document_id=5462956611033117422>💾</emoji> <u><b>Диск:</b></u>\n"
|
||||
f"<b>Read:</b> <code>{bytes2human(io.read_bytes)}</code>\n"
|
||||
f"<b>Write:</b> <code>{bytes2human(io.write_bytes)}</code>\n\n"
|
||||
|
||||
f"<emoji document_id=5321141214735508486>📡</emoji> <u><b>Сеть:</b></u>\n"
|
||||
f"<b>Recv:</b> <code>{bytes2human(net.bytes_recv)}</code>\n"
|
||||
f"<b>Sent:</b> <code>{bytes2human(net.bytes_sent)}</code>\n"
|
||||
f"{chr(10).join(net_info)}\n\n"
|
||||
|
||||
f"<emoji document_id=5382194935057372936>⏱</emoji> <u><b>Аптайм:</b></u>\n"
|
||||
f"<b>Since:</b> <code>{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(boot))}</code>\n"
|
||||
f"<b>Uptime:</b> <code>{format_uptime(uptime)}</code>\n\n"
|
||||
|
||||
f"<emoji document_id=5854908544712707500>📦</emoji> <u><b>Версии:</b></u>\n"
|
||||
f"<b>Python:</b> <code>{platform.python_version()}</code>\n"
|
||||
f"<b>Telethon:</b> <code>{telethon.__version__}</code></blockquote>"
|
||||
)
|
||||
|
||||
await utils.answer(message, text)
|
||||
172
fiksofficial/python-modules/userparser.py
Normal file
172
fiksofficial/python-modules/userparser.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Name: UserParser
|
||||
# Description: Данный модуль позволяет копировать ID, Username и Name участников чата при помощи команды .userpars
|
||||
# meta developer: @PyModule
|
||||
|
||||
from .. import loader, utils
|
||||
import json
|
||||
import os
|
||||
|
||||
class UserIDParserMod(loader.Module):
|
||||
"""Парсер ID, имени, фамилии и юзернейма пользователей с выбором формата файла"""
|
||||
strings = {
|
||||
"name": "UserParser",
|
||||
"format_set": "<emoji document_id=5206607081334906820>✔️</emoji> <b>Формат файла успешно установлен на: {}</b>",
|
||||
"invalid_format": "<emoji document_id=5274099962655816924>❗️</emoji> <b>Неверный формат! Используйте: json, txt или html.</b>",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.file_format = "json"
|
||||
|
||||
async def client_ready(self, client, db):
|
||||
self.client = client
|
||||
self.db = db
|
||||
saved_format = self.db.get("UserParser", "file_format", None)
|
||||
if saved_format:
|
||||
self.file_format = saved_format
|
||||
|
||||
async def formatparscmd(self, message):
|
||||
"""Устанавливает формат файла: json, txt или html"""
|
||||
args = utils.get_args_raw(message)
|
||||
if args and args.lower() in ["json", "txt", "html"]:
|
||||
self.file_format = args.lower()
|
||||
self.db.set("UserParser", "file_format", self.file_format)
|
||||
await message.edit(self.strings["format_set"].format(self.file_format))
|
||||
else:
|
||||
await message.edit(self.strings["invalid_format"])
|
||||
|
||||
async def userparscmd(self, message):
|
||||
"""Собирает информацию о пользователях из чата и сохраняет в файл"""
|
||||
chat = message.chat
|
||||
if not chat:
|
||||
await message.edit("<emoji document_id=5210952531676504517>❌</emoji> <b>Это не чат!</b>")
|
||||
return
|
||||
user_data = []
|
||||
async for user in self.client.iter_participants(chat.id):
|
||||
user_info = {
|
||||
"id": user.id,
|
||||
"username": user.username or "None",
|
||||
"first_name": user.first_name or "None",
|
||||
"last_name": user.last_name or "None"
|
||||
}
|
||||
user_data.append(user_info)
|
||||
chat_title = chat.title or "Без названия"
|
||||
chat_id = chat.id
|
||||
chat_info = f"Чат: {chat_title}\nID чата: {chat_id}"
|
||||
file_format = self.file_format
|
||||
if file_format == "json":
|
||||
file_path = "user_data.json"
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(user_data, f, indent=4, ensure_ascii=False)
|
||||
caption = f"Список пользователей из чата (JSON):\n{chat_info}"
|
||||
elif file_format == "txt":
|
||||
file_path = "user_data.txt"
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"{chat_info}\n\n")
|
||||
for user in user_data:
|
||||
f.write(
|
||||
f"ID: {user['id']}, "
|
||||
f"Username: {user['username']}, "
|
||||
f"Имя: {user['first_name']}, "
|
||||
f"Фамилия: {user['last_name']}\n"
|
||||
)
|
||||
caption = f"Список пользователей из чата (TXT):\n{chat_info}"
|
||||
elif file_format == "html":
|
||||
file_path = "user_data.html"
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Список пользователей</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}}
|
||||
h1 {{
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}}
|
||||
p {{
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
}}
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
font-size: 16px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}}
|
||||
th, td {{
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}}
|
||||
th {{
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}}
|
||||
tr:nth-child(even) {{
|
||||
background-color: #f2f2f2;
|
||||
}}
|
||||
tr:hover {{
|
||||
background-color: #ddd;
|
||||
}}
|
||||
.bold {{
|
||||
font-weight: bold;
|
||||
}}
|
||||
.italic {{
|
||||
font-style: italic;
|
||||
}}
|
||||
.underline {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
.highlight {{
|
||||
background-color: yellow;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="bold">Список пользователей из чата</h1>
|
||||
<p><strong>Чат:</strong> <span class="italic">{chat_title}</span></p>
|
||||
<p><strong>ID чата:</strong> <span class="underline">{chat_id}</span></p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Имя</th>
|
||||
<th>Фамилия</th>
|
||||
</tr>
|
||||
""")
|
||||
for user in user_data:
|
||||
f.write(f"""
|
||||
<tr>
|
||||
<td class="bold">{user['id']}</td>
|
||||
<td class="italic">{user['username']}</td>
|
||||
<td class="underline">{user['first_name']}</td>
|
||||
<td class="highlight">{user['last_name']}</td>
|
||||
</tr>
|
||||
""")
|
||||
f.write("""
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
caption = f"Список пользователей из чата (HTML):\n{chat_info}"
|
||||
else:
|
||||
await message.edit("<emoji document_id=5274099962655816924>❗️</emoji> <b>Неверный формат файла! Укажите 'json', 'txt' или 'html' с помощью команды .formatpars.</b>")
|
||||
return
|
||||
await self.client.send_file("me", file_path, caption=caption)
|
||||
os.remove(file_path)
|
||||
await message.edit("<emoji document_id=5206607081334906820>✔️</emoji> <b>Успешно!</b>")
|
||||
24
fiksofficial/python-modules/vercel.json
Normal file
24
fiksofficial/python-modules/vercel.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*).txt",
|
||||
"headers": [{"key": "Content-Type", "value": "text/plain; charset=utf-8"}]
|
||||
},
|
||||
{
|
||||
"source": "/(.*).html",
|
||||
"headers": [{ "key": "Content-Type", "value": "text/html; charset=utf-8" }]
|
||||
},
|
||||
{
|
||||
"source": "/(.*).css",
|
||||
"headers": [{ "key": "Content-Type", "value": "text/css; charset=utf-8" }]
|
||||
},
|
||||
{
|
||||
"source": "/(.*).js",
|
||||
"headers": [{ "key": "Content-Type", "value": "text/js; charset=utf-8" }]
|
||||
},
|
||||
{
|
||||
"source": "/(.*).py",
|
||||
"headers": [{"key": "Content-Type", "value": "text/plain; charset=utf-8"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
133
fiksofficial/python-modules/wiki.py
Normal file
133
fiksofficial/python-modules/wiki.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# ______ ___ ___ _ _
|
||||
# ____ | ___ \ | \/ | | | | |
|
||||
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
|
||||
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
|
||||
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
|
||||
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
|
||||
# \____/ __/ |
|
||||
# |___/
|
||||
|
||||
# На модуль распространяется лицензия "GNU General Public License v3.0"
|
||||
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
|
||||
|
||||
# meta developer: @pymodule
|
||||
# requires: aiohttp
|
||||
|
||||
from .. import loader, utils
|
||||
from ..inline.types import InlineQuery
|
||||
import aiohttp
|
||||
|
||||
|
||||
@loader.tds
|
||||
class WikiSearchMod(loader.Module):
|
||||
"""Search Wikipedia articles"""
|
||||
|
||||
strings = {
|
||||
"name": "WikiSearch",
|
||||
"no_query": "❗ Please provide a search term.",
|
||||
"searching": "🔎 Searching Wikipedia for: <b>{query}</b>",
|
||||
"not_found": "🚫 No results found for: <code>{query}</code>",
|
||||
"error": "🚫 Error: <code>{error}</code>",
|
||||
"article": "<b>{title}</b>\n\n{summary}\n\n🌐 <a href='{url}'>Read more…</a>",
|
||||
"inline_title": "📚 {title}",
|
||||
"inline_description": "🔍 {summary}",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"name": "WikiSearch",
|
||||
"no_query": "❗ Укажи термин для поиска.",
|
||||
"searching": "🔎 Поиск в Википедии по запросу: <b>{query}</b>",
|
||||
"not_found": "🚫 Ничего не найдено по запросу: <code>{query}</code>",
|
||||
"error": "🚫 Ошибка: <code>{error}</code>",
|
||||
"article": "<b>{title}</b>\n\n{summary}\n\n🌐 <a href='{url}'>Читать далее…</a>",
|
||||
"inline_title": "📚 {title}",
|
||||
"inline_description": "🔍 {summary}",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"lang",
|
||||
"en",
|
||||
lambda: "Language for Wikipedia search (e.g. en, ru, fr)",
|
||||
validator=loader.validators.RegExp(r"^[a-z]{2}$"),
|
||||
)
|
||||
)
|
||||
|
||||
@loader.command(doc="[term] - Search Wikipedia for a term", ru_doc="[термин] - Поиск статьи в Википедии по запросу")
|
||||
async def wiki(self, message):
|
||||
query = utils.get_args_raw(message)
|
||||
if not query:
|
||||
return await utils.answer(message, self.strings("no_query"))
|
||||
|
||||
await utils.answer(message, self.strings("searching").format(query=query))
|
||||
|
||||
article = await self._get_article_async(query)
|
||||
if isinstance(article, str):
|
||||
return await utils.answer(message, self.strings("error").format(error=article))
|
||||
if article is None:
|
||||
return await utils.answer(message, self.strings("not_found").format(query=query))
|
||||
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("article").format(
|
||||
title=article["title"],
|
||||
summary=article["summary"],
|
||||
url=article["url"]
|
||||
)
|
||||
)
|
||||
|
||||
async def _get_article_async(self, query):
|
||||
try:
|
||||
lang = self.config["lang"]
|
||||
search_url = f"https://{lang}.wikipedia.org/w/api.php"
|
||||
params = {
|
||||
"action": "query",
|
||||
"format": "json",
|
||||
"list": "search",
|
||||
"srsearch": query,
|
||||
"srlimit": 1
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(search_url, params=params) as res:
|
||||
data = await res.json()
|
||||
results = data.get("query", {}).get("search", [])
|
||||
if not results:
|
||||
return None
|
||||
|
||||
title = results[0]["title"]
|
||||
summary_url = f"https://{lang}.wikipedia.org/api/rest_v1/page/summary/{title.replace(' ', '_')}"
|
||||
async with session.get(summary_url) as res:
|
||||
data = await res.json()
|
||||
|
||||
return {
|
||||
"title": data.get("title", title),
|
||||
"summary": data.get("extract", "No summary available"),
|
||||
"url": data.get("content_urls", {}).get("desktop", {}).get("page", f"https://{lang}.wikipedia.org/wiki/{title.replace(' ', '_')}")
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
@loader.inline_everyone
|
||||
async def wiki_inline_handler(self, query: InlineQuery):
|
||||
"""[term] - Inline Wikipedia search"""
|
||||
if not query.args:
|
||||
return await query.e400()
|
||||
|
||||
article = await self._get_article_async(query.args)
|
||||
if isinstance(article, str):
|
||||
return await query.e500()
|
||||
if article is None:
|
||||
return await query.e404()
|
||||
|
||||
return [{
|
||||
"title": self.strings("inline_title").format(title=article["title"]),
|
||||
"description": article["summary"][:100],
|
||||
"message": self.strings("article").format(
|
||||
title=article["title"],
|
||||
summary=article["summary"],
|
||||
url=article["url"]
|
||||
)
|
||||
}]
|
||||
0
hikariatama/ftg/README.md
Normal file → Executable file
0
hikariatama/ftg/README.md
Normal file → Executable file
0
hikariatama/ftg/account_switcher.py
Normal file → Executable file
0
hikariatama/ftg/account_switcher.py
Normal file → Executable file
0
hikariatama/ftg/activists.py
Normal file → Executable file
0
hikariatama/ftg/activists.py
Normal file → Executable file
0
hikariatama/ftg/aniquotes.py
Normal file → Executable file
0
hikariatama/ftg/aniquotes.py
Normal file → Executable file
0
hikariatama/ftg/anisearch.py
Normal file → Executable file
0
hikariatama/ftg/anisearch.py
Normal file → Executable file
0
hikariatama/ftg/artai.py
Normal file → Executable file
0
hikariatama/ftg/artai.py
Normal file → Executable file
0
hikariatama/ftg/backuper.py
Normal file → Executable file
0
hikariatama/ftg/backuper.py
Normal file → Executable file
0
hikariatama/ftg/bigtext.py
Normal file → Executable file
0
hikariatama/ftg/bigtext.py
Normal file → Executable file
0
hikariatama/ftg/bincheck.py
Normal file → Executable file
0
hikariatama/ftg/bincheck.py
Normal file → Executable file
0
hikariatama/ftg/bulkcheck.py
Normal file → Executable file
0
hikariatama/ftg/bulkcheck.py
Normal file → Executable file
0
hikariatama/ftg/carbon.py
Normal file → Executable file
0
hikariatama/ftg/carbon.py
Normal file → Executable file
0
hikariatama/ftg/catboy.py
Normal file → Executable file
0
hikariatama/ftg/catboy.py
Normal file → Executable file
0
hikariatama/ftg/catgirl.py
Normal file → Executable file
0
hikariatama/ftg/catgirl.py
Normal file → Executable file
0
hikariatama/ftg/cloud.py
Normal file → Executable file
0
hikariatama/ftg/cloud.py
Normal file → Executable file
0
hikariatama/ftg/deepl.py
Normal file → Executable file
0
hikariatama/ftg/deepl.py
Normal file → Executable file
0
hikariatama/ftg/dictionary.py
Normal file → Executable file
0
hikariatama/ftg/dictionary.py
Normal file → Executable file
0
hikariatama/ftg/dnd_statuses.py
Normal file → Executable file
0
hikariatama/ftg/dnd_statuses.py
Normal file → Executable file
0
hikariatama/ftg/dyslexia.py
Normal file → Executable file
0
hikariatama/ftg/dyslexia.py
Normal file → Executable file
0
hikariatama/ftg/edutatar.py
Normal file → Executable file
0
hikariatama/ftg/edutatar.py
Normal file → Executable file
0
hikariatama/ftg/emotionless.py
Normal file → Executable file
0
hikariatama/ftg/emotionless.py
Normal file → Executable file
0
hikariatama/ftg/fancyfonts.py
Normal file → Executable file
0
hikariatama/ftg/fancyfonts.py
Normal file → Executable file
0
hikariatama/ftg/feedback.py
Normal file → Executable file
0
hikariatama/ftg/feedback.py
Normal file → Executable file
0
hikariatama/ftg/flash_cards.py
Normal file → Executable file
0
hikariatama/ftg/flash_cards.py
Normal file → Executable file
0
hikariatama/ftg/forex_wss.py
Normal file → Executable file
0
hikariatama/ftg/forex_wss.py
Normal file → Executable file
0
hikariatama/ftg/fuck_tags.py
Normal file → Executable file
0
hikariatama/ftg/fuck_tags.py
Normal file → Executable file
0
hikariatama/ftg/git_pusher.py
Normal file → Executable file
0
hikariatama/ftg/git_pusher.py
Normal file → Executable file
0
hikariatama/ftg/grustnogram.py
Normal file → Executable file
0
hikariatama/ftg/grustnogram.py
Normal file → Executable file
0
hikariatama/ftg/hikarichat.py
Normal file → Executable file
0
hikariatama/ftg/hikarichat.py
Normal file → Executable file
0
hikariatama/ftg/httpsc.py
Normal file → Executable file
0
hikariatama/ftg/httpsc.py
Normal file → Executable file
0
hikariatama/ftg/hw.py
Normal file → Executable file
0
hikariatama/ftg/hw.py
Normal file → Executable file
0
hikariatama/ftg/img2pdf.py
Normal file → Executable file
0
hikariatama/ftg/img2pdf.py
Normal file → Executable file
0
hikariatama/ftg/inline_ghoul.py
Normal file → Executable file
0
hikariatama/ftg/inline_ghoul.py
Normal file → Executable file
0
hikariatama/ftg/inline_random.py
Normal file → Executable file
0
hikariatama/ftg/inline_random.py
Normal file → Executable file
0
hikariatama/ftg/inline_spotify.py
Normal file → Executable file
0
hikariatama/ftg/inline_spotify.py
Normal file → Executable file
0
hikariatama/ftg/insult.py
Normal file → Executable file
0
hikariatama/ftg/insult.py
Normal file → Executable file
0
hikariatama/ftg/keyword.py
Normal file → Executable file
0
hikariatama/ftg/keyword.py
Normal file → Executable file
0
hikariatama/ftg/lastcommand.py
Normal file → Executable file
0
hikariatama/ftg/lastcommand.py
Normal file → Executable file
0
hikariatama/ftg/linter.py
Normal file → Executable file
0
hikariatama/ftg/linter.py
Normal file → Executable file
0
hikariatama/ftg/longread.py
Normal file → Executable file
0
hikariatama/ftg/longread.py
Normal file → Executable file
0
hikariatama/ftg/lovemagic.py
Normal file → Executable file
0
hikariatama/ftg/lovemagic.py
Normal file → Executable file
0
hikariatama/ftg/moonlove.py
Normal file → Executable file
0
hikariatama/ftg/moonlove.py
Normal file → Executable file
0
hikariatama/ftg/neko.py
Normal file → Executable file
0
hikariatama/ftg/neko.py
Normal file → Executable file
0
hikariatama/ftg/nometa.py
Normal file → Executable file
0
hikariatama/ftg/nometa.py
Normal file → Executable file
0
hikariatama/ftg/notes.py
Normal file → Executable file
0
hikariatama/ftg/notes.py
Normal file → Executable file
0
hikariatama/ftg/onload.py
Normal file → Executable file
0
hikariatama/ftg/onload.py
Normal file → Executable file
0
hikariatama/ftg/pmbl.py
Normal file → Executable file
0
hikariatama/ftg/pmbl.py
Normal file → Executable file
0
hikariatama/ftg/pollplot.py
Normal file → Executable file
0
hikariatama/ftg/pollplot.py
Normal file → Executable file
0
hikariatama/ftg/purr.py
Normal file → Executable file
0
hikariatama/ftg/purr.py
Normal file → Executable file
0
hikariatama/ftg/ratemod.py
Normal file → Executable file
0
hikariatama/ftg/ratemod.py
Normal file → Executable file
0
hikariatama/ftg/rpmod.py
Normal file → Executable file
0
hikariatama/ftg/rpmod.py
Normal file → Executable file
0
hikariatama/ftg/scrolller.py
Normal file → Executable file
0
hikariatama/ftg/scrolller.py
Normal file → Executable file
0
hikariatama/ftg/secret_chat.py
Normal file → Executable file
0
hikariatama/ftg/secret_chat.py
Normal file → Executable file
0
hikariatama/ftg/serverinfo.py
Normal file → Executable file
0
hikariatama/ftg/serverinfo.py
Normal file → Executable file
0
hikariatama/ftg/shikimori.py
Normal file → Executable file
0
hikariatama/ftg/shikimori.py
Normal file → Executable file
0
hikariatama/ftg/silent_tags.py
Normal file → Executable file
0
hikariatama/ftg/silent_tags.py
Normal file → Executable file
0
hikariatama/ftg/speller.py
Normal file → Executable file
0
hikariatama/ftg/speller.py
Normal file → Executable file
0
hikariatama/ftg/spoilers.py
Normal file → Executable file
0
hikariatama/ftg/spoilers.py
Normal file → Executable file
0
hikariatama/ftg/spotify.py
Normal file → Executable file
0
hikariatama/ftg/spotify.py
Normal file → Executable file
0
hikariatama/ftg/sticks.py
Normal file → Executable file
0
hikariatama/ftg/sticks.py
Normal file → Executable file
0
hikariatama/ftg/surl.py
Normal file → Executable file
0
hikariatama/ftg/surl.py
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user