diff --git a/.gitignore b/.gitignore index 4e37f50..10ff706 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +credentials +config.py + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] 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/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..c2fb8ba --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,18 @@ +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 +lost = db.lost +found = db.found + +# slack client +slack_client = SlackClient(BOT_TOKEN) diff --git a/app/core.py b/app/core.py new file mode 100644 index 0000000..ee28b66 --- /dev/null +++ b/app/core.py @@ -0,0 +1,114 @@ +from app import chargers, macbooks, thunderbolts, lost, found, slack_client + + +def get_equipment(equipment_id, equipment_type): + ''' + Get equipment from database + ''' + 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 add_lost_equipment(owner, equipment_lost): + ''' + Add a lost item to the database + ''' + 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 add_found_equipment(submitter, equipment_found): + ''' + Add a found item to the database + ''' + 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_search_reply_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.", + "Oh boy, more work! :face_with_rolling_eyes:..." +] diff --git a/app/handlers.py b/app/handlers.py new file mode 100644 index 0000000..6273688 --- /dev/null +++ b/app/handlers.py @@ -0,0 +1,142 @@ +# coding: UTF-8 +import re +import random +import time +import json +from slackbot.bot import respond_to +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('Hello stranger. What can I do you for?') + + +@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 = [] + equipment_type = equipment_type.strip().lower() + print equipment_type + # get equipment from db + equipment = get_equipment(int(equipment_id), equipment_type) + + if equipment: + attachments.extend( + 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 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/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/app/sprawler/worker.py b/app/sprawler/worker.py new file mode 100644 index 0000000..7171aaf --- /dev/null +++ b/app/sprawler/worker.py @@ -0,0 +1,82 @@ +import os +import gspread +from oauth2client.service_account import ServiceAccountCredentials +from pymongo import MongoClient +import pymongo + +sheet_list = [] +# json credentials you downloaded earlier +rel_path = '/credentials/sakabot-cred.json' +home_dir = os.path.dirname(os.path.abspath(__file__)) +CLIENT_SECRET_FILE = home_dir + rel_path +SCOPE = ['https://spreadsheets.google.com/feeds'] + +# get email and key from creds +cred = ServiceAccountCredentials.from_json_keyfile_name(CLIENT_SECRET_FILE, + SCOPE) + +gsheet = gspread.authorize(cred) # authenticate with Google +master_sheet = gsheet.open_by_key( + "1lJ4VUcP1t7kXZNtdVBlfgNDpXVrg--vSlA0iL7H5b4E") # open sheet + +macbook_sheet = master_sheet.get_worksheet(0) +charger_sheet = master_sheet.get_worksheet(1) +thunderbolt_sheet = master_sheet.get_worksheet(2) + +macbook_data = macbook_sheet.get_all_records() +charger_data = charger_sheet.get_all_records() +thunderbolt_data = thunderbolt_sheet.get_all_records() + +macbook_search_data = [] +charger_search_data = [] +thunderbolt_search_data = [] + + +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": int(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": int(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": int(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 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/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 diff --git a/run.py b/run.py new file mode 100644 index 0000000..1eb6166 --- /dev/null +++ b/run.py @@ -0,0 +1,12 @@ +from slackbot.bot import Bot +import logging + + +def main(): + bot = Bot() + bot.run() + + +if __name__ == "__main__": + logging.basicConfig() + main() diff --git a/slackbot_settings.py b/slackbot_settings.py new file mode 100644 index 0000000..273545c --- /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. Type `help` for assistance" +PLUGINS = [ + 'app', +] diff --git a/sprawler/worker.py b/sprawler/worker.py deleted file mode 100644 index 50fa515..0000000 --- a/sprawler/worker.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import gspread -from oauth2client.service_account import ServiceAccountCredentials - -sheet_list = [] -# json credentials you downloaded earlier -rel_path = '/credentials/sakabot-cred.json' -home_dir = os.path.dirname(os.path.abspath(__file__)) -CLIENT_SECRET_FILE = home_dir + rel_path -SCOPE = ['https://spreadsheets.google.com/feeds'] - -# get email and key from creds -cred = ServiceAccountCredentials.from_json_keyfile_name(CLIENT_SECRET_FILE, - SCOPE) - -gsheet = gspread.authorize(cred) # authenticate with Google -master_sheet = gsheet.open_by_key( - "1lJ4VUcP1t7kXZNtdVBlfgNDpXVrg--vSlA0iL7H5b4E") # open sheet - -macbook_sheet = master_sheet.get_worksheet(0) -charger_sheet = master_sheet.get_worksheet(1) -thunderbolt_sheet = master_sheet.get_worksheet(2) - -macbook_data = macbook_sheet.get_all_records() -charger_data = charger_sheet.get_all_records() -thunderbolt_data = thunderbolt_sheet.get_all_records() - -macbook_search_data = [] -charger_search_data = [] -thunderbolt_search_data = [] - - -def populate_search_data(list_of_dictionaries, search_list): - for records in list_of_dictionaries: - parsed_data = {records['Andela Code']: records['Fellow Name']} - search_list.append(parsed_data) - return search_list