WIP: Complete overhaul
This commit is contained in:
parent
bd62149a2c
commit
fc303ee127
24
.gitignore
vendored
24
.gitignore
vendored
@ -152,21 +152,13 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
# ---> VisualStudioCode
|
# Custom
|
||||||
.vscode/*
|
config.json
|
||||||
!.vscode/tasks.json
|
*.session
|
||||||
!.vscode/launch.json
|
*.session-journal
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
venv
|
||||||
.history/
|
venv_linux
|
||||||
|
venv_windows
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
.vscode
|
||||||
*.vsix
|
|
||||||
|
|
||||||
# Project
|
|
||||||
cache
|
|
||||||
data
|
|
||||||
logs
|
|
||||||
config.json
|
|
29
classes/commandset.py
Normal file
29
classes/commandset.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Union
|
||||||
|
from pyrogram.types import (
|
||||||
|
BotCommandScopeAllChatAdministrators,
|
||||||
|
BotCommandScopeAllGroupChats,
|
||||||
|
BotCommandScopeAllPrivateChats,
|
||||||
|
BotCommandScopeChat,
|
||||||
|
BotCommandScopeChatAdministrators,
|
||||||
|
BotCommandScopeChatMember,
|
||||||
|
BotCommandScopeDefault,
|
||||||
|
BotCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CommandSet:
|
||||||
|
"""Command stored in PyroClient's 'commands' attribute"""
|
||||||
|
|
||||||
|
commands: List[BotCommand]
|
||||||
|
scope: Union[
|
||||||
|
BotCommandScopeDefault,
|
||||||
|
BotCommandScopeAllPrivateChats,
|
||||||
|
BotCommandScopeAllGroupChats,
|
||||||
|
BotCommandScopeAllChatAdministrators,
|
||||||
|
BotCommandScopeChat,
|
||||||
|
BotCommandScopeChatAdministrators,
|
||||||
|
BotCommandScopeChatMember,
|
||||||
|
] = BotCommandScopeDefault
|
||||||
|
language_code: str = ""
|
@ -22,6 +22,11 @@ class SubmissionDuplicatesError(Exception):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionUnsupportedError(Exception):
|
||||||
|
def __init__(self, file_path: str) -> None:
|
||||||
|
super().__init__(f"Type of file does not seem to be supported: '{file_path}'")
|
||||||
|
|
||||||
|
|
||||||
class UserCreationError(Exception):
|
class UserCreationError(Exception):
|
||||||
def __init__(self, code: int, data: str) -> None:
|
def __init__(self, code: int, data: str) -> None:
|
||||||
self.code = code
|
self.code = code
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
from os import path, remove, sep
|
|
||||||
from shutil import rmtree
|
|
||||||
from typing import Tuple, Union
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.types import Message
|
|
||||||
from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError
|
|
||||||
from modules.api_client import upload_pic
|
|
||||||
from modules.database import col_submitted
|
|
||||||
from bson import ObjectId
|
|
||||||
from modules.logger import logWrite
|
|
||||||
|
|
||||||
from modules.utils import configGet
|
|
||||||
|
|
||||||
|
|
||||||
class PosterClient(Client):
|
|
||||||
def __init__(self, name: str, **kwargs): # type: ignore
|
|
||||||
super().__init__(name, **kwargs)
|
|
||||||
self.owner = configGet("owner")
|
|
||||||
self.admins = configGet("admins") + [configGet("owner")]
|
|
||||||
|
|
||||||
async def submit_photo(
|
|
||||||
self, id: str
|
|
||||||
) -> Tuple[Union[Message, None], Union[str, None]]:
|
|
||||||
db_entry = col_submitted.find_one({"_id": ObjectId(id)})
|
|
||||||
submission = None
|
|
||||||
|
|
||||||
if db_entry is None:
|
|
||||||
raise SubmissionUnavailableError()
|
|
||||||
else:
|
|
||||||
if db_entry["temp"]["uuid"] is not None:
|
|
||||||
if not path.exists(
|
|
||||||
path.join(
|
|
||||||
configGet("data", "locations"),
|
|
||||||
"submissions",
|
|
||||||
db_entry["temp"]["uuid"],
|
|
||||||
db_entry["temp"]["file"],
|
|
||||||
)
|
|
||||||
):
|
|
||||||
raise SubmissionUnavailableError()
|
|
||||||
else:
|
|
||||||
filepath = path.join(
|
|
||||||
configGet("data", "locations"),
|
|
||||||
"submissions",
|
|
||||||
db_entry["temp"]["uuid"],
|
|
||||||
db_entry["temp"]["file"],
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
submission = await self.get_messages(
|
|
||||||
db_entry["user"], db_entry["telegram"]["msg_id"]
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
submission = await self.get_messages(
|
|
||||||
db_entry["user"], db_entry["telegram"]["msg_id"]
|
|
||||||
)
|
|
||||||
filepath = await self.download_media(
|
|
||||||
submission, file_name=configGet("tmp", "locations") + sep
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
raise SubmissionUnavailableError()
|
|
||||||
|
|
||||||
response = await upload_pic(
|
|
||||||
str(filepath), allow_duplicates=configGet("allow_duplicates", "submission")
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(response[1]) > 0:
|
|
||||||
raise SubmissionDuplicatesError(str(filepath), response[1])
|
|
||||||
|
|
||||||
col_submitted.find_one_and_update(
|
|
||||||
{"_id": ObjectId(id)}, {"$set": {"done": True}}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if db_entry["temp"]["uuid"] is not None:
|
|
||||||
rmtree(
|
|
||||||
path.join(
|
|
||||||
configGet("data", "locations"),
|
|
||||||
"submissions",
|
|
||||||
db_entry["temp"]["uuid"],
|
|
||||||
),
|
|
||||||
ignore_errors=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
remove(str(filepath))
|
|
||||||
except (FileNotFoundError, NotADirectoryError):
|
|
||||||
logWrite(
|
|
||||||
f"Could not delete '{filepath}' on submission accepted", debug=True
|
|
||||||
)
|
|
||||||
|
|
||||||
return submission, response[2]
|
|
||||||
|
|
||||||
async def ban_user(self, id: int) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def unban_user(self, id: int) -> None:
|
|
||||||
pass
|
|
465
classes/pyroclient.py
Normal file
465
classes/pyroclient.py
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from io import BytesIO
|
||||||
|
from os import getpid, makedirs, remove, sep
|
||||||
|
from pathlib import Path
|
||||||
|
from shutil import rmtree
|
||||||
|
from time import time
|
||||||
|
from traceback import format_exc
|
||||||
|
from typing import List, Tuple, Union
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from bson import ObjectId
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from libbot import json_read, json_write
|
||||||
|
from libbot.i18n import BotLocale
|
||||||
|
from libbot.i18n.sync import _
|
||||||
|
from photosapi_client.errors import UnexpectedStatus
|
||||||
|
from pyrogram.client import Client
|
||||||
|
from pyrogram.errors import BadRequest, bad_request_400
|
||||||
|
from pyrogram.handlers.message_handler import MessageHandler
|
||||||
|
from pyrogram.raw.all import layer
|
||||||
|
from pyrogram.types import (
|
||||||
|
BotCommand,
|
||||||
|
BotCommandScopeAllChatAdministrators,
|
||||||
|
BotCommandScopeAllGroupChats,
|
||||||
|
BotCommandScopeAllPrivateChats,
|
||||||
|
BotCommandScopeChat,
|
||||||
|
BotCommandScopeChatAdministrators,
|
||||||
|
BotCommandScopeChatMember,
|
||||||
|
BotCommandScopeDefault,
|
||||||
|
Message,
|
||||||
|
)
|
||||||
|
from ujson import dumps, loads
|
||||||
|
|
||||||
|
from classes.commandset import CommandSet
|
||||||
|
from classes.exceptions import (
|
||||||
|
SubmissionDuplicatesError,
|
||||||
|
SubmissionUnavailableError,
|
||||||
|
SubmissionUnsupportedError,
|
||||||
|
)
|
||||||
|
from classes.pyrocommand import PyroCommand
|
||||||
|
from modules.api_client import (
|
||||||
|
BodyPhotoUploadAlbumsAlbumPhotosPost,
|
||||||
|
File,
|
||||||
|
client,
|
||||||
|
photo_upload,
|
||||||
|
)
|
||||||
|
from modules.database import col_submitted
|
||||||
|
from modules.http_client import http_session
|
||||||
|
from modules.scheduler import scheduler
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PyroClient(Client):
|
||||||
|
def __init__(self):
|
||||||
|
with open("config.json", "r", encoding="utf-8") as f:
|
||||||
|
self.config: dict = loads(f.read())
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
name="bot_client",
|
||||||
|
api_id=self.config["bot"]["api_id"],
|
||||||
|
api_hash=self.config["bot"]["api_hash"],
|
||||||
|
bot_token=self.config["bot"]["bot_token"],
|
||||||
|
plugins=dict(root="plugins", exclude=self.config["disabled_plugins"]),
|
||||||
|
sleep_threshold=120,
|
||||||
|
max_concurrent_transmissions=self.config["bot"][
|
||||||
|
"max_concurrent_transmissions"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.version: float = 0.2
|
||||||
|
|
||||||
|
self.owner: int = self.config["bot"]["owner"]
|
||||||
|
self.admins: List[int] = self.config["bot"]["admins"] + [
|
||||||
|
self.config["bot"]["owner"]
|
||||||
|
]
|
||||||
|
self.commands: List[PyroCommand] = []
|
||||||
|
self.scoped_commands: bool = self.config["bot"]["scoped_commands"]
|
||||||
|
self.start_time: float = 0
|
||||||
|
|
||||||
|
self.bot_locale: BotLocale = BotLocale(Path(self.config["locations"]["locale"]))
|
||||||
|
self.default_locale: str = self.bot_locale.default
|
||||||
|
self.locales: dict = self.bot_locale.locales
|
||||||
|
|
||||||
|
self._ = self.bot_locale._
|
||||||
|
self.in_all_locales = self.bot_locale.in_all_locales
|
||||||
|
self.in_every_locale = self.bot_locale.in_every_locale
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
await super().start()
|
||||||
|
|
||||||
|
self.start_time = time()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Bot is running with Pyrogram v%s (Layer %s) and has started as @%s on PID %s.",
|
||||||
|
pyrogram.__version__,
|
||||||
|
layer,
|
||||||
|
self.me.username,
|
||||||
|
getpid(),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if Path(f"{self.config['locations']['cache']}/shutdown_time").exists():
|
||||||
|
downtime = relativedelta(
|
||||||
|
datetime.now(),
|
||||||
|
datetime.fromtimestamp(
|
||||||
|
(
|
||||||
|
await json_read(
|
||||||
|
Path(
|
||||||
|
f"{self.config['locations']['cache']}/shutdown_time"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)["timestamp"]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if downtime.days >= 1:
|
||||||
|
startup_message = self._(
|
||||||
|
"startup_downtime_days",
|
||||||
|
"message",
|
||||||
|
).format(getpid(), downtime.days)
|
||||||
|
elif downtime.hours >= 1:
|
||||||
|
startup_message = self._(
|
||||||
|
"startup_downtime_hours",
|
||||||
|
"message",
|
||||||
|
).format(getpid(), downtime.hours)
|
||||||
|
else:
|
||||||
|
startup_message = self._(
|
||||||
|
"startup_downtime_minutes",
|
||||||
|
"message",
|
||||||
|
).format(getpid(), downtime.minutes)
|
||||||
|
else:
|
||||||
|
startup_message = (self._("startup", "message").format(getpid()),)
|
||||||
|
|
||||||
|
await self.send_message(
|
||||||
|
chat_id=self.config["reports"]["chat_id"],
|
||||||
|
text=startup_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.config["reports"]["update"]:
|
||||||
|
try:
|
||||||
|
async with ClientSession(
|
||||||
|
json_serialize=dumps,
|
||||||
|
) as http_session:
|
||||||
|
check_update = await http_session.get(
|
||||||
|
"https://git.end-play.xyz/api/v1/repos/profitroll/TelegramPoster/releases?page=1&limit=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await check_update.json()
|
||||||
|
|
||||||
|
if len(response) == 0:
|
||||||
|
raise ValueError("No bot releases on git found.")
|
||||||
|
|
||||||
|
if float(response[0]["tag_name"].replace("v", "")) > self.version:
|
||||||
|
logger.info(
|
||||||
|
"New version %s found (current %s)",
|
||||||
|
response[0]["tag_name"].replace("v", ""),
|
||||||
|
self.version,
|
||||||
|
)
|
||||||
|
await self.send_message(
|
||||||
|
self.owner,
|
||||||
|
self._(
|
||||||
|
"update_available",
|
||||||
|
"message",
|
||||||
|
).format(
|
||||||
|
response[0]["tag_name"],
|
||||||
|
response[0]["html_url"],
|
||||||
|
response[0]["body"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("No updates found, bot is up to date.")
|
||||||
|
except bad_request_400.PeerIdInvalid:
|
||||||
|
logger.warning(
|
||||||
|
"Could not send startup message to bot owner. Perhaps user has not started the bot yet."
|
||||||
|
)
|
||||||
|
except Exception as exp:
|
||||||
|
logger.exception(
|
||||||
|
"Update check failed due to %s: %s", exp, format_exc()
|
||||||
|
)
|
||||||
|
|
||||||
|
scheduler.add_job(
|
||||||
|
self.register_commands,
|
||||||
|
trigger="date",
|
||||||
|
run_date=datetime.now() + timedelta(seconds=5),
|
||||||
|
kwargs={"command_sets": await self.collect_commands()},
|
||||||
|
)
|
||||||
|
|
||||||
|
scheduler.start()
|
||||||
|
except BadRequest:
|
||||||
|
logger.warning("Unable to send message to report chat.")
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
makedirs(self.config["locations"]["cache"], exist_ok=True)
|
||||||
|
await json_write(
|
||||||
|
{"timestamp": time()},
|
||||||
|
Path(f"{self.config['locations']['cache']}/shutdown_time"),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.send_message(
|
||||||
|
chat_id=self.config["reports"]["chat_id"],
|
||||||
|
text=f"Bot stopped with PID `{getpid()}`",
|
||||||
|
)
|
||||||
|
except BadRequest:
|
||||||
|
logger.warning("Unable to send message to report chat.")
|
||||||
|
await http_session.close()
|
||||||
|
await super().stop()
|
||||||
|
logger.warning("Bot stopped with PID %s.", getpid())
|
||||||
|
|
||||||
|
async def collect_commands(self) -> Union[List[CommandSet], None]:
|
||||||
|
"""Gather list of the bot's commands
|
||||||
|
|
||||||
|
### Returns:
|
||||||
|
* `List[CommandSet]`: List of the commands' sets
|
||||||
|
"""
|
||||||
|
command_sets = None
|
||||||
|
|
||||||
|
# If config get bot.scoped_commands is true - more complicated
|
||||||
|
# scopes system will be used instead of simple global commands
|
||||||
|
if self.scoped_commands:
|
||||||
|
scopes = {}
|
||||||
|
command_sets = []
|
||||||
|
|
||||||
|
# Iterate through all commands in config
|
||||||
|
for command, contents in self.config["commands"].items():
|
||||||
|
# Iterate through all scopes of a command
|
||||||
|
for scope in contents["scopes"]:
|
||||||
|
if dumps(scope) not in scopes:
|
||||||
|
scopes[dumps(scope)] = {"_": []}
|
||||||
|
|
||||||
|
# Add command to the scope's flattened key in scopes dict
|
||||||
|
scopes[dumps(scope)]["_"].append(
|
||||||
|
BotCommand(command, _(command, "commands"))
|
||||||
|
)
|
||||||
|
|
||||||
|
for locale, string in (
|
||||||
|
self.in_every_locale(command, "commands")
|
||||||
|
).items():
|
||||||
|
if locale not in scopes[dumps(scope)]:
|
||||||
|
scopes[dumps(scope)][locale] = []
|
||||||
|
|
||||||
|
scopes[dumps(scope)][locale].append(BotCommand(command, string))
|
||||||
|
|
||||||
|
# Iterate through all scopes and its commands
|
||||||
|
for scope, locales in scopes.items():
|
||||||
|
# Make flat key a dict again
|
||||||
|
scope_dict = loads(scope)
|
||||||
|
|
||||||
|
# Replace "owner" in the bot scope with owner's id
|
||||||
|
if "chat_id" in scope_dict and scope_dict["chat_id"] == "owner":
|
||||||
|
scope_dict["chat_id"] = self.owner
|
||||||
|
|
||||||
|
# Create object with the same name and args from the dict
|
||||||
|
try:
|
||||||
|
scope_obj = globals()[scope_dict["name"]](
|
||||||
|
**{
|
||||||
|
key: value
|
||||||
|
for key, value in scope_dict.items()
|
||||||
|
if key != "name"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except NameError:
|
||||||
|
logger.error(
|
||||||
|
"Could not register commands of the scope '%s' due to an invalid scope class provided!",
|
||||||
|
scope_dict["name"],
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
except TypeError:
|
||||||
|
logger.error(
|
||||||
|
"Could not register commands of the scope '%s' due to an invalid class arguments provided!",
|
||||||
|
scope_dict["name"],
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add set of commands to the list of the command sets
|
||||||
|
for locale, commands in locales.items():
|
||||||
|
if locale == "_":
|
||||||
|
command_sets.append(
|
||||||
|
CommandSet(commands, scope=scope_obj, language_code="")
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
command_sets.append(
|
||||||
|
CommandSet(commands, scope=scope_obj, language_code=locale)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Registering the following command sets: %s", command_sets)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# This part here looks into the handlers and looks for commands
|
||||||
|
# in it, if there are any. Then adds them to self.commands
|
||||||
|
for handler in self.dispatcher.groups[0]:
|
||||||
|
if isinstance(handler, MessageHandler):
|
||||||
|
for entry in [handler.filters.base, handler.filters.other]:
|
||||||
|
if hasattr(entry, "commands"):
|
||||||
|
for command in entry.commands:
|
||||||
|
logger.info("I see a command %s in my filters", command)
|
||||||
|
self.add_command(command)
|
||||||
|
|
||||||
|
return command_sets
|
||||||
|
|
||||||
|
def add_command(
|
||||||
|
self,
|
||||||
|
command: str,
|
||||||
|
):
|
||||||
|
"""Add command to the bot's internal commands list
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* command (`str`)
|
||||||
|
"""
|
||||||
|
self.commands.append(
|
||||||
|
PyroCommand(
|
||||||
|
command,
|
||||||
|
_(command, "commands"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"Added command '%s' to the bot's internal commands list",
|
||||||
|
command,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def register_commands(
|
||||||
|
self, command_sets: Union[List[CommandSet], None] = None
|
||||||
|
):
|
||||||
|
"""Register commands stored in bot's 'commands' attribute"""
|
||||||
|
|
||||||
|
if command_sets is None:
|
||||||
|
commands = [
|
||||||
|
BotCommand(command=command.command, description=command.description)
|
||||||
|
for command in self.commands
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Registering commands %s with a default scope 'BotCommandScopeDefault'"
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.set_bot_commands(commands)
|
||||||
|
return
|
||||||
|
|
||||||
|
for command_set in command_sets:
|
||||||
|
logger.info(
|
||||||
|
"Registering command set with commands %s and scope '%s' (%s)",
|
||||||
|
command_set.commands,
|
||||||
|
command_set.scope,
|
||||||
|
command_set.language_code,
|
||||||
|
)
|
||||||
|
await self.set_bot_commands(
|
||||||
|
command_set.commands,
|
||||||
|
command_set.scope,
|
||||||
|
language_code=command_set.language_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def remove_commands(self, command_sets: Union[List[CommandSet], None] = None):
|
||||||
|
"""Remove commands stored in bot's 'commands' attribute"""
|
||||||
|
|
||||||
|
if command_sets is None:
|
||||||
|
logger.info(
|
||||||
|
"Removing commands with a default scope 'BotCommandScopeDefault'"
|
||||||
|
)
|
||||||
|
await self.delete_bot_commands(BotCommandScopeDefault())
|
||||||
|
return
|
||||||
|
|
||||||
|
for command_set in command_sets:
|
||||||
|
logger.info(
|
||||||
|
"Removing command set with scope '%s' (%s)",
|
||||||
|
command_set.scope,
|
||||||
|
command_set.language_code,
|
||||||
|
)
|
||||||
|
await self.delete_bot_commands(
|
||||||
|
command_set.scope,
|
||||||
|
language_code=command_set.language_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def submit_photo(
|
||||||
|
self, id: str
|
||||||
|
) -> Tuple[Union[Message, None], Union[str, None]]:
|
||||||
|
db_entry = col_submitted.find_one({"_id": ObjectId(id)})
|
||||||
|
submission = None
|
||||||
|
|
||||||
|
if db_entry is None:
|
||||||
|
raise SubmissionUnavailableError()
|
||||||
|
|
||||||
|
if db_entry["temp"]["uuid"] is None:
|
||||||
|
try:
|
||||||
|
submission = await self.get_messages(
|
||||||
|
db_entry["user"], db_entry["telegram"]["msg_id"]
|
||||||
|
)
|
||||||
|
filepath = await self.download_media(
|
||||||
|
submission, file_name=self.config["locations"]["tmp"] + sep
|
||||||
|
)
|
||||||
|
except Exception as exp:
|
||||||
|
raise SubmissionUnavailableError()
|
||||||
|
|
||||||
|
elif not Path(
|
||||||
|
f"{self.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}/{db_entry['temp']['file']}",
|
||||||
|
).exists():
|
||||||
|
raise SubmissionUnavailableError()
|
||||||
|
else:
|
||||||
|
filepath = Path(
|
||||||
|
f"{self.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}/{db_entry['temp']['file']}",
|
||||||
|
)
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
submission = await self.get_messages(
|
||||||
|
db_entry["user"], db_entry["telegram"]["msg_id"]
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(str(filepath), "rb") as fh:
|
||||||
|
photo_bytes = BytesIO(fh.read())
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await photo_upload(
|
||||||
|
self.config["posting"]["api"]["album"],
|
||||||
|
client=client,
|
||||||
|
multipart_data=BodyPhotoUploadAlbumsAlbumPhotosPost(
|
||||||
|
File(photo_bytes, filepath.name, "image/jpeg")
|
||||||
|
),
|
||||||
|
ignore_duplicates=self.config["submission"]["allow_duplicates"],
|
||||||
|
compress=False,
|
||||||
|
caption="queue",
|
||||||
|
)
|
||||||
|
except UnexpectedStatus:
|
||||||
|
raise SubmissionUnsupportedError(str(filepath))
|
||||||
|
|
||||||
|
response_dict = loads(response.content.decode("utf-8"))
|
||||||
|
|
||||||
|
if "duplicates" in response_dict and len(response_dict["duplicates"]) > 0:
|
||||||
|
duplicates = []
|
||||||
|
for index, duplicate in enumerate(response_dict["duplicates"]): # type: ignore
|
||||||
|
if response_dict["access_token"] is None:
|
||||||
|
duplicates.append(
|
||||||
|
f"`{duplicate['id']}`:\n{self.config['posting']['api']['address_external']}/photos/{duplicate['id']}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
duplicates.append(
|
||||||
|
f"`{duplicate['id']}`:\n{self.config['posting']['api']['address_external']}/token/photo/{response_dict['access_token']}?id={index}"
|
||||||
|
)
|
||||||
|
raise SubmissionDuplicatesError(str(filepath), duplicates)
|
||||||
|
|
||||||
|
col_submitted.find_one_and_update(
|
||||||
|
{"_id": ObjectId(id)}, {"$set": {"done": True}}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if db_entry["temp"]["uuid"] is not None:
|
||||||
|
rmtree(
|
||||||
|
Path(
|
||||||
|
f"{self.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}",
|
||||||
|
),
|
||||||
|
ignore_errors=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
remove(str(filepath))
|
||||||
|
except (FileNotFoundError, NotADirectoryError):
|
||||||
|
logger.error("Could not delete '%s' on submission accepted", filepath)
|
||||||
|
|
||||||
|
return submission, response.parsed.id
|
||||||
|
|
||||||
|
async def ban_user(self, id: int) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def unban_user(self, id: int) -> None:
|
||||||
|
pass
|
9
classes/pyrocommand.py
Normal file
9
classes/pyrocommand.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PyroCommand:
|
||||||
|
"""Command stored in PyroClient's 'commands' attribute"""
|
||||||
|
|
||||||
|
command: str
|
||||||
|
description: str
|
@ -1,7 +1,8 @@
|
|||||||
from modules.app import app
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from libbot import sync
|
||||||
|
|
||||||
from modules.database import col_banned, col_users
|
from modules.database import col_banned, col_users
|
||||||
from modules.utils import configGet
|
|
||||||
|
|
||||||
|
|
||||||
class PosterUser:
|
class PosterUser:
|
||||||
@ -31,18 +32,20 @@ class PosterUser:
|
|||||||
### Returns:
|
### Returns:
|
||||||
`bool`: Must be `True` if on the cooldown and `False` if not
|
`bool`: Must be `True` if on the cooldown and `False` if not
|
||||||
"""
|
"""
|
||||||
if self.id in app.admins:
|
if self.id in sync.config_get("admins", "bot"):
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
db_record = col_users.find_one({"user": self.id})
|
db_record = col_users.find_one({"user": self.id})
|
||||||
if db_record is None:
|
|
||||||
return False
|
if db_record is None:
|
||||||
return (
|
return False
|
||||||
True
|
|
||||||
if (datetime.now() - db_record["cooldown"]).total_seconds()
|
return (
|
||||||
< configGet("timeout", "submission")
|
True
|
||||||
else False
|
if (datetime.now() - db_record["cooldown"]).total_seconds()
|
||||||
)
|
< sync.config_get("timeout", "submission")
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
def limit(self) -> None:
|
def limit(self) -> None:
|
||||||
"""Restart user's cooldown. Used after post has been submitted."""
|
"""Restart user's cooldown. Used after post has been submitted."""
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
"locale": "en",
|
"locale": "en",
|
||||||
"locale_log": "en",
|
"locale_log": "en",
|
||||||
"locale_fallback": "en",
|
"locale_fallback": "en",
|
||||||
"owner": 0,
|
|
||||||
"admins": [],
|
|
||||||
"bot": {
|
"bot": {
|
||||||
|
"owner": 0,
|
||||||
|
"admins": [],
|
||||||
"api_id": 0,
|
"api_id": 0,
|
||||||
"api_hash": "",
|
"api_hash": "",
|
||||||
"bot_token": ""
|
"bot_token": "",
|
||||||
|
"max_concurrent_transmissions": 5,
|
||||||
|
"scoped_commands": true
|
||||||
},
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"user": null,
|
"user": null,
|
||||||
@ -16,17 +18,18 @@
|
|||||||
"port": 27017,
|
"port": 27017,
|
||||||
"name": "tgposter"
|
"name": "tgposter"
|
||||||
},
|
},
|
||||||
"mode": {
|
|
||||||
"post": true,
|
|
||||||
"submit": true
|
|
||||||
},
|
|
||||||
"reports": {
|
"reports": {
|
||||||
|
"chat_id": 0,
|
||||||
"sent": false,
|
"sent": false,
|
||||||
"error": true,
|
"error": true,
|
||||||
"update": true,
|
"update": true,
|
||||||
"startup": true,
|
"startup": true,
|
||||||
"shutdown": true
|
"shutdown": true
|
||||||
},
|
},
|
||||||
|
"mode": {
|
||||||
|
"post": true,
|
||||||
|
"submit": true
|
||||||
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
"size": 512,
|
"size": 512,
|
||||||
"location": "logs"
|
"location": "logs"
|
||||||
@ -40,6 +43,7 @@
|
|||||||
"index": "data/index.json",
|
"index": "data/index.json",
|
||||||
"locale": "locale"
|
"locale": "locale"
|
||||||
},
|
},
|
||||||
|
"disabled_plugins": [],
|
||||||
"posting": {
|
"posting": {
|
||||||
"channel": 0,
|
"channel": 0,
|
||||||
"silent": false,
|
"silent": false,
|
||||||
@ -110,14 +114,76 @@
|
|||||||
"video/quicktime"
|
"video/quicktime"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"commands": [
|
"commands": {
|
||||||
"start",
|
"start": {
|
||||||
"rules"
|
"scopes": [
|
||||||
],
|
{
|
||||||
"commands_admin": [
|
"name": "BotCommandScopeDefault"
|
||||||
"import",
|
},
|
||||||
"export",
|
{
|
||||||
"remove",
|
"name": "BotCommandScopeChat",
|
||||||
"shutdown"
|
"chat_id": "owner"
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"forwards": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"remove": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"purge": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shutdown": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"commands": {
|
"commands": {
|
||||||
"start": "Start using the bot",
|
"start": "Start using the bot",
|
||||||
"rules": "Photos submission rules"
|
"rules": "Photos submission rules",
|
||||||
},
|
|
||||||
"commands_admin": {
|
|
||||||
"forwards": "Check post forwards",
|
"forwards": "Check post forwards",
|
||||||
"import": "Submit .zip archive with photos",
|
"import": "Submit .zip archive with photos",
|
||||||
"export": "Get .zip archive with all photos",
|
"export": "Get .zip archive with all photos",
|
||||||
@ -60,7 +58,8 @@
|
|||||||
"remove_abort": "Removal aborted.",
|
"remove_abort": "Removal aborted.",
|
||||||
"remove_success": "Removed media with ID `{0}`.",
|
"remove_success": "Removed media with ID `{0}`.",
|
||||||
"remove_failure": "Could not remove media with ID `{0}`. Check if provided ID is correct and if it is - you can also check bot's log for details.",
|
"remove_failure": "Could not remove media with ID `{0}`. Check if provided ID is correct and if it is - you can also check bot's log for details.",
|
||||||
"update_available": "**New version found**\nThere's a newer version of a bot found. You can update your bot to [{0}]({1}) using command line of your host.\n\n**Release notes**\n{2}\n\nRead more about updating you bot on the [wiki page](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nPlease not that you can also disable this notification by editing `reports.update` key of the config."
|
"update_available": "**New version found**\nThere's a newer version of a bot found. You can update your bot to [{0}]({1}) using command line of your host.\n\n**Release notes**\n{2}\n\nRead more about updating you bot on the [wiki page](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nPlease not that you can also disable this notification by editing `reports.update` key of the config.",
|
||||||
|
"shutdown_confirm": "There are {0} unfinished users' contexts. If you turn off the bot, those will be lost. Please confirm shutdown using a button below."
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"sub_yes": "✅ Accept",
|
"sub_yes": "✅ Accept",
|
||||||
@ -70,7 +69,8 @@
|
|||||||
"sub_unblock": "🏳️ Unblock sender",
|
"sub_unblock": "🏳️ Unblock sender",
|
||||||
"post_view": "View in channel",
|
"post_view": "View in channel",
|
||||||
"accepted": "✅ Accepted",
|
"accepted": "✅ Accepted",
|
||||||
"declined": "❌ Declined"
|
"declined": "❌ Declined",
|
||||||
|
"shutdown": "Confirm shutdown"
|
||||||
},
|
},
|
||||||
"callback": {
|
"callback": {
|
||||||
"sub_yes": "✅ Submission approved",
|
"sub_yes": "✅ Submission approved",
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"commands": {
|
"commands": {
|
||||||
"start": "Почати користуватись ботом",
|
"start": "Почати користуватись ботом",
|
||||||
"rules": "Правила пропонування фото"
|
"rules": "Правила пропонування фото",
|
||||||
},
|
|
||||||
"commands_admin": {
|
|
||||||
"forwards": "Переглянути репости",
|
"forwards": "Переглянути репости",
|
||||||
"import": "Надати боту .zip архів з фотографіями",
|
"import": "Надати боту .zip архів з фотографіями",
|
||||||
"export": "Отримати .zip архів з усіма фотографіями",
|
"export": "Отримати .zip архів з усіма фотографіями",
|
||||||
@ -60,7 +58,8 @@
|
|||||||
"remove_abort": "Видалення перервано.",
|
"remove_abort": "Видалення перервано.",
|
||||||
"remove_success": "Видалено медіа з ID `{0}`.",
|
"remove_success": "Видалено медіа з ID `{0}`.",
|
||||||
"remove_failure": "Не вдалося видалити медіа з ID `{0}`. Перевірте, чи вказано правильний ID, і якщо він правильний, ви також можете переглянути логи бота для отримання більш детальної інформації.",
|
"remove_failure": "Не вдалося видалити медіа з ID `{0}`. Перевірте, чи вказано правильний ID, і якщо він правильний, ви також можете переглянути логи бота для отримання більш детальної інформації.",
|
||||||
"update_available": "**Знайдено нову версію**\nЗнайдено нову версію бота. Ви можете оновити бота до [{0}]({1}) за допомогою командного рядка вашого хосту.\n\n**Примітки до релізу**\n{2}\n\nДетальніше про оновлення бота можна знайти на [вікі-сторінці](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nЗверніть увагу, що ви також можете вимкнути це сповіщення, відредагувавши ключ `reports.update` у конфігурації."
|
"update_available": "**Знайдено нову версію**\nЗнайдено нову версію бота. Ви можете оновити бота до [{0}]({1}) за допомогою командного рядка вашого хосту.\n\n**Примітки до релізу**\n{2}\n\nДетальніше про оновлення бота можна знайти на [вікі-сторінці](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nЗверніть увагу, що ви також можете вимкнути це сповіщення, відредагувавши ключ `reports.update` у конфігурації.",
|
||||||
|
"shutdown_confirm": "Існує {0} незавершених контекстів користувачів. Якщо ви вимкнете бота, вони будуть втрачені. Будь ласка, підтвердіть вимкнення за допомогою кнопки нижче."
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"sub_yes": "✅ Прийняти",
|
"sub_yes": "✅ Прийняти",
|
||||||
@ -70,7 +69,8 @@
|
|||||||
"sub_unblock": "🏳️ Розблокувати відправника",
|
"sub_unblock": "🏳️ Розблокувати відправника",
|
||||||
"post_view": "Переглянути на каналі",
|
"post_view": "Переглянути на каналі",
|
||||||
"accepted": "✅ Прийнято",
|
"accepted": "✅ Прийнято",
|
||||||
"declined": "❌ Відхилено"
|
"declined": "❌ Відхилено",
|
||||||
|
"shutdown": "Підтвердити вимкнення"
|
||||||
},
|
},
|
||||||
"callback": {
|
"callback": {
|
||||||
"sub_yes": "✅ Подання схвалено",
|
"sub_yes": "✅ Подання схвалено",
|
||||||
|
39
main.py
Normal file
39
main.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
|
from os import getpid
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
from modules.scheduler import scheduler
|
||||||
|
|
||||||
|
from convopyro import Conversation
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(name)s.%(funcName)s | %(levelname)s | %(message)s",
|
||||||
|
datefmt="[%X]",
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
with contextlib.suppress(ImportError):
|
||||||
|
import uvloop
|
||||||
|
|
||||||
|
uvloop.install()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client = PyroClient()
|
||||||
|
Conversation(client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.warning("Forcefully shutting down with PID %s...", getpid())
|
||||||
|
finally:
|
||||||
|
scheduler.shutdown()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,38 +1,63 @@
|
|||||||
"""This is only a temporary solution. Complete Photos API client is yet to be developed."""
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
from os import makedirs, path, sep
|
from os import makedirs, path, sep
|
||||||
from random import choice
|
from pathlib import Path
|
||||||
from traceback import print_exc
|
|
||||||
from typing import Tuple, Union
|
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from aiohttp import FormData
|
from libbot import config_get, i18n, sync
|
||||||
|
from photosapi_client import AuthenticatedClient, Client
|
||||||
from classes.exceptions import (
|
from photosapi_client.api.default.album_create_albums_post import (
|
||||||
AlbumCreationDuplicateError,
|
asyncio as album_create,
|
||||||
AlbumCreationError,
|
|
||||||
AlbumCreationNameError,
|
|
||||||
SubmissionUploadError,
|
|
||||||
UserCreationDuplicateError,
|
|
||||||
UserCreationError,
|
|
||||||
)
|
)
|
||||||
from modules.logger import logWrite
|
from photosapi_client.api.default.album_delete_album_id_delete import (
|
||||||
from modules.utils import configGet, locale
|
asyncio as album_delete,
|
||||||
|
)
|
||||||
|
from photosapi_client.api.default.album_find_albums_get import asyncio as album_find
|
||||||
|
from photosapi_client.api.default.login_for_access_token_token_post import sync as login
|
||||||
|
from photosapi_client.api.default.photo_delete_photos_id_delete import (
|
||||||
|
asyncio as photo_delete,
|
||||||
|
)
|
||||||
|
from photosapi_client.api.default.photo_find_albums_album_photos_get import (
|
||||||
|
asyncio as photo_find,
|
||||||
|
)
|
||||||
|
from photosapi_client.api.default.photo_get_photos_id_get import asyncio as photo_get
|
||||||
|
from photosapi_client.api.default.photo_patch_photos_id_patch import (
|
||||||
|
asyncio as photo_patch,
|
||||||
|
)
|
||||||
|
from photosapi_client.api.default.photo_upload_albums_album_photos_post import (
|
||||||
|
asyncio_detailed as photo_upload,
|
||||||
|
)
|
||||||
|
from photosapi_client.api.default.user_create_users_post import asyncio as user_create
|
||||||
|
from photosapi_client.api.default.user_me_users_me_get import sync as user_me
|
||||||
|
from photosapi_client.api.default.video_find_albums_album_videos_get import (
|
||||||
|
asyncio as video_find,
|
||||||
|
)
|
||||||
|
from photosapi_client.models.body_login_for_access_token_token_post import (
|
||||||
|
BodyLoginForAccessTokenTokenPost,
|
||||||
|
)
|
||||||
|
from photosapi_client.models.body_photo_upload_albums_album_photos_post import (
|
||||||
|
BodyPhotoUploadAlbumsAlbumPhotosPost,
|
||||||
|
)
|
||||||
|
from photosapi_client.models.http_validation_error import HTTPValidationError
|
||||||
|
from photosapi_client.models.token import Token
|
||||||
|
from photosapi_client.types import File
|
||||||
|
|
||||||
from modules.http_client import http_session
|
from modules.http_client import http_session
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def authorize() -> str:
|
async def authorize() -> str:
|
||||||
makedirs(configGet("cache", "locations"), exist_ok=True)
|
makedirs(await config_get("cache", "locations"), exist_ok=True)
|
||||||
if path.exists(configGet("cache", "locations") + sep + "api_access") is True:
|
if path.exists(await config_get("cache", "locations") + sep + "api_access") is True:
|
||||||
async with aiofiles.open(
|
async with aiofiles.open(
|
||||||
configGet("cache", "locations") + sep + "api_access", "rb"
|
await config_get("cache", "locations") + sep + "api_access", "rb"
|
||||||
) as file:
|
) as file:
|
||||||
token = b64decode(await file.read()).decode("utf-8")
|
token = b64decode(await file.read()).decode("utf-8")
|
||||||
if (
|
if (
|
||||||
await http_session.get(
|
await http_session.get(
|
||||||
configGet("address", "posting", "api") + "/users/me/",
|
await config_get("address", "posting", "api") + "/users/me/",
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
headers={"Authorization": f"Bearer {token}"},
|
||||||
)
|
)
|
||||||
).status == 200:
|
).status == 200:
|
||||||
@ -40,27 +65,28 @@ async def authorize() -> str:
|
|||||||
payload = {
|
payload = {
|
||||||
"grant_type": "password",
|
"grant_type": "password",
|
||||||
"scope": "me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write",
|
"scope": "me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write",
|
||||||
"username": configGet("username", "posting", "api"),
|
"username": await config_get("username", "posting", "api"),
|
||||||
"password": configGet("password", "posting", "api"),
|
"password": await config_get("password", "posting", "api"),
|
||||||
}
|
}
|
||||||
response = await http_session.post(
|
response = await http_session.post(
|
||||||
configGet("address", "posting", "api") + "/token", data=payload
|
await config_get("address", "posting", "api") + "/token", data=payload
|
||||||
)
|
)
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
logWrite(
|
logger.warning(
|
||||||
locale(
|
i18n._(
|
||||||
"api_creds_invalid",
|
"api_creds_invalid",
|
||||||
"console",
|
"console",
|
||||||
locale=configGet("locale_log").format(
|
locale=(await config_get("locale_log")).format(
|
||||||
configGet("address", "posting", "api"),
|
await config_get("address", "posting", "api"),
|
||||||
configGet("username", "posting", "api"),
|
await config_get("username", "posting", "api"),
|
||||||
response.status,
|
response.status,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
raise ValueError
|
raise ValueError
|
||||||
async with aiofiles.open(
|
async with aiofiles.open(
|
||||||
configGet("cache", "locations") + sep + "api_access", "wb"
|
str(Path(f"{await config_get('cache', 'locations')}/api_access")),
|
||||||
|
"wb",
|
||||||
) as file:
|
) as file:
|
||||||
await file.write(
|
await file.write(
|
||||||
b64encode((await response.json())["access_token"].encode("utf-8"))
|
b64encode((await response.json())["access_token"].encode("utf-8"))
|
||||||
@ -68,196 +94,36 @@ async def authorize() -> str:
|
|||||||
return (await response.json())["access_token"]
|
return (await response.json())["access_token"]
|
||||||
|
|
||||||
|
|
||||||
async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]:
|
unauthorized_client = Client(
|
||||||
"""Returns random image id and filename from the queue.
|
base_url=sync.config_get("address", "posting", "api"),
|
||||||
|
timeout=5.0,
|
||||||
|
verify_ssl=True,
|
||||||
|
raise_on_unexpected_status=True,
|
||||||
|
)
|
||||||
|
|
||||||
### Returns:
|
login_token = login(
|
||||||
* `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed.
|
client=unauthorized_client,
|
||||||
"""
|
form_data=BodyLoginForAccessTokenTokenPost(
|
||||||
token = await authorize() if token is None else token
|
grant_type="password",
|
||||||
logWrite(
|
scope="me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write",
|
||||||
f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue'
|
username=sync.config_get("username", "posting", "api"),
|
||||||
|
password=sync.config_get("password", "posting", "api"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(login_token, Token):
|
||||||
|
logger.warning(
|
||||||
|
"Could not initialize connection due to invalid token: %s", login_token
|
||||||
)
|
)
|
||||||
resp = await http_session.get(
|
exit()
|
||||||
f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue',
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
# logWrite(
|
|
||||||
# locale("random_pic_response", "console", locale=configGet("locale_log")).format(
|
|
||||||
# await resp.json()
|
|
||||||
# ),
|
|
||||||
# debug=True,
|
|
||||||
# )
|
|
||||||
if resp.status != 200:
|
|
||||||
logWrite(
|
|
||||||
locale(
|
|
||||||
"random_pic_error_code",
|
|
||||||
"console",
|
|
||||||
locale=configGet("locale_log").format(
|
|
||||||
configGet("album", "posting", "api"), resp.status
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
logWrite(
|
|
||||||
locale(
|
|
||||||
"random_pic_error_debug",
|
|
||||||
"console",
|
|
||||||
locale=configGet("locale_log").format(
|
|
||||||
configGet("address", "posting", "api"),
|
|
||||||
configGet("album", "posting", "api"),
|
|
||||||
configGet("page_size", "posting"),
|
|
||||||
token,
|
|
||||||
resp.status,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
debug=True,
|
|
||||||
)
|
|
||||||
raise ValueError
|
|
||||||
if len((await resp.json())["results"]) == 0:
|
|
||||||
raise KeyError
|
|
||||||
pic = choice((await resp.json())["results"])
|
|
||||||
return pic["id"], pic["filename"]
|
|
||||||
|
|
||||||
|
|
||||||
async def upload_pic(
|
|
||||||
filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None
|
|
||||||
) -> Tuple[bool, list, Union[str, None]]:
|
|
||||||
token = await authorize() if token is None else token
|
|
||||||
try:
|
|
||||||
pic_name = path.basename(filepath)
|
|
||||||
logWrite(f"Uploading {pic_name} to the API...", debug=True)
|
|
||||||
async with aiofiles.open(filepath, "rb") as f:
|
|
||||||
file_bytes = await f.read()
|
|
||||||
formdata = FormData()
|
|
||||||
formdata.add_field(
|
|
||||||
"file", file_bytes, filename=pic_name, content_type="image/jpeg"
|
|
||||||
)
|
|
||||||
response = await http_session.post(
|
|
||||||
f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos',
|
|
||||||
params={
|
|
||||||
"caption": "queue",
|
|
||||||
"compress": "false",
|
|
||||||
"ignore_duplicates": str(allow_duplicates).lower(),
|
|
||||||
},
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
data=formdata,
|
|
||||||
)
|
|
||||||
response_json = await response.json()
|
|
||||||
if response.status != 200 and response.status != 409:
|
|
||||||
logWrite(
|
|
||||||
locale(
|
|
||||||
"pic_upload_error",
|
|
||||||
"console",
|
|
||||||
locale=configGet("locale_log").format(
|
|
||||||
filepath, response.status, response.content
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
raise SubmissionUploadError(
|
|
||||||
str(filepath), response.status, response.content
|
|
||||||
)
|
|
||||||
id = response_json["id"] if "id" in await response.json() else None
|
|
||||||
duplicates = []
|
|
||||||
if "duplicates" in response_json:
|
|
||||||
for index, duplicate in enumerate(response_json["duplicates"]): # type: ignore
|
|
||||||
if response_json["access_token"] is None:
|
|
||||||
duplicates.append(
|
|
||||||
f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
duplicates.append(
|
|
||||||
f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{response_json["access_token"]}?id={index}'
|
|
||||||
)
|
|
||||||
return True, duplicates, id
|
|
||||||
except Exception as exp:
|
|
||||||
print_exc()
|
|
||||||
return False, [], None
|
|
||||||
|
|
||||||
|
|
||||||
async def find_pic(
|
|
||||||
name: str, caption: Union[str, None] = None, token: Union[str, None] = None
|
|
||||||
) -> Union[dict, None]:
|
|
||||||
token = await authorize() if token is None else token
|
|
||||||
try:
|
|
||||||
response = await http_session.get(
|
|
||||||
f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos',
|
|
||||||
params={"q": name, "caption": caption},
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
# logWrite(response.json())
|
|
||||||
if response.status != 200:
|
|
||||||
return None
|
|
||||||
if len((await response.json())["results"]) == 0:
|
|
||||||
return None
|
|
||||||
return (await response.json())["results"]
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(
|
|
||||||
locale(
|
|
||||||
"find_pic_error",
|
|
||||||
"console",
|
|
||||||
locale=configGet("locale_log").format(name, caption, exp),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def move_pic(id: str, token: Union[str, None] = None) -> bool:
|
|
||||||
token = await authorize() if token is None else token
|
|
||||||
try:
|
|
||||||
response = await http_session.patch(
|
|
||||||
f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent',
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
if response.status != 200:
|
|
||||||
logWrite(f"Media moving failed with HTTP {response.status}", debug=True)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def remove_pic(id: str, token: Union[str, None] = None) -> bool:
|
|
||||||
token = await authorize() if token is None else token
|
|
||||||
try:
|
|
||||||
response = await http_session.delete(
|
|
||||||
f'{configGet("address", "posting", "api")}/photos/{id}',
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
if response.status != 204:
|
|
||||||
logWrite(f"Media removal failed with HTTP {response.status}", debug=True)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def create_user(username: str, email: str, password: str) -> None:
|
|
||||||
response = await http_session.post(
|
|
||||||
f'{configGet("address", "posting", "api")}/users',
|
|
||||||
data={"user": username, "email": email, "password": password},
|
|
||||||
)
|
|
||||||
if response.status == 409:
|
|
||||||
raise UserCreationDuplicateError(username)
|
|
||||||
elif response.status != 204:
|
|
||||||
raise UserCreationError(response.status, await response.text(encoding="utf-8"))
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def create_album(name: str, title: str) -> None:
|
|
||||||
token = await authorize()
|
|
||||||
response = await http_session.post(
|
|
||||||
f'{configGet("address", "posting", "api")}/albums',
|
|
||||||
params={"name": name, "title": title},
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
if response.status == 409:
|
|
||||||
raise AlbumCreationDuplicateError(name)
|
|
||||||
elif response.status == 406:
|
|
||||||
raise AlbumCreationNameError(await response.json())
|
|
||||||
elif response.status != 200:
|
|
||||||
raise AlbumCreationError(response.status, await response.text(encoding="utf-8"))
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
client = AuthenticatedClient(
|
||||||
|
base_url=sync.config_get("address", "posting", "api"),
|
||||||
|
timeout=5.0,
|
||||||
|
verify_ssl=True,
|
||||||
|
raise_on_unexpected_status=True,
|
||||||
|
token=login_token.access_token,
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(asyncio.run(authorize()))
|
print(asyncio.run(authorize()))
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
from modules.utils import configGet
|
|
||||||
from classes.poster_client import PosterClient
|
|
||||||
from convopyro import Conversation
|
|
||||||
|
|
||||||
app = PosterClient(
|
|
||||||
"duptsiaposter",
|
|
||||||
bot_token=configGet("bot_token", "bot"),
|
|
||||||
api_id=configGet("api_id", "bot"),
|
|
||||||
api_hash=configGet("api_hash", "bot"),
|
|
||||||
)
|
|
||||||
|
|
||||||
Conversation(app)
|
|
||||||
|
|
||||||
users_with_context = []
|
|
@ -1,78 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from sys import exit
|
|
||||||
from traceback import print_exc
|
|
||||||
from modules.api_client import create_album, create_user, http_session
|
|
||||||
from argparse import ArgumentParser
|
|
||||||
|
|
||||||
from modules.utils import configSet
|
|
||||||
|
|
||||||
parser = ArgumentParser(
|
|
||||||
prog="Telegram Poster",
|
|
||||||
description="Bot for posting some of your stuff and also receiving submissions.",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument("--create-user", action="store_true")
|
|
||||||
parser.add_argument("--create-album", action="store_true")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
async def cli_create_user() -> None:
|
|
||||||
print(
|
|
||||||
"To set up Photos API connection you need to create a new user.\nIf you have email confirmation enabled in your Photos API config - you need to use a real email that will get a confirmation code afterwards.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
username = input("Choose username for new Photos API user: ").strip()
|
|
||||||
email = input(f"Choose email for user '{username}': ").strip()
|
|
||||||
password = input(f"Choose password for user '{username}': ").strip()
|
|
||||||
try:
|
|
||||||
result_1 = await create_user(username, email, password)
|
|
||||||
# asyncio.run(create_user(username, email, password))
|
|
||||||
configSet("username", username, "posting", "api")
|
|
||||||
configSet("password", password, "posting", "api")
|
|
||||||
none = input(
|
|
||||||
"Alright. If you have email confirmation enabled - please confirm registration by using the link in your email. After that press Enter. Otherwise just press Enter."
|
|
||||||
)
|
|
||||||
except Exception as exp:
|
|
||||||
print(f"Could not create a user due to {exp}", flush=True)
|
|
||||||
print_exc()
|
|
||||||
exit()
|
|
||||||
if not args.create_album:
|
|
||||||
print("You're done!", flush=True)
|
|
||||||
await http_session.close()
|
|
||||||
exit()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def cli_create_album() -> None:
|
|
||||||
print(
|
|
||||||
"To use Photos API your user needs to have an album to store its data.\nThis wizard will help you to create a new album with its name and title.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
name = input("Choose a name for your album: ").strip()
|
|
||||||
title = input(f"Choose a title for album '{name}': ").strip()
|
|
||||||
try:
|
|
||||||
result_2 = await create_album(name, title)
|
|
||||||
# asyncio.run(create_album(name, title))
|
|
||||||
configSet("album", name, "posting", "api")
|
|
||||||
except Exception as exp:
|
|
||||||
print(f"Could not create an album due to {exp}", flush=True)
|
|
||||||
print_exc()
|
|
||||||
exit()
|
|
||||||
print("You're done!", flush=True)
|
|
||||||
await http_session.close()
|
|
||||||
exit()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
if args.create_user or args.create_album:
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
tasks = []
|
|
||||||
|
|
||||||
if args.create_user:
|
|
||||||
loop.run_until_complete(asyncio.wait([loop.create_task(cli_create_user())]))
|
|
||||||
|
|
||||||
if args.create_album:
|
|
||||||
loop.run_until_complete(asyncio.wait([loop.create_task(cli_create_album())]))
|
|
||||||
|
|
||||||
loop.close()
|
|
@ -1,57 +0,0 @@
|
|||||||
from os import listdir
|
|
||||||
from classes.poster_client import PosterClient
|
|
||||||
from pyrogram.types import BotCommand, BotCommandScopeChat
|
|
||||||
from modules.utils import configGet, locale
|
|
||||||
|
|
||||||
|
|
||||||
async def register_commands(app: PosterClient) -> None:
|
|
||||||
if configGet("submit", "mode"):
|
|
||||||
# Registering user commands
|
|
||||||
for entry in listdir(configGet("locale", "locations")):
|
|
||||||
if entry.endswith(".json"):
|
|
||||||
commands_list = []
|
|
||||||
for command in configGet("commands"):
|
|
||||||
commands_list.append(
|
|
||||||
BotCommand(
|
|
||||||
command,
|
|
||||||
locale(
|
|
||||||
command, "commands", locale=entry.replace(".json", "")
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await app.set_bot_commands(
|
|
||||||
commands_list, language_code=entry.replace(".json", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Registering user commands for fallback locale
|
|
||||||
commands_list = []
|
|
||||||
for command in configGet("commands"):
|
|
||||||
commands_list.append(
|
|
||||||
BotCommand(
|
|
||||||
command,
|
|
||||||
locale(command, "commands", locale=configGet("locale_fallback")),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await app.set_bot_commands(commands_list)
|
|
||||||
|
|
||||||
# Registering admin commands
|
|
||||||
commands_admin_list = []
|
|
||||||
|
|
||||||
if configGet("submit", "mode"):
|
|
||||||
for command in configGet("commands"):
|
|
||||||
commands_admin_list.append(
|
|
||||||
BotCommand(
|
|
||||||
command, locale(command, "commands", locale=configGet("locale"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for command in configGet("commands_admin"):
|
|
||||||
commands_admin_list.append(
|
|
||||||
BotCommand(
|
|
||||||
command, locale(command, "commands_admin", locale=configGet("locale"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for admin in app.admins:
|
|
||||||
await app.set_bot_commands(
|
|
||||||
commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)
|
|
||||||
)
|
|
@ -1,67 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from gzip import open as gzipopen
|
|
||||||
from os import getcwd, makedirs, path, stat
|
|
||||||
from shutil import copyfileobj
|
|
||||||
|
|
||||||
from ujson import loads
|
|
||||||
|
|
||||||
with open(getcwd() + path.sep + "config.json", "r", encoding="utf8") as file:
|
|
||||||
json_contents = loads(file.read())
|
|
||||||
log_size = json_contents["logging"]["size"]
|
|
||||||
log_folder = json_contents["logging"]["location"]
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
|
|
||||||
# Check latest log size
|
|
||||||
def checkSize(debug=False) -> None:
|
|
||||||
global log_folder
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
log_file = "debug.log"
|
|
||||||
else:
|
|
||||||
log_file = "latest.log"
|
|
||||||
|
|
||||||
try:
|
|
||||||
makedirs(log_folder, exist_ok=True)
|
|
||||||
log = stat(path.join(log_folder, log_file))
|
|
||||||
if (log.st_size / 1024) > log_size:
|
|
||||||
with open(path.join(log_folder, log_file), "rb") as f_in:
|
|
||||||
with gzipopen(
|
|
||||||
path.join(
|
|
||||||
log_folder,
|
|
||||||
f'{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.log.gz',
|
|
||||||
),
|
|
||||||
"wb",
|
|
||||||
) as f_out:
|
|
||||||
copyfileobj(f_in, f_out)
|
|
||||||
print(
|
|
||||||
f'Copied {path.join(log_folder, datetime.now().strftime("%d.%m.%Y_%H:%M:%S"))}.log.gz'
|
|
||||||
)
|
|
||||||
open(path.join(log_folder, log_file), "w").close()
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"Log file {path.join(log_folder, log_file)} does not exist")
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Append string to log
|
|
||||||
def logAppend(message, debug=False) -> None:
|
|
||||||
global log_folder
|
|
||||||
|
|
||||||
message_formatted = f'[{datetime.now().strftime("%d.%m.%Y")}] [{datetime.now().strftime("%H:%M:%S")}] {message}'
|
|
||||||
checkSize(debug=debug)
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
log_file = "debug.log"
|
|
||||||
else:
|
|
||||||
log_file = "latest.log"
|
|
||||||
|
|
||||||
log = open(path.join(log_folder, log_file), "a")
|
|
||||||
log.write(f"{message_formatted}\n")
|
|
||||||
log.close()
|
|
||||||
|
|
||||||
|
|
||||||
# Print to stdout and then to log
|
|
||||||
def logWrite(message, debug=False) -> None:
|
|
||||||
# save to log file and rotation is to be done
|
|
||||||
logAppend(f"{message}", debug=debug)
|
|
||||||
print(f"{message}", flush=True)
|
|
@ -1,31 +1,24 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
from libbot import sync
|
||||||
from pytimeparse.timeparse import timeparse
|
from pytimeparse.timeparse import timeparse
|
||||||
from modules.utils import configGet
|
|
||||||
from modules.sender import send_content
|
# from modules.sender import send_content
|
||||||
from modules.commands_register import register_commands
|
|
||||||
from modules.app import app
|
|
||||||
|
|
||||||
scheduler = AsyncIOScheduler()
|
scheduler = AsyncIOScheduler()
|
||||||
|
|
||||||
if configGet("post", "mode"):
|
# if sync.config_get("post", "mode"):
|
||||||
if configGet("use_interval", "posting"):
|
# if sync.config_get("use_interval", "posting"):
|
||||||
scheduler.add_job(
|
# scheduler.add_job(
|
||||||
send_content,
|
# send_content,
|
||||||
"interval",
|
# "interval",
|
||||||
seconds=timeparse(configGet("interval", "posting")),
|
# seconds=timeparse(sync.config_get("interval", "posting")),
|
||||||
args=[app],
|
# args=[app],
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
for entry in configGet("time", "posting"):
|
# for entry in sync.config_get("time", "posting"):
|
||||||
dt_obj = datetime.strptime(entry, "%H:%M")
|
# dt_obj = datetime.strptime(entry, "%H:%M")
|
||||||
scheduler.add_job(
|
# scheduler.add_job(
|
||||||
send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app]
|
# send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app]
|
||||||
)
|
# )
|
||||||
|
|
||||||
scheduler.add_job(
|
|
||||||
register_commands,
|
|
||||||
"date",
|
|
||||||
run_date=datetime.now() + timedelta(seconds=10),
|
|
||||||
args=[app],
|
|
||||||
)
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
from os import makedirs, path
|
from os import makedirs, path
|
||||||
from random import choice
|
from random import choice
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
@ -7,107 +8,107 @@ from uuid import uuid4
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
|
||||||
from classes.poster_client import PosterClient
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
from modules.api_client import authorize, move_pic, random_pic, http_session
|
from modules.api_client import authorize, http_session, photo_patch, photo_find, client
|
||||||
from modules.database import col_sent, col_submitted
|
from modules.database import col_sent, col_submitted
|
||||||
from modules.logger import logWrite
|
|
||||||
from modules.utils import configGet, locale
|
from photosapi_client.errors import UnexpectedStatus
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def send_content(app: PosterClient) -> None:
|
async def send_content(app: PyroClient) -> None:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
token = await authorize()
|
token = await authorize()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale("api_creds_invalid", "message", locale=configGet("locale")),
|
app._("api_creds_invalid", "message"),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pic = await random_pic()
|
pic = choice((await photo_find(album=app.config["posting"]["api"]["album"], caption="queue", page_size=app.config["posting"]["page_size"], client=client)).results)
|
||||||
except KeyError:
|
except (KeyError, AttributeError, TypeError):
|
||||||
logWrite(locale("post_empty", "console", locale=configGet("locale")))
|
logger.info(app._("post_empty", "console"))
|
||||||
if configGet("error", "reports"):
|
if app.config["reports"]["error"]:
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale("api_queue_empty", "message", locale=configGet("locale")),
|
app._("api_queue_empty", "message"),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except ValueError:
|
except (ValueError, UnexpectedStatus):
|
||||||
if configGet("error", "reports"):
|
if app.config["reports"]["error"]:
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale("api_queue_error", "message", locale=configGet("locale")),
|
app._("api_queue_error", "message"),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
response = await http_session.get(
|
response = await http_session.get(
|
||||||
f'{configGet("address", "posting", "api")}/photos/{pic[0]}',
|
f"{app.config['posting']['api']['address']}/photos/{pic.id}",
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
headers={"Authorization": f"Bearer {token}"},
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
logWrite(
|
logger.warning(
|
||||||
locale(
|
app._("post_invalid_pic", "console").format(
|
||||||
"post_invalid_pic", "console", locale=configGet("locale")
|
response.status, str(await response.json())
|
||||||
).format(response.status, str(await response.json()))
|
)
|
||||||
)
|
)
|
||||||
if configGet("error", "reports"):
|
if app.config["reports"]["error"]:
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale(
|
app._("post_invalid_pic", "message").format(
|
||||||
"post_invalid_pic", "message", locale=configGet("locale")
|
response.status, await response.json()
|
||||||
).format(response.status, await response.json()),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tmp_dir = str(uuid4())
|
tmp_dir = str(uuid4())
|
||||||
|
|
||||||
makedirs(path.join(configGet("tmp", "locations"), tmp_dir), exist_ok=True)
|
makedirs(path.join(app.config['locations']['tmp'], tmp_dir), exist_ok=True)
|
||||||
|
|
||||||
tmp_path = path.join(tmp_dir, pic[1])
|
tmp_path = path.join(tmp_dir, pic.filename)
|
||||||
|
|
||||||
async with aiofiles.open(
|
async with aiofiles.open(
|
||||||
path.join(configGet("tmp", "locations"), tmp_path), "wb"
|
path.join(app.config['locations']['tmp'], tmp_path), "wb"
|
||||||
) as out_file:
|
) as out_file:
|
||||||
await out_file.write(await response.read())
|
await out_file.write(await response.read())
|
||||||
|
|
||||||
logWrite(
|
logger.info(
|
||||||
f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big',
|
f'Candidate {pic.filename} ({pic.id}) is {path.getsize(path.join(app.config['locations']['tmp'], tmp_path))} bytes big',
|
||||||
debug=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880:
|
if path.getsize(path.join(app.config['locations']['tmp'], tmp_path)) > 5242880:
|
||||||
image = Image.open(path.join(configGet("tmp", "locations"), tmp_path))
|
image = Image.open(path.join(app.config['locations']['tmp'], tmp_path))
|
||||||
width, height = image.size
|
width, height = image.size
|
||||||
image = image.resize((int(width / 2), int(height / 2)), Image.ANTIALIAS)
|
image = image.resize((int(width / 2), int(height / 2)), Image.ANTIALIAS)
|
||||||
if tmp_path.lower().endswith(".jpeg") or tmp_path.lower().endswith(".jpg"):
|
if tmp_path.lower().endswith(".jpeg") or tmp_path.lower().endswith(".jpg"):
|
||||||
image.save(
|
image.save(
|
||||||
path.join(configGet("tmp", "locations"), tmp_path),
|
path.join(app.config['locations']['tmp'], tmp_path),
|
||||||
"JPEG",
|
"JPEG",
|
||||||
optimize=True,
|
optimize=True,
|
||||||
quality=50,
|
quality=50,
|
||||||
)
|
)
|
||||||
elif tmp_path.lower().endswith(".png"):
|
elif tmp_path.lower().endswith(".png"):
|
||||||
image.save(
|
image.save(
|
||||||
path.join(configGet("tmp", "locations"), tmp_path),
|
path.join(app.config['locations']['tmp'], tmp_path),
|
||||||
"PNG",
|
"PNG",
|
||||||
optimize=True,
|
optimize=True,
|
||||||
compress_level=8,
|
compress_level=8,
|
||||||
)
|
)
|
||||||
image.close()
|
image.close()
|
||||||
|
|
||||||
if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880:
|
if path.getsize(path.join(app.config['locations']['tmp'], tmp_path)) > 5242880:
|
||||||
rmtree(
|
rmtree(
|
||||||
path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True
|
path.join(app.config['locations']['tmp'], tmp_dir), ignore_errors=True
|
||||||
)
|
)
|
||||||
raise BytesWarning
|
raise BytesWarning
|
||||||
|
|
||||||
del response
|
del response
|
||||||
|
|
||||||
submitted = col_submitted.find_one({"temp.file": pic[1]})
|
submitted = col_submitted.find_one({"temp.file": pic.filename})
|
||||||
|
|
||||||
if submitted is not None and submitted["caption"] is not None:
|
if submitted is not None and submitted["caption"] is not None:
|
||||||
caption = submitted["caption"].strip()
|
caption = submitted["caption"].strip()
|
||||||
@ -116,86 +117,78 @@ async def send_content(app: PosterClient) -> None:
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
submitted is not None
|
submitted is not None
|
||||||
and configGet("enabled", "posting", "submitted_caption")
|
and app.config["posting"]["submitted_caption"]["enabled"]
|
||||||
and (
|
and (
|
||||||
(submitted["user"] not in app.admins)
|
(submitted["user"] not in app.admins)
|
||||||
or (configGet("ignore_admins", "posting", "submitted_caption") is False)
|
or (app.config["posting"]["submitted_caption"]["ignore_admins"] is False)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
caption = (
|
caption = (
|
||||||
f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n"
|
f"{caption}\n\n{app.config['posting']['submitted_caption']['text']}\n"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
caption = f"{caption}\n\n"
|
caption = f"{caption}\n\n"
|
||||||
|
|
||||||
if configGet("enabled", "caption"):
|
if app.config["caption"]["enabled"]:
|
||||||
if configGet("link", "caption") != None:
|
if app.config["caption"]["link"] is not None:
|
||||||
caption = f"{caption}[{choice(configGet('text', 'caption'))}]({configGet('link', 'caption')})"
|
caption = f"{caption}[{choice(app.config['caption']['text'])}]({app.config['caption']['link']})"
|
||||||
else:
|
else:
|
||||||
caption = f"{caption}{choice(configGet('text', 'caption'))}"
|
caption = f"{caption}{choice(app.config['caption']['text'])}"
|
||||||
else:
|
else:
|
||||||
caption = caption
|
caption = caption
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sent = await app.send_photo(
|
sent = await app.send_photo(
|
||||||
configGet("channel", "posting"),
|
app.config["posting"]["channel"],
|
||||||
path.join(configGet("tmp", "locations"), tmp_path),
|
path.join(app.config['locations']['tmp'], tmp_path),
|
||||||
caption=caption,
|
caption=caption,
|
||||||
disable_notification=configGet("silent", "posting"),
|
disable_notification=app.config["posting"]["silent"],
|
||||||
)
|
)
|
||||||
except Exception as exp:
|
except Exception as exp:
|
||||||
logWrite(f"Could not send image {pic[1]} ({pic[0]}) due to {exp}")
|
logger.error(f"Could not send image {pic.filename} ({pic.id}) due to {exp}")
|
||||||
if configGet("error", "reports"):
|
if app.config["reports"]["error"]:
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale(
|
app._("post_exception", "message").format(exp, format_exc()),
|
||||||
"post_exception", "message", locale=configGet("locale")
|
|
||||||
).format(exp, format_exc()),
|
|
||||||
)
|
)
|
||||||
# rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
|
# rmtree(path.join(app.config['locations']['tmp'], tmp_dir), ignore_errors=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
col_sent.insert_one(
|
col_sent.insert_one(
|
||||||
{
|
{
|
||||||
"date": datetime.now(),
|
"date": datetime.now(),
|
||||||
"image": pic[0],
|
"image": pic.id,
|
||||||
"filename": pic[1],
|
"filename": pic.filename,
|
||||||
"channel": configGet("channel", "posting"),
|
"channel": app.config["posting"]["channel"],
|
||||||
"caption": None
|
"caption": None
|
||||||
if (submitted is None or submitted["caption"] is None)
|
if (submitted is None or submitted["caption"] is None)
|
||||||
else submitted["caption"].strip(),
|
else submitted["caption"].strip(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await move_pic(pic[0])
|
await photo_patch(id=pic.id, client=client, caption="sent")
|
||||||
|
|
||||||
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
|
rmtree(path.join(app.config['locations']['tmp'], tmp_dir), ignore_errors=True)
|
||||||
|
|
||||||
logWrite(
|
logger.info(
|
||||||
locale("post_sent", "console", locale=configGet("locale")).format(
|
app._("post_sent", "console").format(
|
||||||
pic[0],
|
pic.id,
|
||||||
str(configGet("channel", "posting")),
|
str(app.config["posting"]["channel"]),
|
||||||
caption.replace("\n", "%n"),
|
caption.replace("\n", "%n"),
|
||||||
str(configGet("silent", "posting")),
|
str(app.config["posting"]["silent"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as exp:
|
except Exception as exp:
|
||||||
logWrite(
|
logger.error(app._("post_exception", "console").format(str(exp), format_exc()))
|
||||||
locale("post_exception", "console", locale=configGet("locale")).format(
|
if app.config["reports"]["error"]:
|
||||||
str(exp), format_exc()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if configGet("error", "reports"):
|
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale("post_exception", "message", locale=configGet("locale")).format(
|
app._("post_exception", "message").format(exp, format_exc()),
|
||||||
exp, format_exc()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
rmtree(
|
rmtree(
|
||||||
path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True
|
path.join(app.config['locations']['tmp'], tmp_dir), ignore_errors=True
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
245
modules/utils.py
245
modules/utils.py
@ -1,252 +1,33 @@
|
|||||||
from os import kill, makedirs
|
import logging
|
||||||
from os import name as osname
|
from os import makedirs, path
|
||||||
from os import path, sep
|
from pathlib import Path
|
||||||
from sys import exit
|
from typing import List, Union
|
||||||
from traceback import print_exc
|
|
||||||
from typing import Any
|
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from ujson import JSONDecodeError, dumps, loads
|
|
||||||
|
|
||||||
from modules.logger import logWrite
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
default_config = {
|
USERS_WITH_CONTEXT: List[int] = []
|
||||||
"locale": "en",
|
|
||||||
"locale_log": "en",
|
|
||||||
"locale_fallback": "en",
|
|
||||||
"owner": 0,
|
|
||||||
"admins": [],
|
|
||||||
"bot": {"api_id": 0, "api_hash": "", "bot_token": ""},
|
|
||||||
"database": {
|
|
||||||
"user": None,
|
|
||||||
"password": None,
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 27017,
|
|
||||||
"name": "tgposter",
|
|
||||||
},
|
|
||||||
"mode": {"post": True, "submit": True},
|
|
||||||
"reports": {"sent": False, "error": True, "startup": True, "shutdown": True},
|
|
||||||
"logging": {"size": 512, "location": "logs"},
|
|
||||||
"locations": {
|
|
||||||
"tmp": "tmp",
|
|
||||||
"data": "data",
|
|
||||||
"cache": "cache",
|
|
||||||
"sent": "data/sent",
|
|
||||||
"queue": "data/queue",
|
|
||||||
"index": "data/index.json",
|
|
||||||
"locale": "locale",
|
|
||||||
},
|
|
||||||
"posting": {
|
|
||||||
"channel": 0,
|
|
||||||
"silent": False,
|
|
||||||
"move_sent": False,
|
|
||||||
"use_interval": False,
|
|
||||||
"interval": "1h30m",
|
|
||||||
"page_size": 300,
|
|
||||||
"submitted_caption": {
|
|
||||||
"enabled": True,
|
|
||||||
"ignore_admins": True,
|
|
||||||
"text": "#submitted",
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
"photo": ["jpg", "png", "gif", "jpeg"],
|
|
||||||
"video": ["mp4", "avi", "mkv", "webm", "mov"],
|
|
||||||
},
|
|
||||||
"time": [
|
|
||||||
"08:00",
|
|
||||||
"10:00",
|
|
||||||
"12:00",
|
|
||||||
"14:00",
|
|
||||||
"16:00",
|
|
||||||
"18:00",
|
|
||||||
"20:00",
|
|
||||||
"22:00",
|
|
||||||
],
|
|
||||||
"api": {
|
|
||||||
"address": "http://localhost:8054",
|
|
||||||
"address_external": "https://photos.domain.com",
|
|
||||||
"username": "",
|
|
||||||
"password": "",
|
|
||||||
"album": "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"caption": {"enabled": False, "link": None, "text": ["sample text"]},
|
|
||||||
"submission": {
|
|
||||||
"timeout": 30,
|
|
||||||
"file_size": 15728640,
|
|
||||||
"tmp_size": 15728640,
|
|
||||||
"allow_duplicates": False,
|
|
||||||
"send_uploaded_id": False,
|
|
||||||
"require_confirmation": {"users": True, "admins": True},
|
|
||||||
"mime_types": [
|
|
||||||
"image/png",
|
|
||||||
"image/gif",
|
|
||||||
"image/jpeg",
|
|
||||||
"video/mp4",
|
|
||||||
"video/quicktime",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"commands": ["start", "rules"],
|
|
||||||
"commands_admin": ["import", "export", "shutdown"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def jsonLoad(filename: str) -> Any:
|
async def extract_and_save(handle: ZipFile, filename: str, destpath: Union[str, Path]):
|
||||||
"""Loads arg1 as json and returns its contents"""
|
|
||||||
with open(filename, "r", encoding="utf8") as file:
|
|
||||||
try:
|
|
||||||
output = loads(file.read())
|
|
||||||
except JSONDecodeError:
|
|
||||||
logWrite(
|
|
||||||
f"Could not load json file {filename}: file seems to be incorrect!\n{print_exc()}"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
except FileNotFoundError:
|
|
||||||
logWrite(
|
|
||||||
f"Could not load json file {filename}: file does not seem to exist!\n{print_exc()}"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
file.close()
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def jsonSave(contents: Any, filename: str) -> None:
|
|
||||||
"""Dumps dict/list arg1 to file arg2"""
|
|
||||||
try:
|
|
||||||
with open(filename, "w", encoding="utf8") as file:
|
|
||||||
file.write(
|
|
||||||
dumps(
|
|
||||||
contents, ensure_ascii=False, indent=4, escape_forward_slashes=False
|
|
||||||
)
|
|
||||||
)
|
|
||||||
file.close()
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(f"Could not save json file {filename}: {exp}\n{print_exc()}")
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def configSet(key: str, value, *args: str):
|
|
||||||
"""Set key to a value
|
|
||||||
Args:
|
|
||||||
* key (str): The last key of the keys path.
|
|
||||||
* value (str/int/float/list/dict/None): Some needed value.
|
|
||||||
* *args (str): Path to key like: dict[args][key].
|
|
||||||
"""
|
|
||||||
this_dict = jsonLoad("config.json")
|
|
||||||
string = "this_dict"
|
|
||||||
for arg in args:
|
|
||||||
string += f'["{arg}"]'
|
|
||||||
if type(value) in [str]:
|
|
||||||
string += f'["{key}"] = "{value}"'
|
|
||||||
else:
|
|
||||||
string += f'["{key}"] = {value}'
|
|
||||||
exec(string)
|
|
||||||
jsonSave(this_dict, "config.json")
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def configGet(key: str, *args: str):
|
|
||||||
"""Get value of the config key
|
|
||||||
Args:
|
|
||||||
* key (str): The last key of the keys path.
|
|
||||||
* *args (str): Path to key like: dict[args][key].
|
|
||||||
Returns:
|
|
||||||
* any: Value of provided key
|
|
||||||
"""
|
|
||||||
this_dict = jsonLoad("config.json")
|
|
||||||
try:
|
|
||||||
this_key = this_dict
|
|
||||||
for dict_key in args:
|
|
||||||
this_key = this_key[dict_key]
|
|
||||||
this_key[key]
|
|
||||||
except KeyError:
|
|
||||||
print(
|
|
||||||
f"Could not find config key '{key}' under path {args}: falling back to default config",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
this_key = default_config
|
|
||||||
for dict_key in args:
|
|
||||||
this_key = this_key[dict_key]
|
|
||||||
configSet(key, this_key[key], *args)
|
|
||||||
return this_key[key]
|
|
||||||
|
|
||||||
|
|
||||||
def locale(key: str, *args: str, locale=configGet("locale")):
|
|
||||||
"""Get value of locale string
|
|
||||||
Args:
|
|
||||||
* key (str): The last key of the locale's keys path.
|
|
||||||
* *args (list): Path to key like: dict[args][key].
|
|
||||||
* locale (str): Locale to looked up in. Defaults to config's locale value.
|
|
||||||
Returns:
|
|
||||||
* any: Value of provided locale key
|
|
||||||
"""
|
|
||||||
if locale == None:
|
|
||||||
locale = configGet("locale")
|
|
||||||
|
|
||||||
try:
|
|
||||||
this_dict = jsonLoad(f'{configGet("locale", "locations")}{sep}{locale}.json')
|
|
||||||
except FileNotFoundError:
|
|
||||||
try:
|
|
||||||
this_dict = jsonLoad(
|
|
||||||
f'{configGet("locale", "locations")}{sep}{configGet("locale")}.json'
|
|
||||||
)
|
|
||||||
except FileNotFoundError:
|
|
||||||
try:
|
|
||||||
this_dict = jsonLoad(
|
|
||||||
f'{configGet("locale_fallback", "locations")}{sep}{configGet("locale")}.json'
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"'
|
|
||||||
|
|
||||||
this_key = this_dict
|
|
||||||
for dict_key in args:
|
|
||||||
this_key = this_key[dict_key]
|
|
||||||
|
|
||||||
try:
|
|
||||||
return this_key[key]
|
|
||||||
except KeyError:
|
|
||||||
return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"'
|
|
||||||
|
|
||||||
|
|
||||||
async def extract_and_save(handle: ZipFile, filename: str, destpath: str):
|
|
||||||
"""Extract and save file from archive
|
"""Extract and save file from archive
|
||||||
|
|
||||||
Args:
|
### Args:
|
||||||
* handle (ZipFile): ZipFile handler
|
* handle (`ZipFile`): ZipFile handler
|
||||||
* filename (str): File base name
|
* filename (`str`): File base name
|
||||||
* path (str): Path where to store
|
* path (`Union[str, Path]`): Path where to store
|
||||||
"""
|
"""
|
||||||
data = handle.read(filename)
|
data = handle.read(filename)
|
||||||
filepath = path.join(destpath, filename)
|
filepath = path.join(str(destpath), filename)
|
||||||
try:
|
try:
|
||||||
makedirs(path.dirname(filepath), exist_ok=True)
|
makedirs(path.dirname(filepath), exist_ok=True)
|
||||||
async with aiofiles.open(filepath, "wb") as fd:
|
async with aiofiles.open(filepath, "wb") as fd:
|
||||||
await fd.write(data)
|
await fd.write(data)
|
||||||
logWrite(f"Unzipped {filename}", debug=True)
|
logger.debug("Unzipped %s", filename)
|
||||||
except IsADirectoryError:
|
except IsADirectoryError:
|
||||||
makedirs(filepath, exist_ok=True)
|
makedirs(filepath, exist_ok=True)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from psutil import Process
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
print(locale("deps_missing", "console", locale=configGet("locale")), flush=True)
|
|
||||||
exit()
|
|
||||||
|
|
||||||
|
|
||||||
def killProc(pid: int) -> None:
|
|
||||||
"""Kill process by its PID. Meant to be used to kill the main process of bot itself.
|
|
||||||
|
|
||||||
### Args:
|
|
||||||
* pid (`int`): PID of the target
|
|
||||||
"""
|
|
||||||
if osname == "posix":
|
|
||||||
from signal import SIGKILL
|
|
||||||
|
|
||||||
kill(pid, SIGKILL)
|
|
||||||
else:
|
|
||||||
Process(pid).kill()
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from modules.app import app
|
|
||||||
from pyrogram import filters
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
from pyrogram.types import CallbackQuery
|
from pyrogram.types import CallbackQuery
|
||||||
from classes.poster_client import PosterClient
|
|
||||||
from modules.utils import locale
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
|
||||||
@app.on_callback_query(filters.regex("nothing"))
|
@Client.on_callback_query(filters.regex("nothing"))
|
||||||
async def callback_query_nothing(app: PosterClient, clb: CallbackQuery):
|
async def callback_query_nothing(app: PyroClient, clb: CallbackQuery):
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("nothing", "callback", locale=clb.from_user.language_code)
|
text=app._("nothing", "callback", locale=clb.from_user.language_code)
|
||||||
)
|
)
|
||||||
|
@ -1,29 +1,25 @@
|
|||||||
from os import getpid, makedirs, path
|
from os import makedirs, path
|
||||||
from time import time
|
from time import time
|
||||||
from modules.app import app
|
|
||||||
|
from libbot import config_get, json_write
|
||||||
from pyrogram import filters
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
from pyrogram.types import CallbackQuery
|
from pyrogram.types import CallbackQuery
|
||||||
from classes.poster_client import PosterClient
|
|
||||||
from modules.scheduler import scheduler
|
from classes.pyroclient import PyroClient
|
||||||
from modules.logger import logWrite
|
|
||||||
from modules.utils import configGet, jsonSave, locale
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_callback_query(filters.regex("shutdown"))
|
@Client.on_callback_query(filters.regex("shutdown"))
|
||||||
async def callback_query_nothing(app: PosterClient, clb: CallbackQuery):
|
async def callback_query_nothing(app: PyroClient, clb: CallbackQuery):
|
||||||
if clb.from_user.id in app.admins:
|
if clb.from_user.id not in app.admins:
|
||||||
pid = getpid()
|
return
|
||||||
logWrite(f"Shutting down bot with pid {pid}")
|
|
||||||
await clb.answer()
|
await clb.answer()
|
||||||
await clb.message.reply_text(
|
|
||||||
locale("shutdown", "message", locale=clb.from_user.language_code).format(
|
makedirs(await config_get("cache", "locations"), exist_ok=True)
|
||||||
pid
|
await json_write(
|
||||||
),
|
{"timestamp": time()},
|
||||||
)
|
path.join(await config_get("cache", "locations"), "shutdown_time"),
|
||||||
scheduler.shutdown()
|
)
|
||||||
makedirs(configGet("cache", "locations"), exist_ok=True)
|
|
||||||
jsonSave(
|
exit()
|
||||||
{"timestamp": time()},
|
|
||||||
path.join(configGet("cache", "locations"), "shutdown_time"),
|
|
||||||
)
|
|
||||||
exit()
|
|
||||||
|
@ -1,20 +1,28 @@
|
|||||||
|
import logging
|
||||||
from os import path
|
from os import path
|
||||||
|
from pathlib import Path
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
||||||
from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError
|
|
||||||
from classes.poster_client import PosterClient
|
|
||||||
from classes.user import PosterUser
|
|
||||||
|
|
||||||
from modules.app import app
|
|
||||||
from modules.logger import logWrite
|
|
||||||
from modules.utils import configGet, locale
|
|
||||||
from modules.database import col_submitted
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
from libbot import config_get
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
|
from pyrogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
|
||||||
|
from classes.exceptions import (
|
||||||
|
SubmissionDuplicatesError,
|
||||||
|
SubmissionUnavailableError,
|
||||||
|
SubmissionUnsupportedError,
|
||||||
|
)
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
from classes.user import PosterUser
|
||||||
|
from modules.database import col_submitted
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
|
@Client.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
|
||||||
async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
|
async def callback_query_yes(app: PyroClient, clb: CallbackQuery):
|
||||||
fullclb = str(clb.data).split("_")
|
fullclb = str(clb.data).split("_")
|
||||||
user_locale = clb.from_user.language_code
|
user_locale = clb.from_user.language_code
|
||||||
|
|
||||||
@ -24,44 +32,49 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
|
|||||||
submission = await app.submit_photo(fullclb[2])
|
submission = await app.submit_photo(fullclb[2])
|
||||||
except SubmissionUnavailableError:
|
except SubmissionUnavailableError:
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_msg_unavail", "callback", locale=user_locale),
|
text=app._("sub_msg_unavail", "callback", locale=user_locale),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except SubmissionUnsupportedError:
|
||||||
|
await clb.answer(
|
||||||
|
text="Unsupported.",
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except SubmissionDuplicatesError as exp:
|
except SubmissionDuplicatesError as exp:
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_duplicates_found", "callback", locale=user_locale),
|
text=app._("sub_duplicates_found", "callback", locale=user_locale),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
await clb.message.reply_text(
|
await clb.message.reply_text(
|
||||||
locale("sub_media_duplicates_list", "message", locale=user_locale).format(
|
app._("sub_media_duplicates_list", "message", locale=user_locale).format(
|
||||||
"\n • ".join(exp.duplicates)
|
"\n • ".join(exp.duplicates)
|
||||||
),
|
),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
logWrite(
|
logger.info(
|
||||||
locale(
|
app._(
|
||||||
"submission_duplicate",
|
"submission_duplicate",
|
||||||
"console",
|
"console",
|
||||||
locale=configGet("locale_log").format(
|
locale=app.config["locale_log"],
|
||||||
fullclb[2],
|
).format(
|
||||||
str(exp.duplicates),
|
fullclb[2],
|
||||||
),
|
str(exp.duplicates),
|
||||||
),
|
),
|
||||||
debug=True,
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if submission[0] is not None:
|
if submission[0] is not None:
|
||||||
await submission[0].reply_text(
|
await submission[0].reply_text(
|
||||||
locale("sub_yes", "message", locale=submission[0].from_user.language_code),
|
app._("sub_yes", "message", locale=submission[0].from_user.language_code),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
elif db_entry is not None:
|
elif db_entry is not None:
|
||||||
await app.send_message(db_entry["user"], locale("sub_yes", "message"))
|
await app.send_message(db_entry["user"], app._("sub_yes", "message"))
|
||||||
|
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]),
|
text=app._("sub_yes", "callback", locale=user_locale).format(fullclb[2]),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,7 +82,7 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=str(locale("accepted", "button", locale=user_locale)),
|
text=str(app._("accepted", "button", locale=user_locale)),
|
||||||
callback_data="nothing",
|
callback_data="nothing",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -79,14 +92,14 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
|
|||||||
else [
|
else [
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=str(locale("accepted", "button", locale=user_locale)),
|
text=str(app._("accepted", "button", locale=user_locale)),
|
||||||
callback_data="nothing",
|
callback_data="nothing",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if configGet("send_uploaded_id", "submission"):
|
if await config_get("send_uploaded_id", "submission"):
|
||||||
await clb.message.edit_caption(
|
await clb.message.edit_caption(
|
||||||
clb.message.caption + f"\n\nID: `{submission[1]}`"
|
clb.message.caption + f"\n\nID: `{submission[1]}`"
|
||||||
)
|
)
|
||||||
@ -95,79 +108,52 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
|
|||||||
reply_markup=InlineKeyboardMarkup(edited_markup)
|
reply_markup=InlineKeyboardMarkup(edited_markup)
|
||||||
)
|
)
|
||||||
|
|
||||||
logWrite(
|
logger.info(
|
||||||
locale(
|
app._(
|
||||||
"submission_accepted",
|
"submission_accepted",
|
||||||
"console",
|
"console",
|
||||||
locale=configGet("locale_log").format(fullclb[2], submission[1]),
|
locale=app.config["locale_log"],
|
||||||
),
|
).format(fullclb[2], submission[1]),
|
||||||
debug=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# try:
|
|
||||||
# if configGet("api_based", "mode") is True:
|
|
||||||
# media = await app.download_media(submission, file_name=configGet("tmp", "locations")+sep)
|
|
||||||
# upload = upload_pic(media)
|
|
||||||
# if upload[0] is False:
|
|
||||||
# await clb.answer(text=locale("sub_media_failed", "message", locale=user_locale), show_alert=True)
|
|
||||||
# elif len(upload[1]) > 0:
|
|
||||||
# await clb.answer(text=locale("sub_media_duplicates", "message", locale=user_locale))
|
|
||||||
# await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(upload[1])))
|
|
||||||
# else:
|
|
||||||
# if clb.data.endswith("_caption"):
|
|
||||||
# index = jsonLoad(configGet("index", "locations"))
|
|
||||||
# index["captions"][Path(media).name] = submission.caption
|
|
||||||
# jsonSave(index, configGet("index", "locations"))
|
|
||||||
# else:
|
|
||||||
# media = await app.download_media(submission, file_name=configGet("queue", "locations")+sep)
|
|
||||||
# if clb.data.endswith("_caption"):
|
|
||||||
# index = jsonLoad(configGet("index", "locations"))
|
|
||||||
# index["captions"][Path(media).name] = submission.caption
|
|
||||||
# jsonSave(index, configGet("index", "locations"))
|
|
||||||
# except:
|
|
||||||
# await clb.answer(text=locale("sub_media_unavail", "message", locale=user_locale), show_alert=True)
|
|
||||||
# return
|
|
||||||
|
|
||||||
|
@Client.on_callback_query(filters.regex("sub_no_[\s\S]*"))
|
||||||
@app.on_callback_query(filters.regex("sub_no_[\s\S]*"))
|
async def callback_query_no(app: PyroClient, clb: CallbackQuery):
|
||||||
async def callback_query_no(app: PosterClient, clb: CallbackQuery):
|
|
||||||
fullclb = str(clb.data).split("_")
|
fullclb = str(clb.data).split("_")
|
||||||
user_locale = clb.from_user.language_code
|
user_locale = clb.from_user.language_code
|
||||||
|
|
||||||
db_entry = col_submitted.find_one_and_delete({"_id": ObjectId(fullclb[2])})
|
db_entry = col_submitted.find_one_and_delete({"_id": ObjectId(fullclb[2])})
|
||||||
|
|
||||||
if db_entry["temp"]["uuid"] is not None:
|
if (
|
||||||
if path.exists(
|
db_entry["temp"]["uuid"] is not None
|
||||||
path.join(
|
and Path(
|
||||||
configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]
|
f"{app.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}"
|
||||||
)
|
).exists()
|
||||||
):
|
):
|
||||||
rmtree(
|
rmtree(
|
||||||
path.join(
|
Path(
|
||||||
configGet("data", "locations"),
|
f"{app.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}"
|
||||||
"submissions",
|
),
|
||||||
db_entry["temp"]["uuid"],
|
ignore_errors=True,
|
||||||
),
|
)
|
||||||
ignore_errors=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
submission = await app.get_messages(
|
submission = await app.get_messages(
|
||||||
db_entry["user"], db_entry["telegram"]["msg_id"]
|
db_entry["user"], db_entry["telegram"]["msg_id"]
|
||||||
)
|
)
|
||||||
except:
|
except Exception as exp:
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_msg_unavail", "message", locale=user_locale),
|
text=app._("sub_msg_unavail", "message", locale=user_locale),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await submission.reply_text(
|
await submission.reply_text(
|
||||||
locale("sub_no", "message", locale=submission.from_user.language_code),
|
app._("sub_no", "message", locale=submission.from_user.language_code),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]),
|
text=app._("sub_no", "callback", locale=user_locale).format(fullclb[2]),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -175,7 +161,7 @@ async def callback_query_no(app: PosterClient, clb: CallbackQuery):
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=str(locale("declined", "button", locale=user_locale)),
|
text=str(app._("declined", "button", locale=user_locale)),
|
||||||
callback_data="nothing",
|
callback_data="nothing",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -185,7 +171,7 @@ async def callback_query_no(app: PosterClient, clb: CallbackQuery):
|
|||||||
else [
|
else [
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=str(locale("declined", "button", locale=user_locale)),
|
text=str(app._("declined", "button", locale=user_locale)),
|
||||||
callback_data="nothing",
|
callback_data="nothing",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -194,26 +180,28 @@ async def callback_query_no(app: PosterClient, clb: CallbackQuery):
|
|||||||
await clb.message.edit_reply_markup(
|
await clb.message.edit_reply_markup(
|
||||||
reply_markup=InlineKeyboardMarkup(edited_markup)
|
reply_markup=InlineKeyboardMarkup(edited_markup)
|
||||||
)
|
)
|
||||||
logWrite(
|
logger.info(
|
||||||
locale(
|
app._(
|
||||||
"submission_rejected",
|
"submission_rejected",
|
||||||
"console",
|
"console",
|
||||||
locale=configGet("locale_log").format(fullclb[2]),
|
locale=app.config["locale_log"],
|
||||||
),
|
).format(fullclb[2]),
|
||||||
debug=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.on_callback_query(filters.regex("sub_block_[\s\S]*"))
|
@Client.on_callback_query(filters.regex("sub_block_[\s\S]*"))
|
||||||
async def callback_query_block(app: PosterClient, clb: CallbackQuery):
|
async def callback_query_block(app: PyroClient, clb: CallbackQuery):
|
||||||
fullclb = str(clb.data).split("_")
|
fullclb = str(clb.data).split("_")
|
||||||
user_locale = clb.from_user.language_code
|
user_locale = clb.from_user.language_code
|
||||||
|
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
int(fullclb[2]), locale("sub_blocked", "message", locale=configGet("locale"))
|
int(fullclb[2]),
|
||||||
|
app._("sub_blocked", "message"),
|
||||||
)
|
)
|
||||||
PosterUser(int(fullclb[2])).block()
|
PosterUser(int(fullclb[2])).block()
|
||||||
|
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]),
|
text=app._("sub_block", "callback", locale=user_locale).format(fullclb[2]),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -221,7 +209,7 @@ async def callback_query_block(app: PosterClient, clb: CallbackQuery):
|
|||||||
clb.message.reply_markup.inline_keyboard[0],
|
clb.message.reply_markup.inline_keyboard[0],
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=str(locale("sub_unblock", "button", locale=user_locale)),
|
text=str(app._("sub_unblock", "button", locale=user_locale)),
|
||||||
callback_data=f"sub_unblock_{fullclb[2]}",
|
callback_data=f"sub_unblock_{fullclb[2]}",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -229,26 +217,26 @@ async def callback_query_block(app: PosterClient, clb: CallbackQuery):
|
|||||||
await clb.message.edit_reply_markup(
|
await clb.message.edit_reply_markup(
|
||||||
reply_markup=InlineKeyboardMarkup(edited_markup)
|
reply_markup=InlineKeyboardMarkup(edited_markup)
|
||||||
)
|
)
|
||||||
logWrite(
|
logger.info(
|
||||||
locale(
|
app._(
|
||||||
"user_blocked",
|
"user_blocked",
|
||||||
"console",
|
"console",
|
||||||
locale=configGet("locale_log").format(fullclb[2]),
|
locale=app.config["locale_log"],
|
||||||
),
|
).format(fullclb[2]),
|
||||||
debug=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*"))
|
@Client.on_callback_query(filters.regex("sub_unblock_[\s\S]*"))
|
||||||
async def callback_query_unblock(app: PosterClient, clb: CallbackQuery):
|
async def callback_query_unblock(app: PyroClient, clb: CallbackQuery):
|
||||||
fullclb = str(clb.data).split("_")
|
fullclb = str(clb.data).split("_")
|
||||||
user_locale = clb.from_user.language_code
|
user_locale = clb.from_user.language_code
|
||||||
await app.send_message(
|
|
||||||
int(fullclb[2]), locale("sub_unblocked", "message", locale=configGet("locale"))
|
await app.send_message(int(fullclb[2]), app._("sub_unblocked", "message"))
|
||||||
)
|
|
||||||
PosterUser(int(fullclb[2])).unblock()
|
PosterUser(int(fullclb[2])).unblock()
|
||||||
|
|
||||||
await clb.answer(
|
await clb.answer(
|
||||||
text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]),
|
text=app._("sub_unblock", "callback", locale=user_locale).format(fullclb[2]),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -256,7 +244,7 @@ async def callback_query_unblock(app: PosterClient, clb: CallbackQuery):
|
|||||||
clb.message.reply_markup.inline_keyboard[0],
|
clb.message.reply_markup.inline_keyboard[0],
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=str(locale("sub_block", "button", locale=user_locale)),
|
text=str(app._("sub_block", "button", locale=user_locale)),
|
||||||
callback_data=f"sub_block_{fullclb[2]}",
|
callback_data=f"sub_block_{fullclb[2]}",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -264,11 +252,10 @@ async def callback_query_unblock(app: PosterClient, clb: CallbackQuery):
|
|||||||
await clb.message.edit_reply_markup(
|
await clb.message.edit_reply_markup(
|
||||||
reply_markup=InlineKeyboardMarkup(edited_markup)
|
reply_markup=InlineKeyboardMarkup(edited_markup)
|
||||||
)
|
)
|
||||||
logWrite(
|
logger.info(
|
||||||
locale(
|
app._(
|
||||||
"user_unblocked",
|
"user_unblocked",
|
||||||
"console",
|
"console",
|
||||||
locale=configGet("locale_log").format(fullclb[2]),
|
locale=app.config["locale_log"],
|
||||||
),
|
).format(fullclb[2]),
|
||||||
debug=True,
|
|
||||||
)
|
)
|
||||||
|
@ -1,45 +1,45 @@
|
|||||||
from os import getpid, makedirs, path
|
from os import makedirs
|
||||||
|
from pathlib import Path
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
from libbot import json_write
|
||||||
from pyrogram import filters
|
from pyrogram import filters
|
||||||
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
from pyrogram.client import Client
|
||||||
|
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||||
|
|
||||||
from classes.poster_client import PosterClient
|
from classes.pyroclient import PyroClient
|
||||||
from modules.app import app, users_with_context
|
from modules.utils import USERS_WITH_CONTEXT
|
||||||
from modules.logger import logWrite
|
|
||||||
from modules.scheduler import scheduler
|
|
||||||
from modules.utils import configGet, jsonSave, locale
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(~filters.scheduled & filters.command(["shutdown"], prefixes=["", "/"]))
|
@Client.on_message(
|
||||||
async def cmd_kill(app: PosterClient, msg: Message):
|
~filters.scheduled & filters.command(["shutdown"], prefixes=["", "/"])
|
||||||
if msg.from_user.id in app.admins:
|
)
|
||||||
global users_with_context
|
async def cmd_kill(app: PyroClient, msg: Message):
|
||||||
if len(users_with_context) > 0:
|
if msg.from_user.id not in app.admins:
|
||||||
await msg.reply_text(
|
return
|
||||||
f"There're {len(users_with_context)} unfinished users' contexts. If you turn off the bot, those will be lost. Please confirm shutdown using a button below.",
|
|
||||||
reply_markup=InlineKeyboardMarkup(
|
if len(USERS_WITH_CONTEXT) > 0:
|
||||||
[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(
|
|
||||||
"Confirm shutdown", callback_data="shutdown"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
pid = getpid()
|
|
||||||
logWrite(f"Shutting down bot with pid {pid}")
|
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("shutdown", "message", locale=msg.from_user.language_code).format(
|
app._("shutdown_confirm", "message").format(len(USERS_WITH_CONTEXT)),
|
||||||
pid
|
reply_markup=InlineKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
app._(
|
||||||
|
"shutdown", "button", locale=msg.from_user.language_code
|
||||||
|
),
|
||||||
|
callback_data="shutdown",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
scheduler.shutdown()
|
return
|
||||||
makedirs(configGet("cache", "locations"), exist_ok=True)
|
|
||||||
jsonSave(
|
makedirs(app.config["locations"]["cache"], exist_ok=True)
|
||||||
{"timestamp": time()},
|
await json_write(
|
||||||
path.join(configGet("cache", "locations"), "shutdown_time"),
|
{"timestamp": time()},
|
||||||
)
|
Path(f"{app.config['locations']['cache']}/shutdown_time"),
|
||||||
exit()
|
)
|
||||||
|
|
||||||
|
exit()
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
from pyrogram import filters
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
from pyrogram.types import Message
|
from pyrogram.types import Message
|
||||||
|
|
||||||
from modules.app import app
|
from classes.pyroclient import PyroClient
|
||||||
from modules.utils import locale
|
|
||||||
from classes.user import PosterUser
|
from classes.user import PosterUser
|
||||||
from classes.poster_client import PosterClient
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(~filters.scheduled & filters.command(["start"], prefixes="/"))
|
@Client.on_message(~filters.scheduled & filters.command(["start"], prefixes="/"))
|
||||||
async def cmd_start(app: PosterClient, msg: Message):
|
async def cmd_start(app: PyroClient, msg: Message):
|
||||||
if PosterUser(msg.from_user.id).is_blocked() is False:
|
if PosterUser(msg.from_user.id).is_blocked():
|
||||||
await msg.reply_text(
|
return
|
||||||
locale("start", "message", locale=msg.from_user.language_code)
|
|
||||||
)
|
await msg.reply_text(app._("start", "message", locale=msg.from_user.language_code))
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(~filters.scheduled & filters.command(["rules", "help"], prefixes="/"))
|
@Client.on_message(
|
||||||
async def cmd_rules(app: PosterClient, msg: Message):
|
~filters.scheduled & filters.command(["rules", "help"], prefixes="/")
|
||||||
if PosterUser(msg.from_user.id).is_blocked() is False:
|
)
|
||||||
await msg.reply_text(
|
async def cmd_rules(app: PyroClient, msg: Message):
|
||||||
locale("rules", "message", locale=msg.from_user.language_code)
|
if PosterUser(msg.from_user.id).is_blocked():
|
||||||
)
|
return
|
||||||
|
|
||||||
|
await msg.reply_text(app._("rules", "message", locale=msg.from_user.language_code))
|
||||||
|
@ -1,214 +1,303 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from glob import iglob
|
from glob import iglob
|
||||||
|
from io import BytesIO
|
||||||
from os import getcwd, makedirs, path, remove
|
from os import getcwd, makedirs, path, remove
|
||||||
|
from pathlib import Path
|
||||||
from shutil import disk_usage, rmtree
|
from shutil import disk_usage, rmtree
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from convopyro import listen_message
|
from convopyro import listen_message
|
||||||
|
from photosapi_client.errors import UnexpectedStatus
|
||||||
from pyrogram import filters
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
from pyrogram.types import Message
|
from pyrogram.types import Message
|
||||||
|
from ujson import loads
|
||||||
|
|
||||||
from classes.poster_client import PosterClient
|
from classes.pyroclient import PyroClient
|
||||||
from modules.api_client import remove_pic, upload_pic
|
from modules.api_client import (
|
||||||
from modules.app import app, users_with_context
|
BodyPhotoUploadAlbumsAlbumPhotosPost,
|
||||||
from modules.logger import logWrite
|
File,
|
||||||
from modules.utils import configGet, extract_and_save, locale
|
client,
|
||||||
|
photo_delete,
|
||||||
|
photo_upload,
|
||||||
|
)
|
||||||
|
from modules.utils import USERS_WITH_CONTEXT, extract_and_save
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"]))
|
@Client.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"]))
|
||||||
async def cmd_import(app: PosterClient, msg: Message):
|
async def cmd_import(app: PyroClient, msg: Message):
|
||||||
if msg.from_user.id in app.admins:
|
if msg.from_user.id not in app.admins:
|
||||||
global users_with_context
|
return
|
||||||
if msg.from_user.id not in users_with_context:
|
|
||||||
users_with_context.append(msg.from_user.id)
|
global USERS_WITH_CONTEXT
|
||||||
else:
|
|
||||||
return
|
if msg.from_user.id not in USERS_WITH_CONTEXT:
|
||||||
|
USERS_WITH_CONTEXT.append(msg.from_user.id)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
await msg.reply_text(
|
||||||
|
app._("import_request", "message", locale=msg.from_user.language_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
answer = await listen_message(app, msg.chat.id, timeout=600)
|
||||||
|
|
||||||
|
USERS_WITH_CONTEXT.remove(msg.from_user.id)
|
||||||
|
|
||||||
|
if answer is None:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("import_request", "message", locale=msg.from_user.language_code)
|
app._("import_ignored", "message", locale=msg.from_user.language_code),
|
||||||
)
|
|
||||||
answer = await listen_message(app, msg.chat.id, timeout=600)
|
|
||||||
users_with_context.remove(msg.from_user.id)
|
|
||||||
if answer is None:
|
|
||||||
await msg.reply_text(
|
|
||||||
locale("import_ignored", "message", locale=msg.from_user.language_code),
|
|
||||||
quote=True,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if answer.text == "/cancel":
|
|
||||||
await answer.reply_text(
|
|
||||||
locale("import_abort", "message", locale=msg.from_user.language_code)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if answer.document is None:
|
|
||||||
await answer.reply_text(
|
|
||||||
locale(
|
|
||||||
"import_invalid_media",
|
|
||||||
"message",
|
|
||||||
locale=msg.from_user.language_code,
|
|
||||||
),
|
|
||||||
quote=True,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if answer.document.mime_type != "application/zip":
|
|
||||||
await answer.reply_text(
|
|
||||||
locale(
|
|
||||||
"import_invalid_mime", "message", locale=msg.from_user.language_code
|
|
||||||
),
|
|
||||||
quote=True,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if disk_usage(getcwd())[2] < (answer.document.file_size) * 3:
|
|
||||||
await msg.reply_text(
|
|
||||||
locale(
|
|
||||||
"import_too_big", "message", locale=msg.from_user.language_code
|
|
||||||
).format(
|
|
||||||
answer.document.file_size // (2**30),
|
|
||||||
disk_usage(getcwd())[2] // (2**30),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
tmp_dir = str(uuid4())
|
|
||||||
logWrite(
|
|
||||||
f"Importing '{answer.document.file_name}' file {answer.document.file_size} bytes big (TMP ID {tmp_dir})"
|
|
||||||
)
|
|
||||||
makedirs(path.join(configGet("tmp", "locations"), tmp_dir), exist_ok=True)
|
|
||||||
tmp_path = path.join(configGet("tmp", "locations"), answer.document.file_id)
|
|
||||||
downloading = await answer.reply_text(
|
|
||||||
locale("import_downloading", "message", locale=msg.from_user.language_code),
|
|
||||||
quote=True,
|
|
||||||
)
|
|
||||||
await app.download_media(answer, file_name=tmp_path)
|
|
||||||
await downloading.edit(
|
|
||||||
locale("import_unpacking", "message", locale=msg.from_user.language_code)
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
with ZipFile(tmp_path, "r") as handle:
|
|
||||||
tasks = [
|
|
||||||
extract_and_save(
|
|
||||||
handle, name, path.join(configGet("tmp", "locations"), tmp_dir)
|
|
||||||
)
|
|
||||||
for name in handle.namelist()
|
|
||||||
]
|
|
||||||
_ = await asyncio.gather(*tasks)
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(
|
|
||||||
f"Could not import '{answer.document.file_name}' due to {exp}: {format_exc}"
|
|
||||||
)
|
|
||||||
await answer.reply_text(
|
|
||||||
locale(
|
|
||||||
"import_unpack_error", "message", locale=msg.from_user.language_code
|
|
||||||
).format(exp, format_exc())
|
|
||||||
)
|
|
||||||
return
|
|
||||||
logWrite(f"Downloaded '{answer.document.file_name}' - awaiting upload")
|
|
||||||
await downloading.edit(
|
|
||||||
locale("import_uploading", "message", locale=msg.from_user.language_code)
|
|
||||||
)
|
|
||||||
remove(tmp_path)
|
|
||||||
|
|
||||||
for filename in iglob(
|
|
||||||
path.join(configGet("tmp", "locations"), tmp_dir) + "**/**", recursive=True
|
|
||||||
):
|
|
||||||
if not path.isfile(filename):
|
|
||||||
continue
|
|
||||||
# upload filename
|
|
||||||
uploaded = await upload_pic(filename)
|
|
||||||
if uploaded[0] is False:
|
|
||||||
logWrite(
|
|
||||||
f"Could not upload '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}'. Duplicates: {str(uploaded[1])}",
|
|
||||||
debug=True,
|
|
||||||
)
|
|
||||||
if len(uploaded[1]) > 0:
|
|
||||||
await msg.reply_text(
|
|
||||||
locale(
|
|
||||||
"import_upload_error_duplicate",
|
|
||||||
"message",
|
|
||||||
locale=msg.from_user.language_code,
|
|
||||||
).format(path.basename(filename)),
|
|
||||||
disable_notification=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await msg.reply_text(
|
|
||||||
locale(
|
|
||||||
"import_upload_error_other",
|
|
||||||
"message",
|
|
||||||
locale=msg.from_user.language_code,
|
|
||||||
).format(path.basename(filename)),
|
|
||||||
disable_notification=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logWrite(
|
|
||||||
f"Uploaded '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}' and got ID {uploaded[2]}",
|
|
||||||
debug=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
await downloading.delete()
|
|
||||||
logWrite(
|
|
||||||
f"Removing '{path.join(configGet('tmp', 'locations'), tmp_dir)}' after uploading",
|
|
||||||
debug=True,
|
|
||||||
)
|
|
||||||
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
|
|
||||||
await answer.reply_text(
|
|
||||||
locale("import_finished", "message", locale=msg.from_user.language_code),
|
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if answer.text == "/cancel":
|
||||||
@app.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"]))
|
await answer.reply_text(
|
||||||
async def cmd_export(app: PosterClient, msg: Message):
|
app._("import_abort", "message", locale=msg.from_user.language_code)
|
||||||
if msg.from_user.id in app.admins:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"]))
|
|
||||||
async def cmd_remove(app: PosterClient, msg: Message):
|
|
||||||
if msg.from_user.id in app.admins:
|
|
||||||
global users_with_context
|
|
||||||
if msg.from_user.id not in users_with_context:
|
|
||||||
users_with_context.append(msg.from_user.id)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
await msg.reply_text(
|
|
||||||
locale("remove_request", "message", locale=msg.from_user.language_code)
|
|
||||||
)
|
)
|
||||||
answer = await listen_message(app, msg.chat.id, timeout=600)
|
return
|
||||||
users_with_context.remove(msg.from_user.id)
|
|
||||||
if answer is None:
|
if answer.document is None:
|
||||||
|
await answer.reply_text(
|
||||||
|
app._(
|
||||||
|
"import_invalid_media",
|
||||||
|
"message",
|
||||||
|
locale=msg.from_user.language_code,
|
||||||
|
),
|
||||||
|
quote=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if answer.document.mime_type != "application/zip":
|
||||||
|
await answer.reply_text(
|
||||||
|
app._("import_invalid_mime", "message", locale=msg.from_user.language_code),
|
||||||
|
quote=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if disk_usage(getcwd())[2] < (answer.document.file_size) * 3:
|
||||||
|
await msg.reply_text(
|
||||||
|
app._(
|
||||||
|
"import_too_big", "message", locale=msg.from_user.language_code
|
||||||
|
).format(
|
||||||
|
answer.document.file_size // (2**30),
|
||||||
|
disk_usage(getcwd())[2] // (2**30),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
tmp_dir = str(uuid4())
|
||||||
|
|
||||||
|
logging.info(
|
||||||
|
"Importing '%s' file %s bytes big (TMP ID %s)",
|
||||||
|
answer.document.file_name,
|
||||||
|
answer.document.file_size,
|
||||||
|
tmp_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
makedirs(Path(f"{app.config['locations']['tmp']}/{tmp_dir}"), exist_ok=True)
|
||||||
|
tmp_path = Path(f"{app.config['locations']['tmp']}/{answer.document.file_id}")
|
||||||
|
|
||||||
|
downloading = await answer.reply_text(
|
||||||
|
app._("import_downloading", "message", locale=msg.from_user.language_code),
|
||||||
|
quote=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await app.download_media(answer, file_name=str(tmp_path))
|
||||||
|
await downloading.edit(
|
||||||
|
app._("import_unpacking", "message", locale=msg.from_user.language_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with ZipFile(tmp_path, "r") as handle:
|
||||||
|
tasks = [
|
||||||
|
extract_and_save(
|
||||||
|
handle, name, Path(f"{app.config['locations']['tmp']}/{tmp_dir}")
|
||||||
|
)
|
||||||
|
for name in handle.namelist()
|
||||||
|
]
|
||||||
|
_ = await asyncio.gather(*tasks)
|
||||||
|
except Exception as exp:
|
||||||
|
logger.error(
|
||||||
|
"Could not import '%s' due to %s: %s",
|
||||||
|
answer.document.file_name,
|
||||||
|
exp,
|
||||||
|
format_exc(),
|
||||||
|
)
|
||||||
|
await answer.reply_text(
|
||||||
|
app._(
|
||||||
|
"import_unpack_error", "message", locale=msg.from_user.language_code
|
||||||
|
).format(exp, format_exc())
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Downloaded '%s' - awaiting upload", answer.document.file_name)
|
||||||
|
|
||||||
|
await downloading.edit(
|
||||||
|
app._("import_uploading", "message", locale=msg.from_user.language_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
remove(tmp_path)
|
||||||
|
|
||||||
|
for filename in iglob(
|
||||||
|
str(Path(f"{app.config['locations']['tmp']}/{tmp_dir}")) + "**/**",
|
||||||
|
recursive=True,
|
||||||
|
):
|
||||||
|
if not path.isfile(filename):
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(str(filename), "rb") as fh:
|
||||||
|
photo_bytes = BytesIO(fh.read())
|
||||||
|
|
||||||
|
try:
|
||||||
|
uploaded = await photo_upload(
|
||||||
|
app.config["posting"]["api"]["album"],
|
||||||
|
client=client,
|
||||||
|
multipart_data=BodyPhotoUploadAlbumsAlbumPhotosPost(
|
||||||
|
File(photo_bytes, Path(filename).name, "image/jpeg")
|
||||||
|
),
|
||||||
|
ignore_duplicates=app.config["submission"]["allow_duplicates"],
|
||||||
|
compress=False,
|
||||||
|
caption="queue",
|
||||||
|
)
|
||||||
|
except UnexpectedStatus as exp:
|
||||||
|
logger.error(
|
||||||
|
"Could not upload '%s' from '%s': %s",
|
||||||
|
filename,
|
||||||
|
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
|
||||||
|
exp,
|
||||||
|
)
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("remove_ignored", "message", locale=msg.from_user.language_code),
|
app._(
|
||||||
quote=True,
|
"import_upload_error_other",
|
||||||
|
"message",
|
||||||
|
locale=msg.from_user.language_code,
|
||||||
|
).format(path.basename(filename)),
|
||||||
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
return
|
continue
|
||||||
if answer.text == "/cancel":
|
|
||||||
await answer.reply_text(
|
uploaded_dict = loads(uploaded.content.decode("utf-8"))
|
||||||
locale("remove_abort", "message", locale=msg.from_user.language_code)
|
|
||||||
)
|
if "duplicates" in uploaded_dict:
|
||||||
return
|
logger.warning(
|
||||||
response = await remove_pic(answer.text)
|
"Could not upload '%s' from '%s'. Duplicates: %s",
|
||||||
if response:
|
filename,
|
||||||
logWrite(
|
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
|
||||||
f"Removed '{answer.text}' by request of user {answer.from_user.id}"
|
str(uploaded_dict["duplicates"]),
|
||||||
)
|
|
||||||
await answer.reply_text(
|
|
||||||
locale(
|
|
||||||
"remove_success", "message", locale=msg.from_user.language_code
|
|
||||||
).format(answer.text)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if len(uploaded_dict["duplicates"]) > 0:
|
||||||
|
await msg.reply_text(
|
||||||
|
app._(
|
||||||
|
"import_upload_error_duplicate",
|
||||||
|
"message",
|
||||||
|
locale=msg.from_user.language_code,
|
||||||
|
).format(path.basename(filename)),
|
||||||
|
disable_notification=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await msg.reply_text(
|
||||||
|
app._(
|
||||||
|
"import_upload_error_other",
|
||||||
|
"message",
|
||||||
|
locale=msg.from_user.language_code,
|
||||||
|
).format(path.basename(filename)),
|
||||||
|
disable_notification=True,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logWrite(
|
logger.info(
|
||||||
f"Could not remove '{answer.text}' by request of user {answer.from_user.id}"
|
"Uploaded '%s' from '%s' and got ID %s",
|
||||||
)
|
filename,
|
||||||
await answer.reply_text(
|
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
|
||||||
locale(
|
uploaded.parsed.id,
|
||||||
"remove_failure", "message", locale=msg.from_user.language_code
|
|
||||||
).format(answer.text)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await downloading.delete()
|
||||||
|
|
||||||
@app.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"]))
|
logger.info(
|
||||||
async def cmd_purge(app: PosterClient, msg: Message):
|
"Removing '%s' after uploading",
|
||||||
if msg.from_user.id in app.admins:
|
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
|
||||||
pass
|
)
|
||||||
|
rmtree(Path(f"{app.config['locations']['tmp']}/{tmp_dir}"), ignore_errors=True)
|
||||||
|
|
||||||
|
await answer.reply_text(
|
||||||
|
app._("import_finished", "message", locale=msg.from_user.language_code),
|
||||||
|
quote=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@Client.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"]))
|
||||||
|
async def cmd_export(app: PyroClient, msg: Message):
|
||||||
|
if msg.from_user.id not in app.admins:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@Client.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"]))
|
||||||
|
async def cmd_remove(app: PyroClient, msg: Message):
|
||||||
|
if msg.from_user.id not in app.admins:
|
||||||
|
return
|
||||||
|
|
||||||
|
global USERS_WITH_CONTEXT
|
||||||
|
|
||||||
|
if msg.from_user.id not in USERS_WITH_CONTEXT:
|
||||||
|
USERS_WITH_CONTEXT.append(msg.from_user.id)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
await msg.reply_text(
|
||||||
|
app._("remove_request", "message", locale=msg.from_user.language_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
answer = await listen_message(app, msg.chat.id, timeout=600)
|
||||||
|
|
||||||
|
USERS_WITH_CONTEXT.remove(msg.from_user.id)
|
||||||
|
|
||||||
|
if answer is None:
|
||||||
|
await msg.reply_text(
|
||||||
|
app._("remove_ignored", "message", locale=msg.from_user.language_code),
|
||||||
|
quote=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if answer.text == "/cancel":
|
||||||
|
await answer.reply_text(
|
||||||
|
app._("remove_abort", "message", locale=msg.from_user.language_code)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
response = await photo_delete(id=answer.text, client=client)
|
||||||
|
|
||||||
|
if response:
|
||||||
|
logger.info(
|
||||||
|
"Removed '%s' by request of user %s", answer.text, answer.from_user.id
|
||||||
|
)
|
||||||
|
await answer.reply_text(
|
||||||
|
app._(
|
||||||
|
"remove_success", "message", locale=msg.from_user.language_code
|
||||||
|
).format(answer.text)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"Could not remove '%s' by request of user %s",
|
||||||
|
answer.text,
|
||||||
|
answer.from_user.id,
|
||||||
|
)
|
||||||
|
await answer.reply_text(
|
||||||
|
app._(
|
||||||
|
"remove_failure", "message", locale=msg.from_user.language_code
|
||||||
|
).format(answer.text)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Client.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"]))
|
||||||
|
async def cmd_purge(app: PyroClient, msg: Message):
|
||||||
|
if msg.from_user.id not in app.admins:
|
||||||
|
return
|
||||||
|
@ -1,32 +1,37 @@
|
|||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from os import makedirs, path, sep
|
from os import makedirs, path, sep
|
||||||
|
from pathlib import Path
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from pyrogram import filters
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
from pyrogram.enums.chat_action import ChatAction
|
from pyrogram.enums.chat_action import ChatAction
|
||||||
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
|
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||||
|
|
||||||
from classes.enums.submission_types import SubmissionType
|
from classes.enums.submission_types import SubmissionType
|
||||||
from classes.exceptions import SubmissionDuplicatesError
|
from classes.exceptions import SubmissionDuplicatesError, SubmissionUnsupportedError
|
||||||
from classes.poster_client import PosterClient
|
from classes.pyroclient import PyroClient
|
||||||
from classes.user import PosterUser
|
from classes.user import PosterUser
|
||||||
from modules.app import app, users_with_context
|
|
||||||
from modules.database import col_banned, col_submitted
|
from modules.database import col_banned, col_submitted
|
||||||
from modules.logger import logWrite
|
from modules.utils import USERS_WITH_CONTEXT
|
||||||
from modules.utils import configGet, locale
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(
|
@Client.on_message(
|
||||||
~filters.scheduled & filters.private & filters.photo
|
~filters.scheduled & filters.private & filters.photo
|
||||||
| filters.video
|
| filters.video
|
||||||
| filters.animation
|
| filters.animation
|
||||||
| filters.document
|
| filters.document
|
||||||
)
|
)
|
||||||
async def get_submission(app: PosterClient, msg: Message):
|
async def get_submission(app: PyroClient, msg: Message):
|
||||||
global users_with_context
|
global USERS_WITH_CONTEXT
|
||||||
if msg.from_user.id in users_with_context:
|
|
||||||
|
if msg.from_user.id in USERS_WITH_CONTEXT:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if col_banned.find_one({"user": msg.from_user.id}) is not None:
|
if col_banned.find_one({"user": msg.from_user.id}) is not None:
|
||||||
return
|
return
|
||||||
@ -39,34 +44,37 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
|
|
||||||
if PosterUser(msg.from_user.id).is_limited():
|
if PosterUser(msg.from_user.id).is_limited():
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("sub_cooldown", "message", locale=user_locale).format(
|
app._("sub_cooldown", "message", locale=user_locale).format(
|
||||||
str(configGet("timeout", "submission"))
|
str(app.config["submission"]["timeout"])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if msg.document is not None:
|
if msg.document is not None:
|
||||||
logWrite(
|
logger.info(
|
||||||
f"User {msg.from_user.id} is trying to submit a file of type '{msg.document.mime_type}' with name '{msg.document.file_name}' and size of {msg.document.file_size / 1024 / 1024} MB",
|
"User %s is trying to submit a file of type '%s' with name '%s' and size of %s MB",
|
||||||
debug=True,
|
msg.from_user.id,
|
||||||
|
msg.document.mime_type,
|
||||||
|
msg.document.file_name,
|
||||||
|
msg.document.file_size / 1024 / 1024,
|
||||||
)
|
)
|
||||||
if msg.document.mime_type not in configGet("mime_types", "submission"):
|
if msg.document.mime_type not in app.config["submission"]["mime_types"]:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("mime_not_allowed", "message", locale=user_locale).format(
|
app._("mime_not_allowed", "message", locale=user_locale).format(
|
||||||
", ".join(configGet("mime_types", "submission"))
|
", ".join(app.config["submission"]["mime_types"])
|
||||||
),
|
),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if msg.document.file_size > configGet("file_size", "submission"):
|
if msg.document.file_size > app.config["submission"]["file_size"]:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("document_too_large", "message", locale=user_locale).format(
|
app._("document_too_large", "message", locale=user_locale).format(
|
||||||
str(configGet("file_size", "submission") / 1024 / 1024)
|
str(app.config["submission"]["file_size"] / 1024 / 1024)
|
||||||
),
|
),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if msg.document.file_size > configGet("tmp_size", "submission"):
|
if msg.document.file_size > app.config["submission"]["tmp_size"]:
|
||||||
save_tmp = False
|
save_tmp = False
|
||||||
contents = (
|
contents = (
|
||||||
msg.document.file_id,
|
msg.document.file_id,
|
||||||
@ -74,36 +82,40 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
) # , msg.document.file_name
|
) # , msg.document.file_name
|
||||||
|
|
||||||
if msg.video is not None:
|
if msg.video is not None:
|
||||||
logWrite(
|
logger.info(
|
||||||
f"User {msg.from_user.id} is trying to submit a video with name '{msg.video.file_name}' and size of {msg.video.file_size / 1024 / 1024} MB",
|
"User %s is trying to submit a video with name '%s' and size of %s MB",
|
||||||
debug=True,
|
msg.from_user.id,
|
||||||
|
msg.video.file_name,
|
||||||
|
msg.video.file_size / 1024 / 1024,
|
||||||
)
|
)
|
||||||
if msg.video.file_size > configGet("file_size", "submission"):
|
if msg.video.file_size > app.config["submission"]["file_size"]:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("document_too_large", "message", locale=user_locale).format(
|
app._("document_too_large", "message", locale=user_locale).format(
|
||||||
str(configGet("file_size", "submission") / 1024 / 1024)
|
str(app.config["submission"]["file_size"] / 1024 / 1024)
|
||||||
),
|
),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if msg.video.file_size > configGet("tmp_size", "submission"):
|
if msg.video.file_size > app.config["submission"]["tmp_size"]:
|
||||||
save_tmp = False
|
save_tmp = False
|
||||||
contents = msg.video.file_id, SubmissionType.VIDEO # , msg.video.file_name
|
contents = msg.video.file_id, SubmissionType.VIDEO # , msg.video.file_name
|
||||||
|
|
||||||
if msg.animation is not None:
|
if msg.animation is not None:
|
||||||
logWrite(
|
logger.info(
|
||||||
f"User {msg.from_user.id} is trying to submit an animation with name '{msg.animation.file_name}' and size of {msg.animation.file_size / 1024 / 1024} MB",
|
"User %s is trying to submit an animation with name '%s' and size of %s MB",
|
||||||
debug=True,
|
msg.from_user.id,
|
||||||
|
msg.animation.file_name,
|
||||||
|
msg.animation.file_size / 1024 / 1024,
|
||||||
)
|
)
|
||||||
if msg.animation.file_size > configGet("file_size", "submission"):
|
if msg.animation.file_size > app.config["submission"]["file_size"]:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("document_too_large", "message", locale=user_locale).format(
|
app._("document_too_large", "message", locale=user_locale).format(
|
||||||
str(configGet("file_size", "submission") / 1024 / 1024)
|
str(app.config["submission"]["file_size"] / 1024 / 1024)
|
||||||
),
|
),
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if msg.animation.file_size > configGet("tmp_size", "submission"):
|
if msg.animation.file_size > app.config["submission"]["tmp_size"]:
|
||||||
save_tmp = False
|
save_tmp = False
|
||||||
contents = (
|
contents = (
|
||||||
msg.animation.file_id,
|
msg.animation.file_id,
|
||||||
@ -111,26 +123,31 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
) # , msg.animation.file_name
|
) # , msg.animation.file_name
|
||||||
|
|
||||||
if msg.photo is not None:
|
if msg.photo is not None:
|
||||||
logWrite(
|
logger.info(
|
||||||
f"User {msg.from_user.id} is trying to submit a photo with ID '{msg.photo.file_id}' and size of {msg.photo.file_size / 1024 / 1024} MB",
|
"User %s is trying to submit a photo with ID '%s' and size of %s MB",
|
||||||
debug=True,
|
msg.from_user.id,
|
||||||
|
msg.photo.file_id,
|
||||||
|
msg.photo.file_size / 1024 / 1024,
|
||||||
)
|
)
|
||||||
contents = msg.photo.file_id, SubmissionType.PHOTO # , "please_generate"
|
contents = msg.photo.file_id, SubmissionType.PHOTO # , "please_generate"
|
||||||
|
|
||||||
if save_tmp is not None:
|
if contents is None:
|
||||||
if contents is None:
|
return
|
||||||
return
|
|
||||||
|
|
||||||
|
if save_tmp is not None:
|
||||||
tmp_id = str(uuid4())
|
tmp_id = str(uuid4())
|
||||||
|
|
||||||
# filename = tmp_id if contents[1] == "please_generate" else contents[1]
|
# filename = tmp_id if contents[1] == "please_generate" else contents[1]
|
||||||
makedirs(
|
makedirs(
|
||||||
path.join(configGet("data", "locations"), "submissions", tmp_id),
|
Path(f"{app.config['locations']['data']}/submissions/{tmp_id}"),
|
||||||
exist_ok=True,
|
exist_ok=True,
|
||||||
)
|
)
|
||||||
downloaded = await app.download_media(
|
downloaded = await app.download_media(
|
||||||
msg,
|
msg,
|
||||||
path.join(configGet("data", "locations"), "submissions", tmp_id) + sep,
|
str(Path(f"{app.config['locations']['data']}/submissions/{tmp_id}"))
|
||||||
|
+ sep,
|
||||||
)
|
)
|
||||||
|
|
||||||
inserted = col_submitted.insert_one(
|
inserted = col_submitted.insert_one(
|
||||||
{
|
{
|
||||||
"user": msg.from_user.id,
|
"user": msg.from_user.id,
|
||||||
@ -144,9 +161,6 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if contents is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
inserted = col_submitted.insert_one(
|
inserted = col_submitted.insert_one(
|
||||||
{
|
{
|
||||||
"user": msg.from_user.id,
|
"user": msg.from_user.id,
|
||||||
@ -162,7 +176,7 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
buttons = [
|
buttons = [
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=locale("sub_yes", "button", locale=configGet("locale")),
|
text=app._("sub_yes", "button"),
|
||||||
callback_data=f"sub_yes_{str(inserted.inserted_id)}",
|
callback_data=f"sub_yes_{str(inserted.inserted_id)}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -172,28 +186,20 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
caption = str(msg.caption)
|
caption = str(msg.caption)
|
||||||
buttons[0].append(
|
buttons[0].append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=locale(
|
text=app._("sub_yes_caption", "button"),
|
||||||
"sub_yes_caption", "button", locale=configGet("locale")
|
|
||||||
),
|
|
||||||
callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption",
|
callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
buttons[0].append(
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text=locale("sub_no", "button", locale=configGet("locale")),
|
|
||||||
callback_data=f"sub_no_{str(inserted.inserted_id)}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
caption = ""
|
caption = ""
|
||||||
buttons[0].append(
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text=locale("sub_no", "button", locale=configGet("locale")),
|
|
||||||
callback_data=f"sub_no_{str(inserted.inserted_id)}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
caption += locale("sub_by", "message", locale=locale(configGet("locale")))
|
buttons[0].append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=app._("sub_no", "button"),
|
||||||
|
callback_data=f"sub_no_{str(inserted.inserted_id)}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
caption += app._("sub_by", "message")
|
||||||
|
|
||||||
if msg.from_user.first_name is not None:
|
if msg.from_user.first_name is not None:
|
||||||
caption += f" {msg.from_user.first_name}"
|
caption += f" {msg.from_user.first_name}"
|
||||||
@ -206,22 +212,28 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
msg.from_user.id in app.admins
|
msg.from_user.id in app.admins
|
||||||
and configGet("admins", "submission", "require_confirmation") is False
|
and app.config["submission"]["require_confirmation"]["admins"] is False
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
submitted = await app.submit_photo(str(inserted.inserted_id))
|
submitted = await app.submit_photo(str(inserted.inserted_id))
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("sub_yes_auto", "message", locale=user_locale),
|
app._("sub_yes_auto", "message", locale=user_locale),
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
if configGet("send_uploaded_id", "submission"):
|
if app.config["submission"]["send_uploaded_id"]:
|
||||||
caption += f"\n\nID: `{submitted[1]}`"
|
caption += f"\n\nID: `{submitted[1]}`"
|
||||||
await msg.copy(app.owner, caption=caption, disable_notification=True)
|
await msg.copy(app.owner, caption=caption, disable_notification=True)
|
||||||
return
|
return
|
||||||
|
except SubmissionUnsupportedError:
|
||||||
|
await msg.reply_text(
|
||||||
|
"Unsupported.",
|
||||||
|
quote=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
except SubmissionDuplicatesError as exp:
|
except SubmissionDuplicatesError as exp:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale(
|
app._(
|
||||||
"sub_media_duplicates_list", "message", locale=user_locale
|
"sub_media_duplicates_list", "message", locale=user_locale
|
||||||
).format("\n • ".join(exp.duplicates)),
|
).format("\n • ".join(exp.duplicates)),
|
||||||
quote=True,
|
quote=True,
|
||||||
@ -232,28 +244,31 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
return
|
return
|
||||||
elif (
|
elif (
|
||||||
msg.from_user.id not in app.admins
|
msg.from_user.id not in app.admins
|
||||||
and configGet("users", "submission", "require_confirmation") is False
|
and app.config["submission"]["require_confirmation"]["users"] is False
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
submitted = await app.submit_photo(str(inserted.inserted_id))
|
submitted = await app.submit_photo(str(inserted.inserted_id))
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("sub_yes_auto", "message", locale=user_locale),
|
app._("sub_yes_auto", "message", locale=user_locale),
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
if configGet("send_uploaded_id", "submission"):
|
if app.config["submission"]["send_uploaded_id"]:
|
||||||
caption += f"\n\nID: `{submitted[1]}`"
|
caption += f"\n\nID: `{submitted[1]}`"
|
||||||
await msg.copy(app.owner, caption=caption)
|
await msg.copy(app.owner, caption=caption)
|
||||||
return
|
return
|
||||||
|
except SubmissionUnsupportedError:
|
||||||
|
await msg.reply_text("Unsupported.", quote=True)
|
||||||
|
return
|
||||||
except SubmissionDuplicatesError as exp:
|
except SubmissionDuplicatesError as exp:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("sub_dup", "message", locale=user_locale), quote=True
|
app._("sub_dup", "message", locale=user_locale), quote=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except Exception as exp:
|
except Exception as exp:
|
||||||
await app.send_message(
|
await app.send_message(
|
||||||
app.owner,
|
app.owner,
|
||||||
locale("sub_error_admin", "message").format(
|
app._("sub_error_admin", "message").format(
|
||||||
msg.from_user.id, format_exc()
|
msg.from_user.id, format_exc()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -264,7 +279,7 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
buttons += [
|
buttons += [
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=locale("sub_block", "button", locale=configGet("locale")),
|
text=app._("sub_block", "button"),
|
||||||
callback_data=f"sub_block_{msg.from_user.id}",
|
callback_data=f"sub_block_{msg.from_user.id}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -274,7 +289,7 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
|
|
||||||
if msg.from_user.id != app.owner:
|
if msg.from_user.id != app.owner:
|
||||||
await msg.reply_text(
|
await msg.reply_text(
|
||||||
locale("sub_sent", "message", locale=user_locale),
|
app._("sub_sent", "message", locale=user_locale),
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
quote=True,
|
quote=True,
|
||||||
)
|
)
|
||||||
@ -284,4 +299,4 @@ async def get_submission(app: PosterClient, msg: Message):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logWrite(f"from_user in function get_submission does not seem to contain id")
|
logger.error("'from_user' does not seem to contain 'id'")
|
||||||
|
13
plugins/remove_commands.py
Normal file
13
plugins/remove_commands.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.client import Client
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
|
||||||
|
@Client.on_message(
|
||||||
|
~filters.scheduled & filters.private & filters.command(["remove_commands"], prefixes=["/"]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_remove_commands(app: PyroClient, msg: Message):
|
||||||
|
await msg.reply_text("Okay.")
|
||||||
|
await app.remove_commands(command_sets=await app.collect_commands())
|
266
poster.py
266
poster.py
@ -1,266 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from os import getpid, path
|
|
||||||
from sys import exit
|
|
||||||
from time import time
|
|
||||||
from traceback import format_exc
|
|
||||||
from modules.api_client import authorize
|
|
||||||
|
|
||||||
from modules.cli import *
|
|
||||||
from modules.http_client import http_session
|
|
||||||
from modules.logger import logWrite
|
|
||||||
from modules.scheduler import scheduler
|
|
||||||
from modules.utils import configGet, jsonLoad, jsonSave, locale
|
|
||||||
|
|
||||||
# Import ===================================================================================================================================
|
|
||||||
try:
|
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from pyrogram.errors import bad_request_400
|
|
||||||
from pyrogram.sync import idle
|
|
||||||
|
|
||||||
from modules.app import app
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
print(locale("deps_missing", "console", locale=configGet("locale")), flush=True)
|
|
||||||
exit()
|
|
||||||
# ===========================================================================================================================================
|
|
||||||
|
|
||||||
|
|
||||||
pid = getpid()
|
|
||||||
version = 0.1
|
|
||||||
|
|
||||||
# Work in progress
|
|
||||||
# def check_forwards(app):
|
|
||||||
|
|
||||||
# try:
|
|
||||||
|
|
||||||
# index = jsonLoad(configGet("index", "locations"))
|
|
||||||
# channel = app.get_chat(configGet("channel", "posting"))
|
|
||||||
|
|
||||||
# peer = app.resolve_peer(configGet("channel", "posting"))
|
|
||||||
# print(peer, flush=True)
|
|
||||||
|
|
||||||
# posts_list = [i for i in range(index["last_id"]-100,index["last_id"])]
|
|
||||||
# last_posts = app.get_messages(configGet("channel", "posting"), message_ids=posts_list)
|
|
||||||
|
|
||||||
# for post in last_posts:
|
|
||||||
# post_forwards = GetMessagePublicForwards(channel=peer, msg_id=post.id, offset_peer=peer, offset_rate=0, offset_id=0, limit=100)
|
|
||||||
# print(post_forwards, flush=True)
|
|
||||||
# for forward in post_forwards:
|
|
||||||
# print(forward, flush=True)
|
|
||||||
|
|
||||||
# except Exception as exp:
|
|
||||||
|
|
||||||
# logWrite("Could not get last posts forwards due to {0} with traceback {1}".format(str(exp), traceback.format_exc()), debug=True)
|
|
||||||
|
|
||||||
# if configGet("error", "reports"):
|
|
||||||
# app.send_message(configGet("admin"), traceback.format_exc())
|
|
||||||
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# Work in progress
|
|
||||||
# @app.on_message(~ filters.scheduled & filters.command(["forwards"], prefixes="/"))
|
|
||||||
# def cmd_forwards(app, msg):
|
|
||||||
# check_forwards(app)
|
|
||||||
|
|
||||||
|
|
||||||
from plugins.callbacks.shutdown import *
|
|
||||||
|
|
||||||
# Imports ==================================================================================================================================
|
|
||||||
from plugins.commands.general import *
|
|
||||||
|
|
||||||
if configGet("submit", "mode"):
|
|
||||||
from plugins.callbacks.nothing import *
|
|
||||||
from plugins.callbacks.submission import *
|
|
||||||
from plugins.commands.mode_submit import *
|
|
||||||
from plugins.handlers.submission import *
|
|
||||||
|
|
||||||
if configGet("post", "mode"):
|
|
||||||
from plugins.commands.photos import *
|
|
||||||
|
|
||||||
# if configGet("api_based", "mode"):
|
|
||||||
# from modules.api_client import authorize
|
|
||||||
# ===========================================================================================================================================
|
|
||||||
|
|
||||||
# Work in progress
|
|
||||||
# Handle new forwards
|
|
||||||
# @app.on_raw_update()
|
|
||||||
# def fwd_got(app, update, users, chats):
|
|
||||||
# if isinstance(update, UpdateChannelMessageForwards):
|
|
||||||
# logWrite(f'Forward count increased to {update["forwards"]} on post {update["id"]} in channel {update["channel_id"]}')
|
|
||||||
# logWrite(str(users), debug=True)
|
|
||||||
# logWrite(str(chats), debug=True)
|
|
||||||
# # else:
|
|
||||||
# # logWrite(f"Got raw update of type {type(update)} with contents {update}", debug=True)
|
|
||||||
|
|
||||||
# async def main():
|
|
||||||
|
|
||||||
# await app.start()
|
|
||||||
|
|
||||||
# logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid)))
|
|
||||||
|
|
||||||
# if configGet("startup", "reports"):
|
|
||||||
# await app.send_message(configGet("admin"), locale("startup", "message", locale=configGet("locale")).format(str(pid)))
|
|
||||||
|
|
||||||
# if configGet("post", "mode"):
|
|
||||||
# scheduler.start()
|
|
||||||
|
|
||||||
# if configGet("api_based", "mode"):
|
|
||||||
# token = authorize()
|
|
||||||
# if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0:
|
|
||||||
# post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"})
|
|
||||||
|
|
||||||
# await idle()
|
|
||||||
|
|
||||||
# await app.send_message(configGet("admin"), locale("shutdown", "message", locale=configGet("locale")).format(str(pid)))
|
|
||||||
# logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid)))
|
|
||||||
|
|
||||||
# killProc(pid)
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# if find_spec("uvloop") is not None:
|
|
||||||
# uvloop.install()
|
|
||||||
# asyncio.run(main())
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
logWrite(locale("startup", "console").format(str(pid)))
|
|
||||||
|
|
||||||
await app.start()
|
|
||||||
|
|
||||||
if configGet("startup", "reports"):
|
|
||||||
try:
|
|
||||||
if path.exists(path.join(configGet("cache", "locations"), "shutdown_time")):
|
|
||||||
downtime = relativedelta(
|
|
||||||
datetime.now(),
|
|
||||||
datetime.fromtimestamp(
|
|
||||||
jsonLoad(
|
|
||||||
path.join(configGet("cache", "locations"), "shutdown_time")
|
|
||||||
)["timestamp"]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if downtime.days >= 1:
|
|
||||||
await app.send_message(
|
|
||||||
configGet("owner"),
|
|
||||||
locale(
|
|
||||||
"startup_downtime_days",
|
|
||||||
"message",
|
|
||||||
).format(pid, downtime.days),
|
|
||||||
)
|
|
||||||
elif downtime.hours >= 1:
|
|
||||||
await app.send_message(
|
|
||||||
configGet("owner"),
|
|
||||||
locale(
|
|
||||||
"startup_downtime_hours",
|
|
||||||
"message",
|
|
||||||
).format(pid, downtime.hours),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await app.send_message(
|
|
||||||
configGet("owner"),
|
|
||||||
locale(
|
|
||||||
"startup_downtime_minutes",
|
|
||||||
"message",
|
|
||||||
locale=configGet("locale"),
|
|
||||||
).format(pid, downtime.minutes),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await app.send_message(
|
|
||||||
configGet("owner"),
|
|
||||||
locale("startup", "message").format(pid),
|
|
||||||
)
|
|
||||||
except bad_request_400.PeerIdInvalid:
|
|
||||||
logWrite(
|
|
||||||
f"Could not send startup message to bot owner. Perhaps user has not started the bot yet."
|
|
||||||
)
|
|
||||||
|
|
||||||
if configGet("update", "reports"):
|
|
||||||
try:
|
|
||||||
check_update = await http_session.get(
|
|
||||||
"https://git.end-play.xyz/api/v1/repos/profitroll/TelegramPoster/releases?page=1&limit=1"
|
|
||||||
)
|
|
||||||
response = await check_update.json()
|
|
||||||
if len(response) == 0:
|
|
||||||
raise ValueError("No bot releases on git found.")
|
|
||||||
if float(response[0]["tag_name"].replace("v", "")) > version:
|
|
||||||
logWrite(
|
|
||||||
f'New version {response[0]["tag_name"].replace("v", "")} found (current {version})'
|
|
||||||
)
|
|
||||||
await app.send_message(
|
|
||||||
configGet("owner"),
|
|
||||||
locale(
|
|
||||||
"update_available",
|
|
||||||
"message",
|
|
||||||
).format(
|
|
||||||
response[0]["tag_name"],
|
|
||||||
response[0]["html_url"],
|
|
||||||
response[0]["body"],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logWrite(f"No updates found, bot is up to date.")
|
|
||||||
except bad_request_400.PeerIdInvalid:
|
|
||||||
logWrite(
|
|
||||||
f"Could not send startup message to bot owner. Perhaps user has not started the bot yet."
|
|
||||||
)
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(f"Update check failed due to {exp}: {format_exc()}")
|
|
||||||
|
|
||||||
if configGet("post", "mode"):
|
|
||||||
scheduler.start()
|
|
||||||
|
|
||||||
try:
|
|
||||||
token = await authorize()
|
|
||||||
|
|
||||||
if (
|
|
||||||
len(
|
|
||||||
(
|
|
||||||
await (
|
|
||||||
await http_session.get(
|
|
||||||
f'{configGet("address", "posting", "api")}/albums?q={configGet("album", "posting", "api")}',
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
).json()
|
|
||||||
)["results"]
|
|
||||||
)
|
|
||||||
== 0
|
|
||||||
):
|
|
||||||
logWrite("Media album does not exist on API server. Trying to create it...")
|
|
||||||
try:
|
|
||||||
await http_session.post(
|
|
||||||
f'{configGet("address", "posting", "api")}/albums?name={configGet("album", "posting", "api")}&title={configGet("album", "posting", "api")}',
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
logWrite("Created media album on API server.")
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(
|
|
||||||
f"Could not create media album on API server due to {exp}: {format_exc()}"
|
|
||||||
)
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(f"Could not check if API album exists due to {exp}: {format_exc()}")
|
|
||||||
|
|
||||||
await idle()
|
|
||||||
|
|
||||||
try:
|
|
||||||
await app.send_message(
|
|
||||||
configGet("owner"),
|
|
||||||
locale("shutdown", "message").format(pid),
|
|
||||||
)
|
|
||||||
except bad_request_400.PeerIdInvalid:
|
|
||||||
logWrite(
|
|
||||||
f"Could not send shutdown message to bot owner. Perhaps user has not started the bot yet."
|
|
||||||
)
|
|
||||||
|
|
||||||
makedirs(configGet("cache", "locations"), exist_ok=True)
|
|
||||||
jsonSave(
|
|
||||||
{"timestamp": time()},
|
|
||||||
path.join(configGet("cache", "locations"), "shutdown_time"),
|
|
||||||
)
|
|
||||||
|
|
||||||
logWrite(locale("shutdown", "console").format(str(pid)))
|
|
||||||
|
|
||||||
scheduler.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
@ -1,12 +1,17 @@
|
|||||||
python_dateutil==2.8.2
|
|
||||||
apscheduler==3.10.1
|
|
||||||
pytimeparse==1.1.8
|
|
||||||
convopyro==0.5
|
|
||||||
pyrogram==2.0.106
|
|
||||||
aiofiles~=23.1.0
|
aiofiles~=23.1.0
|
||||||
tgcrypto==1.2.5
|
|
||||||
aiohttp~=3.8.4
|
aiohttp~=3.8.4
|
||||||
psutil==5.9.5
|
apscheduler~=3.10.1
|
||||||
pymongo==4.3.3
|
black~=23.3.0
|
||||||
pillow~=9.5.0
|
convopyro==0.5
|
||||||
ujson==5.8.0
|
pillow~=9.4.0
|
||||||
|
psutil~=5.9.4
|
||||||
|
pymongo~=4.3.3
|
||||||
|
pyrogram==2.0.106
|
||||||
|
python_dateutil==2.8.2
|
||||||
|
pytimeparse~=1.1.8
|
||||||
|
tgcrypto==1.2.5
|
||||||
|
ujson==5.8.0
|
||||||
|
uvloop==0.17.0
|
||||||
|
--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple
|
||||||
|
libbot[speed,pyrogram]==1.0
|
||||||
|
photosapi_client==0.2.0
|
Reference in New Issue
Block a user