diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b85207 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +docker/dev-env.rc +__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..db372c2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ + +# Dockerfile References: https://docs.docker.com/engine/reference/builder/ + +# Start from the latest golang base image +FROM python:3.7-slim + +COPY requirements.txt . + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN pip install -r ./requirements.txt + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Set the Current Working Directory inside the container +WORKDIR / + +# Expose port 8000 to the outside world +EXPOSE 8000 + +# Command to run the executable +CMD python ./bot/bot.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6bee764 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +DOCKER_IMAGE ?= slackbot +DOCKER_CONTAINER ?= slackbot +HIDE ?= @ +PORT ?= 8000 +HOSTPORT ?= 8000 +NETWORK ?= bridge + + +build: + $(HIDE)docker build -f Dockerfile -t $(DOCKER_IMAGE) $(PWD) + +start: + $(HIDE)docker-compose -f docker/docker-compose.yml up --build $(DOCKER_CONTAINER) + +daemon: + $(HIDE)docker-compose -f docker/docker-compose.yml up -d --build $(DOCKER_CONTAINER) + +stop: + $(HIDE)docker stop $(DOCKER_CONTAINER) + $(HIDE)docker container rm $(DOCKER_CONTAINER) + +rm: + $(HIDE)docker rm $(docker ps -a -q) + +rm-all: + $(HIDE)docker system prune diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..d9d8568 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,96 @@ +import slack +import requests +import json +import os + +import workspaceHandlers + +# TODO load this from .env docker file +client = slack.WebClient(token=os.environ['VERIFICATION_TOKEN']) +API_DEV_ENDPOINT = 'http://0.0.0.0:8080' +HEADERS = {'Content-Type': 'application/json'} + + +'''Member will hold all data needed for full member creation +Note that this will also include Discord ID as well + but for now it will be left blank? +''' +class Member: + def __init__(self, slackID, slackName, discordID=''): + self.slackID = slackID + self.slackName = slackName + self.discordID = discordID + + def toJSON(self): + dvcMemberStruct = { + 'Name': self.slackName, + 'SlackUserID': self.slackID, + 'DiscordUserID': '', + 'CreatedFrom': '', + } + jsonData = json.dumps(dvcMemberStruct) + return jsonData + + +def postChatMessage(message): + client.chat_postMessage(channel='CT1R99NER', + text=message) + +# Gets master json response of all users in slack workspace +def getAllSlackUsers(): + members = client.users_list() + return members['members'] + +def getSlackUserIDs(): + slackUsers = getAllSlackUsers() + slackIDs = set() + for id in slackUsers: + slackIDs.add(id['id']) + return slackIDs + +def getSlackUserNames(): + slackUsers = getAllSlackUsers() + slackFullNames = set() + for name in slackUsers: + slackFullNames.add(name['name']) + return slackFullNames + +# Will send relevant data to create member in backend dvc api +# also note that discord data will be left empty for now +def sendNamesToDVC_API(): + #slackUserIDs = getSlackUserIDs() + slackUserNames = getSlackUserNames() + + #for user_id in slackUserIDs: + # for name in slackUserNames: + # dvcMember = Member(user_id, name) + # memberJSON = json.dumps(dvcMember.toJSON()) + # apiReq = requests.post(API_DEV_ENDPOINT + '/member/create', + # data=memberJSON, + # headers=HEADERS) + # print(apiReq.text) + #apiReq = requests.get(API_DEV_ENDPOINT + '/name/show') + jsonData = {'name': 'hennyg#'} + apiReq = requests.post(API_DEV_ENDPOINT + '/name/create', + data=jsonData, + headers=HEADERS) + print(apiReq.text) + print(slackUserNames) + + +if __name__ == "__main__": + # sendNamesToDVC_API() + hireoutEvent1 = workspaceHandlers.Hireout( + "#slackbot-test", + "Sungod hireout created", + "March 15th", + "Full DJ Packake" + ) + + officeGear1 = workspaceHandlers.OfficeEquipmentTracker( + hireoutEvent1, + "In office", + "Pioneer CDJ Nexus 2", + "CDJ Player" + ) + officeGear1.sendMessageJSON() diff --git a/bot/jsonModels.py b/bot/jsonModels.py new file mode 100644 index 0000000..b828ab9 --- /dev/null +++ b/bot/jsonModels.py @@ -0,0 +1,131 @@ + + +# (1) new hireout notification +def makeNotifTitleBlock(titleText): + notification_title_block = { + "type": "section", + "text": { + "type": "mrkdwn", + "text": titleText + } + } + return notification_title_block + +def makeTextBlock(text): + text_body_block = { + "type": "section", + "text": { + "type": "mrkdwn", + "text": text + } + } + +def makeNotifBodyBlock(theme, time, notes, package, djs): + notification_body_block = { + "type": + "section", + "fields": [{ + "type": "mrkdwn", + "text": "*Theme:*\n" + theme + }, { + "type": "mrkdwn", + "text": "*When:*\n" + time + }, { + "type": "mrkdwn", + "text": "*Notes:*\n" + notes + }, { + "type": "mrkdwn", + "text": "*Package:*\n" + package + }, { + "type": "mrkdwn", + "text": "*DJs:*\n" + djs + }] + } + return notification_body_block + + +# TODO Need to hook this up to api call to golang server +def makeEditButton(style="primary"): + edit_button_block = { + "type": "button", + "text": { + "type": "plain_text", + "text": "Edit" + }, + "style": style, + "value": "click_me_123" + } + return edit_button_block + + +def makeActionBlocks(): + actions = { + "type": + "actions", + "elements": [ + makeEditButton() + ], + } + return actions + + +# Static select dropdown menu +def makeDropDownMenu(gearText): + actions = { + "type": "section", + "text": { + "type": "mrkdwn", + "text": gearText, + }, + "accessory": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Manage Gear" + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "DEFAULT_TEXT" + }, + "value": "value-0" + } + ] + } + } + return actions + +def makeSignupSection(djSetTime=""): + signupArea = { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*{}*".format(djSetTime) + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "Choose" + }, + "value": "click_me_123" + } + } + return signupArea + + +def makeSpacer(): + divider = { + "type": "divider" + } + return divider + + +def insertImageHorizontally(img_url): + image = { + "type": "image", + "image_url": img_url, + "alt_text": "calendar image link" + } + return image diff --git a/bot/workspaceHandlers.py b/bot/workspaceHandlers.py new file mode 100644 index 0000000..6b9809c --- /dev/null +++ b/bot/workspaceHandlers.py @@ -0,0 +1,160 @@ +import os +import logging +from flask import Flask +from slack import WebClient +from slackeventsapi import SlackEventAdapter +import ssl as ssl_lib +import certifi +import json + +import jsonModels + +app = Flask(__name__) +SLACK_EVENTS_ADAPTER = SlackEventAdapter(os.environ['SLACK_SIGNING_SECRET'], + '/slack/events', app) +SLACK_WEB_CLIENT = WebClient(token=os.environ['VERIFICATION_TOKEN']) + + +class Hireout: + def __init__(self, + channel, + hireoutTitleText, + when, + package, + djs='na', + theme='na', + notes='na'): + self.channel = channel + self.hireoutTitleText = hireoutTitleText + self.when = when + self.package = package + self.djs = djs + self.theme = theme + self.notes = notes + + # main json body that'll encapsulate the data + self.hireout_created_message = [] + + + def sendStaticHireoutMessage(self): + self._buildStaticMessageJSON() + finalData = json.dumps(self.hireout_created_message, sort_keys=True, indent=4) + SLACK_WEB_CLIENT.chat_postMessage(channel=self.channel, + blocks=finalData + ) + + def djSignup(self): + pass + + def _buildStaticMessageJSON(self): + self.hireout_created_message.append( + jsonModels.makeNotifTitleBlock(self.hireoutTitleText) + ) + self.hireout_created_message.append( + jsonModels.makeNotifBodyBlock(self.theme, + self.when, + self.notes, + self.package, + self.djs + ) + ) + self.hireout_created_message.append( + jsonModels.makeActionBlocks() + ) + + def sendHireoutSignupMessage(self): + self._buildDynamicMessageJSON() + finalData = json.dumps(self.hireout_created_message, + sort_keys=True, + indent=4 + ) + SLACK_WEB_CLIENT.chat_postMessage(channel=self.channel, + blocks=finalData + ) + + + def _buildDynamicMessageJSON(self): + self.hireout_created_message.append( + jsonModels.makeNotifTitleBlock(self.hireoutTitleText) + ) + self.hireout_created_message.append( + jsonModels.makeSpacer() + ) + self.hireout_created_message.append( + jsonModels.makeNotifBodyBlock( + self.theme, + self.when, + self.notes, + self.package, + self.djs + ) + ) + self.hireout_created_message.append( + jsonModels.makeSpacer() + ) + # Sign up for dj time slots + self.hireout_created_message.append( + jsonModels.makeNotifTitleBlock("*Sign up for a time to spin!*") + ) + self.hireout_created_message.append( + jsonModels.makeSpacer() + ) + self.hireout_created_message.append( + jsonModels.makeSignupSection("4-6pm") + ) + + + +class OfficeEquipmentTracker: + ''' + @args: + - Hireout reference + - state: list that contains + [at hireout, in maitnence, in office, sell it] + can only be in one state at a time + NOTE: would be better represented in an Enum... refractor later + - gearName: ie "Pioneer CDJ Nexus 2"... + - description: any other misc notes about the gear + ''' + def __init__(self, hireout, state, gearName, description): + self.hireout = hireout + self.state = state + self.gearName = gearName + self.description = description + + # make api call to golang server to grab all current gear in office + self.allGear = [] + + # final json to send to slack + self.listAllGearJSON = [] + + # Set channel to whatever hireout it attaches itself to + self.channel = hireout.channel + + def listAllOfficeEquipment(self): + pass + + def sendMessageJSON(self): + self._buildGearJSON() + finalData = json.dumps(self.listAllGearJSON, + sort_keys=True, + indent=4 + ) + SLACK_WEB_CLIENT.chat_postMessage(channel=self.channel, + blocks=finalData + ) + + def _buildGearJSON(self): + # title + self.listAllGearJSON.append( + jsonModels.makeNotifTitleBlock("All current gear in the office") + ) + self.listAllGearJSON.append(jsonModels.makeSpacer()) + + # Gear list with dropdown menu to change its state + self.listAllGearJSON.append( + jsonModels.makeDropDownMenu(self.gearName) + ) + self.listAllGearJSON.append(jsonModels.makeSpacer()) + + diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..73e52b5 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3" +services: + slackbot: + build: + context: .. + container_name: slackbot + ports: + - "8000:8000" + tty: true + env_file: + - ${PWD}/docker/dev-env.rc diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d734e08 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +slackclient>=2.0.0 +slackeventsapi>=2.1.0 +Flask>=1.1.1