/warn and /warnings commands
This commit is contained in:
parent
359f08c021
commit
76d7d7f4c6
@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commands": {
|
|
||||||
"start": "Почати користуватись ботом",
|
|
||||||
"rules": "Правила пропонування фото"
|
|
||||||
},
|
|
||||||
"commands_admin": {
|
|
||||||
"forwards": "Переглянути репости",
|
|
||||||
"reboot": "Перезапустити бота"
|
|
||||||
},
|
|
||||||
"message": {
|
"message": {
|
||||||
"start": "Привіт і ласкаво просимо!\n\nЦей бот створено для прийому заявок на вступ до нашої спільноти. Для продовження нас цікавить відповідь на одне питання:\n\nЧи хочеш ти доєднатися до українського ком'юніті фанатів Хололайв?",
|
"start": "Привіт і ласкаво просимо!\n\nЦей бот створено для прийому заявок на вступ до нашої спільноти. Для продовження нас цікавить відповідь на одне питання:\n\nЧи хочеш ти доєднатися до українського ком'юніті фанатів Хололайв?",
|
||||||
"goodbye": "Добре, дякуємо за чесність! Вибачте, але за таких умов ми не будемо тебе додавати до спільноти. Якщо передумаєш та захочеш приєднатись - просто натисни на кнопку.",
|
"goodbye": "Добре, дякуємо за чесність! Вибачте, але за таких умов ми не будемо тебе додавати до спільноти. Якщо передумаєш та захочеш приєднатись - просто натисни на кнопку.",
|
||||||
@ -55,6 +47,12 @@
|
|||||||
"reapply_in_progress": "❌ **Дія неможлива**\nТи прямо зараз вже заповнюєш анкету. Якщо в ній є помилка - після заповнення просто натисни **{0}** та почни знову.",
|
"reapply_in_progress": "❌ **Дія неможлива**\nТи прямо зараз вже заповнюєш анкету. Якщо в ній є помилка - після заповнення просто натисни **{0}** та почни знову.",
|
||||||
"birthday": "У користувача **{0}** (@{1}) сьогодні день народження! Виповнилось {2} років",
|
"birthday": "У користувача **{0}** (@{1}) сьогодні день народження! Виповнилось {2} років",
|
||||||
"application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`",
|
"application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`",
|
||||||
|
"warned": "Попереджено користувача **{0}** (`{1}`) про порушення правил",
|
||||||
|
"warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження",
|
||||||
|
"warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень",
|
||||||
|
"no_warnings": "Користувач **{0}** (`{1}`) не має попереджень",
|
||||||
|
"no_user_warnings": "Не знайдено користувачів за запитом **{0}**",
|
||||||
|
"syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`",
|
||||||
"question_titles": {
|
"question_titles": {
|
||||||
"question1": "Ім'я/звертання:",
|
"question1": "Ім'я/звертання:",
|
||||||
"question2": "День народження:",
|
"question2": "День народження:",
|
||||||
|
110
main.py
110
main.py
@ -20,7 +20,7 @@ from pyrogram.errors.exceptions import bad_request_400
|
|||||||
pid = getpid()
|
pid = getpid()
|
||||||
|
|
||||||
|
|
||||||
for entry in [f"{configGet('data', 'locations')}{sep}applications.json"]:
|
for entry in [f"{configGet('data', 'locations')}{sep}applications.json", f"{configGet('data', 'locations')}{sep}warnings.json"]:
|
||||||
mode = 'r' if path.exists(entry) else 'w'
|
mode = 'r' if path.exists(entry) else 'w'
|
||||||
with open(entry, mode) as f:
|
with open(entry, mode) as f:
|
||||||
try:
|
try:
|
||||||
@ -50,10 +50,10 @@ async def cmd_start(app, msg):
|
|||||||
|
|
||||||
|
|
||||||
# Shutdown command =============================================================================================================
|
# Shutdown command =============================================================================================================
|
||||||
@app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["", "/"]))
|
@app.on_message(~ filters.scheduled & filters.private & filters.command(["kill", "die", "reboot"], prefixes=["/"]))
|
||||||
async def cmd_kill(app, msg):
|
async def cmd_kill(app, msg):
|
||||||
|
|
||||||
if await isAnAdmin(msg.from_user.id):
|
if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id):
|
||||||
logWrite(f"Shutting down bot with pid {pid}")
|
logWrite(f"Shutting down bot with pid {pid}")
|
||||||
await msg.reply_text(f"Вимкнення бота з підом `{pid}`")
|
await msg.reply_text(f"Вимкнення бота з підом `{pid}`")
|
||||||
killProc(pid)
|
killProc(pid)
|
||||||
@ -61,28 +61,82 @@ async def cmd_kill(app, msg):
|
|||||||
|
|
||||||
|
|
||||||
# Rules command =============================================================================================================
|
# Rules command =============================================================================================================
|
||||||
@app.on_message(~ filters.scheduled & filters.private & filters.command(["rules"], prefixes=["", "/"]))
|
@app.on_message(~ filters.scheduled & filters.private & filters.command(["rules"], prefixes=["/"]))
|
||||||
async def cmd_rules(app, msg):
|
async def cmd_rules(app, msg):
|
||||||
for rule_msg in locale("rules"):
|
for rule_msg in locale("rules"):
|
||||||
await msg.reply_text(rule_msg)
|
await msg.reply_text(rule_msg)
|
||||||
# ==============================================================================================================================
|
# ==============================================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
# Warn command =============================================================================================================
|
||||||
|
@app.on_message(~ filters.scheduled & filters.command(["warn"], prefixes=["/"]))
|
||||||
|
async def cmd_warn(app, msg):
|
||||||
|
|
||||||
|
if msg.chat.id == configGet("destination_group"):
|
||||||
|
if msg.reply_to_message_id != None:
|
||||||
|
if isAnAdmin(msg.from_user.id):
|
||||||
|
warnings = jsonLoad(f"{configGet('data', 'locations')}{sep}warnings.json")
|
||||||
|
if str(msg.reply_to_message.from_user.id) not in warnings:
|
||||||
|
warnings[str(msg.reply_to_message.from_user.id)] = 1
|
||||||
|
else:
|
||||||
|
warnings[str(msg.reply_to_message.from_user.id)] += 1
|
||||||
|
jsonSave(warnings, f"{configGet('data', 'locations')}{sep}warnings.json")
|
||||||
|
await msg.reply_text(locale("warned", "message").format(msg.reply_to_message.from_user.first_name, msg.reply_to_message.from_user.id)) # type: ignore
|
||||||
|
# ==============================================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
# Warnings command =============================================================================================================
|
||||||
|
@app.on_message(~ filters.scheduled & filters.command(["warnings"], prefixes=["/"]))
|
||||||
|
async def cmd_warnings(app, msg):
|
||||||
|
|
||||||
|
if msg.chat.id == configGet("admin_group") or await isAnAdmin(msg.from_user.id):
|
||||||
|
|
||||||
|
warnings = jsonLoad(f"{configGet('data', 'locations')}{sep}warnings.json")
|
||||||
|
|
||||||
|
if len(msg.command) <= 1:
|
||||||
|
await msg.reply_text(locale("syntax_warnings", "message"))
|
||||||
|
|
||||||
|
if path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json"):
|
||||||
|
target_id = str(int(msg.command[1]))
|
||||||
|
target_name = "N/A"
|
||||||
|
else:
|
||||||
|
list_of_users = []
|
||||||
|
async for m in app.get_chat_members(configGet("destination_group"), filter=ChatMembersFilter.SEARCH, query=msg.command[1]):
|
||||||
|
list_of_users.append(m)
|
||||||
|
|
||||||
|
if len(list_of_users) != 0:
|
||||||
|
target = list_of_users[0].user
|
||||||
|
target_name = target.first_name
|
||||||
|
target_id = str(target.id)
|
||||||
|
else:
|
||||||
|
await msg.reply_text(locale("no_user_warnings", "message").format(msg.command[1])) # type: ignore
|
||||||
|
return
|
||||||
|
|
||||||
|
if target_id not in warnings:
|
||||||
|
await msg.reply_text(locale("no_warnings", "message").format(target_name, target_id)) # type: ignore
|
||||||
|
else:
|
||||||
|
if warnings[target_id] <= 5:
|
||||||
|
await msg.reply_text(locale("warnings_1", "message").format(target_name, target_id, warnings[target_id])) # type: ignore
|
||||||
|
else:
|
||||||
|
await msg.reply_text(locale("warnings_2", "message").format(target_name, target_id, warnings[target_id])) # type: ignore
|
||||||
|
# ==============================================================================================================================
|
||||||
|
|
||||||
|
|
||||||
# Applications command =========================================================================================================
|
# Applications command =========================================================================================================
|
||||||
@app.on_message(~ filters.scheduled & filters.private & filters.command(["applications"], prefixes=["", "/"]))
|
@app.on_message(~ filters.scheduled & filters.command(["applications"], prefixes=["/"]))
|
||||||
async def cmd_applications(app, msg):
|
async def cmd_applications(app, msg):
|
||||||
|
|
||||||
if await isAnAdmin(msg.from_user.id):
|
if (await isAnAdmin(msg.from_user.id)) or (msg.chat.id == configGet("admin_group")):
|
||||||
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
|
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
|
||||||
await msg.reply_document(document=f"{configGet('data', 'locations')}{sep}applications.json")
|
await msg.reply_document(document=f"{configGet('data', 'locations')}{sep}applications.json")
|
||||||
# ==============================================================================================================================
|
# ==============================================================================================================================
|
||||||
|
|
||||||
|
|
||||||
# Applications command =========================================================================================================
|
# Applications command =========================================================================================================
|
||||||
@app.on_message(~ filters.scheduled & filters.private & filters.command(["application"], prefixes=["", "/"]))
|
@app.on_message(~ filters.scheduled & filters.command(["application"], prefixes=["/"]))
|
||||||
async def cmd_application(app, msg):
|
async def cmd_application(app, msg):
|
||||||
|
|
||||||
if await isAnAdmin(msg.from_user.id):
|
if (await isAnAdmin(msg.from_user.id)) or (msg.chat.id == configGet("admin_group")):
|
||||||
try:
|
try:
|
||||||
if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json")["approved"]):
|
if (path.exists(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json") and jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{msg.command[1]}.json")["approved"]):
|
||||||
user_id = int(msg.command[1])
|
user_id = int(msg.command[1])
|
||||||
@ -92,17 +146,17 @@ async def cmd_application(app, msg):
|
|||||||
list_of_users.append(m)
|
list_of_users.append(m)
|
||||||
user_id = list_of_users[0].user.id
|
user_id = list_of_users[0].user.id
|
||||||
try:
|
try:
|
||||||
user_data = jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{user_id.user.id}.json")
|
user_data = jsonLoad(f"{configGet('data', 'locations')}{sep}users{sep}{user_id}.json")
|
||||||
application_content = []
|
application_content = []
|
||||||
i = 1
|
i = 1
|
||||||
for question in configGet("application", file=str(msg.from_user.id)):
|
for question in configGet("application", file=str(msg.from_user.id)):
|
||||||
if i == 2:
|
if i == 2:
|
||||||
age = relativedelta(datetime.now(), datetime.strptime(configGet('application', file=str(user_id.user.id))['2'], '%d.%m.%Y'))
|
age = relativedelta(datetime.now(), datetime.strptime(configGet('application', file=str(user_id))['2'], '%d.%m.%Y'))
|
||||||
application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(user_id.user.id))['2']} ({age.years} р.)")
|
application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(user_id))['2']} ({age.years} р.)")
|
||||||
else:
|
else:
|
||||||
application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(user_id.user.id))[question]}")
|
application_content.append(f"{locale('question'+str(i), 'message', 'question_titles')} {configGet('application', file=str(user_id))[question]}")
|
||||||
i += 1
|
i += 1
|
||||||
application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json")[str(user_id.user.id)]
|
application = jsonLoad(f"{configGet('data', 'locations')}{sep}applications.json")[str(user_id)]
|
||||||
if user_data["sent"]:
|
if user_data["sent"]:
|
||||||
if user_data["approved"]:
|
if user_data["approved"]:
|
||||||
application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) # type: ignore
|
application_status = locale("application_status_accepted", "message").format((await app.get_users(application["approved_by"])).first_name, datetime.fromtimestamp(application["approval_date"]).strftime("%d.%m.%Y, %H:%M")) # type: ignore
|
||||||
@ -117,10 +171,10 @@ async def cmd_application(app, msg):
|
|||||||
application_status = locale("application_status_refused", "message").format((await app.get_users(application["refused_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) # type: ignore
|
application_status = locale("application_status_refused", "message").format((await app.get_users(application["refused_by"])).first_name, datetime.fromtimestamp(application["refusal_date"]).strftime("%d.%m.%Y, %H:%M")) # type: ignore
|
||||||
else:
|
else:
|
||||||
application_status = locale("application_status_not_send", "message")
|
application_status = locale("application_status_not_send", "message")
|
||||||
logWrite(f"User {msg.from_user.id} requested application of {user_id.user.id}")
|
logWrite(f"User {msg.from_user.id} requested application of {user_id}")
|
||||||
await msg.reply_text(locale("contact", "message").format(str(user_id.user.id), "\n".join(application_content), application_status)) # type: ignore
|
await msg.reply_text(locale("contact", "message").format(str(user_id), "\n".join(application_content), application_status)) # type: ignore
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logWrite(f"User {msg.from_user.id} requested application of {user_id.user.id} but user does not exists")
|
logWrite(f"User {msg.from_user.id} requested application of {user_id} but user does not exists")
|
||||||
await msg.reply_text(locale("contact_invalid", "message"))
|
await msg.reply_text(locale("contact_invalid", "message"))
|
||||||
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@ -129,7 +183,7 @@ async def cmd_application(app, msg):
|
|||||||
|
|
||||||
|
|
||||||
# Reapply command ==============================================================================================================
|
# Reapply command ==============================================================================================================
|
||||||
@app.on_message(~ filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["", "/"]))
|
@app.on_message(~ filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"]))
|
||||||
async def cmd_reapply(app, msg):
|
async def cmd_reapply(app, msg):
|
||||||
|
|
||||||
if configGet("approved", file=str(msg.from_user.id)) or configGet("refused", file=str(msg.from_user.id)):
|
if configGet("approved", file=str(msg.from_user.id)) or configGet("refused", file=str(msg.from_user.id)):
|
||||||
@ -138,12 +192,12 @@ async def cmd_reapply(app, msg):
|
|||||||
configSet("confirmed", False, file=str(msg.from_user.id))
|
configSet("confirmed", False, file=str(msg.from_user.id))
|
||||||
await welcome_pass(app, msg, once_again=True)
|
await welcome_pass(app, msg, once_again=True)
|
||||||
else:
|
else:
|
||||||
await msg.reply_text(locale("reapply_in_progress", "message").format(locale("confirm", "keyboard")[1][0]))
|
await msg.reply_text(locale("reapply_in_progress", "message").format(locale("confirm", "keyboard")[1][0])) # type: ignore
|
||||||
else:
|
else:
|
||||||
if configGet("sent", file=str(msg.from_user.id)):
|
if configGet("sent", file=str(msg.from_user.id)):
|
||||||
await msg.reply_text(locale("reapply_forbidden", "message"))
|
await msg.reply_text(locale("reapply_forbidden", "message"))
|
||||||
else:
|
else:
|
||||||
await msg.reply_text(locale("reapply_in_progress", "message").format(locale("confirm", "keyboard")[1][0]))
|
await msg.reply_text(locale("reapply_in_progress", "message").format(locale("confirm", "keyboard")[1][0])) # type: ignore
|
||||||
# ==============================================================================================================================
|
# ==============================================================================================================================
|
||||||
|
|
||||||
|
|
||||||
@ -380,7 +434,7 @@ async def callback_query_refuse(app, clb):
|
|||||||
|
|
||||||
# Callbacks application ========================================================================================================
|
# Callbacks application ========================================================================================================
|
||||||
@app.on_callback_query(filters.regex("reapply_yes_[\s\S]*")) # type: ignore
|
@app.on_callback_query(filters.regex("reapply_yes_[\s\S]*")) # type: ignore
|
||||||
async def callback_query_accept(app, clb):
|
async def callback_reapply_query_accept(app, clb):
|
||||||
|
|
||||||
fullclb = clb.data.split("_")
|
fullclb = clb.data.split("_")
|
||||||
|
|
||||||
@ -404,7 +458,7 @@ async def callback_query_accept(app, clb):
|
|||||||
await clb.answer(text=locale("sub_accepted", "callback").format(fullclb[2]), show_alert=True) # type: ignore
|
await clb.answer(text=locale("sub_accepted", "callback").format(fullclb[2]), show_alert=True) # type: ignore
|
||||||
|
|
||||||
@app.on_callback_query(filters.regex("reapply_no_[\s\S]*")) # type: ignore
|
@app.on_callback_query(filters.regex("reapply_no_[\s\S]*")) # type: ignore
|
||||||
async def callback_query_refuse(app, clb):
|
async def callback_query_reapply_refuse(app, clb):
|
||||||
|
|
||||||
fullclb = clb.data.split("_")
|
fullclb = clb.data.split("_")
|
||||||
|
|
||||||
@ -666,9 +720,21 @@ if __name__ == "__main__":
|
|||||||
app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)) # type: ignore
|
app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)) # type: ignore
|
||||||
except bad_request_400.PeerIdInvalid:
|
except bad_request_400.PeerIdInvalid:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=configGet("owner"))) # type: ignore
|
app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=configGet("owner"))) # type: ignore
|
||||||
|
|
||||||
|
# Registering admin group commands
|
||||||
|
commands_group_admin_list = []
|
||||||
|
for command in configGet("commands_group_admin"):
|
||||||
|
commands_group_admin_list.append(BotCommand(command, configGet("commands_group_admin")[command]))
|
||||||
|
app.set_bot_commands(commands_group_admin_list, scope=BotCommandScopeChat(chat_id=configGet("admin_group"))) # type: ignore
|
||||||
|
|
||||||
|
# Registering destination group commands
|
||||||
|
commands_group_destination_list = []
|
||||||
|
for command in configGet("commands_group_destination"):
|
||||||
|
commands_group_destination_list.append(BotCommand(command, configGet("commands_group_destination")[command]))
|
||||||
|
app.set_bot_commands(commands_group_destination_list, scope=BotCommandScopeChat(chat_id=configGet("destination_group"))) # type: ignore
|
||||||
|
|
||||||
idle()
|
idle()
|
||||||
|
|
||||||
app.send_message(configGet("owner"), f"Shutting with pid `{pid}`") # type: ignore
|
app.send_message(configGet("owner"), f"Shutting with pid `{pid}`") # type: ignore
|
||||||
|
@ -20,7 +20,6 @@ def jsonLoad(filename):
|
|||||||
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()
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def jsonSave(contents, filename):
|
def jsonSave(contents, filename):
|
||||||
@ -28,7 +27,6 @@ def jsonSave(contents, filename):
|
|||||||
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()
|
|
||||||
except Exception as exp:
|
except Exception as exp:
|
||||||
logWrite(f"Could not save json file {filename}: {exp}\n{print_exc()}")
|
logWrite(f"Could not save json file {filename}: {exp}\n{print_exc()}")
|
||||||
return
|
return
|
||||||
@ -44,7 +42,8 @@ def configSet(key: str, value, *args: str, file: str = "config"):
|
|||||||
"""
|
"""
|
||||||
if file == "config":
|
if file == "config":
|
||||||
filepath = ""
|
filepath = ""
|
||||||
if this_dict["debug"]:
|
this_dict = jsonLoad(f"{filepath}{file}.json")
|
||||||
|
if this_dict["debug"] is True:
|
||||||
try:
|
try:
|
||||||
this_dict = jsonLoad("config_debug.json")
|
this_dict = jsonLoad("config_debug.json")
|
||||||
file = "config_debug"
|
file = "config_debug"
|
||||||
@ -52,7 +51,7 @@ def configSet(key: str, value, *args: str, file: str = "config"):
|
|||||||
print("Debug mode is set but config_debug.json is not there! Falling back to config.json", flush=True)
|
print("Debug mode is set but config_debug.json is not there! Falling back to config.json", flush=True)
|
||||||
else:
|
else:
|
||||||
filepath = f"data{sep}users{sep}"
|
filepath = f"data{sep}users{sep}"
|
||||||
this_dict = jsonLoad(f"{filepath}{file}.json")
|
this_dict = jsonLoad(f"{filepath}{file}.json")
|
||||||
string = "this_dict"
|
string = "this_dict"
|
||||||
for arg in args:
|
for arg in args:
|
||||||
string += f'["{arg}"]'
|
string += f'["{arg}"]'
|
||||||
@ -79,7 +78,7 @@ def configGet(key: str, *args: str, file: str = "config"):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("Config file not found! Copy config_example.json to config.json, configure it and rerun the bot!", flush=True)
|
print("Config file not found! Copy config_example.json to config.json, configure it and rerun the bot!", flush=True)
|
||||||
exit()
|
exit()
|
||||||
if this_dict["debug"]:
|
if this_dict["debug"] is True:
|
||||||
try:
|
try:
|
||||||
this_dict = jsonLoad("config_debug.json")
|
this_dict = jsonLoad("config_debug.json")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
Reference in New Issue
Block a user