4 Commits

14 changed files with 150 additions and 46 deletions

View File

@@ -51,7 +51,8 @@
"enabled": false "enabled": false
}, },
"spoilers": { "spoilers": {
"enabled": true "enabled": true,
"allow_external": true
} }
}, },
"scheduler": { "scheduler": {
@@ -74,6 +75,11 @@
"cache_admins": { "cache_admins": {
"interval": 120, "interval": 120,
"enabled": true "enabled": true
},
"channels_monitor": {
"interval": 5,
"enabled": true,
"channels": []
} }
}, },
"locations": { "locations": {

View File

@@ -31,7 +31,7 @@ from modules.commands.warnings import *
from modules.callbacks.nothing import * from modules.callbacks.nothing import *
from modules.callbacks.reapply import * from modules.callbacks.reapply import *
from modules.callbacks.rules import * from modules.callbacks.rules import *
from modules.callbacks.sid import * from modules.callbacks.spoiler import *
from modules.callbacks.sponsorship import * from modules.callbacks.sponsorship import *
from modules.callbacks.sub import * from modules.callbacks.sub import *
from modules.callbacks.sus import * from modules.callbacks.sus import *

View File

@@ -109,7 +109,9 @@
"spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення", "spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення",
"spoiler_cancel": "Створення спойлера було припинено", "spoiler_cancel": "Створення спойлера було припинено",
"spoiler_empty": "Спойлер категорії \"{0}\" без опису", "spoiler_empty": "Спойлер категорії \"{0}\" без опису",
"spoiler_empty_named": "Спойлер категорії \"{0}\" без опису від **{1}**",
"spoiler_described": "Спойлер категорії \"{0}\": {1}", "spoiler_described": "Спойлер категорії \"{0}\": {1}",
"spoiler_described_named": "Спойлер категорії \"{0}\" від **{1}**: {2}",
"spoiler_description_enter": "Добре, введіть бажаний опис спойлера", "spoiler_description_enter": "Добре, введіть бажаний опис спойлера",
"spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.", "spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.",
"spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)", "spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)",
@@ -118,6 +120,7 @@
"spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).", "spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).",
"spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.", "spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.",
"spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.", "spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.",
"youtube_video": "На каналі [{0}]({1}) нове відео!\n\n**[{2}]({3})**",
"yes": "Так", "yes": "Так",
"no": "Ні", "no": "Ні",
"voice_message": [ "voice_message": [
@@ -226,8 +229,10 @@
"done": "✅ Готово", "done": "✅ Готово",
"sponsor_apply": "Заповнити форму", "sponsor_apply": "Заповнити форму",
"sponsor_started": "Форму розпочато", "sponsor_started": "Форму розпочато",
"spoiler_send": "Надіслати", "spoiler_view": "Переглянути",
"spoiler_view": ереглянути" "spoiler_preview": опередній перегляд",
"spoiler_send_chat": "Надіслати в холо-чат",
"spoiler_send_other": "Надіслати в інший чат"
}, },
"callback": { "callback": {
"sub_accepted": "✅ Анкету {0} схвалено", "sub_accepted": "✅ Анкету {0} схвалено",
@@ -243,7 +248,8 @@
"reapply_stopped": " Перервано заповнення анкети", "reapply_stopped": " Перервано заповнення анкети",
"sponsor_started": " Заповнення форми розпочато", "sponsor_started": " Заповнення форми розпочато",
"sponsor_accepted": "✅ Форму {0} схвалено", "sponsor_accepted": "✅ Форму {0} схвалено",
"sponsor_rejected": "❌ Форму {0} відхилено" "sponsor_rejected": "❌ Форму {0} відхилено",
"spoiler_sent": "✅ Повідомлення надіслано в холо-чат"
}, },
"inline": { "inline": {
"forbidden": { "forbidden": {

View File

@@ -1,10 +0,0 @@
from app import app
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from pyrogram import filters
# Callback rule ================================================================================================================
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
async def callback_query_rule(app: Client, clb: CallbackQuery):
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
# ==============================================================================================================================

View File

@@ -0,0 +1,31 @@
from app import app
from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.client import Client
from pyrogram import filters
from modules.database import col_spoilers
from bson.objectid import ObjectId
from modules.utils import configGet, locale
# Callback sid =================================================================================================================
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
async def callback_query_sid(app: Client, clb: CallbackQuery):
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
# ==============================================================================================================================
# Callback shc =================================================================================================================
@app.on_callback_query(filters.regex("shc_[\s\S]*"))
async def callback_query_shc(app: Client, clb: CallbackQuery):
spoil = col_spoilers.find_one( {"_id": ObjectId(clb.data.split("_")[1])} )
if spoil["description"] == "":
desc = locale("spoiler_empty_named", "message", locale=clb.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), clb.from_user.first_name)
else:
desc = locale("spoiler_described_named", "message", locale=clb.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), clb.from_user.first_name, spoil["description"])
await app.send_message(configGet("users", "groups"), desc, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=clb.from_user), callback_data=f'sid_{clb.data.split("_")[1]}')]]))
await app.send_message(configGet("admin", "groups"), desc, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=clb.from_user), callback_data=f'sid_{clb.data.split("_")[1]}')]]))
await clb.answer(locale("spoiler_sent", "callback", locale=clb.from_user), show_alert=True)
# ==============================================================================================================================

