From f67f0b86b3afcae232bf4ebcbd7be41363731073 Mon Sep 17 00:00:00 2001 From: adityabisoi Date: Thu, 2 Apr 2020 20:56:18 +0530 Subject: [PATCH 1/4] Initial commit --- .gitignore | 104 ++++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 6 +++ LICENSE | 21 +++++++++ Procfile | 1 + README.md | 25 ++++++++++ SETUP.md | 69 ++++++++++++++++++++++++++++ bot.py | 55 ++++++++++++++++++++++ example.env | 6 +++ modules.py | 77 +++++++++++++++++++++++++++++++ requirements.txt | 18 ++++++++ runtime.txt | 1 + settings.py | 2 + utils.py | 26 +++++++++++ 13 files changed, 411 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 Procfile create mode 100644 README.md create mode 100644 SETUP.md create mode 100644 bot.py create mode 100644 example.env create mode 100644 modules.py create mode 100644 requirements.txt create mode 100644 runtime.txt create mode 100644 settings.py create mode 100644 utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..894a44c --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0123c09 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.pythonPath": "/home/devaditya/anaconda3/envs/jarvis-telegram/bin/python", + "python.linting.pylintEnabled": false, + "python.linting.pycodestyleEnabled": false, + "python.linting.enabled": true +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b4d1c7c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Vision + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..bf2bd3c --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: python bot.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..804741a --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# jarvis-telegram + +Just A Rather Very Intelligent System, now on Telegram! + +![Python](https://img.shields.io/badge/python-3.7-blue.svg) +[![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/the-vision/jarvis-telegram/master/LICENSE) +[![first-timers-only](https://img.shields.io/badge/first--timers--only-friendly-blue.svg?style=flat-square)](https://www.firsttimersonly.com/) + +## Usage + +Open https://t.me/jarvis_chatbot and start a chat! Here are some supported queries: +* show me a random xkcd comic + +## Setup + +```python +brew install python +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python bot.py +``` + +Create a `.env` file following the template of the `example.env` file provided in the repository with your own tokens. Here's a detailed document containing step-by-step instructions to get your own bot working locally: [SETUP](SETUP.md) diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..1eee666 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,69 @@ +**Steps to get Jarvis Telegram app up and running in your machine** + +Step 1: + +Install the libraries mentioned in requirements.txt and go to the link provided in the github page - [https://t.me/jarvis_chatbot](https://t.me/jarvis_chatbot) +![1](https://user-images.githubusercontent.com/34595292/75851033-49ad2c80-5dae-11ea-8597-df8511079e4d.png) + +Step 2: + +To install the Telegram app click on “Don’t have Telegram yet? Try it now!>” +![2](https://user-images.githubusercontent.com/34595292/75851034-49ad2c80-5dae-11ea-9264-66a218015525.png) + +Step 3: + +Install the app based on your machine OS/device type +![3](https://user-images.githubusercontent.com/34595292/75851035-4a45c300-5dae-11ea-92a7-e940ca7236fe.png) + +Step 4: + +Upon successful installation, clone the git repository - [https://github.com/the-vision/jarvis-telegram-playground](https://github.com/the-vision/jarvis-telegram-playground) to your local machine. +![4](https://user-images.githubusercontent.com/34595292/75851036-4ade5980-5dae-11ea-8cf6-637cc2e8baf3.png) + +Step 5: + +Click on the BotFather link provided in the repository - [https://t.me/BotFather](https://t.me/BotFather) +![5](https://user-images.githubusercontent.com/34595292/75851038-4ade5980-5dae-11ea-9d38-a1ef36bbb64c.png) + +Step 6: + +If you have successfully installed the Telegram app, you should see a notification asking to open the app when you navigate to the BotFather website. + +![6](https://user-images.githubusercontent.com/34595292/75851039-4ade5980-5dae-11ea-9956-85ca102b68d8.png) + +Step 7: + +Type /start when the app opens +![7](https://user-images.githubusercontent.com/34595292/75851040-4b76f000-5dae-11ea-99a4-5cf3a5a26abc.png) + +Step 8: + +Type in /newbot to create a new bot +![8](https://user-images.githubusercontent.com/34595292/75851043-4b76f000-5dae-11ea-8123-dbfe09233233.png) + +Step 9: + +Select a name for your bot along with a UNIQUE username. +![9](https://user-images.githubusercontent.com/34595292/75851044-4b76f000-5dae-11ea-8fc4-edfd201e3d72.png) + +Step 10: + +Copy the token provided after successful creation of your bot. This needs to be copied on to the API_TOKEN variable in the bot.py file, which was cloned to your local machine. +![10](https://user-images.githubusercontent.com/34595292/75851045-4c0f8680-5dae-11ea-93e8-25b4e85354eb.png) + +Step 11: + +Make changes to your local bot.py file as you wish. To run the bot, go to your terminal and run the bot.py file +![11](https://user-images.githubusercontent.com/34595292/75851047-4c0f8680-5dae-11ea-8605-a38a8a701f8e.png) + +Step 12: + +The changes will be reflected in the app. For example, here I have made changes to bot.py file to display a custom message when the user types in ‘help’. +![12](https://user-images.githubusercontent.com/34595292/75851049-4ca81d00-5dae-11ea-922d-e4edff03d32a.png) + +![13](https://user-images.githubusercontent.com/34595292/75851029-49149600-5dae-11ea-8573-4f3a20486b07.png) + +Note: The py file needs to be run in the background to have the bot responding to your requests + + + diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..2b8ff91 --- /dev/null +++ b/bot.py @@ -0,0 +1,55 @@ +from flask import Flask, request +from modules import reply +from utils import extract_structured_data, log + +import json +import os +import psycopg2 +import requests +import settings +import telebot + +DB = os.environ.get('DATABASE_URL') +MIND_STONE = os.environ.get('MIND_STONE') +TOKEN = os.environ.get('TELEGRAM_BOT_API_TOKEN') +URL = os.environ.get('HEROKU_PROJECT_URL') + +bot = telebot.TeleBot(TOKEN) +conn = psycopg2.connect(DB, sslmode='require') +server = Flask(__name__) + + +@bot.message_handler(commands=['start']) +def start(message): + log(conn, 'start', None, message.text, message.from_user.id, None) + markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True) + markup.add(telebot.types.KeyboardButton('show me a random xkcd comic')) + bot.reply_to(message, 'At your service, ' + message.from_user.first_name + + '! 👋', reply_markup=markup) + + +@bot.message_handler(func=lambda message: True, content_types=['text']) +def process_query(message): + response = requests.get(MIND_STONE + '/parse?q=' + message.text) + data = extract_structured_data(response.json()) + intent = data['intent'] + entities = data['entities'] + log(conn, intent, json.dumps(entities), message.text, message.from_user.id, None) + reply(bot, message, intent, entities) + + +@server.route('/' + TOKEN, methods=['POST']) +def getMessage(): + bot.process_new_updates([telebot.types.Update.de_json(request.stream.read().decode("utf-8"))]) + return "!", 200 + + +@server.route("/") +def webhook(): + bot.remove_webhook() + bot.set_webhook(url=URL + '/' + TOKEN) + return "!", 200 + + +if __name__ == "__main__": + server.run(host="0.0.0.0", port=int(os.environ.get('PORT', 5000))) diff --git a/example.env b/example.env new file mode 100644 index 0000000..ca713d4 --- /dev/null +++ b/example.env @@ -0,0 +1,6 @@ +CONFIDENCE_THRESHOLD=XXXXXXXXXXXX +DATABASE_URL=XXXXXXXXXXXX +FALLBACK_INTENT=XXXXXXXXXXXX +HEROKU_PROJECT_URL=XXXXXXXXXXXX +MIND_STONE=XXXXXXXXXXXX +TELEGRAM_BOT_API_TOKEN=XXXXXXXXXXXX diff --git a/modules.py b/modules.py new file mode 100644 index 0000000..0911637 --- /dev/null +++ b/modules.py @@ -0,0 +1,77 @@ +from telebot import types + +import pyjokes +import random +import requests +import xkcd + + +def reply(bot, message, intent, entities): + if intent == 'xkcd': + random_comic = xkcd.getRandomComic() + markup = types.ReplyKeyboardMarkup(resize_keyboard=True) + markup.add(types.KeyboardButton('Check out another xkcd comic!')) + bot.send_photo(message.chat.id, random_comic.getImageLink(), caption='*' + + random_comic.getTitle() + '*\n' + random_comic.getAltText() + + '\n' + random_comic.getExplanation(), parse_mode='Markdown', + reply_to_message_id=message.message_id, reply_markup=markup) + elif intent == 'hello': + greetings = [ + 'Hello there!', + 'Hey!', + 'Hi!', + 'Oh hello!' + ] + greeting = random.choice(greetings) + bot.reply_to(message, greeting) + elif intent == 'thanks': + bot.reply_to(message, u'\u2764') + elif intent == 'bye': + greetings = [ + 'Bye!', + 'Have a great day ahead!', + 'See you soon!' + ] + greeting = random.choice(greetings) + bot.reply_to(message, greeting) + gifs = [ + 'https://media.giphy.com/media/UrcXN0zTfzTPi/giphy.gif', + 'https://media.giphy.com/media/3o6EhGvKschtbrRjX2/giphy.gif' + ] + gif = random.choice(gifs) + bot.send_animation(message.chat.id, gif) + elif intent == 'coin': + coin_images = { + 'Heads': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/01/heads.png', + 'Tails': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/01/tails.png' + } + result = random.choice(['Heads', 'Tails']) + bot.send_photo(message.chat.id, photo=coin_images[result], + reply_to_message_id=message.message_id) + elif intent == 'dice': + dice_images = { + '1': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/05/dice_1.png', + '2': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/05/dice_2.png', + '3': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/05/dice_3.png', + '4': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/05/dice_4.png', + '5': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/05/dice_5.png', + '6': 'https://www.ssaurel.com/blog/wp-content/uploads/2017/05/dice_6.png' + } + result = random.choice(['1', '2', '3', '4', '5', '6']) + bot.send_photo(message.chat.id, photo=dice_images[result], + reply_to_message_id=message.message_id) + elif intent == 'joke': + bot.reply_to(message, text=pyjokes.get_joke()) + elif intent == 'fact': + response = requests.get('http://numbersapi.com/random/trivia') + if (response.status_code == 200): + bot.reply_to(message, response.text) + else: + bot.reply_to(message, 'I could not fetch a fact for you this time. Please try again later!') + else: + title = "Unhandled+query:+" + message.text + body = "What's+the+expected+result?+PLACEHOLDER_TEXT" + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text='Report', url="https://github.com/the-vision/jarvis-telegram/issues/new?title=" + title + "&body=" + body)) + bot.send_message(message.chat.id, text="Sorry, this feature isn't available yet!", + parse_mode='Markdown', reply_to_message_id=message.message_id, reply_markup=markup) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5da8dd9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +certifi==2018.11.29 +chardet==3.0.4 +Click==7.0 +Flask==1.0.2 +idna==2.8 +itsdangerous==1.1.0 +Jinja2==2.10 +MarkupSafe==1.1.0 +pep8==1.7.1 +psycopg2-binary==2.8.4 +pyjokes==0.6.0 +pyTelegramBotAPI==3.6.7 +python-dotenv==0.12.0 +requests==2.21.0 +six==1.12.0 +urllib3==1.24.1 +Werkzeug==0.14.1 +xkcd==2.4.2 diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000..6919bf9 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.7.6 diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..9b0ccb7 --- /dev/null +++ b/settings.py @@ -0,0 +1,2 @@ +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv()) diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..4833881 --- /dev/null +++ b/utils.py @@ -0,0 +1,26 @@ +import os + +CONFIDENCE_THRESHOLD = float(os.environ.get('CONFIDENCE_THRESHOLD')) +FALLBACK_INTENT = os.environ.get('FALLBACK_INTENT') + + +def extract_structured_data(result): + data = { + 'intent': FALLBACK_INTENT, + 'entities': [] + } + if result['intent']['confidence'] > CONFIDENCE_THRESHOLD: + data['intent'] = result['intent']['name'] + for entity in result['entities']: + if entity['confidence'] > CONFIDENCE_THRESHOLD: + data['entities'].append({ + 'name': entity['entity'], + 'value': entity['value'] + }) + return data + + +def log(conn, intent, entities, input, sender, postback): + with conn.cursor() as cur: + cur.execute("INSERT INTO logs (intent, entities, input, sender, postback) VALUES (%s, %s, %s, %s, %s)", (intent, entities, input, sender, postback)); + conn.commit() From 8350595b9bd9b2ac557740750da7d6fc1b92ad62 Mon Sep 17 00:00:00 2001 From: Aditya Bisoi Date: Thu, 23 Apr 2020 14:46:41 +0530 Subject: [PATCH 2/4] unit module added --- .vscode/settings.json | 6 +++--- modules.py | 15 ++++++++++++++- requirements.txt | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0123c09..33ea509 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { - "python.pythonPath": "/home/devaditya/anaconda3/envs/jarvis-telegram/bin/python", + "python.pythonPath": "venv/bin/python", "python.linting.pylintEnabled": false, - "python.linting.pycodestyleEnabled": false, + "python.linting.pycodestyleEnabled": true, "python.linting.enabled": true -} +} \ No newline at end of file diff --git a/modules.py b/modules.py index 0911637..d07158b 100644 --- a/modules.py +++ b/modules.py @@ -4,7 +4,7 @@ import random import requests import xkcd - +from unit_convert import UnitConvert def reply(bot, message, intent, entities): if intent == 'xkcd': @@ -68,6 +68,19 @@ def reply(bot, message, intent, entities): bot.reply_to(message, response.text) else: bot.reply_to(message, 'I could not fetch a fact for you this time. Please try again later!') + elif intent == 'unit': + from_value = entities['value'] + from_unit = entities['from_unit'] + to_unit = entities['to_unit'] + url = "https://community-neutrino-currency-conversion.p.rapidapi.com/convert" + payload = "from-type=NZD&to-type=GBP&from-value=10" + headers = { + 'x-rapidapi-host': "community-neutrino-currency-conversion.p.rapidapi.com", + 'x-rapidapi-key': "", # Get your key from https://rapidapi.com/neutrinoapi/api/convert-1 + 'content-type': "application/x-www-form-urlencoded" + } + response = requests.request("POST", url, data=payload, headers=headers) + bot.reply_to(message, response.text['result']) else: title = "Unhandled+query:+" + message.text body = "What's+the+expected+result?+PLACEHOLDER_TEXT" diff --git a/requirements.txt b/requirements.txt index 5da8dd9..7d82378 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ pyTelegramBotAPI==3.6.7 python-dotenv==0.12.0 requests==2.21.0 six==1.12.0 +unit-convert==1.0.0 urllib3==1.24.1 Werkzeug==0.14.1 xkcd==2.4.2 From f345f886c1e0fe5dd73b68fb0c70d6d96d05ff2d Mon Sep 17 00:00:00 2001 From: Aditya Bisoi Date: Thu, 23 Apr 2020 14:49:34 +0530 Subject: [PATCH 3/4] bad commit fixed --- .gitignore | 2 +- .vscode/settings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fb3177b..894a44c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ __pycache__/ *.py[cod] *$py.class -.vscode/ + # C extensions *.so diff --git a/.vscode/settings.json b/.vscode/settings.json index 33ea509..a54fc19 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "python.pythonPath": "venv/bin/python", "python.linting.pylintEnabled": false, - "python.linting.pycodestyleEnabled": true, + "python.linting.pep8Enabled": true, "python.linting.enabled": true } \ No newline at end of file From 34cff3004c757b9b52dfa905e0f8c85547d8454d Mon Sep 17 00:00:00 2001 From: Aditya Bisoi Date: Wed, 29 Apr 2020 12:39:34 +0530 Subject: [PATCH 4/4] conflict fixed --- .vscode/settings.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a54fc19..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "python.pythonPath": "venv/bin/python", - "python.linting.pylintEnabled": false, - "python.linting.pep8Enabled": true, - "python.linting.enabled": true -} \ No newline at end of file