Now using black for formatting

This commit is contained in:
Profitroll 2023-03-09 11:33:02 +01:00
parent 4331af415e
commit 88692ebc85
19 changed files with 701 additions and 250 deletions

View File

@ -5,4 +5,4 @@ class SubmissionType(Enum):
DOCUMENT = "document" DOCUMENT = "document"
VIDEO = "video" VIDEO = "video"
ANIMATION = "animation" ANIMATION = "animation"
PHOTO = "photo" PHOTO = "photo"

View File

@ -4,13 +4,19 @@ from typing import Any
class SubmissionUnavailableError(Exception): class SubmissionUnavailableError(Exception):
pass pass
class SubmissionUploadError(Exception): class SubmissionUploadError(Exception):
def __init__(self, file_path: str, status_code: int, content: Any) -> None: def __init__(self, file_path: str, status_code: int, content: Any) -> None:
self.status_code = status_code self.status_code = status_code
self.content = content self.content = content
super().__init__(f"Could not upload photo '{file_path}' due to HTTP {self.status_code}: {self.content}") super().__init__(
f"Could not upload photo '{file_path}' due to HTTP {self.status_code}: {self.content}"
)
class SubmissionDuplicatesError(Exception): class SubmissionDuplicatesError(Exception):
def __init__(self, file_path: str, duplicates: list) -> None: def __init__(self, file_path: str, duplicates: list) -> None:
self.duplicates = duplicates self.duplicates = duplicates
super().__init__(f"Found duplicates of a photo '{file_path}': {self.duplicates}") super().__init__(
f"Found duplicates of a photo '{file_path}': {self.duplicates}"
)

View File

@ -11,15 +11,14 @@ from modules.logger import logWrite
from modules.utils import configGet from modules.utils import configGet
class PosterClient(Client):
def __init__(self, name: str, **kwargs): # type: ignore class PosterClient(Client):
def __init__(self, name: str, **kwargs): # type: ignore
super().__init__(name, **kwargs) super().__init__(name, **kwargs)
self.owner = configGet("owner") self.owner = configGet("owner")
self.admins = configGet("admins")+[configGet("owner")] self.admins = configGet("admins") + [configGet("owner")]
async def submit_photo(self, id: str) -> Union[Message, None]: async def submit_photo(self, id: str) -> Union[Message, None]:
db_entry = col_submitted.find_one({"_id": ObjectId(id)}) db_entry = col_submitted.find_one({"_id": ObjectId(id)})
submission = None submission = None
@ -27,35 +26,66 @@ class PosterClient(Client):
raise SubmissionUnavailableError() raise SubmissionUnavailableError()
else: else:
if db_entry["temp"]["uuid"] is not None: 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"])): if not path.exists(
path.join(
configGet("data", "locations"),
"submissions",
db_entry["temp"]["uuid"],
db_entry["temp"]["file"],
)
):
raise SubmissionUnavailableError() raise SubmissionUnavailableError()
else: else:
filepath = path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"], db_entry["temp"]["file"]) filepath = path.join(
configGet("data", "locations"),
"submissions",
db_entry["temp"]["uuid"],
db_entry["temp"]["file"],
)
try: try:
submission = await self.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) submission = await self.get_messages(
db_entry["user"], db_entry["telegram"]["msg_id"]
)
except: except:
pass pass
else: else:
try: try:
submission = await self.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) submission = await self.get_messages(
filepath = await self.download_media(submission, file_name=configGet("tmp", "locations")+sep) db_entry["user"], db_entry["telegram"]["msg_id"]
)
filepath = await self.download_media(
submission, file_name=configGet("tmp", "locations") + sep
)
except: except:
raise SubmissionUnavailableError() raise SubmissionUnavailableError()
response = await upload_pic(str(filepath), allow_duplicates=configGet("allow_duplicates", "submission")) response = await upload_pic(
str(filepath), allow_duplicates=configGet("allow_duplicates", "submission")
)
if len(response[1]) > 0: if len(response[1]) > 0:
raise SubmissionDuplicatesError(str(filepath), response[1]) raise SubmissionDuplicatesError(str(filepath), response[1])
col_submitted.find_one_and_update({"_id": ObjectId(id)}, {"$set": {"done": True}}) col_submitted.find_one_and_update(
{"_id": ObjectId(id)}, {"$set": {"done": True}}
)
try: try:
if db_entry["temp"]["uuid"] is not None: if db_entry["temp"]["uuid"] is not None:
rmtree(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]), ignore_errors=True) rmtree(
path.join(
configGet("data", "locations"),
"submissions",
db_entry["temp"]["uuid"],
),
ignore_errors=True,
)
else: else:
remove(str(filepath)) remove(str(filepath))
except (FileNotFoundError, NotADirectoryError): except (FileNotFoundError, NotADirectoryError):
logWrite(f"Could not delete '{filepath}' on submission accepted", debug=True) logWrite(
f"Could not delete '{filepath}' on submission accepted", debug=True
)
return submission return submission
@ -63,4 +93,4 @@ class PosterClient(Client):
pass pass
async def unban_user(self, id: int) -> None: async def unban_user(self, id: int) -> None:
pass pass

View File

