import codecs import json import logging import os import re import sys import time import uuid from collections import Counter from io import BytesIO from urllib.parse import urlparse import requests import simplejson from datetime import datetime from PIL import Image from requests.exceptions import InvalidURL, HTTPError, RequestException, ConnectionError, Timeout, ConnectTimeout from telegram import Bot, Update, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, \ InputTextMessageContent, InlineQueryResultCachedDocument from telegram.error import TelegramError, TimedOut, BadRequest, Unauthorized from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler, InlineQueryHandler, \ ChosenInlineResultHandler, CallbackContext from telegram.ext.dispatcher import run_async directory = os.path.dirname(__file__) # set up logging log_formatter = logging.Formatter("\n%(asctime)s [%(name)s] [%(levelname)s] %(message)s") logger = logging.getLogger() logger.setLevel(logging.INFO) file_handler = logging.FileHandler(os.path.join(directory, "ez-sticker-bot.log")) file_handler.setFormatter(log_formatter) logger.addHandler(file_handler) console_handler = logging.StreamHandler() console_handler.setFormatter(log_formatter) logger.addHandler(console_handler) bot: Bot = None config = {} users = {} lang = {} recent_uses = {} def main(): load_files() updater = Updater(config['token'], use_context=True, workers=10) dispatcher = updater.dispatcher global bot bot = updater.bot # register a handler to ignore all non-private updates dispatcher.add_handler(MessageHandler(~ Filters.private, do_fucking_nothing)) # register commands dispatcher.add_handler(CommandHandler('broadcast', broadcast_command)) dispatcher.add_handler(CommandHandler('donate', donate_command)) dispatcher.add_handler(CommandHandler('help', help_command)) dispatcher.add_handler(CommandHandler('icon', icon_command)) dispatcher.add_handler(CommandHandler('info', info_command)) dispatcher.add_handler(CommandHandler('lang', change_lang_command)) dispatcher.add_handler(CommandHandler('langstats', lang_stats_command)) dispatcher.add_handler(CommandHandler('log', log_command)) dispatcher.add_handler(CommandHandler(['optin', 'optout'], opt_command)) dispatcher.add_handler(CommandHandler('restart', restart_command)) dispatcher.add_handler(CommandHandler('start', start_command)) dispatcher.add_handler(CommandHandler('stats', stats_command)) # register invalid command handler dispatcher.add_handler(MessageHandler(Filters.command, invalid_command)) # register media listener dispatcher.add_handler(MessageHandler((Filters.photo | Filters.document), image_received)) dispatcher.add_handler(MessageHandler(Filters.sticker, sticker_received)) dispatcher.add_handler(MessageHandler(Filters.text, url_received)) dispatcher.add_handler(MessageHandler(Filters.all, invalid_content)) # register button handlers dispatcher.add_handler(CallbackQueryHandler(change_lang_callback, pattern="lang")) dispatcher.add_handler(CallbackQueryHandler(icon_cancel_callback, pattern="icon_cancel")) # register inline handlers dispatcher.add_handler(InlineQueryHandler(share_query_received, pattern=re.compile("^share$", re.IGNORECASE))) dispatcher.add_handler(InlineQueryHandler(file_id_query_received, pattern=re.compile(""))) dispatcher.add_handler(InlineQueryHandler(share_query_received)) dispatcher.add_handler(ChosenInlineResultHandler(inline_result_chosen)) # register variable dump loop updater.job_queue.run_repeating(save_files, config['save_interval'], config['save_interval']) # register error handler dispatcher.add_error_handler(handle_error) updater.start_polling(clean=True, timeout=99999) print("Bot finished starting") updater.idle() # ____ # / ___| ___ _ __ ___ # | | / _ \ | '__| / _ \ # | |___ | (_) | | | | __/ # \____| \___/ |_| \___| @run_async def image_received(update: Update, context: CallbackContext): message = update.message user_id = message.from_user.id # check spam filter cooldown_info = user_on_cooldown(user_id) if cooldown_info[0]: minutes = int(config['spam_interval'] / 60) message_text = get_message(user_id, 'spam_limit_reached').format(config['spam_max'], minutes, cooldown_info[1], cooldown_info[2]) message.reply_markdown(message_text) return # get file id if message.document: # check that document is image document = message.document if document.mime_type.lower() in ('image/png', 'image/jpeg', 'image/webp'): photo_id = document.file_id else: # feedback to show bot is processing bot.send_chat_action(user_id, 'typing') message.reply_markdown(get_message(user_id, 'doc_not_img')) return else: photo_id = message.photo[-1].file_id # feedback to show bot is processing bot.send_chat_action(user_id, 'upload_document') try: download_path = download_file(photo_id) image = Image.open(download_path) create_sticker_file(message, image, context) # delete local file os.remove(download_path) except TimedOut: message.reply_text(get_message(user_id, "send_timeout")) except FileNotFoundError: # if file does not exist ignore pass @run_async def sticker_received(update: Update, context: CallbackContext): message = update.message user_id = message.from_user.id # check spam filter cooldown_info = user_on_cooldown(user_id) if cooldown_info[0]: minutes = int(config['spam_interval'] / 60) message_text = get_message(user_id, 'spam_limit_reached').format(config['spam_max'], minutes, cooldown_info[1], cooldown_info[2]) message.reply_markdown(message_text) return # check if sticker is animated if message.sticker.is_animated: animated_sticker_received(update, context) return sticker_id = message.sticker.file_id # feedback to show bot is processing bot.send_chat_action(user_id, 'upload_document') try: download_path = download_file(sticker_id) image = Image.open(download_path) create_sticker_file(message, image, context) # delete local file os.remove(download_path) except Unauthorized: pass except TelegramError: message.reply_text(get_message(user_id, "send_timeout")) except FileNotFoundError: # if file does not exist ignore pass def animated_sticker_received(update: Update, context: CallbackContext): message = update.message user_id = message.from_user.id # feedback to show bot is processing bot.send_chat_action(user_id, 'upload_document') sticker_id = message.sticker.file_id # download sticker and send as document try: download_path = download_file(sticker_id) document = open(download_path, 'rb') sticker_message = message.reply_document(document=document) sent_message = sticker_message.reply_markdown(get_message(user_id, "forward_animated_sticker"), quote=True) # add a keyboard with a forward button to the document file_id = sticker_message.sticker.file_id markup = InlineKeyboardMarkup( [[InlineKeyboardButton(get_message(user_id, "forward"), switch_inline_query=file_id)]]) sent_message.edit_reply_markup(reply_markup=markup) # delete local file os.remove(download_path) except TelegramError: message.reply_text(get_message(user_id, "send_timeout")) except FileNotFoundError: # if file does not exist ignore pass # record use in spam filter record_use(user_id, context) # increase total uses count by one global config config['uses'] += 1 global users users[str(user_id)]['uses'] += 1 donate_suggest(user_id) @run_async def url_received(update: Update, context: CallbackContext): message = update.message user_id = message.from_user.id text = message.text.split(' ') # check spam filter cooldown_info = user_on_cooldown(user_id) if cooldown_info[0]: message.reply_markdown(get_message(user_id, 'spam_limit_reached').format(cooldown_info[1], cooldown_info[2])) return if len(text) > 1: message.reply_text(get_message(message.chat_id, "too_many_urls")) return text = text[0] url = urlparse(text, 'https').geturl() # remove extra backslash after https if it exists if url.lower().startswith("https:///"): url = url.replace("https:///", "https://", 1) # get request try: request = requests.get(url, timeout=3) request.raise_for_status() except InvalidURL: message.reply_markdown(get_message(message.chat_id, "invalid_url").format(url)) return except HTTPError: message.reply_markdown(get_message(message.chat_id, "url_does_not_exist").format(url)) return except Timeout or ConnectTimeout: message.reply_markdown(get_message(message.chat_id, "url_timeout").format(url)) return except ConnectionError or RequestException or UnicodeError: message.reply_markdown(get_message(message.chat_id, "unable_to_connect").format(url)) return except UnicodeError: message.reply_markdown(get_message(message.chat_id, "unable_to_connect").format(url)) return # read image from url try: image = Image.open(BytesIO(request.content)) except OSError: message.reply_markdown(get_message(message.chat_id, "url_not_img").format(url)) return # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'upload_document') create_sticker_file(message, image, context) def create_sticker_file(message, image, context: CallbackContext): user_id = message.from_user.id user_data = context.user_data # set make_icon if not already set if 'make_icon' not in user_data: user_data['make_icon'] = False # if user is making icon if user_data['make_icon']: image.thumbnail((100, 100), Image.ANTIALIAS) background = Image.new('RGBA', (100, 100), (255, 255, 255, 0)) background.paste(image, (int(((100 - image.size[0]) / 2)), int(((100 - image.size[1]) / 2)))) image = background # else format image to sticker else: width, height = image.size reference_length = max(width, height) ratio = 512 / reference_length new_width = width * ratio new_height = height * ratio # round up if new dimension has .999 or more if new_width % 1 >= .999: new_width = int(round(new_width)) else: new_width = int(new_width) if new_height % 1 >= .999: new_height = int(round(new_height)) else: new_height = int(new_height) image = image.resize((new_width, new_height), Image.ANTIALIAS) # save image object to temporary file temp_path = os.path.join(temp_dir(), (uuid.uuid4().hex[:6].upper() + '.png')) image.save(temp_path, format="PNG", optimize=True) # send formatted image as a document document = open(temp_path, 'rb') try: filename = 'icon.png' if user_data['make_icon'] else 'sticker.png' sent_message = message.reply_document(document=document, filename=filename, caption=get_message(user_id, "forward_to_stickers"), quote=True, timeout=30) # add a keyboard with a forward button to the document file_id = sent_message.document.file_id markup = InlineKeyboardMarkup( [[InlineKeyboardButton(get_message(user_id, "forward"), switch_inline_query=file_id)]]) sent_message.edit_reply_markup(reply_markup=markup) except Unauthorized: pass except TelegramError: message.reply_text(get_message(user_id, "send_timeout")) # delete local files and close image object image.close() os.remove(temp_path) # remove user from make_icon if icon was created if user_data['make_icon']: user_data['make_icon'] = False # record use in spam filter record_use(user_id, context) # increase total uses count by one global config config['uses'] += 1 global users users[str(user_id)]['uses'] += 1 donate_suggest(user_id) def download_file(file_id): try: # download file file = bot.get_file(file_id=file_id, timeout=30) ext = '.' + file.file_path.split('/')[-1].split('.')[1] download_path = os.path.join(temp_dir(), (file_id + ext)) file.download(custom_path=download_path) return download_path except TimedOut: raise TimedOut # _____ _ _ _ _ _ # | ____| __ __ ___ _ __ | |_ | | | | __ _ _ __ __| | | | ___ _ __ ___ # | _| \ \ / / / _ \ | '_ \ | __| | |_| | / _` | | '_ \ / _` | | | / _ \ | '__| / __| # | |___ \ V / | __/ | | | | | |_ | _ | | (_| | | | | | | (_| | | | | __/ | | \__ \ # |_____| \_/ \___| |_| |_| \__| |_| |_| \__,_| |_| |_| \__,_| |_| \___| |_| |___/ @run_async def change_lang_callback(update: Update, context: CallbackContext): query = update.callback_query lang_code = query.data.split(':')[-1] user_id = str(query.from_user.id) global users users[user_id]['lang'] = lang_code # replace instances of $userid with username or name if no username message = get_message(user_id, "lang_set").split(' ') for i in range(len(message)): word = message[i] if word[0] == '$': try: _id = int(''.join(c for c in word if c.isdigit())) user = bot.get_chat(_id) message[i] = '<a href="tg://user?id={}">{}{}</a>'.format(_id, user.first_name, ' ' + user.last_name if user.last_name else '') except ValueError: message[i] = 'UNKNOWN_USER_ID' continue except TelegramError: message[i] = 'INVALID_USER_ID' continue message = ' '.join(message) # set icon_warned to false users[user_id]['icon_warned'] = False query.edit_message_text(text=message, reply_markup=None, parse_mode='HTML') query.answer() @run_async def share_query_received(update: Update, context: CallbackContext): query = update.inline_query user_id = query.from_user.id # get labels in user's language title = get_message(user_id, "share") description = get_message(user_id, "share_desc") thumb_url = config['share_thumb_url'] markup = InlineKeyboardMarkup( [[InlineKeyboardButton(text=get_message(user_id, "make_sticker_button"), url="https://t.me/EzStickerBot")]]) input_message_content = InputTextMessageContent(get_message(user_id, "share_text"), parse_mode='Markdown') # build response and answer query results = [InlineQueryResultArticle(id="share", title=title, description=description, thumb_url=thumb_url, reply_markup=markup, input_message_content=input_message_content)] try: query.answer(results=results, cache_time=5, is_personal=True) # if user waited too long to click result BadRequest is thrown except BadRequest as e: # only ignore BadRequest errors caused by query being too old if e.message == "Query is too old and response timeout expired or query id is invalid": return else: raise e @run_async def file_id_query_received(update: Update, context: CallbackContext): # get query query = update.inline_query user_id = query.from_user.id results = None try: file = bot.get_file(query.query) _id = uuid.uuid4() title = get_message(user_id, "your_sticker") desc = get_message(user_id, "forward_desc") caption = "@EzStickerBot" results = [InlineQueryResultCachedDocument(_id, title, file.file_id, description=desc, caption=caption)] query.answer(results=results, cache_time=5, is_personal=True) # if file_id wasn't found show share option except TelegramError: share_query_received(update, context) @run_async def icon_cancel_callback(update: Update, context: CallbackContext): query = update.callback_query user_id = str(query.from_user.id) # set make_icon in user_data to false context.user_data['make_icon'] = False query.edit_message_text(text=get_message(user_id, "icon_canceled"), reply_markup=None) query.answer() @run_async def inline_result_chosen(update: Update, context: CallbackContext): chosen_result = update.chosen_inline_result result_id = chosen_result.result_id global config # if was a share increase count by one if result_id == 'share': config['times_shared'] += 1 @run_async def invalid_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') message.reply_text(get_message(message.chat_id, "invalid_command")) @run_async def invalid_content(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') message.reply_text(get_message(message.chat_id, "cant_process")) message.reply_markdown(get_message(message.chat_id, "send_sticker_photo")) def do_fucking_nothing(update: Update, context: CallbackContext): pass # ____ _ # / ___| ___ _ __ ___ _ __ ___ __ _ _ __ __| | ___ # | | / _ \ | '_ ` _ \ | '_ ` _ \ / _` | | '_ \ / _` | / __| # | |___ | (_) | | | | | | | | | | | | | | (_| | | | | | | (_| | \__ \ # \____| \___/ |_| |_| |_| |_| |_| |_| \__,_| |_| |_| \__,_| |___/ @run_async def broadcast_command(update: Update, context: CallbackContext): message = update.message chat_id = message.chat_id # feedback to show bot is processing bot.send_chat_action(chat_id, 'typing') # check for permission if chat_id not in config['admins']: message.reply_text(get_message(chat_id, "no_permission")) return target_message = message.reply_to_message # check that command was used in reply to a message if target_message is None: message.reply_markdown(get_message(chat_id, "broadcast_in_reply")) return broadcast_message = target_message.text_html # check that target message is a text message if broadcast_message is None: message.reply_markdown(get_message(chat_id, "broadcast_only_text")) return message.reply_text(get_message(chat_id, "will_broadcast")) context.job_queue.run_once(broadcast_thread, 2, context=broadcast_message) @run_async def change_lang_command(update: Update, context: CallbackContext): message = update.message ordered_langs = [None] * len(lang) for lang_code in lang.keys(): ordered_langs[int(lang[lang_code]['order'])] = lang_code keyboard = [[]] row = 0 for lang_code in ordered_langs: if len(keyboard[row]) == 3: row += 1 keyboard.append([]) keyboard[row].append( InlineKeyboardButton(lang[lang_code]['lang_name'], callback_data="lang:{}".format(lang_code))) markup = InlineKeyboardMarkup(keyboard) message.reply_text(get_message(message.chat_id, "select_lang"), reply_markup=markup) @run_async def donate_command(update: Update, context: CallbackContext): message = update.message message_text = get_message(message.chat_id, "donate") + "\n\n*Paypal:* {}\n*CashApp:* {}\n*BTC:* `{}`\n*ETH:* `{}`"\ .format(config['donate_paypal'], config['donate_cashapp'], config['donate_btc'], config['donate_eth']) message.reply_markdown(message_text, disable_web_page_preview=True) @run_async def help_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') message.reply_text(get_message(message.chat_id, "help")) @run_async def icon_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') # set make_icon to True in user_data context.user_data['make_icon'] = True # create keyboard with cancel button keyboard = [[ InlineKeyboardButton(get_message(message.chat_id, "cancel"), callback_data="icon_cancel") ]] markup = InlineKeyboardMarkup(keyboard) # if user has not been sent icon info message send it if not get_user_config(message.chat_id, 'icon_warned'): message.reply_markdown(get_message(message.chat_id, "icon_command_info")) global users users[str(message.chat_id)]['icon_warned'] = True message.reply_markdown(get_message(message.chat_id, "icon_command"), reply_markup=markup) @run_async def info_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') keyboard = [ [InlineKeyboardButton(get_message(message.chat_id, "contact_dev"), url=config['contact_dev_link']), InlineKeyboardButton(get_message(message.chat_id, "source"), url=config['source_link'])], [InlineKeyboardButton(get_message(message.chat_id, "rate"), url=config['rate_link']), InlineKeyboardButton(get_message(message.chat_id, "share"), switch_inline_query="share")]] markup = InlineKeyboardMarkup(keyboard) message.reply_markdown(get_message(message.chat_id, "info").format(config['uses']), reply_markup=markup) @run_async def lang_stats_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') # get message header lang_stats_message = get_message(message.chat_id, "lang_stats") # count lang usage langs = [user['lang'] for user in users.values()] lang_usage = dict(Counter(langs)) sorted_usage = [(code, lang_usage[code]) for code in sorted(lang_usage, key=lang_usage.get, reverse=True)] # create stats message entries message_lines = {} for code, count in sorted_usage: lang_stats_message += "\n" + u"\u200E" + "{}: {:,}".format(lang[code]['lang_name'], count) # compile stats message in order for index in range(0, len(lang)): try: lang_stats_message += message_lines[str(index)] # Skip langs with 0 users except KeyError: continue # send message message.reply_markdown(lang_stats_message) @run_async def log_command(update: Update, context: CallbackContext): message = update.message # check if user is admin if message.from_user.id in config['admins']: # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'upload_document') # send log file as document log_file_path = os.path.join(directory, 'ez-sticker-bot.log') with open(log_file_path, 'rb') as log_document: try: message.reply_document(log_document) # if log file is empty throws BadRequest exception except BadRequest: message.reply_text(get_message(message.chat_id, "empty_log")) log_document.close() else: # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') message.reply_text(get_message(message.chat_id, "no_permission")) @run_async def opt_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') # get user opt_in status global users user_id = str(message.from_user.id) opt_in = get_user_config(user_id, "opt_in") command = message.text.split(' ')[0][1:].lower() if command == 'optin': if opt_in: message.reply_text(get_message(user_id, "already_opted_in")) else: users[user_id]['opt_in'] = True message.reply_text(get_message(user_id, "opted_in")) else: if not opt_in: message.reply_text(get_message(user_id, "already_opted_out")) else: users[user_id]['opt_in'] = False message.reply_text(get_message(user_id, "opted_out")) def restart_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') if message.from_user.id in config['admins']: message.reply_text(get_message(message.chat_id, "restarting")) save_files() logger.info("Bot restarted by {} ({})".format(message.from_user.first_name, message.from_user.id)) os.execl(sys.executable, sys.executable, *sys.argv) else: message.reply_text(get_message(message.chat_id, "no_permission")) @run_async def start_command(update: Update, context: CallbackContext): message = update.message # feedback to show bot is processing bot.send_chat_action(message.chat_id, 'typing') message.reply_markdown(get_message(message.chat_id, "start")) @run_async def stats_command(update: Update, context: CallbackContext): message = update.message user_id = message.chat_id # feedback to show bot is processing bot.send_chat_action(user_id, 'typing') opted_in = 0 opted_out = 0 for user in users.values(): if user['opt_in']: opted_in += 1 else: opted_out += 1 personal_uses = get_user_config(user_id, "uses") stats_message = get_message(user_id, "stats").format(config['uses'], len(users), personal_uses, config['langs_auto_set'], config['times_shared'], opted_in + opted_out, opted_in, opted_out) message.reply_markdown(stats_message) # ____ _____ _ _ _ # / ___| _ __ __ _ _ __ ___ | ___| (_) | | | |_ ___ _ __ # \___ \ | '_ \ / _` | | '_ ` _ \ | |_ | | | | | __| / _ \ | '__| # ___) | | |_) | | (_| | | | | | | | | _| | | | | | |_ | __/ | | # |____/ | .__/ \__,_| |_| |_| |_| |_| |_| |_| \__| \___| |_| # |_| def record_use(user_id, context: CallbackContext): # ensure user_id is string user_id = str(user_id) # ensure user_id has list in recent_uses global recent_uses if user_id not in recent_uses: recent_uses[user_id] = [] job = context.job_queue.run_once(remove_use, config['spam_interval'], context=(user_id, datetime.now())) recent_uses[user_id].append(job) def remove_use(context: CallbackContext): job = context.job user_id = job.context[0] global recent_uses recent_uses[user_id].remove(job) def user_on_cooldown(user_id): # ensure user_id is string user_id = str(user_id) recent_uses_count = len(recent_uses[user_id]) if user_id in recent_uses else 0 on_cooldown = recent_uses_count >= config['spam_max'] if on_cooldown: oldest_job_time = recent_uses[user_id][0].context[1] seconds_left = int(config['spam_interval'] - (datetime.now() - oldest_job_time).total_seconds()) time_left = divmod(seconds_left, 60) else: time_left = 0, 0 # check to make sure on_cooldown is true while time_left evaluated to 0, 0 if time_left[0] == 0 and time_left[1] == 0: on_cooldown = False return on_cooldown, time_left[0], time_left[1] # _ _ _ _ _ # | | | | | |_ (_) | | ___ # | | | | | __| | | | | / __| # | |_| | | |_ | | | | \__ \ # \___/ \__| |_| |_| |___/ @run_async def broadcast_thread(context: CallbackContext): # check that message was included with the job obj if context.job.context is None: print("Broadcast thread created without message stored in job context") return global config index = 0 for user_id in list(users): # check if user is opted in opt_in = get_user_config(user_id, "opt_in") # catch any errors thrown by users who have stopped bot try: if opt_in and not config['override_opt_out']: bot.send_message(chat_id=int(user_id), text=context.job.context, parse_mode='HTML', disable_web_page_preview=True) # send opt out message if config['send_opt_out_message']: bot.send_message(chat_id=int(user_id), text=get_message(user_id, "opt_out_info")) except Unauthorized: pass except TelegramError as e: logger.warning("Error '{}' when broadcasting message to {}".format(e.message, user_id)) index += 1 if index >= config['broadcast_batch_size']: time.sleep(config['broadcast_batch_interval']) index = 0 def donate_suggest(user_id): user_uses = users[str(user_id)]['uses'] if user_uses % config['donate_suggest_interval'] == 0: bot.send_message(user_id, get_message(user_id, "donate_suggest").format(user_uses), parse_mode='Markdown') def get_message(user_id, message): lang_pref = get_user_config(user_id, "lang") # if message doesn't have translation in user's language default to english if message not in lang[lang_pref]: lang_pref = 'en' return lang[lang_pref][message] def get_user_config(user_id, key): global users user_id = str(user_id) # if user not registered register with default values if user_id not in users: users[user_id] = config['default_user'].copy() # attempt to automatically set language lang_code = bot.get_chat(user_id).get_member(user_id).user.language_code.lower() if lang_code is not None: for code in lang.keys(): if lang_code.startswith(code): users[user_id]['lang'] = code if code != 'en': config['langs_auto_set'] += 1 # if user is registered but does not have requested key set to default value from config elif key not in users[user_id]: try: users[user_id][key] = config['default_user'][key].copy() # if value isn't a type with a copy function like a string or int except AttributeError: users[user_id][key] = config['default_user'][key] # return value return users[user_id][key] # logs bot errors thrown def handle_error(update: Update, context: CallbackContext): # prevent spammy errors from logging if context.error in ("Forbidden: bot was blocked by the user", "Timed out"): return logger.warning('Update "{}" caused error "{}"'.format(update, context.error)) def load_lang(): path = os.path.join(directory, 'lang.json') data = json.load(codecs.open(path, 'r', 'utf-8-sig')) return data def load_json(file_name): file_path = os.path.join(directory, file_name if file_name.endswith('.json') else file_name + '.json') with open(file_path) as json_file: data = json.load(json_file) json_file.close() return data def save_json(json_obj, file_name): data = json.dumps(json_obj) file_path = os.path.join(directory, file_name if file_name.endswith('.json') else file_name + '.json') with open(file_path, "w") as json_file: json_file.write(simplejson.dumps(simplejson.loads(data), indent=4, sort_keys=True)) json_file.close() def load_files(): try: global config config = load_json('config.json') except FileNotFoundError: sys.exit("config.json is missing; exiting") try: global lang lang = load_lang() except FileNotFoundError: sys.exit("lang.json is missing; exiting") try: global users users = load_json('users.json') except FileNotFoundError: # if users.json is missing create an empty file and continue save_json({}, 'users.json') def save_files(context: CallbackContext = None): save_json(config, 'config.json') save_json(users, 'users.json') def temp_dir(): temp_path = os.path.join(directory, 'temp') if not os.path.exists(temp_path): os.mkdir(temp_path) return temp_path if __name__ == '__main__': main()