mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 14:34:17 +02:00
Added and updated repositories 2026-05-31 02:47:15
This commit is contained in:
4
archquise/q.mods/.gitignore
vendored
4
archquise/q.mods/.gitignore
vendored
@@ -4,4 +4,6 @@
|
||||
.ruff_cache
|
||||
ruff.log
|
||||
ruff.log.2
|
||||
ruff.toml
|
||||
ruff.toml
|
||||
# Heroku files
|
||||
heroku/
|
||||
|
||||
438
archquise/q.mods/QNotes.py
Normal file
438
archquise/q.mods/QNotes.py
Normal file
@@ -0,0 +1,438 @@
|
||||
__version__ = (1, 1, 6)
|
||||
|
||||
# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀
|
||||
# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█
|
||||
|
||||
# #### Copyright (c) 2026 Archquise #####
|
||||
|
||||
# 💬 Contact: https://t.me/archquise
|
||||
# 🔒 Licensed under the GNU AGPLv3.
|
||||
# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE
|
||||
# ---------------------------------------------------------------------------------
|
||||
# Name: QNotes
|
||||
# Description: A notes module that just works
|
||||
# Author: @quise_m
|
||||
# ---------------------------------------------------------------------------------
|
||||
# meta developer: @quise_m
|
||||
# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/qnotes.png
|
||||
# ---------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from datetime import date
|
||||
from typing import cast
|
||||
|
||||
from herokutl.tl.functions.users import GetUsersRequest
|
||||
from herokutl.tl.types import InputUserSelf
|
||||
|
||||
from .. import loader, utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@loader.tds
|
||||
class QNotes(loader.Module):
|
||||
"""A notes module that just works\nUsage: #notetag in any chat"""
|
||||
|
||||
strings = {
|
||||
"name": "QNotes",
|
||||
"topic_desc": "Stores your notes content\nUsage: #notetag in any chat",
|
||||
"wrongargs": "<emoji document_id=5980953710157632545>❌</emoji> <b>Wrong arguments. Check command usage.</b>",
|
||||
"not_exist": "There is no such note!",
|
||||
"no_reply": "No reply! Reply to the message, which text will become a note.",
|
||||
"already_exists": "Seems like note with the same tag already exists. Overwrite?",
|
||||
"show_note_inline": "<blockquote>#{}</blockquote>\n\n<blockquote>{}</blockquote>",
|
||||
"notelist": "Note list:",
|
||||
"msg_not_found_inline": "Message with this note wasn't found. Probably, it was been removed. Note has been removed from the database.",
|
||||
"remnote_inline": "🗑 Remove",
|
||||
"close_inline": "❌ Close",
|
||||
"yes": "✔️ Yes",
|
||||
"no": "❌ No",
|
||||
"true": "yes",
|
||||
"false": "no",
|
||||
"saved": "Note saved!",
|
||||
"removed": "Note removed!",
|
||||
"nonotes": "You don't have any notes!",
|
||||
"privacy_switch": "Determines whose data will be used by the my_* placeholders\n\nTrue - the account that is issuing the note\nFalse - the account on which the userbot is running",
|
||||
"note_prefix": "The prefix used to call up notes",
|
||||
"placeholders": """
|
||||
<b>Available placeholders</b>:
|
||||
|
||||
about the account on which userbot is installed:
|
||||
{my_id} - ID
|
||||
@{my_username} - username, tag
|
||||
{my_phone} - phone number
|
||||
{my_premium} - premium status (yes/no)
|
||||
|
||||
about reply author:
|
||||
{reply_id} - ID
|
||||
{reply_name} - name
|
||||
{reply_surname} - surname
|
||||
{reply_fullname} - full name (name + surname (if specified))
|
||||
@{reply_username} - username, tag
|
||||
{reply_phone} - phone number (if not hidden)
|
||||
{reply_premium} - premium status (yes/no)
|
||||
|
||||
general:
|
||||
{today} - current date
|
||||
""",
|
||||
}
|
||||
|
||||
strings_ru = {
|
||||
"_cls_doc": "Модуль для заметок, который просто работает\nИспользование: #тегзаметки в любом чате",
|
||||
"topic_desc": "Хранит содержимое ваших заметок\nИспользование: #тегзаметки в любом чате",
|
||||
"wrongargs": "<emoji document_id=5980953710157632545>❌</emoji> <b>Неверные аргументы. Проверьте использование команды.</b>",
|
||||
"no_reply": "Нет реплая! Ответьте на сообщение, текст которого станет заметкой.",
|
||||
"not_exist": "Такой заметки не найдено!",
|
||||
"already_exists": "Кажется, заметка с таким тегом уже существует. Перезаписать?",
|
||||
"show_note_inline": "<blockquote>#{}</blockquote>\n\n<blockquote>{}</blockquote>",
|
||||
"notelist": "Список заметок:",
|
||||
"msg_not_found_inline": "Сообщение с этой заметкой не было найдено. Вероятно, оно было удалено. Заметка очищена из базы данных.",
|
||||
"remnote_inline": "🗑 Удалить",
|
||||
"close_inline": "❌ Закрыть",
|
||||
"yes": "✔️ Да",
|
||||
"no": "❌ Нет",
|
||||
"saved": "Заметка сохранена!",
|
||||
"removed": "Заметка удалена!",
|
||||
"true": "да",
|
||||
"false": "нет",
|
||||
"nonotes": "Нет заметок!",
|
||||
"privacy_switch": "Влияет на то, чьи данные будут использовать my_* плейсхолдеры\n\nTrue - аккаунта, который вызывает заметку\nFalse - аккаунта на котором стоит юзербот",
|
||||
"note_prefix": "Префикс, с которым вызываются заметки",
|
||||
"placeholders": """
|
||||
<b>Доступные плейсхолдеры</b>:
|
||||
|
||||
об аккаунте, на котором стоит юзербот:
|
||||
{my_id} - айди
|
||||
@{my_username} - юзернейм, тег
|
||||
{my_phone} - номер телефона
|
||||
{my_premium} - статус премиум (да/нет)
|
||||
|
||||
об авторе реплая:
|
||||
{reply_id} - айди
|
||||
{reply_name} - имя
|
||||
{reply_surname} - фамилия
|
||||
{reply_fullname} - полное имя (имя + фамилия (если указана))
|
||||
@{reply_username} - юзернейм, тег
|
||||
{reply_phone} - номер телефона (если не скрыт)
|
||||
{reply_premium} - статус премиум (да/нет)
|
||||
|
||||
общее:
|
||||
{today} - текущая дата
|
||||
""",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config = loader.ModuleConfig(
|
||||
loader.ConfigValue(
|
||||
"privacy_switch",
|
||||
True,
|
||||
lambda: self.strings["privacy_switch"],
|
||||
validator=loader.validators.Boolean(), # type: ignore
|
||||
),
|
||||
loader.ConfigValue(
|
||||
"note_prefix",
|
||||
"#",
|
||||
lambda: self.strings["note_prefix"],
|
||||
validator=loader.validators.RegExp(r"^\S+$"), # type: ignore
|
||||
),
|
||||
)
|
||||
|
||||
async def client_ready(self, client, db): # type: ignore
|
||||
self._content_channel_id = await utils.wait_for_content_channel(self._db)
|
||||
self._notes_topic = await utils.asset_forum_topic(
|
||||
client=self._client,
|
||||
db=self._db,
|
||||
peer=self._content_channel_id, # type: ignore
|
||||
title="QNotes | Storage",
|
||||
description=self.strings["topic_desc"],
|
||||
icon_emoji_id=5272001961326049733,
|
||||
)
|
||||
|
||||
self.my_phone = (await self._client(GetUsersRequest(id=[InputUserSelf()])))[
|
||||
0
|
||||
].phone
|
||||
|
||||
self.placeholders = {
|
||||
"my_phone": self.my_phone,
|
||||
"my_username": self._client.heroku_me.username,
|
||||
"my_id": self.tg_id,
|
||||
"my_premium": self.strings["true"]
|
||||
if self._client.heroku_me.premium
|
||||
else self.strings["false"],
|
||||
}
|
||||
|
||||
self._notemap = cast(dict, self.pointer("notemap", default={}))
|
||||
|
||||
async def _ask_overwrite(self, message):
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
future = loop.create_future()
|
||||
|
||||
form = await self.inline.form(
|
||||
self.strings["already_exists"],
|
||||
message=message,
|
||||
reply_markup=[
|
||||
[
|
||||
{
|
||||
"text": self.strings["yes"],
|
||||
"callback": (
|
||||
lambda call, flag: (
|
||||
future.set_result(flag) if not future.done() else None
|
||||
)
|
||||
),
|
||||
"args": (True,),
|
||||
},
|
||||
{
|
||||
"text": self.strings["no"],
|
||||
"callback": (
|
||||
lambda call, flag: (
|
||||
future.set_result(flag) if not future.done() else None
|
||||
)
|
||||
),
|
||||
"args": (False,),
|
||||
},
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
try:
|
||||
async with asyncio.timeout(15):
|
||||
overwrite_answer = await future
|
||||
except TimeoutError:
|
||||
await form.delete() # type: ignore
|
||||
return False, message
|
||||
|
||||
if not overwrite_answer:
|
||||
await form.delete() # type: ignore
|
||||
return False, form
|
||||
|
||||
return True, form
|
||||
|
||||
async def _show_note_inline(self, call, note, page=0):
|
||||
async def _remnote(call, notetag, note_msg):
|
||||
await note_msg.delete()
|
||||
self._notemap.pop(notetag, None)
|
||||
|
||||
await call.edit(self.strings["removed"])
|
||||
|
||||
note_msg = await self._client.get_messages(
|
||||
self._content_channel_id, ids=note[1]
|
||||
)
|
||||
|
||||
if not note_msg:
|
||||
self._notemap.pop(note[0], None)
|
||||
|
||||
await call.edit(
|
||||
self.strings["msg_not_found_inline"],
|
||||
reply_markup=[
|
||||
{"text": "⬅️ Назад", "callback": self._list_page, "args": (page,)},
|
||||
{"text": self.strings["close_inline"], "action": "close"},
|
||||
],
|
||||
)
|
||||
return
|
||||
|
||||
await call.edit(
|
||||
self.strings["show_note_inline"].format(note[0], note_msg.text), # type: ignore
|
||||
reply_markup=[
|
||||
[
|
||||
{"text": "⬅️ Назад", "callback": self._list_page, "args": (page,)},
|
||||
{
|
||||
"text": self.strings["remnote_inline"],
|
||||
"callback": _remnote,
|
||||
"args": (note[0], note_msg),
|
||||
},
|
||||
],
|
||||
[{"text": self.strings["close_inline"], "action": "close"}],
|
||||
],
|
||||
)
|
||||
|
||||
def _build_list_markup(self, page: int):
|
||||
items = list(self._notemap.items())
|
||||
total = -(-len(items) // 3)
|
||||
page = max(0, min(page, total - 1))
|
||||
rows = [
|
||||
[
|
||||
{
|
||||
"text": notetag,
|
||||
"callback": self._show_note_inline,
|
||||
"args": ([notetag, msg_id], page),
|
||||
}
|
||||
]
|
||||
for notetag, msg_id in items[page * 3 : (page + 1) * 3]
|
||||
]
|
||||
return (
|
||||
rows
|
||||
+ self.inline.build_pagination(
|
||||
callback=self._list_page, # type: ignore
|
||||
total_pages=total,
|
||||
current_page=page + 1,
|
||||
)
|
||||
+ [[{"text": self.strings["close_inline"], "action": "close"}]]
|
||||
)
|
||||
|
||||
async def _list_page(self, call, page):
|
||||
await call.edit(
|
||||
text=self.strings["notelist"], reply_markup=self._build_list_markup(page)
|
||||
)
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Сохраняет заметку под тегом | Пример: .qnsave заметка",
|
||||
en_doc="Saves note by tag | Example: .qnsave note",
|
||||
)
|
||||
async def qnsave(self, message) -> None:
|
||||
args = utils.get_args(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings["wrongargs"])
|
||||
return
|
||||
|
||||
current_message = message
|
||||
|
||||
if not (reply := await message.get_reply_message()):
|
||||
await utils.answer(message, self.strings["no_reply"])
|
||||
return
|
||||
try:
|
||||
if args[0].strip() in self._notemap:
|
||||
need_overwrite, msg = await self._ask_overwrite(message)
|
||||
if not need_overwrite:
|
||||
return
|
||||
old_note_message = await self._client.get_messages(
|
||||
self._content_channel_id,
|
||||
ids=self._notemap[args[0].strip()],
|
||||
)
|
||||
old_note_message and await old_note_message.delete() # type: ignore
|
||||
current_message = msg
|
||||
|
||||
note_message = await self._client.send_message(
|
||||
self._content_channel_id,
|
||||
reply.text,
|
||||
reply_to=self._notes_topic.id,
|
||||
file=reply.media,
|
||||
)
|
||||
self._notemap[args[0].strip()] = note_message.id
|
||||
|
||||
except Exception as e:
|
||||
await utils.answer(current_message, f"Произошла ошибка: {e}")
|
||||
logger.exception("Произошла ошибка при сохранении заметки!")
|
||||
return
|
||||
await utils.answer(current_message, self.strings["saved"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Удаляет заметку по тегу | Пример: .qnrem заметка",
|
||||
en_doc="Removes note by tag | Example: .qnrem note",
|
||||
)
|
||||
async def qnrem(self, message) -> None:
|
||||
args = utils.get_args(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings["wrongargs"])
|
||||
return
|
||||
|
||||
if args[0] not in self._notemap or not (
|
||||
note_message := await self._client.get_messages(
|
||||
self._content_channel_id,
|
||||
ids=self._notemap[args[0]],
|
||||
)
|
||||
):
|
||||
await utils.answer(message, self.strings["not_exist"])
|
||||
return
|
||||
|
||||
await note_message.delete() # type: ignore
|
||||
self._notemap.pop(args[0], None)
|
||||
|
||||
await utils.answer(message, self.strings["removed"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Выводит список всех заметок и позволяет управлять ими",
|
||||
en_doc="Shows note list and allows managing them",
|
||||
)
|
||||
async def qnlist(self, message) -> None:
|
||||
if self._notemap:
|
||||
await self.inline.form(
|
||||
text=self.strings["notelist"],
|
||||
reply_markup=self._build_list_markup(0),
|
||||
message=message,
|
||||
)
|
||||
return
|
||||
await utils.answer(message, self.strings["nonotes"])
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Выводит список доступных плейсхолдеров",
|
||||
en_doc="Displays a list of available placeholders",
|
||||
)
|
||||
async def qnp(self, message) -> None:
|
||||
await utils.answer(message, self.strings["placeholders"])
|
||||
|
||||
@loader.watcher()
|
||||
async def _note_watcher(self, message):
|
||||
if not message.text.startswith(prefix := self.config["note_prefix"]) or not (
|
||||
await self._client.dispatcher.security.check(message, self._note_watcher)
|
||||
):
|
||||
return
|
||||
|
||||
notetag = message.text.split(prefix, maxsplit=1)[1]
|
||||
|
||||
if notetag in self._notemap:
|
||||
if not (
|
||||
note_message := await self._client.get_messages(
|
||||
self._content_channel_id,
|
||||
ids=self._notemap[notetag],
|
||||
)
|
||||
):
|
||||
self._notemap.pop(notetag, None)
|
||||
return
|
||||
notetext = note_message.text or "" # type: ignore
|
||||
if re.search(r"\{\w+\}", notetext):
|
||||
if (
|
||||
not self.config["privacy_switch"]
|
||||
or message.sender_id == self._client.heroku_me.id
|
||||
):
|
||||
placeholders = {**self.placeholders}
|
||||
else:
|
||||
message_author_entity = await self._client.get_entity(
|
||||
message.sender_id
|
||||
)
|
||||
placeholders = {
|
||||
"my_phone": (
|
||||
await self._client(GetUsersRequest(id=[message.sender_id]))
|
||||
)[0].phone,
|
||||
"my_username": message_author_entity.username,
|
||||
"my_id": message.sender_id,
|
||||
"my_premium": self.strings["true"]
|
||||
if message_author_entity.premium
|
||||
else self.strings["false"],
|
||||
}
|
||||
|
||||
if reply_msg := await message.get_reply_message():
|
||||
reply_user = await self._client.get_entity(reply_msg.sender_id)
|
||||
placeholders = {
|
||||
**placeholders,
|
||||
"reply_id": reply_user.id,
|
||||
"reply_fullname": " ".join(
|
||||
filter(None, [reply_user.first_name, reply_user.last_name])
|
||||
),
|
||||
"reply_name": reply_user.first_name,
|
||||
"reply_surname": reply_user.last_name,
|
||||
"reply_phone": (
|
||||
await self._client(GetUsersRequest(id=[reply_user.id]))
|
||||
)[0].phone,
|
||||
"reply_username": reply_user.username,
|
||||
"reply_premium": self.strings["true"]
|
||||
if reply_user.premium
|
||||
else self.strings["false"],
|
||||
}
|
||||
|
||||
placeholders = placeholders | {"today": date.today()}
|
||||
|
||||
def replacer(match):
|
||||
key = match.group(1)
|
||||
if key not in placeholders or not placeholders[key]:
|
||||
return match.group(0)
|
||||
return utils.escape_html(str(placeholders[key]))
|
||||
|
||||
notetext = re.sub(r"\{(\w+)\}", replacer, notetext)
|
||||
if media := note_message.media: # type: ignore
|
||||
await utils.answer_file(message, media, notetext) # type: ignore
|
||||
else:
|
||||
await utils.answer(message, notetext)
|
||||
return
|
||||
@@ -59,7 +59,7 @@ class FaceMod(loader.Module):
|
||||
en_doc="Random kaomoji",
|
||||
)
|
||||
async def rfacecmd(self, message) -> None: # noqa: D102, ANN001
|
||||
await utils.answer(message, self.strings("loading"))
|
||||
await utils.answer(message, self.strings["loading"])
|
||||
|
||||
url = "https://files.archquise.ru/kaomoji.txt"
|
||||
|
||||
@@ -72,7 +72,7 @@ class FaceMod(loader.Module):
|
||||
kaomoji = random.choice(kaomoji_list) # noqa: S311
|
||||
await utils.answer(
|
||||
message,
|
||||
self.strings("random_face").format(kaomoji),
|
||||
self.strings["random_face"].format(kaomoji),
|
||||
)
|
||||
else:
|
||||
await utils.answer(message, self.strings("error"))
|
||||
await utils.answer(message, self.strings["error"])
|
||||
|
||||
@@ -113,24 +113,24 @@ class ShortenerMod(loader.Module):
|
||||
async def shortencmd(self, message): # noqa: ANN001, ANN201
|
||||
"""Shorten URL using bit.ly API."""
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_api"))
|
||||
await utils.answer(message, self.strings["no_api"])
|
||||
return
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
await utils.answer(message, self.strings["no_args"])
|
||||
return
|
||||
|
||||
if not self._validate_url(args):
|
||||
await utils.answer(message, self.strings("invalid_url"))
|
||||
await utils.answer(message, self.strings["invalid_url"])
|
||||
return
|
||||
|
||||
try:
|
||||
short_url = await self.shorten_url(url=args, token=self.config["token"])
|
||||
await utils.answer(message, self.strings("shortencmd").format(c=short_url))
|
||||
await utils.answer(message, self.strings["shortencmd"].format(c=short_url))
|
||||
except Exception as e:
|
||||
logger.exception("Error shortening URL!")
|
||||
await utils.answer(message, self.strings("api_error").format(error=str(e)))
|
||||
await utils.answer(message, self.strings["api_error"].format(error=str(e)))
|
||||
|
||||
@loader.command(
|
||||
ru_doc="Посмотреть статистику ссылки через bit.ly (ссылка без https:// | Доступно только на платных аккаунтах)",
|
||||
@@ -139,22 +139,22 @@ class ShortenerMod(loader.Module):
|
||||
async def statclcmd(self, message): # noqa: ANN001, ANN201
|
||||
"""Get click statistics for shortened URL."""
|
||||
if self.config["token"] is None:
|
||||
await utils.answer(message, self.strings("no_api"))
|
||||
await utils.answer(message, self.strings["no_api"])
|
||||
return
|
||||
|
||||
args = utils.get_args_raw(message)
|
||||
if not args:
|
||||
await utils.answer(message, self.strings("no_args"))
|
||||
await utils.answer(message, self.strings["no_args"])
|
||||
return
|
||||
|
||||
try:
|
||||
if not args.startswith("bit.ly/"):
|
||||
await utils.answer(message, self.strings("invalid_url"))
|
||||
await utils.answer(message, self.strings["invalid_url"])
|
||||
return
|
||||
clicks = await self.get_bitlink_stats(
|
||||
bitlink=args, token=self.config["token"]
|
||||
)
|
||||
await utils.answer(message, self.strings("statclcmd").format(c=clicks))
|
||||
await utils.answer(message, self.strings["statclcmd"].format(c=clicks))
|
||||
except Exception as e:
|
||||
logger.exception("Error getting statistics!")
|
||||
await utils.answer(message, self.strings("api_error").format(error=str(e)))
|
||||
await utils.answer(message, self.strings["api_error"].format(error=str(e)))
|
||||
|
||||
Reference in New Issue
Block a user