@ -3,8 +3,8 @@ from datetime import datetime
from modules.database import col_banned, col_users from modules.database import col_banned, col_users
from modules.utils import configGet from modules.utils import configGet
class PosterUser():
class PosterUser:
def __init__(self, id: int): def __init__(self, id: int):
self.id = id self.id = id
@ -13,16 +13,16 @@ class PosterUser():
### Returns: ### Returns:
`bool`: Must be `True` if banned and `False` if not `bool`: Must be `True` if banned and `False` if not
""" """
return False if col_banned.find_one({"user": self.id}) is None else True return False if col_banned.find_one({"user": self.id}) is None else True
def block(self) -> None: def block(self) -> None:
"""Ban user from using command and submitting content.""" """Ban user from using command and submitting content."""
if col_banned.find_one({"user": self.id}) is None: if col_banned.find_one({"user": self.id}) is None:
col_banned.insert_one({"user": self.id, "date": datetime.now()}) col_banned.insert_one({"user": self.id, "date": datetime.now()})
def unblock(self) -> None: def unblock(self) -> None:
"""Allow user to use command and submit posts again.""" """Allow user to use command and submit posts again."""
col_banned.find_one_and_delete({"user": self.id}) col_banned.find_one_and_delete({"user": self.id})
def is_limited(self) -> bool: def is_limited(self) -> bool:
@ -30,16 +30,26 @@ 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 app.admins:
return False return False
else: else:
db_record = col_users.find_one({"user": self.id}) db_record = col_users.find_one({"user": self.id})
if db_record is None: if db_record is None:
return False return False
return True if (datetime.now() - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False return (
True
if (datetime.now() - db_record["cooldown"]).total_seconds()
< configGet("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."""
if col_users.find_one_and_update({"user": self.id}, {"$set": {"cooldown": datetime.now()}}) is None: if (
col_users.insert_one({"user": self.id, "cooldown": datetime.now()}) col_users.find_one_and_update(
{"user": self.id}, {"$set": {"cooldown": datetime.now()}}
)
is None
):
col_users.insert_one({"user": self.id, "cooldown": datetime.now()})

View File

@ -21,50 +21,81 @@ from modules.logger import logWrite
from modules.utils import configGet from modules.utils import configGet
http_session = ClientSession(json_serialize=dumps, ) http_session = ClientSession(
json_serialize=dumps,
)
async def authorize() -> str: async def authorize() -> str:
makedirs(configGet("cache", "locations"), exist_ok=True) makedirs(configGet("cache", "locations"), exist_ok=True)
if path.exists(configGet("cache", "locations")+sep+"api_access") is True: if path.exists(configGet("cache", "locations") + sep + "api_access") is True:
async with aiofiles.open(configGet("cache", "locations")+sep+"api_access", "rb") as file: async with aiofiles.open(
configGet("cache", "locations") + sep + "api_access", "rb"
) as file:
token = b64decode(await file.read()).decode("utf-8") token = b64decode(await file.read()).decode("utf-8")
if (await http_session.get(configGet("address", "posting", "api")+"/users/me/", headers={"Authorization": f"Bearer {token}"})).status == 200: if (
await http_session.get(
configGet("address", "posting", "api") + "/users/me/",
headers={"Authorization": f"Bearer {token}"},
)
).status == 200:
return token return token
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": configGet("username", "posting", "api"),
"password": configGet("password", "posting", "api") "password": configGet("password", "posting", "api"),
} }
response = await http_session.post(configGet("address", "posting", "api")+"/token", data=payload) response = await http_session.post(
configGet("address", "posting", "api") + "/token", data=payload
)
if not response.ok: if not response.ok:
logWrite(f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}') logWrite(
f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}'
)
raise ValueError raise ValueError
async with aiofiles.open(configGet("cache", "locations")+sep+"api_access", "wb") as file: async with aiofiles.open(
await file.write(b64encode((await response.json())["access_token"].encode("utf-8"))) configGet("cache", "locations") + sep + "api_access", "wb"
) as file:
await file.write(
b64encode((await response.json())["access_token"].encode("utf-8"))
)
return (await response.json())["access_token"] return (await response.json())["access_token"]
async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]:
"""Returns random image id and filename from the queue. """Returns random image id and filename from the queue.
### Returns: ### Returns:
* `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed. * `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed.
""" """
token = await authorize() if token is None else token token = await authorize() if token is None else token
logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue') logWrite(
resp = await http_session.get(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}"}) f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue'
)
resp = await http_session.get(
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}"},
)
print(await resp.json(), flush=True) print(await resp.json(), flush=True)
if resp.status != 200: if resp.status != 200:
logWrite(f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}') logWrite(
logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status}', debug=True) f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}'
)
logWrite(
f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status}',
debug=True,
)
raise ValueError raise ValueError
if len((await resp.json())["results"]) == 0: if len((await resp.json())["results"]) == 0:
raise KeyError raise KeyError
pic = choice((await resp.json())["results"]) pic = choice((await resp.json())["results"])
return pic["id"], pic["filename"] return pic["id"], pic["filename"]
async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None) -> Tuple[bool, list]:
async def upload_pic(
filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None
) -> Tuple[bool, list]:
token = await authorize() if token is None else token token = await authorize() if token is None else token
try: try:
pic_name = path.basename(filepath) pic_name = path.basename(filepath)
@ -72,32 +103,53 @@ async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union
async with aiofiles.open(filepath, "rb") as f: async with aiofiles.open(filepath, "rb") as f:
file_bytes = await f.read() file_bytes = await f.read()
formdata = FormData() formdata = FormData()
formdata.add_field('file', file_bytes, filename=pic_name, content_type='image/jpeg') formdata.add_field(
"file", file_bytes, filename=pic_name, content_type="image/jpeg"
)
response = await http_session.post( response = await http_session.post(
f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos',
params={"caption": "queue", "compress": "false", "ignore_duplicates": str(allow_duplicates).lower()}, params={
"caption": "queue",
"compress": "false",
"ignore_duplicates": str(allow_duplicates).lower(),
},
headers={"Authorization": f"Bearer {token}"}, headers={"Authorization": f"Bearer {token}"},
data=formdata data=formdata,
) )
if response.status != 200 and response.status != 409: if response.status != 200 and response.status != 409:
logWrite(f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'") logWrite(
raise SubmissionUploadError(str(filepath), response.status, response.content) f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'"
)
raise SubmissionUploadError(
str(filepath), response.status, response.content
)
duplicates = [] duplicates = []
if "duplicates" in (await response.json()): if "duplicates" in (await response.json()):
for index, duplicate in enumerate((await response.json())["duplicates"]): for index, duplicate in enumerate((await response.json())["duplicates"]):
if (await response.json())["access_token"] is None: if (await response.json())["access_token"] is None:
duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}') duplicates.append(
f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}'
)
else: else:
duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}') duplicates.append(
f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}'
)
return True, duplicates return True, duplicates
except Exception as exp: except Exception as exp:
print_exc() print_exc()
return False, [] return False, []
async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str, None] = None) -> Union[dict, 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 token = await authorize() if token is None else token
try: 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}"}) 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()) # logWrite(response.json())
if response.status != 200: if response.status != 200:
return None return None
@ -105,16 +157,23 @@ async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str
return None return None
return (await response.json())["results"] return (await response.json())["results"]
except Exception as exp: except Exception as exp:
logWrite(f"Could not find image with name '{name}' and caption '{caption}' due to: {exp}") logWrite(
f"Could not find image with name '{name}' and caption '{caption}' due to: {exp}"
)
return None return None
async def move_pic(id: str, token: Union[str, None] = None) -> bool: async def move_pic(id: str, token: Union[str, None] = None) -> bool:
token = await authorize() if token is None else token token = await authorize() if token is None else token
try: try:
await http_session.patch(f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent', headers={"Authorization": f"Bearer {token}"}) await http_session.patch(
f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent',
headers={"Authorization": f"Bearer {token}"},
)
return True return True
except: except:
return False return False
if __name__ == "__main__": if __name__ == "__main__":
print(asyncio.run(authorize())) print(asyncio.run(authorize()))

View File

@ -1,4 +1,9 @@
from modules.utils import configGet from modules.utils import configGet
from classes.poster_client import PosterClient from classes.poster_client import PosterClient
app = PosterClient("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot")) app = PosterClient(
"duptsiaposter",
bot_token=configGet("bot_token", "bot"),
api_id=configGet("api_id", "bot"),
api_hash=configGet("api_hash", "bot"),
)

View File

