From dfeb802fb0b73f2b752c142a871db22d219ffbd1 Mon Sep 17 00:00:00 2001 From: Ryan Marvin Date: Sun, 30 Apr 2017 19:11:19 +0300 Subject: [PATCH 1/4] [Feature] Add sprawler output to DB --- .gitignore | 2 ++ sprawler/worker.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/.gitignore b/.gitignore index 4e37f50..83c1271 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +credentials + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/sprawler/worker.py b/sprawler/worker.py index 50fa515..ec27dfc 100644 --- a/sprawler/worker.py +++ b/sprawler/worker.py @@ -1,6 +1,8 @@ import os import gspread from oauth2client.service_account import ServiceAccountCredentials +from pymongo import MongoClient +import pymongo sheet_list = [] # json credentials you downloaded earlier @@ -31,7 +33,50 @@ def populate_search_data(list_of_dictionaries, search_list): + print "Searching", macbook_data, charger_data, thunderbolt_data for records in list_of_dictionaries: parsed_data = {records['Andela Code']: records['Fellow Name']} search_list.append(parsed_data) return search_list + +mongodb_client = MongoClient() +db = mongodb_client['saka'] + +macbooks = db.macbooks +thunderbolts = db.thunderbolts +chargers = db.chargers + +macbooks.create_index([('equipment_id', pymongo.TEXT)], unique=True) +chargers.create_index([('equipment_id', pymongo.TEXT)], unique=True) +thunderbolts.create_index([('equipment_id', pymongo.TEXT)], unique=True) + +def store_in_db(): + for item in macbook_data: + macbook = { + "equipment_id": item['Andela Code'].split("/")[-1], + "fellow_name": item['Fellow Name'], + "serial_no": item['Device Serial'] + } + macbooks.insert_one(macbook) + print "Inserted macbooks" + for item in charger_data: + charger = { + "equipment_id": item['Andela Code'].split("/")[-1], + "fellow_name": item['Fellow Name'] + } + chargers.insert_one(charger) + + print "Inserted chargers" + + for item in thunderbolt_data: + if item['Andela Code']: + thunderbolt = { + "equipment_id": item['Andela Code'].split("/")[-1], + "fellow_name": item['Fellow Name'] + } + thunderbolts.insert_one(thunderbolt) + + print "Inserted thunderbolts" + +if __name__ == "__main__": + store_in_db() \ No newline at end of file From ed22b4b0de2e34ebb409601a7e0504d76b0a12ee Mon Sep 17 00:00:00 2001 From: Ryan Marvin Date: Sun, 30 Apr 2017 22:40:38 +0300 Subject: [PATCH 2/4] [Feature] User can search for thunderbolt --- .gitignore | 1 + app/__init__.py | 16 +++++++ app/core.py | 58 ++++++++++++++++++++++++ app/handlers.py | 36 +++++++++++++++ {sprawler => app/sprawler}/quickstart.py | 0 {sprawler => app/sprawler}/worker.py | 6 +-- config-sample.py | 6 +++ run.py | 10 ++++ slackbot_settings.py | 9 ++++ 9 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/core.py create mode 100644 app/handlers.py rename {sprawler => app/sprawler}/quickstart.py (100%) rename {sprawler => app/sprawler}/worker.py (92%) create mode 100644 config-sample.py create mode 100644 run.py create mode 100644 slackbot_settings.py diff --git a/.gitignore b/.gitignore index 83c1271..10ff706 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ credentials +config.py # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..f16a1eb --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,16 @@ +from pymongo import MongoClient +from slackclient import SlackClient +from app.config import MONGODB, BOT_TOKEN +import pymongo + + +mongodb_client = MongoClient() +db = mongodb_client[MONGODB] + +# db collection +chargers = db.chargers +macbooks = db.macbooks +thunderbolts = db.thunderbolts + +# slack client +slack_client = SlackClient(BOT_TOKEN) diff --git a/app/core.py b/app/core.py new file mode 100644 index 0000000..d34d6fc --- /dev/null +++ b/app/core.py @@ -0,0 +1,58 @@ +from app import chargers, macbooks, thunderbolts, slack_client + + +def get_charger(charger_id): + ''' + Get charger from database using id + ''' + return chargers.find_one({"equipment_id": charger_id}) + + +def get_macbook(macbook_id): + ''' + Get charger from database using id + ''' + return macbooks.find_one({"equipment_id": macbook_id}) + + +def get_thunderbolt(thunderbolt_id): + ''' + Get charger from database using id + ''' + return thunderbolts.find_one({"equipment_id": thunderbolt_id}) + + +def build_found_equipment_atachment(equipment, category): + ''' + Returns a slack attachment to show a result + ''' + return [{ + "text": "That {} belongs to {}".format(category, equipment["fellow_name"]), + "fallback": "Equipment ID - {} | Owner - {}".format(equipment["equipment_id"], equipment["fellow_name"]), + "color": "#4B719C", + "fields": [ + { + "title": "Equipment ID", + "value": "{}".format(equipment["equipment_id"]), + "short": "true" + }, + { + "title": "Owner", + "value": "{}".format(equipment["fellow_name"]), + "short": "true" + } + ] + }] + + +loading_messages = [ + "We're testing your patience.", + "A few bits tried to escape, we're catching them..." + "It's still faster than slacking OPs :stuck_out_tongue_closed_eyes:", + "Loading humorous message ... Please Wait", + "Firing up the transmogrification device...", + "Time is an illusion. Loading time doubly so.", + "Slacking OPs for the information, this could take a while...", + "Loading completed. Press F13 to continue.", + "Looks like someone's been careless again :face_with_rolling_eyes:..." +] diff --git a/app/handlers.py b/app/handlers.py new file mode 100644 index 0000000..743a0fa --- /dev/null +++ b/app/handlers.py @@ -0,0 +1,36 @@ +# coding: UTF-8 +import re +import random +import time +import json +from slackbot.bot import respond_to +from app.core import get_charger, get_macbook, get_thunderbolt, loading_messages, build_found_equipment_atachment + + +@respond_to('hello$|hi$|hey$|aloha$|bonjour$', re.IGNORECASE) +def hello_reply(message): + time.sleep(1) + message.reply('Bonjour and howdy stranger. What can I do you for?') + + +@respond_to('(tb|thunderbolt|thunder).*?(\d+)', re.IGNORECASE) +def find_thunderbolt(message, equipment_type, equipment_id): + message.reply(random.choice(loading_messages)) + time.sleep(2) + + attachments = [] + # get thunderbolt from db + thunderbolt = get_thunderbolt(int(equipment_id)) + + if thunderbolt: + attachments.extend( + build_found_equipment_atachment(thunderbolt, + "thunderbolt")) + time.sleep(1) + print attachments + message.send_webapi('', json.dumps(attachments)) + return + else: + time.sleep(1) + message.reply("We were unable to find a " + "thunderbolt by the id {} :zap:".format(equipment_id)) diff --git a/sprawler/quickstart.py b/app/sprawler/quickstart.py similarity index 100% rename from sprawler/quickstart.py rename to app/sprawler/quickstart.py diff --git a/sprawler/worker.py b/app/sprawler/worker.py similarity index 92% rename from sprawler/worker.py rename to app/sprawler/worker.py index ec27dfc..7171aaf 100644 --- a/sprawler/worker.py +++ b/app/sprawler/worker.py @@ -53,7 +53,7 @@ def populate_search_data(list_of_dictionaries, search_list): def store_in_db(): for item in macbook_data: macbook = { - "equipment_id": item['Andela Code'].split("/")[-1], + "equipment_id": int(item['Andela Code'].split("/")[-1]), "fellow_name": item['Fellow Name'], "serial_no": item['Device Serial'] } @@ -61,7 +61,7 @@ def store_in_db(): print "Inserted macbooks" for item in charger_data: charger = { - "equipment_id": item['Andela Code'].split("/")[-1], + "equipment_id": int(item['Andela Code'].split("/")[-1]), "fellow_name": item['Fellow Name'] } chargers.insert_one(charger) @@ -71,7 +71,7 @@ def store_in_db(): for item in thunderbolt_data: if item['Andela Code']: thunderbolt = { - "equipment_id": item['Andela Code'].split("/")[-1], + "equipment_id": int(item['Andela Code'].split("/")[-1]), "fellow_name": item['Fellow Name'] } thunderbolts.insert_one(thunderbolt) diff --git a/config-sample.py b/config-sample.py new file mode 100644 index 0000000..064a7a3 --- /dev/null +++ b/config-sample.py @@ -0,0 +1,6 @@ +# slack bot token +API_TOKEN = "" + +# text api credentials +AYLIEN_APP_ID = "" +AYLIEN_CLIENT_SECRET = "" diff --git a/run.py b/run.py new file mode 100644 index 0000000..92e3e47 --- /dev/null +++ b/run.py @@ -0,0 +1,10 @@ +from slackbot.bot import Bot + + +def main(): + bot = Bot() + bot.run() + + +if __name__ == "__main__": + main() diff --git a/slackbot_settings.py b/slackbot_settings.py new file mode 100644 index 0000000..d384bc9 --- /dev/null +++ b/slackbot_settings.py @@ -0,0 +1,9 @@ +from app.config import BOT_TOKEN + + +API_TOKEN = BOT_TOKEN +ERRORS_TO = 'ryan-marvin' +DEFAULT_REPLY = "Sorry but I didn't understand you." +PLUGINS = [ + 'app', +] From 77ec9341bd8593a842f98a5bcb44ab4132e9cbce Mon Sep 17 00:00:00 2001 From: Ryan Marvin Date: Mon, 1 May 2017 01:51:50 +0300 Subject: [PATCH 3/4] [Feature] Add everything --- app/__init__.py | 2 + app/core.py | 86 ++++++++++++++++++++++++----- app/handlers.py | 128 +++++++++++++++++++++++++++++++++++++++---- run.py | 2 + slackbot_settings.py | 2 +- 5 files changed, 193 insertions(+), 27 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index f16a1eb..c2fb8ba 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,6 +11,8 @@ chargers = db.chargers macbooks = db.macbooks thunderbolts = db.thunderbolts +lost = db.lost +found = db.found # slack client slack_client = SlackClient(BOT_TOKEN) diff --git a/app/core.py b/app/core.py index d34d6fc..ee28b66 100644 --- a/app/core.py +++ b/app/core.py @@ -1,33 +1,89 @@ -from app import chargers, macbooks, thunderbolts, slack_client +from app import chargers, macbooks, thunderbolts, lost, found, slack_client -def get_charger(charger_id): +def get_equipment(equipment_id, equipment_type): ''' - Get charger from database using id + Get equipment from database ''' - return chargers.find_one({"equipment_id": charger_id}) + equipment = None + if equipment_type in ["mac", "tmac", "macbook"]: + equipment = chargers.find_one({"equipment_id": equipment_id}) + elif equipment_type in ["charger", "charge", "procharger"]: + equipment = macbooks.find_one({"equipment_id": equipment_id}) + elif equipment_type in ["tb", "thunderbolt", "thunder"]: + equipment = thunderbolts.find_one({"equipment_id": equipment_id}) + return equipment -def get_macbook(macbook_id): + +def add_lost_equipment(owner, equipment_lost): ''' - Get charger from database using id + Add a lost item to the database ''' - return macbooks.find_one({"equipment_id": macbook_id}) + if not lost.find_one({"equipment": equipment_lost}): + slack_profile = slack_client.api_call("users.info", + user=owner)['user']["profile"] + + lost_item = { + "equipment": equipment_lost, + "owner": owner, + "email": slack_profile["email"], + "name": '{} {}'.format(slack_profile["first_name"], + slack_profile["last_name"]) + } + lost.insert_one(lost_item) + return True + return False -def get_thunderbolt(thunderbolt_id): +def add_found_equipment(submitter, equipment_found): ''' - Get charger from database using id + Add a found item to the database ''' - return thunderbolts.find_one({"equipment_id": thunderbolt_id}) + if not found.find_one({"equipment": equipment_found}): + slack_profile = slack_client.api_call("users.info", + user=submitter)['user']["profile"] + + found_item = { + "equipment": equipment_found, + "submitter": submitter, + "email": slack_profile["email"], + "name": '{} {}'.format(slack_profile["first_name"], + slack_profile["last_name"]) + } + found.insert_one(found_item) + return True + return False + + +def remove_from_lost(equipment): + lost.remove({"equipment": equipment}) + + +def remove_from_found(equipment): + found.remove({"equipment": equipment}) + + +def search_found_equipment(equipment): + return found.find_one({"equipment": equipment}) + + +def search_lost_equipment(equipment): + return lost.find_one({"equipment": equipment}) + + +def notify_user_equipment_found(submitter, equipment_type): + message = "The user <@{}> found your `{}`".format( + submitter, equipment_type) + slack_client.api_call("chat.postMessage", text=message, channel=submitter) -def build_found_equipment_atachment(equipment, category): +def build_search_reply_atachment(equipment, category): ''' Returns a slack attachment to show a result ''' return [{ - "text": "That {} belongs to {}".format(category, equipment["fellow_name"]), + "text": "That {} belongs to {}".format(category, equipment["fellow_name"]), "fallback": "Equipment ID - {} | Owner - {}".format(equipment["equipment_id"], equipment["fellow_name"]), "color": "#4B719C", "fields": [ @@ -41,18 +97,18 @@ def build_found_equipment_atachment(equipment, category): "value": "{}".format(equipment["fellow_name"]), "short": "true" } - ] + ] }] loading_messages = [ "We're testing your patience.", - "A few bits tried to escape, we're catching them..." + "A few bits tried to escape, we're catching them...", "It's still faster than slacking OPs :stuck_out_tongue_closed_eyes:", "Loading humorous message ... Please Wait", "Firing up the transmogrification device...", "Time is an illusion. Loading time doubly so.", "Slacking OPs for the information, this could take a while...", "Loading completed. Press F13 to continue.", - "Looks like someone's been careless again :face_with_rolling_eyes:..." + "Oh boy, more work! :face_with_rolling_eyes:..." ] diff --git a/app/handlers.py b/app/handlers.py index 743a0fa..6273688 100644 --- a/app/handlers.py +++ b/app/handlers.py @@ -4,33 +4,139 @@ import time import json from slackbot.bot import respond_to -from app.core import get_charger, get_macbook, get_thunderbolt, loading_messages, build_found_equipment_atachment +from app.core import get_equipment, loading_messages, build_search_reply_atachment, add_lost_equipment, search_found_equipment, remove_from_lost, search_lost_equipment, add_found_equipment, notify_user_equipment_found, remove_from_found @respond_to('hello$|hi$|hey$|aloha$|bonjour$', re.IGNORECASE) def hello_reply(message): time.sleep(1) - message.reply('Bonjour and howdy stranger. What can I do you for?') + message.reply('Hello stranger. What can I do you for?') -@respond_to('(tb|thunderbolt|thunder).*?(\d+)', re.IGNORECASE) -def find_thunderbolt(message, equipment_type, equipment_id): +@respond_to('love', re.IGNORECASE) +def love_reply(message): + love_replies = [ + "I know.", ":heart:", "I like you as a friend", + "So does my cousin @slackbot.", "That’s weird. Why do you love 'U'", + "OK, what do you need?" + ] + time.sleep(1) + message.reply(random.choice(love_replies)) + + +@respond_to('thanks|thank', re.IGNORECASE) +def gratitude_reply(message): + time.sleep(1) + message.reply("No problemo Guillermo") + + +@respond_to("(find|get|search|retrieve) (mac|tmac|macbook|charger|charge|procharger|tb|thunderbolt|thunder).*?(\d+)", re.IGNORECASE) +def find_equipment(message, command, equipment_type, equipment_id): + time.sleep(1) message.reply(random.choice(loading_messages)) time.sleep(2) attachments = [] - # get thunderbolt from db - thunderbolt = get_thunderbolt(int(equipment_id)) + equipment_type = equipment_type.strip().lower() + print equipment_type + # get equipment from db + equipment = get_equipment(int(equipment_id), equipment_type) - if thunderbolt: + if equipment: attachments.extend( - build_found_equipment_atachment(thunderbolt, - "thunderbolt")) + build_search_reply_atachment(equipment, + "item")) time.sleep(1) print attachments message.send_webapi('', json.dumps(attachments)) return else: time.sleep(1) - message.reply("We were unable to find a " - "thunderbolt by the id {} :zap:".format(equipment_id)) + message.reply("We were unable to find an " + "item by the id {} :snowman_without_snow:".format(equipment_id)) + + +@respond_to("lost (mac|tmac|macbook|charger|charge|procharger|tb|thunderbolt|thunder).*?(\d+)") +def report_lost(message, equipment_type, equipment_id): + ''' + Report an item has been lost + ''' + # get equipment from db + equipment = get_equipment(int(equipment_id), equipment_type) + + if equipment: + owner = message.body['user'] + + # try to find if equipment is in the found collection before adding it + found_equipment = search_found_equipment(equipment) + if found_equipment: + time.sleep(1) + message.reply("Woohoo!:tada: The user <@{}> reported they " + "found your {}.\n" + "We're marking this item as found.".format( + found_equipment['submitter'], + equipment_type)) + remove_from_found(found_equipment) + return + else: + if add_lost_equipment(owner, equipment): + time.sleep(1) + message.reply("Added `{}-{}` to our database. We'll slack you " + "in case anyone finds it " + ":envelope_with_arrow:".format(equipment_type, + equipment_id)) + else: + time.sleep(1) + message.reply("The item `{0}-{1}` is already in our " + "database. Send `found {0} {1}` " + "if you meant to report you found it.".format( + equipment_type, + equipment_id)) + else: + time.sleep(1) + message.reply("That item doesn't exist in our database " + "and thus can't be reported as lost.") + + +@respond_to("found (mac|tmac|macbook|charger|charge|procharger|tb|thunderbolt|thunder).*?(\d+)") +def submit_found(message, equipment_type, equipment_id): + ''' + Submit a found item + ''' + # get equipment from db + equipment = get_equipment(int(equipment_id), equipment_type) + + if equipment: + submitter = message.body['user'] + # check if item is in lost collection + equipment_in_lost = search_lost_equipment(equipment) + + if equipment_in_lost: + time.sleep(1) + notify_user_equipment_found(submitter, equipment_type) + message.reply("Woohoo!:tada: We've notified the owner <@{}> " + "that you found their {}.\nI would pat your " + "back if I had any hands." + " Keep being awesome :clap:".format(equipment_in_lost["owner"], + equipment_type) + ) + remove_from_lost(equipment_in_lost) + else: + if add_found_equipment(submitter, equipment): + time.sleep(1) + message.reply("Added `{}-{}` to our database. We'll slack the " + "owner when they report it missing. " + ":outbox_tray:. Thank you".format(equipment_type, + equipment_id)) + else: + time.sleep(1) + message.reply("The item `{0}-{1}` is already in our " + "database. Send `lost {0} {1}` " + "if you meant to report you lost it.".format( + equipment_type, + equipment_id)) + + else: + time.sleep(1) + message.reply("That item doesn't exist in our database " + "and thus can't be reported as found.") diff --git a/run.py b/run.py index 92e3e47..1eb6166 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,5 @@ from slackbot.bot import Bot +import logging def main(): @@ -7,4 +8,5 @@ def main(): if __name__ == "__main__": + logging.basicConfig() main() diff --git a/slackbot_settings.py b/slackbot_settings.py index d384bc9..273545c 100644 --- a/slackbot_settings.py +++ b/slackbot_settings.py @@ -3,7 +3,7 @@ API_TOKEN = BOT_TOKEN ERRORS_TO = 'ryan-marvin' -DEFAULT_REPLY = "Sorry but I didn't understand you." +DEFAULT_REPLY = "Sorry but I didn't understand you. Type `help` for assistance" PLUGINS = [ 'app', ] From 1a065d1460bf33441023ee2f36a9f63454bb53df Mon Sep 17 00:00:00 2001 From: Ryan Marvin Date: Mon, 1 May 2017 01:54:04 +0300 Subject: [PATCH 4/4] [Chore] Add Procfile and update requirements --- Procfile | 1 + requirements.txt | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..c747d05 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python run.py diff --git a/requirements.txt b/requirements.txt index aa9cdb3..c7e19b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,18 +2,26 @@ appdirs==1.4.3 asn1crypto==0.22.0 cffi==1.10.0 cryptography==1.8.1 +enum34==1.1.6 google-api-python-client==1.6.2 gspread==0.6.2 httplib2==0.10.3 idna==2.5 +ipaddress==1.0.18 +logging==0.4.9.6 oauth2client==4.0.0 packaging==16.8 pyasn1==0.2.3 pyasn1-modules==0.0.8 pycparser==2.17 +pymongo==3.4.0 pyOpenSSL==17.0.0 pyparsing==2.2.0 requests==2.13.0 rsa==3.4.2 six==1.10.0 +slackbot==0.4.1 +slackclient==1.0.5 +slacker==0.9.42 uritemplate==3.0.0 +websocket-client==0.40.0