#!/usr/bin/python3 import re import telebot import schedule import time import logging import oauth2 as oauth import logging import firefly import users import traceback import json ######################################################################################### # Basic config # # Load configs content_file = open("config.json", 'r') content = content_file.read() content_file.close() CONFIGS = json.loads(content) #TODO: make error message if configs file doesn't exist or is corrupted MESSAGES = { "welcome": "Welcome!", "asking_to_verify_money_in_pocket": "Let's check money in your pocket. How much do you have?", "you_are_my_user_already": "You are my user already", "you_are_my_master": "You are my first user. I choose you as my master.", # "choose_your_pocket_prefix": "choose pocket ", "choose_your_pocket_account": "choose your pocket account", "choose_your_pocket_account_retry": "choose your pocket account. I didn't get what you've said.", # "excuses_for_bothering": "Okey, I do not bother you", "where_did_you_get_money": "Whoa, that's more than you got before. Where did you get the money?", "no_amount_sent": "You didn't send amount of money you took. I can't handle such messages for the moment.", "thankyou": "Thank you", "choose_budget": "Choose budget:", # "no_connection": "No connection to your firefly server, sorry. Check your server and api key and try again.", "request_for_server": "Please, tell me your firefly server url (for example `http://152.12.51.224` or `http://myfirefly.com`)", "request_for_server_failed_validation": "Doesn't look like server url", "request_for_oauth_key": "Please, tell me firefly access token (for example `eyJ0eXAiOiJKV1QiLCJZboci9iJSUzI1NiIsImp0aSI6ImY1YWY0Yzc2ZTBkNDliNjA2ZTAwZjcyYTc0YjQ4YzM4MTc1Y2JjNWI4MjU1MWU3NDMwNTM5MWJkNGRiYmU0NDk2ODE1MGRmYThhYjg0NzM2In0`)", # "rules_introduction": "You can send me spent money at any time (for example `123 tea`). Once a day I will ask you, how much money do you have in your pocket.", # "money_in_pocket_update_transaction": "updating amount of money" } firefly = firefly.Firefly() ######################################################################################### # Basic classes # class ScheduledTeleBot(telebot.TeleBot): def __non_threaded_polling(self, schedule, none_stop=False, interval=0, timeout=3): logger.info('Started polling.') self._TeleBot__stop_polling.clear() error_interval = .25 while not self._TeleBot__stop_polling.wait(interval): try: schedule.run_pending() self._TeleBot__retrieve_updates(timeout) error_interval = .25 except apihelper.ApiException as e: logger.error(e) if not none_stop: self._TeleBot__stop_polling.set() logger.info("Exception occurred. Stopping.") else: logger.info("Waiting for {0} seconds until retry".format(error_interval)) time.sleep(error_interval) error_interval *= 2 except KeyboardInterrupt: logger.info("KeyboardInterrupt received.") self._TeleBot__stop_polling.set() break logger.info('Stopped polling.') def polling(self, schedule, none_stop=False, interval=0, timeout=20): self.__non_threaded_polling(schedule, none_stop, interval, timeout) ######################################################################################### # Main variables # bot = ScheduledTeleBot(CONFIGS["telegram_token"]) logger = logging.getLogger('TeleBot') users=users.User() ######################################################################################### # Chatting callbacks # # cronjob, that sends message to users def cronjob(): logger.info('cronjob function') for chat_id in users.getUsersIds(): markup = telebot.types.ForceReply(selective=False) bot.send_message(chat_id, MESSAGES["asking_to_verify_money_in_pocket"], reply_markup=markup) ######################################## # Init communication # replies for `/start` @bot.message_handler(commands=['start']) def send_welcome(message): # TODO: security try: bot.reply_to(message, MESSAGES["welcome"]) if users.exists(message.from_user.username): bot.send_message(message.chat.id, MESSAGES["you_are_my_user_already"]) else: users.add(message.from_user.username, message.chat.id) # send message for function _check_if_reply_to_server_request(msg) markup = telebot.types.ForceReply(selective=False) bot.send_message(message.chat.id, MESSAGES["request_for_server"], reply_markup=markup) except Exception as err: print(err) traceback.print_exc() # checking, if message is reply for firefly server request def _check_if_reply_to_server_request(msg): if not hasattr(msg,'reply_to_message'): return False if not hasattr(msg.reply_to_message,'text'): return False return msg.reply_to_message.text == MESSAGES["request_for_server"] @bot.message_handler(func=_check_if_reply_to_server_request) def got_reply_on_server_request(message): if (True): # TODO: validate message users.setServer(message.from_user.username, message.text) # Please, tell me firefly access token (for example `eyJ0eXAiOiJKV1QiLCJZboci9iJSUzI1NiIsImp0aSI6ImY1YWY0Yzc2ZTBkNDliNjA2ZTAwZjcyYTc0YjQ4YzM4MTc1Y2JjNWI4MjU1MWU3NDMwNTM5MWJkNGRiYmU0NDk2ODE1MGRmYThhYjg0NzM2In0`) markup = telebot.types.ForceReply(selective=False) bot.send_message(message.chat.id, MESSAGES["request_for_oauth_key"], reply_markup=markup) else: bot.send_message(message.chat.id, MESSAGES["request_for_server_failed_validation"]) markup = telebot.types.ForceReply(selective=False) bot.send_message(message.chat.id, MESSAGES["request_for_server"], reply_markup=markup) # checking, if message is reply to firefly oauth token request def _check_if_reply_to_access_token_request(msg): if not hasattr(msg,'reply_to_message'): return False if not hasattr(msg.reply_to_message,'text'): return False return msg.reply_to_message.text == MESSAGES["request_for_oauth_key"] @bot.message_handler(func=_check_if_reply_to_access_token_request) def got_reply_on_access_token(message): try: users.setAccessToken(message.from_user.username, message.text) if firefly.testConnection(message.from_user.username, users): if not users.hasMaster(): bot.send_message(message.chat.id, MESSAGES["you_are_my_master"]) # ask user, which account should be locked to this user. Using telegram's buttons: "choose pocket <pocketname>" balances = firefly.getBalances(message.from_user.username, users) markup = telebot.types.ReplyKeyboardMarkup() for balace in balances: markup.row(telebot.types.KeyboardButton(MESSAGES["choose_your_pocket_prefix"]+str(balace))) bot.reply_to(message, MESSAGES["choose_your_pocket_account"], reply_markup=markup) else: bot.send_message(message.chat.id, MESSAGES["no_connection"]) markup = telebot.types.ForceReply(selective=False) bot.send_message(message.chat.id, MESSAGES["request_for_server"], reply_markup=markup) except Exception as err: print(err) traceback.print_exc() @bot.message_handler(regexp=MESSAGES["choose_your_pocket_prefix"]) def choose_pocket(message): try: message_text = message.text # get balances and incomes from firefly pockets_data = firefly.getBalancesExtended(message.from_user.username, users) # get pockets message_pocket = '' account_id = 0 account_currency = "" for pocket in pockets_data: if pocket["attributes"]["name"] in message_text: message_pocket = pocket["attributes"]["name"] account_id = pocket["id"] account_currency = pocket["attributes"]["currency_code"] message_text = message_text.replace(pocket["attributes"]["name"],'') break # message is left in message_text if message_pocket: users.setPocket(message.from_user.username, value=message_pocket, account_id=account_id, account_currency=account_currency) users.setAuthorized(message.from_user.username) # Send welcome message markup = telebot.types.ReplyKeyboardRemove(selective=False) bot.send_message(message.chat.id, MESSAGES["rules_introduction"], reply_markup=markup) else: for pocket in pockets: markup.row(telebot.types.KeyboardButton('choose pocket '+str(pocket))) markup = telebot.types.ReplyKeyboardMarkup() bot.reply_to(message, MESSAGES["choose_your_pocket_account_retry"], reply_markup=markup) except Exception as err: print(err) traceback.print_exc() ######################################## # Crons communication # check for next function def _check_if_message_made_by_cron(msg): if not hasattr(msg,'reply_to_message'): return False if not hasattr(msg.reply_to_message,'text'): return False return msg.reply_to_message.text == MESSAGES["asking_to_verify_money_in_pocket"] # if this is reply to message, made by cron - we expect money in pocket (see _check_if_message_made_by_cron()) @bot.message_handler(func=_check_if_message_made_by_cron) def got_reply_on_cron(message): message_text = message.text # get money in pocket from firefly current_balance = firefly.getCurrentBalance(message.from_user.username, users) # get number message_number = re.findall('\d+', message_text)[0] message_text = message_text.replace(message_number,'') if not message_number: bot.reply_to(message, MESSAGES["excuses_for_bothering"]) bot.send_message(users.getMasterId(), "User @"+message.from_user.username+" ignores me!") pass else: try: message_integer = int(message_number) balance_diff = message_integer-current_balance if message_integer > current_balance: # get balances and incomes from firefly balances = firefly.getBalances(message.from_user.username, users) balances.extend(firefly.getIncomes(message.from_user.username, users)) markup = telebot.types.ReplyKeyboardMarkup() for balance in balances: markup.row(telebot.types.KeyboardButton('took '+str(abs(balance_diff))+' from '+str(balance))) bot.reply_to(message, MESSAGES["where_did_you_get_money"], reply_markup=markup) elif message_integer < current_balance: bot.send_message(message.chat.id, "You have spent "+str(abs(balance_diff))+".") _talk_about_spent_money(message, message_number=str(abs(balance_diff))) else: bot.send_message(message.chat.id, "Nothing changed. Thanks for info!") except Exception as err: print(err) # Adding money to user's balance from another balance # Works with messages: # - took 1231 from balance1 # - took 1231 @bot.message_handler(regexp="took") def took_money(message): message_text = message.text # get balances and incomes from firefly balances = firefly.getBalances(message.from_user.username, users) # get number message_number = re.findall('\d+', message_text)[0] message_text = message_text.replace(message_number,'') # get balance message_balance = '' for balance in balances: if balance in message_text: message_balance = balance message_text = message_text.replace(balance,'') break # message is left in message_text if not message_balance: # TODO: ask user for balance. With buttons. pass elif not message_number: markup = telebot.types.ReplyKeyboardRemove() bot.reply_to(message, MESSAGES["no_amount_sent"], reply_markup=markup) else: firefly.take(message.from_user.username, users, int(message_number), message_balance, message_text) markup = telebot.types.ReplyKeyboardRemove() bot.reply_to(message, MESSAGES["thankyou"], reply_markup=markup) pass ######################################## # Make transaction communication # talking about spent money. Aknowledge needed info. def _talk_about_spent_money(message_to_reply, message_number="",message_budget="",message_text=""): # get budgets from firefly budgets=firefly.getBudgets(message_to_reply.from_user.username, users) if not message_text or message_text.isspace(): message_text=MESSAGES["money_in_pocket_update_transaction"] if not message_budget: markup = telebot.types.ReplyKeyboardMarkup() for budget in budgets: markup.row(telebot.types.KeyboardButton(message_number+' '+budget+' '+message_text)) bot.reply_to(message_to_reply, MESSAGES["choose_budget"], reply_markup=markup) # if everything got - just add it to firefly else: try: markup = telebot.types.ReplyKeyboardRemove() firefly.spend(message_to_reply.from_user.username, users, int(message_number), message_budget, message_text) bot.reply_to(message_to_reply, MESSAGES["thankyou"], reply_markup=markup) except Exception as err: print(err) # If message have numbers and not caught by previous handlers - we suppose it's about spent money @bot.message_handler(regexp="[0-9]+") def recieved_number(message): message_text = message.text # get budgets from firefly budgets=firefly.getBudgets(message.from_user.username, users) # get number message_number = re.findall('\d+', message_text)[0] message_text = message_text.replace(message_number,'') # get budget message_budget = '' for budget in budgets: if budget in message_text: message_budget = budget message_text = message_text.replace(budget,'') break # message is left in message_text # if expense not set - ask for it _talk_about_spent_money(message, message_number=message_number, message_budget=message_budget, message_text=message_text) ######################################################################################### # Main executing code # # TODO: scheduling should be in config or in personal user's settings schedule.every().day.at("20:00").do(cronjob) #schedule.every().minute.do(cronjob) bot.polling(schedule)