@ -3,21 +3,35 @@ from classes.poster_client import PosterClient
from pyrogram.types import BotCommand, BotCommandScopeChat from pyrogram.types import BotCommand, BotCommandScopeChat
from modules.utils import configGet, locale from modules.utils import configGet, locale
async def register_commands(app: PosterClient) -> None:
async def register_commands(app: PosterClient) -> None:
if configGet("submit", "mode"): if configGet("submit", "mode"):
# Registering user commands # Registering user commands
for entry in listdir(configGet("locale", "locations")): for entry in listdir(configGet("locale", "locations")):
if entry.endswith(".json"): if entry.endswith(".json"):
commands_list = [] commands_list = []
for command in configGet("commands"): for command in configGet("commands"):
commands_list.append(BotCommand(command, locale(command, "commands", locale=entry.replace(".json", "")))) commands_list.append(
await app.set_bot_commands(commands_list, language_code=entry.replace(".json", "")) 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 # Registering user commands for fallback locale
commands_list = [] commands_list = []
for command in configGet("commands"): for command in configGet("commands"):
commands_list.append(BotCommand(command, locale(command, "commands", locale=configGet("locale_fallback")))) commands_list.append(
BotCommand(
command,
locale(command, "commands", locale=configGet("locale_fallback")),
)
)
await app.set_bot_commands(commands_list) await app.set_bot_commands(commands_list)
# Registering admin commands # Registering admin commands
@ -25,10 +39,19 @@ async def register_commands(app: PosterClient) -> None:
if configGet("submit", "mode"): if configGet("submit", "mode"):
for command in configGet("commands"): for command in configGet("commands"):
commands_admin_list.append(
commands_admin_list.append(BotCommand(command, locale(command, "commands", locale=configGet("locale")))) BotCommand(
command, locale(command, "commands", locale=configGet("locale"))
)
)
for command in configGet("commands_admin"): for command in configGet("commands_admin"):
commands_admin_list.append(BotCommand(command, locale(command, "commands_admin", locale=configGet("locale")))) commands_admin_list.append(
BotCommand(
command, locale(command, "commands_admin", locale=configGet("locale"))
)
)
for admin in app.admins: for admin in app.admins:
await app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)) await app.set_bot_commands(
commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)
)

View File

@ -8,18 +8,16 @@ with open("config.json", "r", encoding="utf-8") as f:
f.close() f.close()
if db_config["user"] is not None and db_config["password"] is not None: if db_config["user"] is not None and db_config["password"] is not None:
con_string = 'mongodb://{0}:{1}@{2}:{3}/{4}'.format( con_string = "mongodb://{0}:{1}@{2}:{3}/{4}".format(
db_config["user"], db_config["user"],
db_config["password"], db_config["password"],
db_config["host"], db_config["host"],
db_config["port"], db_config["port"],
db_config["name"] db_config["name"],
) )
else: else:
con_string = 'mongodb://{0}:{1}/{2}'.format( con_string = "mongodb://{0}:{1}/{2}".format(
db_config["host"], db_config["host"], db_config["port"], db_config["name"]
db_config["port"],
db_config["name"]
) )
db_client = MongoClient(con_string) db_client = MongoClient(con_string)
@ -34,4 +32,4 @@ for collection in ["sent", "users", "banned", "submitted"]:
col_sent = db.get_collection("sent") col_sent = db.get_collection("sent")
col_users = db.get_collection("users") col_users = db.get_collection("users")
col_banned = db.get_collection("banned") col_banned = db.get_collection("banned")
col_submitted = db.get_collection("submitted") col_submitted = db.get_collection("submitted")

View File

@ -9,15 +9,15 @@ from shutil import copyfileobj
from datetime import datetime from datetime import datetime
with open(getcwd()+path.sep+"config.json", "r", encoding='utf8') as file: with open(getcwd() + path.sep + "config.json", "r", encoding="utf8") as file:
json_contents = loads(file.read()) json_contents = loads(file.read())
log_size = json_contents["logging"]["size"] log_size = json_contents["logging"]["size"]
log_folder = json_contents["logging"]["location"] log_folder = json_contents["logging"]["location"]
file.close() file.close()
# Check latest log size # Check latest log size
def checkSize(debug=False) -> None: def checkSize(debug=False) -> None:
global log_folder global log_folder
if debug: if debug:
@ -29,18 +29,26 @@ def checkSize(debug=False) -> None:
makedirs(log_folder, exist_ok=True) makedirs(log_folder, exist_ok=True)
log = stat(path.join(log_folder, log_file)) log = stat(path.join(log_folder, log_file))
if (log.st_size / 1024) > log_size: if (log.st_size / 1024) > log_size:
with open(path.join(log_folder, log_file), 'rb') as f_in: 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: 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) copyfileobj(f_in, f_out)
print(f'Copied {path.join(log_folder, datetime.now().strftime("%d.%m.%Y_%H:%M:%S"))}.log.gz') print(
open(path.join(log_folder, log_file), 'w').close() 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: except FileNotFoundError:
print(f'Log file {path.join(log_folder, log_file)} does not exist') print(f"Log file {path.join(log_folder, log_file)} does not exist")
pass pass
# Append string to log # Append string to log
def logAppend(message, debug=False) -> None: def logAppend(message, debug=False) -> None:
global log_folder global log_folder
message_formatted = f'[{datetime.now().strftime("%d.%m.%Y")}] [{datetime.now().strftime("%H:%M:%S")}] {message}' message_formatted = f'[{datetime.now().strftime("%d.%m.%Y")}] [{datetime.now().strftime("%H:%M:%S")}] {message}'
@ -51,12 +59,13 @@ def logAppend(message, debug=False) -> None:
else: else:
log_file = "latest.log" log_file = "latest.log"
log = open(path.join(log_folder, log_file), 'a') log = open(path.join(log_folder, log_file), "a")
log.write(f'{message_formatted}\n') log.write(f"{message_formatted}\n")
log.close() log.close()
# Print to stdout and then to log # Print to stdout and then to log
def logWrite(message, debug=False) -> None: def logWrite(message, debug=False) -> None:
# save to log file and rotation is to be done # save to log file and rotation is to be done
logAppend(f'{message}', debug=debug) logAppend(f"{message}", debug=debug)
print(f"{message}", flush=True) print(f"{message}", flush=True)

View File

@ -10,10 +10,22 @@ scheduler = AsyncIOScheduler()
if configGet("post", "mode"): if configGet("post", "mode"):
if configGet("use_interval", "posting"): if configGet("use_interval", "posting"):
scheduler.add_job(send_content, "interval", seconds=timeparse(configGet("interval", "posting")), args=[app]) scheduler.add_job(
send_content,
"interval",
seconds=timeparse(configGet("interval", "posting")),
args=[app],
)
else: else:
for entry in configGet("time", "posting"): for entry in configGet("time", "posting"):
dt_obj = datetime.strptime(entry, "%H:%M") dt_obj = datetime.strptime(entry, "%H:%M")
scheduler.add_job(send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app]) scheduler.add_job(
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]) scheduler.add_job(
register_commands,
"date",
run_date=datetime.now() + timedelta(seconds=10),
args=[app],
)

View File