View File

@@ -51,7 +51,7 @@ async def cmd_identify(app: Client, msg: Message):
if user.photo is not None: if user.photo is not None:
await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO) await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO)
await msg.reply_photo( await msg.reply_photo(
create_tmp(await download_tmp(app, user.photo.big_file_id), kind="image"), create_tmp((await download_tmp(app, user.photo.big_file_id))[1], kind="image"),
quote=should_quote(msg), quote=should_quote(msg),
caption=output caption=output
) )

View File

@@ -10,7 +10,7 @@ from modules.database import col_spoilers
from modules import custom_filters from modules import custom_filters
# Spoiler command ============================================================================================================== # Spoiler command ==============================================================================================================
@app.on_message(custom_filters.member & ~filters.scheduled & filters.private & filters.command(["spoiler"], prefixes=["/"])) @app.on_message(custom_filters.enabled_spoilers & custom_filters.member & ~filters.scheduled & filters.private & filters.command(["spoiler"], prefixes=["/"]))
async def cmd_spoiler(app: Client, msg: Message): async def cmd_spoiler(app: Client, msg: Message):
try: try:

View File

@@ -39,6 +39,9 @@ async def enabled_invites_check_func(_, __, msg: Message):
async def enabled_dinovoice_func(_, __, msg: Message): async def enabled_dinovoice_func(_, __, msg: Message):
return configGet("enabled", "features", "dinovoice") return configGet("enabled", "features", "dinovoice")
async def enabled_spoilers_func(_, __, msg: Message):
return configGet("enabled", "features", "spoilers")
async def filling_sponsorship_func(_, __, msg: Message): async def filling_sponsorship_func(_, __, msg: Message):
return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False
@@ -52,5 +55,6 @@ enabled_sponsorships = filters.create(enabled_sponsorships_func)
enabled_warnings = filters.create(enabled_warnings_func) enabled_warnings = filters.create(enabled_warnings_func)
enabled_invites_check = filters.create(enabled_invites_check_func) enabled_invites_check = filters.create(enabled_invites_check_func)
enabled_dinovoice = filters.create(enabled_dinovoice_func) enabled_dinovoice = filters.create(enabled_dinovoice_func)
enabled_spoilers = filters.create(enabled_spoilers_func)
filling_sponsorship = filters.create(filling_sponsorship_func) filling_sponsorship = filters.create(filling_sponsorship_func)

View File

@@ -28,13 +28,14 @@ db = db_client.get_database(name=db_config["name"])
collections = db.list_collection_names() collections = db.list_collection_names()
for collection in ["tmp", "users", "context", "spoilers", "messages", "warnings", "applications", "sponsorships"]: for collection in ["tmp", "users", "context", "youtube", "spoilers", "messages", "warnings", "applications", "sponsorships"]:
if not collection in collections: if not collection in collections:
db.create_collection(collection) db.create_collection(collection)
col_tmp = db.get_collection("tmp") col_tmp = db.get_collection("tmp")
col_users = db.get_collection("users") col_users = db.get_collection("users")
col_context = db.get_collection("context") col_context = db.get_collection("context")
col_youtube = db.get_collection("youtube")
col_spoilers = db.get_collection("spoilers") col_spoilers = db.get_collection("spoilers")
col_messages = db.get_collection("messages") col_messages = db.get_collection("messages")
col_warnings = db.get_collection("warnings") col_warnings = db.get_collection("warnings")

