diff --git a/README.md b/README.md index 90a4b04..f64d0cf 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,3 @@ - - -# python-telegram-bot-heroku -A guide to hosting a telegram bot created using the python-telegram-bot library with heroku. -![Deploy your Ember project to Heroku from Github - Philip Mutua ...](https://miro.medium.com/max/3600/1*fIjRtO5P8zc3pjs0E5hYkw.png) -See the full article explaining the steps [here](https://towardsdatascience.com/how-to-deploy-a-telegram-bot-using-heroku-for-free-9436f89575d2). - -## Getting Started -Before you begin, you will need a Telegram bot API token from [BotFather](https://t.me/botfather). - -1. Download the three files in this repo: bot.py (containing your python code for the Telegram bot), requirements.txt (containing the python libraries to be installed), and Procfile (containing the command to execute the python file). -2. Login / [create](https://signup.heroku.com/dc) a Heroku account. -3. Install [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). -4. Install the [Heroku CLI](https://devcenter.heroku.com/articles/getting-started-with-python#set-up). -5. Once installed, you can use the _heroku_ command in your terminal / command prompt. Go to the same directory as the files in this repository, and type: - -> heroku login - -A new window will be opened in your browser prompting you to login, so just click on the button. - -6. Once you are logged in, go back to the command line. Type in -> heroku create - -to create your new webapp. Heroku will assign your webapp a name as well as the link to your webapp, which should be of the format [https://{yourherokuappname}.herokuapp.com/.](https://yourherokuappname.herokuapp.com/.) - -7. To the bot.py file, change the TOKEN variable to the API token of your telegram bot, and change the yourherokuappname to the name of your heroku app in the line - -> updater.bot.setWebhook('https://yourherokuappname.herokuapp.com/' + TOKEN) - -8. Next, in your command line, type the following commands in the following order: - -> git init -> git add . -> git commit -m "first commit" - -> heroku git:remote -a YourAppName - -> git push heroku master - -(Make sure to replace YourAppName with the name of your Heroku webapp) - -You should then see the following messages: - -![](https://cdn-images-1.medium.com/max/1000/1*y3JH7a7mY4oYFaAjDCA1Ow.png) - -In particular, it will say that a Python app is detected and it will install the required libraries in the requirements.txt file using pip. Then, it will read the Procfile which specifies that the bot.py file is to be executed. - -9. Go to your conversation with your Telegram bot on Telegram and type /start. The bot should be working now! - -Since you are using the free plan on heroku, the bot will sleep after 30 minutes of inactivity. So do expect the bot to take a few seconds to respond to your /start if you are using it more than 30 minutes after it was previously used. Other than that, the bot will respond almost instantaneously~ - -## What to do if your Bot stops responding -I’ve noticed the bot stops responding after about 24 hours of inactivity (because we are using the free version of Heroku), so if you want to “jolt” the bot awake, one way is to make a change to one of the files (eg. changing the python3 in the procfile to python and vice versa) and then committing the changes with the lines below: -> git add . -> git commit -m "changing python3 to python in Procfile" -> git push heroku master - -You should see again see the messages about a Python app being detected and once it finishes executing, your bot should revive now! +Hello! +Here +telegram bot: https://t.me/expenses_salesforce_bot diff --git a/bot.py b/bot.py index 74d3d8b..164827e 100644 --- a/bot.py +++ b/bot.py @@ -1,6 +1,27 @@ import logging -from telegram.ext import Updater, CommandHandler, MessageHandler, Filters +import calendar +import datetime + +from requests.api import options +from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove +from telegram.ext import ( + Updater, + CommandHandler, + MessageHandler, + Filters, + ConversationHandler, + CallbackContext, +) + +from simple_salesforce import Salesforce import os + +#####################################Data##################################### +sf = Salesforce( +username='max2433186@mindful-impala-acpsha.com', +password='JKlw124O2kanv5kLLf', +security_token='') + PORT = int(os.environ.get('PORT', 5000)) # Enable logging @@ -8,39 +29,285 @@ level=logging.INFO) logger = logging.getLogger(__name__) -TOKEN = 'YOURTELEGRAMBOTTOKEN' +TOKEN = '1841783209:AAHrDitzlrEGtxSyCUgRr2oSl-vQsgBzPK8' +bot = '' + +class UserTelegram: + def __init__(self): + self.login = None + self.password = None + self.exist = False + self.contact = None + self.newCard = None + -# Define a few command handlers. These usually take the two arguments update and -# context. Error handlers also receive the raised TelegramError object in error. +class Card: + def __init__(self, keeper): + self.date = None + self.amount = None + self.description = None + self.keeper = keeper + self.confirmCreate = None + +usersTelegram = {} + + +#######################################Functions########################################### + +######################################Handlers############################################## def start(update, context): """Send a message when the command /start is issued.""" - update.message.reply_text('Hi!') - -def help(update, context): - """Send a message when the command /help is issued.""" - update.message.reply_text('Help!') + + userId = update._effective_user.id + createUserIfItNeed(userId) + isUserLog = usersTelegram[userId].exist + if isUserLog: + update.message.reply_text('Вы уже зарегистрированы!', reply_markup=mainMenuKeyboard()) + else: + update.message.reply_text('Введите логин', reply_markup=ReplyKeyboardRemove()) + # sf.Contact.create({'LastName':'simple_salesforce','Email':'example@example.com'}) def echo(update, context): """Echo the user message.""" - update.message.reply_text(update.message.text) + userId = update._effective_user.id + + if userId not in usersTelegram: + start(update,context) + return + + if usersTelegram[userId].exist == False: + login(update, context) + else: + echoForExistUser(update,context) + + #update.message.reply_text(update.message.text) + +def end(update, context): + userId = update._effective_user.id + usersTelegram.pop(userId) + update.message.reply_text('До свидания!', + reply_markup=ReplyKeyboardRemove()) def error(update, context): """Log Errors caused by Updates.""" logger.warning('Update "%s" caused error "%s"', update, context.error) +######################################Handlers############################################## + +######################################Helper handlers############################################## + +def login(update, context): + userId = update._effective_user.id + user = usersTelegram[userId] + + if user.login == None: + user.login = update.message.text + update.message.reply_text('Введите пароль') + else: + user.password = update.message.text + try: + user.contact = sf.query(f"SELECT Id, Name, Email, Office__c, Admin__c FROM Contact WHERE Email ='{user.login}' AND Password__c ='{user.password}' LIMIT 1") + throwExceptionIfContactEmpty(user.contact) + user.exist = True + update.message.reply_text('Авторизация прошла успешно ', + reply_markup=mainMenuKeyboard()) + except Exception: + update.message.reply_text('Неправильный логин или пароль.Попробуйте Снова') + refreshUser(user) + start(update,context) + return + +def echoForExistUser(update,context): + message = update.message.text.lower() + userId = update._effective_user.id + user = usersTelegram[userId] + userContactId = user.contact['records'][0]['Id'] + if message == 'текущий баланс': + balance = sf.apexecute('Contact/'+userContactId, method='GET') + update.message.reply_text(str(balance)+'$', + reply_markup=mainMenuKeyboard()) + elif message == 'создать карточку': + user.card = Card(userContactId) + update.message.reply_text('На какой день желаете создать карточку?', + reply_markup=createCardKeyboard()) + elif message == 'отмена': + cancelToMainMenu(update,user) + elif user.card != None: + creatingCard(update,user,message) + +def creatingCard(update,user,message): + if user.card.date == None or user.card.date == True: + creatingCardDate(update,user,message) + elif user.card.amount == None or user.card.amount == True: + creatingCardAmount(update,user,message) + elif user.card.description == None or user.card.description == True: + creatingCardDecription(update,user,message) + elif isinstance(user.card.description,str): + confirmCreateCard(update,user,message) + + +def creatingCardDate(update,user,message): + if user.card.date == None: + creatingCardDateNone(update,user,message) + elif user.card.date == True: + try: + cardDate = getStringAsDateAndValidate(message) + user.card.date = cardDate + creatingCardAmount(update,user,message) + except Exception as e: + update.message.reply_text('Кажется вы неправильно ввели дату. Попробуйте еще раз или обратитесь к администратору. Пример: 2021-03-31', + reply_markup=cancelKeyboard()) + #update.message.reply_text(str(e)) + +def creatingCardDateNone(update,user,message): + if message == 'сегодня': + today = datetime.datetime.today() + user.card.date = getStringAsDateAndValidate(str(today.day)) + creatingCardAmount(update,user,message) + elif message == 'календарь': + update.message.reply_text('Выберите число') + replyMessage = """Или Введите дату вручную(день-месяц-число) +Пример: +2021-03-31 + """ + update.message.reply_text(replyMessage, + reply_markup=daysOfMonthKeyboard()) + user.card.date = True + +def creatingCardAmount(update,user,message): + if user.card.amount == None: + update.message.reply_text('Введите сумму') + user.card.amount = True + elif user.card.amount == True: + try: + cardAmount = float(message) + user.card.amount = cardAmount + creatingCardDecription(update,user,message) + except Exception as e: + update.message.reply_text('Кажется вы неправильно ввели сумму. Попробуйте еще раз или обратитесь к администратору. Пример: 5.014', + reply_markup=cancelKeyboard()) + +def creatingCardDecription(update,user,message): + if user.card.description == None: + update.message.reply_text('Введите описание') + user.card.description = True + elif user.card.description == True: + user.card.description = update.message.text + confirmCreateCard(update,user,message) + +def confirmCreateCard(update,user,message): + if user.card.confirmCreate == None: + update.message.reply_text('Вы уверены что хотите создать следующую карточку?') + update.message.reply_text('Дата: ' + str(user.card.date)) + update.message.reply_text('Сумма: ' + str(user.card.amount)+'$') + update.message.reply_text('Описание: ' + user.card.description, reply_markup=confirmKeyboard()) + user.card.confirmCreate = True + elif user.card.confirmCreate == True: + createCardInSalesforce(update,user) + user.card = None + +def createCardInSalesforce(update,user): + try: + something = sf.Expense_Card__c.create({'CardDate__c': user.card.date,'Amount__c':str(user.card.amount),'Description__c':user.card.description,'CardKeeper__c':user.card.keeper}) + user.card = None + update.message.reply_text('Карточка успешно создана!', reply_markup=mainMenuKeyboard()) + except Exception as e: + update.message.reply_text('Извините, карточку не получилось создать', reply_markup=mainMenuKeyboard()) + for err in e.content: + update.message.reply_text(str(err['message'])) + + +def cancelToMainMenu(update,user): + user.card = None + update.message.reply_text('Что вы хотите сделать?', + reply_markup=mainMenuKeyboard()) + +######################################Helper handlers############################################## + + +#########################Keyboards############################ +def mainMenuKeyboard(): + options = [['Текущий баланс'],['Создать карточку']] + return ReplyKeyboardMarkup(options) + +def cancelKeyboard(): + options = [['Отмена']] + return ReplyKeyboardMarkup(options, one_time_keyboard=True) + +def createCardKeyboard(): + options = [['Сегодня'],['Календарь'],['Отмена']] + return ReplyKeyboardMarkup(options, one_time_keyboard=True) + +def daysOfMonthKeyboard(): + options = getOptionsForDaysOfMonthKeyboard() + return ReplyKeyboardMarkup(options, one_time_keyboard=True) + +def confirmKeyboard(): + options = [['Да'],['Отмена']] + return ReplyKeyboardMarkup(options, one_time_keyboard=True) + +#########################Keyboards############################ + +#########################Utils############################ +def getOptionsForDaysOfMonthKeyboard(): + options = [] + rowOptions = [] + now = datetime.datetime.now() + daysInMonth = calendar.monthrange(now.year, now.month)[1] + count = 1 + while count <= daysInMonth: + strCount = str(count) + if len(rowOptions) >= 4 or count == daysInMonth: + rowOptions.append(strCount) + options.append(rowOptions) + rowOptions = [] + else: + rowOptions.append(strCount) + + count += 1 + return options + +def getStringAsDateAndValidate(message): + dateStrResult = '' + if len(message) < 3: + now = datetime.datetime.now() + dateStrResult = str(now.year)+'-'+str(now.month)+'-'+message + else: + dateStrResult = message + + dateStrForValidate = dateStrResult + ' 00:00:00' + dateObject = datetime.datetime.strptime(dateStrForValidate, '%Y-%m-%d %H:%M:%S') + return dateStrResult + +def createUserIfItNeed(userId): + if userId not in usersTelegram: + usersTelegram[userId] = UserTelegram() + +def refreshUser(user): + user.login = None + user.password = None + user.exist = False + user.contact = None + +def throwExceptionIfContactEmpty(contact): + totalSize = int(contact['totalSize']) + if totalSize < 1: + raise Exception('Contact empty') + +#########################Utils############################ + def main(): """Start the bot.""" # Create the Updater and pass it your bot's token. # Make sure to set use_context=True to use the new context based callbacks # Post version 12 this will no longer be necessary updater = Updater(TOKEN, use_context=True) - # Get the dispatcher to register handlers dp = updater.dispatcher # on different commands - answer in Telegram dp.add_handler(CommandHandler("start", start)) - dp.add_handler(CommandHandler("help", help)) + dp.add_handler(CommandHandler('end',end)) # on noncommand i.e message - echo the message on Telegram dp.add_handler(MessageHandler(Filters.text, echo)) @@ -52,7 +319,7 @@ def main(): updater.start_webhook(listen="0.0.0.0", port=int(PORT), url_path=TOKEN) - updater.bot.setWebhook('https://yourherokuappname.herokuapp.com/' + TOKEN) + updater.bot.setWebhook('https://frozen-scrubland-72051.herokuapp.com/' + TOKEN) # Run the bot until you press Ctrl-C or the process receives SIGINT, # SIGTERM or SIGABRT. This should be used most of the time, since diff --git a/requirements.txt b/requirements.txt index 2e299b8..9123cc8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ python-telegram-bot==12.7 +simple_salesforce==1.11.1 \ No newline at end of file