@ -15,13 +15,14 @@ from modules.utils import configGet, locale
async def send_content(app: PosterClient) -> None: async def send_content(app: PosterClient) -> None:
try: try:
try: try:
token = await authorize() token = await authorize()
except ValueError: except ValueError:
await app.send_message(app.owner, locale("api_creds_invalid", "message", locale=configGet("locale"))) await app.send_message(
app.owner,
locale("api_creds_invalid", "message", locale=configGet("locale")),
)
return return
try: try:
@ -29,19 +30,37 @@ async def send_content(app: PosterClient) -> None:
except KeyError: except KeyError:
logWrite(locale("post_empty", "console", locale=configGet("locale"))) logWrite(locale("post_empty", "console", locale=configGet("locale")))
if configGet("error", "reports"): if configGet("error", "reports"):
await app.send_message(app.owner, locale("api_queue_empty", "message", locale=configGet("locale"))) await app.send_message(
app.owner,
locale("api_queue_empty", "message", locale=configGet("locale")),
)
return return
except ValueError: except ValueError:
if configGet("error", "reports"): if configGet("error", "reports"):
await app.send_message(app.owner, locale("api_queue_error", "message", locale=configGet("locale"))) await app.send_message(
app.owner,
locale("api_queue_error", "message", locale=configGet("locale")),
)
return return
response = await http_session.get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}) response = await http_session.get(
f'{configGet("address", "posting", "api")}/photos/{pic[0]}',
headers={"Authorization": f"Bearer {token}"},
)
if response.status != 200: if response.status != 200:
logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(response.status, str(response.json()))) logWrite(
locale(
"post_invalid_pic", "console", locale=configGet("locale")
).format(response.status, str(response.json()))
)
if configGet("error", "reports"): if configGet("error", "reports"):
await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.status, response.json())) await app.send_message(
app.owner,
locale(
"post_invalid_pic", "message", locale=configGet("locale")
).format(response.status, response.json()),
)
tmp_dir = str(uuid4()) tmp_dir = str(uuid4())
@ -49,23 +68,40 @@ async def send_content(app: PosterClient) -> None:
tmp_path = path.join(tmp_dir, pic[1]) tmp_path = path.join(tmp_dir, pic[1])
async with aiofiles.open(path.join(configGet("tmp", "locations"), tmp_path), 'wb') as out_file: async with aiofiles.open(
path.join(configGet("tmp", "locations"), tmp_path), "wb"
) as out_file:
await out_file.write(await response.read()) await out_file.write(await response.read())
logWrite(f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', debug=True) logWrite(
f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big',
debug=True,
)
if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880: if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880:
image = Image.open(path.join(configGet("tmp", "locations"), tmp_path)) image = Image.open(path.join(configGet("tmp", "locations"), 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(path.join(configGet("tmp", "locations"), tmp_path), "JPEG", optimize=True, quality=50) image.save(
path.join(configGet("tmp", "locations"), tmp_path),
"JPEG",
optimize=True,
quality=50,
)
elif tmp_path.lower().endswith(".png"): elif tmp_path.lower().endswith(".png"):
image.save(path.join(configGet("tmp", "locations"), tmp_path), "PNG", optimize=True, compress_level=8) image.save(
path.join(configGet("tmp", "locations"), tmp_path),
"PNG",
optimize=True,
compress_level=8,
)
image.close() image.close()
if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880: if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880:
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) rmtree(
path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True
)
raise BytesWarning raise BytesWarning
del response del response
@ -77,10 +113,17 @@ async def send_content(app: PosterClient) -> None:
else: else:
caption = "" caption = ""
if submitted is not None and configGet("enabled", "posting", "submitted_caption") and ( if (
(submitted["user"] not in app.admins) or (configGet("ignore_admins", "posting", "submitted_caption") is False) submitted is not None
and configGet("enabled", "posting", "submitted_caption")
and (
(submitted["user"] not in app.admins)
or (configGet("ignore_admins", "posting", "submitted_caption") is False)
)
): ):
caption = f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n" caption = (
f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n"
)
else: else:
caption = f"{caption}\n\n" caption = f"{caption}\n\n"
@ -93,11 +136,21 @@ async def send_content(app: PosterClient) -> None:
caption = caption caption = caption
try: try:
sent = await app.send_photo(configGet("channel", "posting"), path.join(configGet("tmp", "locations"), tmp_path), caption=caption, disable_notification=configGet("silent", "posting")) sent = await app.send_photo(
configGet("channel", "posting"),
path.join(configGet("tmp", "locations"), tmp_path),
caption=caption,
disable_notification=configGet("silent", "posting"),
)
except Exception as exp: except Exception as exp:
logWrite(f"Could not send image {pic[1]} ({pic[0]}) due to {exp}") logWrite(f"Could not send image {pic[1]} ({pic[0]}) due to {exp}")
if configGet("error", "reports"): if configGet("error", "reports"):
await app.send_message(app.owner, locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) await app.send_message(
app.owner,
locale(
"post_exception", "message", locale=configGet("locale")
).format(exp, format_exc()),
)
# rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) # rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
return return
@ -107,7 +160,9 @@ async def send_content(app: PosterClient) -> None:
"image": pic[0], "image": pic[0],
"filename": pic[1], "filename": pic[1],
"channel": configGet("channel", "posting"), "channel": configGet("channel", "posting"),
"caption": None if (submitted is None or submitted["caption"] is None) else submitted["caption"].strip() "caption": None
if (submitted is None or submitted["caption"] is None)
else submitted["caption"].strip(),
} }
) )
@ -115,13 +170,31 @@ async def send_content(app: PosterClient) -> None:
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
logWrite(locale("post_sent", "console", locale=configGet("locale")).format(pic[0], str(configGet("channel", "posting")), caption.replace("\n", "%n"), str(configGet("silent", "posting")))) logWrite(
locale("post_sent", "console", locale=configGet("locale")).format(
pic[0],
str(configGet("channel", "posting")),
caption.replace("\n", "%n"),
str(configGet("silent", "posting")),
)
)
except Exception as exp: except Exception as exp:
logWrite(locale("post_exception", "console", locale=configGet("locale")).format(str(exp), format_exc())) logWrite(
locale("post_exception", "console", locale=configGet("locale")).format(
str(exp), format_exc()
)
)
if configGet("error", "reports"): if configGet("error", "reports"):
await app.send_message(app.owner, locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) await app.send_message(
app.owner,
locale("post_exception", "message", locale=configGet("locale")).format(
exp, format_exc()
),
)
try: try:
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) rmtree(
path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True
)
except: except:
pass pass

View File