View File

@@ -66,6 +66,9 @@ async def any_stage(app: Client, msg: Message):
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill": if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
if configGet("enabled", "features", "spoilers") is False:
return
spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} ) spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} )
if spoiler is None: if spoiler is None:
@@ -146,7 +149,37 @@ async def any_stage(app: Client, msg: Message):
ready = True ready = True
if ready is True: if ready is True:
await msg.reply_text(locale("spoiler_ready", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_send", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")]])) if configGet("allow_external", "features", "spoilers") is True:
await msg.reply_text(
locale("spoiler_ready", "message", locale=msg.from_user),
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(locale("spoiler_preview", "button", locale=msg.from_user), callback_data=f"sid_{spoiler['_id'].__str__()}")
],
[
InlineKeyboardButton(locale("spoiler_send_chat", "button", locale=msg.from_user), callback_data=f"shc_{spoiler['_id'].__str__()}")
],
[
InlineKeyboardButton(locale("spoiler_send_other", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")
]
]
)
)
else:
await msg.reply_text(
locale("spoiler_ready", "message", locale=msg.from_user),
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(locale("spoiler_preview", "button", locale=msg.from_user), callback_data=f"sid_{spoiler['_id'].__str__()}")
],
[
InlineKeyboardButton(locale("spoiler_send_chat", "button", locale=msg.from_user), callback_data=f"shc_{spoiler['_id'].__str__()}")
]
]
)
)
else: else:
await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user)) await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user))

View File