@ -13,24 +13,30 @@ from typing import Any
from modules.logger import logWrite from modules.logger import logWrite
def jsonLoad(filename: str) -> Any: def jsonLoad(filename: str) -> Any:
"""Loads arg1 as json and returns its contents""" """Loads arg1 as json and returns its contents"""
with open(filename, "r", encoding='utf8') as file: with open(filename, "r", encoding="utf8") as file:
try: try:
output = loads(file.read()) output = loads(file.read())
except JSONDecodeError: except JSONDecodeError:
logWrite(f"Could not load json file {filename}: file seems to be incorrect!\n{print_exc()}") logWrite(
f"Could not load json file {filename}: file seems to be incorrect!\n{print_exc()}"
)
raise raise
except FileNotFoundError: except FileNotFoundError:
logWrite(f"Could not load json file {filename}: file does not seem to exist!\n{print_exc()}") logWrite(
f"Could not load json file {filename}: file does not seem to exist!\n{print_exc()}"
)
raise raise
file.close() file.close()
return output return output
def jsonSave(contents: Any, filename: str) -> None: def jsonSave(contents: Any, filename: str) -> None:
"""Dumps dict/list arg1 to file arg2""" """Dumps dict/list arg1 to file arg2"""
try: try:
with open(filename, "w", encoding='utf8') as file: with open(filename, "w", encoding="utf8") as file:
file.write(dumps(contents, ensure_ascii=False, indent=4)) file.write(dumps(contents, ensure_ascii=False, indent=4))
file.close() file.close()
except Exception as exp: except Exception as exp:
@ -44,7 +50,7 @@ def configSet(key: str, value, *args: str):
* key (str): The last key of the keys path. * key (str): The last key of the keys path.
* value (str/int/float/list/dict/None): Some needed value. * value (str/int/float/list/dict/None): Some needed value.
* *args (str): Path to key like: dict[args][key]. * *args (str): Path to key like: dict[args][key].
""" """
this_dict = jsonLoad("config.json") this_dict = jsonLoad("config.json")
string = "this_dict" string = "this_dict"
for arg in args: for arg in args:
@ -57,6 +63,7 @@ def configSet(key: str, value, *args: str):
jsonSave(this_dict, "config.json") jsonSave(this_dict, "config.json")
return return
def configGet(key: str, *args: str): def configGet(key: str, *args: str):
"""Get value of the config key """Get value of the config key
Args: Args:
@ -64,13 +71,14 @@ def configGet(key: str, *args: str):
* *args (str): Path to key like: dict[args][key]. * *args (str): Path to key like: dict[args][key].
Returns: Returns:
* any: Value of provided key * any: Value of provided key
""" """
this_dict = jsonLoad("config.json") this_dict = jsonLoad("config.json")
this_key = this_dict this_key = this_dict
for dict_key in args: for dict_key in args:
this_key = this_key[dict_key] this_key = this_key[dict_key]
return this_key[key] return this_key[key]
def locale(key: str, *args: str, locale=configGet("locale")): def locale(key: str, *args: str, locale=configGet("locale")):
"""Get value of locale string """Get value of locale string
Args: Args:
@ -79,36 +87,42 @@ def locale(key: str, *args: str, locale=configGet("locale")):
* locale (str): Locale to looked up in. Defaults to config's locale value. * locale (str): Locale to looked up in. Defaults to config's locale value.
Returns: Returns:
* any: Value of provided locale key * any: Value of provided locale key
""" """
if (locale == None): if locale == None:
locale = configGet("locale") locale = configGet("locale")
try: try:
this_dict = jsonLoad(f'{configGet("locale", "locations")}{sep}{locale}.json') this_dict = jsonLoad(f'{configGet("locale", "locations")}{sep}{locale}.json')
except FileNotFoundError: except FileNotFoundError:
try: try:
this_dict = jsonLoad(f'{configGet("locale", "locations")}{sep}{configGet("locale")}.json') this_dict = jsonLoad(
f'{configGet("locale", "locations")}{sep}{configGet("locale")}.json'
)
except FileNotFoundError: except FileNotFoundError:
try: try:
this_dict = jsonLoad(f'{configGet("locale_fallback", "locations")}{sep}{configGet("locale")}.json') this_dict = jsonLoad(
f'{configGet("locale_fallback", "locations")}{sep}{configGet("locale")}.json'
)
except: except:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"' return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"'
this_key = this_dict this_key = this_dict
for dict_key in args: for dict_key in args:
this_key = this_key[dict_key] this_key = this_key[dict_key]
try: try:
return this_key[key] return this_key[key]
except KeyError: except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"' return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"'
try: try:
from psutil import Process from psutil import Process
except ModuleNotFoundError: except ModuleNotFoundError:
print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) print(locale("deps_missing", "console", locale=configGet("locale")), flush=True)
exit() exit()
def killProc(pid: int) -> None: def killProc(pid: int) -> None:
"""Kill process by its PID. Meant to be used to kill the main process of bot itself. """Kill process by its PID. Meant to be used to kill the main process of bot itself.
@ -117,6 +131,7 @@ def killProc(pid: int) -> None:
""" """
if osname == "posix": if osname == "posix":
from signal import SIGKILL from signal import SIGKILL
kill(pid, SIGKILL) kill(pid, SIGKILL)
else: else:
Process(pid).kill() Process(pid).kill()

View File

@ -4,8 +4,9 @@ from pyrogram.types import CallbackQuery
from classes.poster_client import PosterClient from classes.poster_client import PosterClient
from modules.utils import locale from modules.utils import locale
# Callback empty ===============================================================================================================
@app.on_callback_query(filters.regex("nothing")) @app.on_callback_query(filters.regex("nothing"))
async def callback_query_nothing(app: PosterClient, clb: CallbackQuery): async def callback_query_nothing(app: PosterClient, clb: CallbackQuery):
await clb.answer(text=locale("nothing", "callback", locale=clb.from_user.language_code)) await clb.answer(
# ============================================================================================================================== text=locale("nothing", "callback", locale=clb.from_user.language_code)
)

View File

@ -1,4 +1,3 @@
from os import path from os import path
from shutil import rmtree from shutil import rmtree
from pyrogram import filters from pyrogram import filters
@ -15,7 +14,6 @@ from bson import ObjectId
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*")) @app.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
async def callback_query_yes(app: PosterClient, clb: CallbackQuery): async def callback_query_yes(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
@ -24,22 +22,60 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
try: try:
submission = await app.submit_photo(fullclb[2]) submission = await app.submit_photo(fullclb[2])
except SubmissionUnavailableError: except SubmissionUnavailableError:
await clb.answer(text=locale("sub_msg_unavail", "callback", locale=user_locale), show_alert=True) await clb.answer(
text=locale("sub_msg_unavail", "callback", locale=user_locale),
show_alert=True,
)
return return
except SubmissionDuplicatesError as exp: except SubmissionDuplicatesError as exp:
await clb.answer(text=locale("sub_duplicates_found", "callback", locale=user_locale), show_alert=True) await clb.answer(
await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n".join(exp.duplicates)), quote=True) text=locale("sub_duplicates_found", "callback", locale=user_locale),
show_alert=True,
)
await clb.message.reply_text(
locale("sub_media_duplicates_list", "message", locale=user_locale).format(
"\n".join(exp.duplicates)
),
quote=True,
)
return return
if submission is not None: if submission is not None:
await submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True) await submission.reply_text(
locale("sub_yes", "message", locale=submission.from_user.language_code),
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"], locale("sub_yes", "message"))
await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) await clb.answer(
text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]),
show_alert=True,
)
edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button", locale=user_locale)), callback_data="nothing")], clb.message.reply_markup.inline_keyboard[1]] if len(clb.message.reply_markup.inline_keyboard) > 1 else [[InlineKeyboardButton(text=str(locale("accepted", "button", locale=user_locale)), callback_data="nothing")]] edited_markup = (
await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) [
[
InlineKeyboardButton(
text=str(locale("accepted", "button", locale=user_locale)),
callback_data="nothing",
)
],
clb.message.reply_markup.inline_keyboard[1],
]
if len(clb.message.reply_markup.inline_keyboard) > 1
else [
[
InlineKeyboardButton(
text=str(locale("accepted", "button", locale=user_locale)),
callback_data="nothing",
)
]
]
)
await clb.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
# try: # try:
# if configGet("api_based", "mode") is True: # if configGet("api_based", "mode") is True:
@ -68,48 +104,120 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
@app.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: PosterClient, 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 db_entry["temp"]["uuid"] is not None:
if path.exists(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"])): if path.exists(
rmtree(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]), ignore_errors=True) path.join(
configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]
)
):
rmtree(
path.join(
configGet("data", "locations"),
"submissions",
db_entry["temp"]["uuid"],
),
ignore_errors=True,
)
try: try:
submission = await app.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) submission = await app.get_messages(
db_entry["user"], db_entry["telegram"]["msg_id"]
)
except: except:
await clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) await clb.answer(
text=locale("sub_msg_unavail", "message", locale=user_locale),
show_alert=True,
)
return return
await submission.reply_text(locale("sub_no", "message", locale=submission.from_user.language_code), quote=True)
await clb.answer(text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), show_alert=True)
edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button", locale=user_locale)), callback_data="nothing")], clb.message.reply_markup.inline_keyboard[1]] if len(clb.message.reply_markup.inline_keyboard) > 1 else [[InlineKeyboardButton(text=str(locale("declined", "button", locale=user_locale)), callback_data="nothing")]] await submission.reply_text(
await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) locale("sub_no", "message", locale=submission.from_user.language_code),
quote=True,
)
await clb.answer(
text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]),
show_alert=True,
)
edited_markup = (
[
[
InlineKeyboardButton(
text=str(locale("declined", "button", locale=user_locale)),
callback_data="nothing",
)
],
clb.message.reply_markup.inline_keyboard[1],
]
if len(clb.message.reply_markup.inline_keyboard) > 1
else [
[
InlineKeyboardButton(
text=str(locale("declined", "button", locale=user_locale)),
callback_data="nothing",
)
]
]
)
await clb.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
@app.on_callback_query(filters.regex("sub_block_[\s\S]*")) @app.on_callback_query(filters.regex("sub_block_[\s\S]*"))
async def callback_query_block(app: PosterClient, clb: CallbackQuery): async def callback_query_block(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
await app.send_message(int(fullclb[2]), locale("sub_blocked", "message", locale=configGet("locale"))) await app.send_message(
int(fullclb[2]), locale("sub_blocked", "message", locale=configGet("locale"))
)
PosterUser(int(fullclb[2])).block() PosterUser(int(fullclb[2])).block()
await clb.answer(text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) await clb.answer(
text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]),
show_alert=True,
)
edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_unblock", "button", locale=user_locale)), callback_data=f"sub_unblock_{fullclb[2]}")]] edited_markup = [
await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) clb.message.reply_markup.inline_keyboard[0],
[
InlineKeyboardButton(
text=str(locale("sub_unblock", "button", locale=user_locale)),
callback_data=f"sub_unblock_{fullclb[2]}",
)
],
]
await clb.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) @app.on_callback_query(filters.regex("sub_unblock_[\s\S]*"))
async def callback_query_unblock(app: PosterClient, clb: CallbackQuery): async def callback_query_unblock(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
await app.send_message(int(fullclb[2]), locale("sub_unblocked", "message", locale=configGet("locale"))) await app.send_message(
int(fullclb[2]), locale("sub_unblocked", "message", locale=configGet("locale"))
)
PosterUser(int(fullclb[2])).unblock() PosterUser(int(fullclb[2])).unblock()
await clb.answer(text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) await clb.answer(
text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]),
show_alert=True,
)
edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_block", "button", locale=user_locale)), callback_data=f"sub_block_{fullclb[2]}")]] edited_markup = [
await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) clb.message.reply_markup.inline_keyboard[0],
[
InlineKeyboardButton(
text=str(locale("sub_block", "button", locale=user_locale)),
callback_data=f"sub_block_{fullclb[2]}",
)
],
]
await clb.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)

View File

@ -9,11 +9,16 @@ from modules.logger import logWrite
from modules.utils import configGet, killProc, locale from modules.utils import configGet, killProc, locale
@app.on_message(~ filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"])) @app.on_message(
~filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"])
)
async def cmd_kill(app: PosterClient, msg: Message): async def cmd_kill(app: PosterClient, msg: Message):
if msg.from_user.id in app.admins: if msg.from_user.id in app.admins:
pid = getpid() pid = getpid()
logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) logWrite(
await msg.reply_text(locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) locale("shutdown", "console", locale=configGet("locale")).format(str(pid))
killProc(pid) )
await msg.reply_text(
locale("shutdown", "message", locale=configGet("locale")).format(str(pid))
)
killProc(pid)

View File

@ -7,12 +7,17 @@ from classes.user import PosterUser
from classes.poster_client import PosterClient from classes.poster_client import PosterClient
@app.on_message(~ filters.scheduled & filters.command(["start"], prefixes="/")) @app.on_message(~filters.scheduled & filters.command(["start"], prefixes="/"))
async def cmd_start(app: PosterClient, msg: Message): async def cmd_start(app: PosterClient, msg: Message):
if PosterUser(msg.from_user.id).is_blocked() is False: if PosterUser(msg.from_user.id).is_blocked() is False:
await msg.reply_text(locale("start", "message", locale=msg.from_user.language_code)) await msg.reply_text(
locale("start", "message", locale=msg.from_user.language_code)
)
@app.on_message(~ filters.scheduled & filters.command(["rules", "help"], prefixes="/"))
@app.on_message(~filters.scheduled & filters.command(["rules", "help"], prefixes="/"))
async def cmd_rules(app: PosterClient, msg: Message): async def cmd_rules(app: PosterClient, msg: Message):
if PosterUser(msg.from_user.id).is_blocked() is False: if PosterUser(msg.from_user.id).is_blocked() is False:
await msg.reply_text(locale("rules", "message", locale=msg.from_user.language_code)) await msg.reply_text(
locale("rules", "message", locale=msg.from_user.language_code)
)

View File

@ -5,29 +5,25 @@ from classes.poster_client import PosterClient
from modules.app import app from modules.app import app
@app.on_message(~ filters.scheduled & filters.command(["import"], prefixes=["", "/"])) @app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"]))
async def cmd_import(app: PosterClient, msg: Message): async def cmd_import(app: PosterClient, msg: Message):
if msg.from_user.id in app.admins: if msg.from_user.id in app.admins:
pass pass
@app.on_message(~ filters.scheduled & filters.command(["export"], prefixes=["", "/"])) @app.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"]))
async def cmd_export(app: PosterClient, msg: Message): async def cmd_export(app: PosterClient, msg: Message):
if msg.from_user.id in app.admins: if msg.from_user.id in app.admins:
pass pass
@app.on_message(~ filters.scheduled & filters.command(["remove"], prefixes=["", "/"])) @app.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"]))
async def cmd_remove(app: PosterClient, msg: Message): async def cmd_remove(app: PosterClient, msg: Message):
if msg.from_user.id in app.admins: if msg.from_user.id in app.admins:
pass pass
@app.on_message(~ filters.scheduled & filters.command(["purge"], prefixes=["", "/"])) @app.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"]))
async def cmd_purge(app: PosterClient, msg: Message): async def cmd_purge(app: PosterClient, msg: Message):
if msg.from_user.id in app.admins: if msg.from_user.id in app.admins:
pass pass

View File