@@ -22,34 +22,36 @@ async def inline_answer(client: Client, inline_query: InlineQuery):
results = [] results = []
if inline_query.query.startswith("spoiler:"): if configGet("allow_external", "features", "spoilers") is True:
try: if inline_query.query.startswith("spoiler:"):
try:
spoil = col_spoilers.find_one( {"_id": ObjectId(inline_query.query.removeprefix("spoiler:"))} ) spoil = col_spoilers.find_one( {"_id": ObjectId(inline_query.query.removeprefix("spoiler:"))} )
if spoil is not None: if spoil is not None:
desc = locale("spoiler_empty", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories")) if spoil["description"] == "" else locale("spoiler_described", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), spoil["description"]) desc = locale("spoiler_empty", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories")) if spoil["description"] == "" else locale("spoiler_described", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), spoil["description"])
results = [ results = [
InlineQueryResultArticle( InlineQueryResultArticle(
title=locale("title", "inline", "spoiler", locale=inline_query.from_user), title=locale("title", "inline", "spoiler", locale=inline_query.from_user),
description=locale("description", "inline", "spoiler", locale=inline_query.from_user), description=locale("description", "inline", "spoiler", locale=inline_query.from_user),
input_message_content=InputTextMessageContent(desc, disable_web_page_preview=True), input_message_content=InputTextMessageContent(desc, disable_web_page_preview=True),
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=inline_query.from_user), callback_data=f'sid_{inline_query.query.removeprefix("spoiler:")}')]]) reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=inline_query.from_user), callback_data=f'sid_{inline_query.query.removeprefix("spoiler:")}')]])
) )
] ]
except InvalidId: except InvalidId:
results = [] results = []
await inline_query.answer( await inline_query.answer(
results=results results=results
) )
return return
if inline_query.chat_type in [ChatType.CHANNEL]: if inline_query.chat_type in [ChatType.CHANNEL]:
await inline_query.answer( await inline_query.answer(

View File

@@ -1,7 +1,9 @@
"""Automatically register commands and execute """Automatically register commands and execute
some scheduled tasks is the main idea of this module""" some scheduled tasks is the main idea of this module"""
from asyncio import sleep
from os import listdir, makedirs, path, sep from os import listdir, makedirs, path, sep
from traceback import format_exc
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ujson import dumps from ujson import dumps
@@ -12,7 +14,9 @@ from pyrogram.enums.chat_members_filter import ChatMembersFilter
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import configGet, jsonSave, locale, logWrite from modules.utils import configGet, jsonSave, locale, logWrite
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from modules.database import col_applications, col_sponsorships from modules.database import col_applications, col_sponsorships, col_youtube
from xmltodict import parse
from requests import get
scheduler = AsyncIOScheduler() scheduler = AsyncIOScheduler()
@@ -233,4 +237,30 @@ async def commands_register():
if configGet("debug") is True: if configGet("debug") is True:
print(commands, flush=True) print(commands, flush=True)
logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}", debug=True) logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}", debug=True)
if configGet("enabled", "scheduler", "channels_monitor"):
@scheduler.scheduled_job(trigger="interval", minutes=configGet("interval", "scheduler", "channels_monitor"))
async def channels_monitor():
for channel in configGet("channels", "scheduler", "channels_monitor"):
if configGet("debug") is True:
logWrite(f'Processing videos of {channel["name"]} ({channel["id"]})', debug=True)
try:
req = get(f'https://www.youtube.com/feeds/videos.xml?channel_id={channel["id"]}')
parsed = parse(req.content)
if "feed" not in parsed:
continue
if "entry" not in parsed["feed"]:
continue
for entry in parsed["feed"]["entry"]:
if "yt:videoId" not in entry:
continue
if col_youtube.find_one( {"channel": channel["id"], "video": entry["yt:videoId"]} ) is None:
col_youtube.insert_one( {"channel": channel["id"], "video": entry["yt:videoId"], "date": datetime.fromisoformat(entry["published"])} )
await app.send_message(configGet("users", "groups"), locale("youtube_video", "message").format(channel["name"], channel["link"], entry["title"], entry["link"]["@href"]), disable_web_page_preview=False)
await sleep(2)
except Exception as exp:
logWrite(f'Could not get last videos of {channel["name"]} ({channel["id"]}) due to {exp}: {format_exc()}')
if configGet("debug") is True:
logWrite("Admin group caching performed", debug=True)

View File

@@ -1,4 +1,4 @@
from typing import Any, Literal, Union from typing import Any, Literal, Tuple, Union
from uuid import uuid1 from uuid import uuid1
from requests import get from requests import get
from pyrogram.enums.chat_type import ChatType from pyrogram.enums.chat_type import ChatType
@@ -212,7 +212,7 @@ def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "
file.write(bytedata) file.write(bytedata)
return path.join("tmp", filename) return path.join("tmp", filename)
async def download_tmp(app: Client, file_id: str) -> bytes: async def download_tmp(app: Client, file_id: str) -> Tuple[str, bytes]:
"""Download file by its ID and return its bytes """Download file by its ID and return its bytes
### Args: ### Args:
@@ -220,14 +220,14 @@ async def download_tmp(app: Client, file_id: str) -> bytes:
* file_id (`str`): File's unique id * file_id (`str`): File's unique id
### Returns: ### Returns:
* `bytes`: Bytes of downloaded file * `Tuple[str, bytes]`: First is a filepath and the second is file's bytes
""" """
filename = str(uuid1()) filename = str(uuid1())
makedirs("tmp", exist_ok=True) makedirs("tmp", exist_ok=True)
await app.download_media(file_id, path.join("tmp", filename)) await app.download_media(file_id, path.join("tmp", filename))
with open(path.join("tmp", filename), "rb") as f: with open(path.join("tmp", filename), "rb") as f:
bytedata = f.read() bytedata = f.read()
return bytedata return path.join("tmp", filename), bytedata
try: try:
from psutil import Process from psutil import Process

View File

@@ -8,4 +8,5 @@ tgcrypto==1.2.5
python_dateutil==2.8.2 python_dateutil==2.8.2
starlette~=0.22.0 starlette~=0.22.0
ujson~=5.7.0 ujson~=5.7.0
ftfy~=6.1.1 ftfy~=6.1.1
xmltodict~=0.13.0