@ -17,14 +17,17 @@ from modules.utils import configGet, locale
from classes.enums.submission_types import SubmissionType from classes.enums.submission_types import SubmissionType
@app.on_message(~filters.scheduled & filters.private & filters.photo | filters.video | filters.animation | filters.document) @app.on_message(
~filters.scheduled & filters.private & filters.photo
| filters.video
| filters.animation
| filters.document
)
async def get_submission(app: PosterClient, msg: Message): async def get_submission(app: PosterClient, msg: Message):
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
await app.send_chat_action(msg.chat.id, ChatAction.TYPING) await app.send_chat_action(msg.chat.id, ChatAction.TYPING)
user_locale = msg.from_user.language_code user_locale = msg.from_user.language_code
@ -32,68 +35,94 @@ async def get_submission(app: PosterClient, msg: Message):
contents = None contents = None
if PosterUser(msg.from_user.id).is_limited(): if PosterUser(msg.from_user.id).is_limited():
await msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission")))) await msg.reply_text(
locale("sub_cooldown", "message", locale=user_locale).format(
str(configGet("timeout", "submission"))
)
)
return return
if msg.document is not None: if msg.document is not None:
if msg.document.mime_type not in configGet("mime_types", "submission"): if msg.document.mime_type not in configGet("mime_types", "submission"):
await msg.reply_text(locale("mime_not_allowed", "message", locale=user_locale), quote=True) await msg.reply_text(
locale("mime_not_allowed", "message", locale=user_locale),
quote=True,
)
return return
if msg.document.file_size > configGet("file_size", "submission"): if msg.document.file_size > configGet("file_size", "submission"):
await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) await msg.reply_text(
locale("document_too_large", "message", locale=user_locale).format(
str(configGet("file_size", "submission") / 1024 / 1024)
),
quote=True,
)
return return
if msg.document.file_size > configGet("tmp_size", "submission"): if msg.document.file_size > configGet("tmp_size", "submission"):
save_tmp = False save_tmp = False
contents = msg.document.file_id, SubmissionType.DOCUMENT #, msg.document.file_name contents = (
msg.document.file_id,
SubmissionType.DOCUMENT,
) # , msg.document.file_name
if msg.video is not None: if msg.video is not None:
if msg.video.file_size > configGet("file_size", "submission"): if msg.video.file_size > configGet("file_size", "submission"):
await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) await msg.reply_text(
locale("document_too_large", "message", locale=user_locale).format(
str(configGet("file_size", "submission") / 1024 / 1024)
),
quote=True,
)
return return
if msg.video.file_size > configGet("tmp_size", "submission"): if msg.video.file_size > configGet("tmp_size", "submission"):
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:
if msg.animation.file_size > configGet("file_size", "submission"): if msg.animation.file_size > configGet("file_size", "submission"):
await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) await msg.reply_text(
locale("document_too_large", "message", locale=user_locale).format(
str(configGet("file_size", "submission") / 1024 / 1024)
),
quote=True,
)
return return
if msg.animation.file_size > configGet("tmp_size", "submission"): if msg.animation.file_size > configGet("tmp_size", "submission"):
save_tmp = False save_tmp = False
contents = msg.animation.file_id, SubmissionType.ANIMATION #, msg.animation.file_name contents = (
msg.animation.file_id,
SubmissionType.ANIMATION,
) # , msg.animation.file_name
if msg.photo is not None: if msg.photo is not None:
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 save_tmp is not None:
if contents is None: if contents is None:
return return
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(path.join(configGet("data", "locations"), "submissions", tmp_id), exist_ok=True) makedirs(
downloaded = await app.download_media(msg, path.join(configGet("data", "locations"), "submissions", tmp_id)+sep) path.join(configGet("data", "locations"), "submissions", tmp_id),
exist_ok=True,
)
downloaded = await app.download_media(
msg,
path.join(configGet("data", "locations"), "submissions", tmp_id) + sep,
)
inserted = col_submitted.insert_one( inserted = col_submitted.insert_one(
{ {
"user": msg.from_user.id, "user": msg.from_user.id,
"date": datetime.now(), "date": datetime.now(),
"done": False, "done": False,
"type": contents[1].value, "type": contents[1].value,
"temp": { "temp": {"uuid": tmp_id, "file": path.basename(str(downloaded))},
"uuid": tmp_id, "telegram": {"msg_id": msg.id, "file_id": contents[0]},
"file": path.basename(str(downloaded)) "caption": str(msg.caption) if msg.caption is not None else None,
},
"telegram": {
"msg_id": msg.id,
"file_id": contents[0]
},
"caption": str(msg.caption) if msg.caption is not None else None
} }
) )
else:
else:
if contents is None: if contents is None:
return return
@ -103,36 +132,44 @@ async def get_submission(app: PosterClient, msg: Message):
"date": datetime.now(), "date": datetime.now(),
"done": False, "done": False,
"type": contents[1].value, "type": contents[1].value,
"temp": { "temp": {"uuid": None, "file": None},
"uuid": None, "telegram": {"msg_id": msg.id, "file_id": contents[0]},
"file": None "caption": str(msg.caption) if msg.caption is not None else None,
},
"telegram": {
"msg_id": msg.id,
"file_id": contents[0]
},
"caption": str(msg.caption) if msg.caption is not None else None
} }
) )
buttons = [ buttons = [
[ [
InlineKeyboardButton(text=locale("sub_yes", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}") InlineKeyboardButton(
text=locale("sub_yes", "button", locale=configGet("locale")),
callback_data=f"sub_yes_{str(inserted.inserted_id)}",
)
] ]
] ]
if msg.caption is not None: if msg.caption is not None:
caption = str(msg.caption) caption = str(msg.caption)
buttons[0].append( buttons[0].append(
InlineKeyboardButton(text=locale("sub_yes_caption", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption") InlineKeyboardButton(
text=locale(
"sub_yes_caption", "button", locale=configGet("locale")
),
callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption",
)
) )
buttons[0].append( buttons[0].append(
InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") 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( buttons[0].append(
InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") 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"))) caption += locale("sub_by", "message", locale=locale(configGet("locale")))
@ -146,45 +183,80 @@ async def get_submission(app: PosterClient, msg: Message):
if msg.from_user.phone_number is not None: if msg.from_user.phone_number is not None:
caption += f" ({msg.from_user.phone_number})" caption += f" ({msg.from_user.phone_number})"
if msg.from_user.id in app.admins and configGet("admins", "submission", "require_confirmation") is False: if (
msg.from_user.id in app.admins
and configGet("admins", "submission", "require_confirmation") is False
):
try: try:
await app.submit_photo(str(inserted.inserted_id)) await app.submit_photo(str(inserted.inserted_id))
await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True) await msg.reply_text(
locale("sub_yes_auto", "message", locale=user_locale),
disable_notification=True,
quote=True,
)
await msg.copy(app.owner, caption=caption, disable_notification=True) await msg.copy(app.owner, caption=caption, disable_notification=True)
return return
except SubmissionDuplicatesError as exp: except SubmissionDuplicatesError as exp:
await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n".join(exp.duplicates)), quote=True) await msg.reply_text(
locale(
"sub_media_duplicates_list", "message", locale=user_locale
).format("\n".join(exp.duplicates)),
quote=True,
)
return return
except Exception as exp: except Exception as exp:
await msg.reply_text(format_exc(), quote=True) await msg.reply_text(format_exc(), quote=True)
return return
elif msg.from_user.id not in app.admins and configGet("users", "submission", "require_confirmation") is False: elif (
msg.from_user.id not in app.admins
and configGet("users", "submission", "require_confirmation") is False
):
try: try:
await app.submit_photo(str(inserted.inserted_id)) await app.submit_photo(str(inserted.inserted_id))
await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True) await msg.reply_text(
locale("sub_yes_auto", "message", locale=user_locale),
disable_notification=True,
quote=True,
)
await msg.copy(app.owner, caption=caption) await msg.copy(app.owner, caption=caption)
return return
except SubmissionDuplicatesError as exp: except SubmissionDuplicatesError as exp:
await msg.reply_text(locale("sub_dup", "message", locale=user_locale), quote=True) await msg.reply_text(
locale("sub_dup", "message", locale=user_locale), quote=True
)
return return
except Exception as exp: except Exception as exp:
await app.send_message(app.owner, locale("sub_error_admin", "message").format(msg.from_user.id, format_exc())) await app.send_message(
app.owner,
locale("sub_error_admin", "message").format(
msg.from_user.id, format_exc()
),
)
await msg.reply_text("sub_error", quote=True) await msg.reply_text("sub_error", quote=True)
return return
if msg.from_user.id not in app.admins: if msg.from_user.id not in app.admins:
buttons += [ buttons += [
[ [
InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}") InlineKeyboardButton(
text=locale("sub_block", "button", locale=configGet("locale")),
callback_data=f"sub_block_{msg.from_user.id}",
)
] ]
] ]
PosterUser(msg.from_user.id).limit() PosterUser(msg.from_user.id).limit()
if msg.from_user.id != app.owner: if msg.from_user.id != app.owner:
await msg.reply_text(locale("sub_sent", "message", locale=user_locale), disable_notification=True, quote=True) await msg.reply_text(
locale("sub_sent", "message", locale=user_locale),
disable_notification=True,
quote=True,
)
await msg.copy(app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) await msg.copy(
app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)
)
except AttributeError: except AttributeError:
logWrite(f"from_user in function get_submission does not seem to contain id") logWrite(f"from_user in function get_submission does not seem to contain id")

View File

@ -9,8 +9,8 @@ from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale
# Args ===================================================================================================================================== # Args =====================================================================================================================================
parser = ArgumentParser( parser = ArgumentParser(
prog = "Telegram Poster", prog="Telegram Poster",
description = "Bot for posting some of your stuff and also receiving submissions." description="Bot for posting some of your stuff and also receiving submissions.",
) )
parser.add_argument("-m", "--move-sent", action="store_true") parser.add_argument("-m", "--move-sent", action="store_true")
@ -24,11 +24,22 @@ args = parser.parse_args()
if args.move_sent: if args.move_sent:
for entry in jsonLoad(configGet("index", "locations"))["sent"]: for entry in jsonLoad(configGet("index", "locations"))["sent"]:
try: try:
move(configGet("queue", "locations")+sep+entry, configGet("sent", "locations")+sep+entry) move(
configGet("queue", "locations") + sep + entry,
configGet("sent", "locations") + sep + entry,
)
except FileNotFoundError: except FileNotFoundError:
logWrite(locale("move_sent_doesnt_exist", "console", locale=configGet("locale")).format(entry)) logWrite(
locale(
"move_sent_doesnt_exist", "console", locale=configGet("locale")
).format(entry)
)
except Exception as exp: except Exception as exp:
logWrite(locale("move_sent_doesnt_exception", "console", locale=configGet("locale")).format(entry, exp)) logWrite(
locale(
"move_sent_doesnt_exception", "console", locale=configGet("locale")
).format(entry, exp)
)
logWrite(locale("move_sent_completed", "console", locale=configGet("locale"))) logWrite(locale("move_sent_completed", "console", locale=configGet("locale")))
if args.cleanup: if args.cleanup:
@ -37,15 +48,19 @@ if args.cleanup:
for entry in index["sent"]: for entry in index["sent"]:
try: try:
try: try:
remove(configGet("queue", "locations")+sep+entry) remove(configGet("queue", "locations") + sep + entry)
except FileNotFoundError: except FileNotFoundError:
pass pass
try: try:
remove(configGet("sent", "locations")+sep+entry) remove(configGet("sent", "locations") + sep + entry)
except FileNotFoundError: except FileNotFoundError:
pass pass
except Exception as exp: except Exception as exp:
logWrite(locale("cleanup_exception", "console", locale=configGet("locale")).format(entry, exp)) logWrite(
locale(
"cleanup_exception", "console", locale=configGet("locale")
).format(entry, exp)
)
jsonSave(index, jsonLoad(configGet("index", "locations"))) jsonSave(index, jsonLoad(configGet("index", "locations")))
logWrite(locale("cleanup_completed", "console", locale=configGet("locale"))) logWrite(locale("cleanup_completed", "console", locale=configGet("locale")))
else: else:
@ -56,14 +71,18 @@ if args.cleanup_index:
index = jsonLoad(configGet("index", "locations")) index = jsonLoad(configGet("index", "locations"))
index["sent"] = [] index["sent"] = []
jsonSave(index, jsonLoad(configGet("index", "locations"))) jsonSave(index, jsonLoad(configGet("index", "locations")))
logWrite(locale("cleanup_index_completed", "console", locale=configGet("locale"))) logWrite(
locale("cleanup_index_completed", "console", locale=configGet("locale"))
)
else: else:
logWrite(locale("cleanup_index_unathorized", "console", locale=configGet("locale"))) logWrite(
locale("cleanup_index_unathorized", "console", locale=configGet("locale"))
)
if args.norun: if args.norun:
logWrite(locale("passed_norun", "console", locale=configGet("locale_log"))) logWrite(locale("passed_norun", "console", locale=configGet("locale_log")))
exit() exit()
#=========================================================================================================================================== # ===========================================================================================================================================
# Import =================================================================================================================================== # Import ===================================================================================================================================
@ -73,7 +92,7 @@ try:
except ModuleNotFoundError: except ModuleNotFoundError:
print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) print(locale("deps_missing", "console", locale=configGet("locale")), flush=True)
exit() exit()
#=========================================================================================================================================== # ===========================================================================================================================================
pid = getpid() pid = getpid()
@ -125,7 +144,7 @@ if configGet("submit", "mode"):
if configGet("api_based", "mode"): if configGet("api_based", "mode"):
from modules.api_client import authorize from modules.api_client import authorize
#=========================================================================================================================================== # ===========================================================================================================================================
# Work in progress # Work in progress
# Handle new forwards # Handle new forwards
@ -168,25 +187,30 @@ if configGet("api_based", "mode"):
# asyncio.run(main()) # asyncio.run(main())
if __name__ == "__main__": if __name__ == "__main__":
logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid))) logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid)))
app.start() app.start()
if configGet("startup", "reports"): if configGet("startup", "reports"):
app.send_message(app.owner, locale("startup", "message", locale=configGet("locale")).format(str(pid))) app.send_message(
app.owner,
locale("startup", "message", locale=configGet("locale")).format(str(pid)),
)
if configGet("post", "mode"): if configGet("post", "mode"):
scheduler.start() scheduler.start()
#if configGet("api_based", "mode"): # if configGet("api_based", "mode"):
# token = await authorize() # token = await authorize()
# if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: # 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}"}) # post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"})
idle() idle()
app.send_message(app.owner, locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) app.send_message(
app.owner,
locale("shutdown", "message", locale=configGet("locale")).format(str(pid)),
)
logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid)))
killProc(pid) killProc(pid)