init
parent
4f47dd1429
commit
92fba1ac81
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 lza_menace
|
||||
|
||||
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.
|
||||
|
@ -0,0 +1,9 @@
|
||||
setup:
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
|
||||
dev:
|
||||
./bin/dev_app
|
||||
|
||||
prod:
|
||||
./bin/prod_app
|
@ -1,2 +1,6 @@
|
||||
# slackbkpy
|
||||
Block kit implementation in Python
|
||||
Block kit implementation in Python
|
||||
|
||||
## Setup
|
||||
|
||||
`make setup`
|
||||
|
@ -0,0 +1,194 @@
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
from logging.config import dictConfig
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.signature import SignatureVerifier
|
||||
from slack_sdk.errors import SlackApiError
|
||||
from slugify import slugify
|
||||
from flask import Flask, request, make_response
|
||||
from app.ux import SlackInterface
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
client = WebClient(token=os.getenv('SLACK_API_TOKEN'))
|
||||
signature_verifier = SignatureVerifier(os.getenv('SLACK_SIGNING_SECRET'))
|
||||
# client.api_test()
|
||||
|
||||
dictConfig({
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'formatters': {
|
||||
'default': {
|
||||
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
|
||||
},
|
||||
'access': {
|
||||
'format': '%(message)s',
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'default',
|
||||
'stream': 'ext://sys.stdout',
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'gunicorn.error': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'gunicorn.access': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
}
|
||||
},
|
||||
'root': {
|
||||
'level': 'DEBUG',
|
||||
'handlers': ['console'],
|
||||
}
|
||||
})
|
||||
|
||||
def verify_slack(req: request):
|
||||
signature_verifier = SignatureVerifier(os.getenv('SLACK_SIGNING_SECRET'))
|
||||
if not signature_verifier.is_valid_request(req.get_data(), req.headers):
|
||||
return make_response('invalid request', 403)
|
||||
|
||||
@app.route('/slack/command', methods=['POST'])
|
||||
def slack_command():
|
||||
verify_slack(request)
|
||||
logging.info(request.form)
|
||||
client.views_open(
|
||||
trigger_id=request.form.get('trigger_id'),
|
||||
view=SlackInterface().select_action_modal()
|
||||
)
|
||||
return make_response('', 200)
|
||||
|
||||
|
||||
@app.route('/slack/events', methods=['POST'])
|
||||
def slack_events():
|
||||
verify_slack(request)
|
||||
if 'payload' in request.form:
|
||||
payload = json.loads(request.form.get('payload'))
|
||||
print('debug: %s \n' % payload)
|
||||
|
||||
if 'actions' in payload:
|
||||
action_id = payload['actions'][0]['action_id']
|
||||
trigger_id = payload['trigger_id']
|
||||
|
||||
if action_id == 'create_new_channel_modal':
|
||||
client.views_push(
|
||||
trigger_id=trigger_id,
|
||||
view=SlackInterface().new_channel_modal(payload['user']['id'])
|
||||
)
|
||||
elif action_id == 'generate_documents_modal':
|
||||
client.views_push(
|
||||
trigger_id=trigger_id,
|
||||
view=SlackInterface().generate_docs_modal()
|
||||
)
|
||||
|
||||
return make_response('', 200)
|
||||
|
||||
elif 'view' in payload:
|
||||
|
||||
callback_id = payload['view']['callback_id']
|
||||
|
||||
if callback_id == 'submit_new_channel':
|
||||
users_to_add = list()
|
||||
values = payload['view']['state']['values']
|
||||
cx_name = values['customer_name']['customer_name']['value']
|
||||
cx_slug = slugify(cx_name)
|
||||
selected_users = values['users_to_add']['users_to_add']['selected_users']
|
||||
for i in selected_users:
|
||||
username = client.users_info(user=i)
|
||||
users_to_add.append(username['user']['name'])
|
||||
|
||||
# Create channel
|
||||
try:
|
||||
res = client.conversations_create(name=cx_slug)
|
||||
# Notify da peeps
|
||||
client.chat_postMessage(
|
||||
channel=res['channel']['id'],
|
||||
text=f'sup bros! {" ".join(["@" + i for i in users_to_add])}'
|
||||
)
|
||||
return make_response('', 200)
|
||||
except SlackApiError as e:
|
||||
logging.error("Error creating conversation: {}".format(e))
|
||||
return make_response('', 403)
|
||||
|
||||
elif callback_id == 'generate_documents':
|
||||
pass
|
||||
else:
|
||||
return make_response('', 404)
|
||||
|
||||
|
||||
#
|
||||
# // Handle actions based upon user selection and inputs
|
||||
# if ( body.view.callback_id == 'submit_new_channel' ) {
|
||||
# console.log('[+] Creating new channel for @' + body.user.username);
|
||||
#
|
||||
# // Gather vars and setup slug from customer name
|
||||
# let cx_name = body.view.state.values.customer_name.customer_name.value;
|
||||
# let cx_char = cx_name.charAt(0);
|
||||
# let cx_slug = slugify(cx_name, {
|
||||
# strict: true,
|
||||
# lower: true
|
||||
# });
|
||||
#
|
||||
# // Check if first character is a number so it can go into numeric group
|
||||
# if ( !isNaN(cx_char) ) {
|
||||
# var gdrive_prefix = '0-9';
|
||||
# } else {
|
||||
# var gdrive_prefix = cx_char.toUpperCase();
|
||||
# }
|
||||
#
|
||||
# // Create users array to add to channel
|
||||
# const users = body.view.state.values.users_to_add.users_to_add.selected_users.map(async function(item) {
|
||||
# var result = await api.callSlackAPI('users.info', {
|
||||
# user: item
|
||||
# });
|
||||
# let slack_username = '@' + result.user.name;
|
||||
# return slack_username
|
||||
# });
|
||||
#
|
||||
# await Promise.all(users).then(async function(result) {
|
||||
# // Post to Zapier to run Zap to create new channel
|
||||
# await api.postZapierWebhook(process.env.ZAPIER_WEBHOOK_submit_new_channel, {
|
||||
# 'customer_name': cx_name,
|
||||
# 'gdrive_prefix': gdrive_prefix,
|
||||
# 'slack_channel': cx_slug,
|
||||
# 'users': result
|
||||
# });
|
||||
# })
|
||||
# } else if ( body.view.callback_id == 'generate_documents' ) {
|
||||
# console.log('[+] Generating documents for @' + body.user.username);
|
||||
#
|
||||
# // Gather vars and setup slug from customer name
|
||||
# let cx_name = body.view.state.values.customer_name.customer_name.value;
|
||||
# let opp_name = body.view.state.values.opportunity_name.opportunity_name.value;
|
||||
# let cx_char = cx_name.charAt(0);
|
||||
#
|
||||
# // Check if first character is a number so it can go into numeric group
|
||||
# if ( !isNaN(cx_char) ) {
|
||||
# var gdrive_prefix = '0-9';
|
||||
# } else {
|
||||
# var gdrive_prefix = cx_char.toUpperCase();
|
||||
# }
|
||||
#
|
||||
# // Post to Zapier to run Zap to generate new docs in the channel
|
||||
# await api.postZapierWebhook(process.env.ZAPIER_WEBHOOK_generate_documents, {
|
||||
# 'customer_name': cx_name,
|
||||
# 'gdrive_prefix': gdrive_prefix,
|
||||
# 'gdrive_item': cx_name + ' - ' + opp_name,
|
||||
# 'slack_channel': body.view.state.values.channel_to_post_to.channel_to_post_to.selected_channels[0],
|
||||
# });
|
||||
# }
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
@ -0,0 +1,54 @@
|
||||
from __future__ import print_function
|
||||
import pickle
|
||||
import os.path
|
||||
from googleapiclient.discovery import build
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from google.auth.transport.requests import Request
|
||||
|
||||
# If modifying these scopes, delete the file token.pickle.
|
||||
SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly']
|
||||
|
||||
def gdrive_client():
|
||||
creds = None
|
||||
# The file token.pickle stores the user's access and refresh tokens, and is
|
||||
# created automatically when the authorization flow completes for the first
|
||||
# time.
|
||||
if os.path.exists('token.pickle'):
|
||||
with open('token.pickle', 'rb') as token:
|
||||
creds = pickle.load(token)
|
||||
# If there are no (valid) credentials available, let the user log in.
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
flow = InstalledAppFlow.from_client_secrets_file(
|
||||
'credentials.json',
|
||||
SCOPES
|
||||
)
|
||||
creds = flow.run_local_server(port=0)
|
||||
# Save the credentials for the next run
|
||||
with open('token.pickle', 'wb') as token:
|
||||
pickle.dump(creds, token)
|
||||
service = build('drive', 'v3', credentials=creds)
|
||||
return service
|
||||
|
||||
|
||||
def get_files():
|
||||
service = gdrive_client()
|
||||
# Call the Drive v3 API
|
||||
results = service.files().list(
|
||||
pageSize=10,
|
||||
spaces='Customers (Clients)'
|
||||
fields="nextPageToken, files(id, name)"
|
||||
).execute()
|
||||
items = results.get('files', [])
|
||||
|
||||
if not items:
|
||||
print('No files found.')
|
||||
else:
|
||||
print('Files:')
|
||||
for item in items:
|
||||
print(u'{0} ({1})'.format(item['name'], item['id']))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,163 @@
|
||||
class SlackInterface(object):
|
||||
def select_action_modal(self):
|
||||
return {
|
||||
'type': 'modal',
|
||||
'title': {
|
||||
'type': 'plain_text',
|
||||
'text': 'What do you want to do?'
|
||||
},
|
||||
'callback_id': 'select-action',
|
||||
'submit': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Submit'
|
||||
},
|
||||
'blocks': [
|
||||
{
|
||||
'type': 'section',
|
||||
'text': {
|
||||
'type': 'mrkdwn',
|
||||
'text': 'Create new Slack channel for opportunity.'
|
||||
},
|
||||
'accessory': {
|
||||
'type': 'button',
|
||||
'text': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Go'
|
||||
},
|
||||
'action_id': 'create_new_channel_modal'
|
||||
}
|
||||
},
|
||||
{
|
||||
'type': 'section',
|
||||
'text': {
|
||||
'type': 'mrkdwn',
|
||||
'text': 'Generate new documents for opportunity.'
|
||||
},
|
||||
'accessory': {
|
||||
'type': 'button',
|
||||
'text': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Go'
|
||||
},
|
||||
'action_id': 'generate_documents_modal'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def new_channel_modal(self, initial_user=None):
|
||||
initial_users = list()
|
||||
if initial_user:
|
||||
initial_users.append(initial_user)
|
||||
return {
|
||||
'type': 'modal',
|
||||
'title': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Create new channel'
|
||||
},
|
||||
'callback_id': 'submit_new_channel',
|
||||
'submit': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Submit'
|
||||
},
|
||||
'blocks': [
|
||||
{
|
||||
'block_id': 'customer_name',
|
||||
'type': 'input',
|
||||
'label': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Customer Name'
|
||||
},
|
||||
'element': {
|
||||
'action_id': 'customer_name',
|
||||
'type': 'plain_text_input'
|
||||
},
|
||||
'hint': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Please use full spelling of the company'
|
||||
}
|
||||
},
|
||||
{
|
||||
'block_id': 'users_to_add',
|
||||
'type': 'input',
|
||||
'label': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Select Users to Add'
|
||||
},
|
||||
'element': {
|
||||
'action_id': 'users_to_add',
|
||||
'type': 'multi_users_select',
|
||||
'initial_users': initial_users
|
||||
},
|
||||
'hint': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Include others who will need to be present'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def generate_docs_modal(self):
|
||||
return {
|
||||
'type': 'modal',
|
||||
'title': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Generate Documents'
|
||||
},
|
||||
'callback_id': 'generate_documents',
|
||||
'submit': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Submit'
|
||||
},
|
||||
'blocks': [
|
||||
{
|
||||
'block_id': 'customer_name',
|
||||
'type': 'input',
|
||||
'label': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Customer Name'
|
||||
},
|
||||
'element': {
|
||||
'action_id': 'customer_name',
|
||||
'type': 'plain_text_input'
|
||||
},
|
||||
'hint': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Name of the customer ie. ACME Corp, Fancy Corp LLC, Such and Such Enterprises, etc'
|
||||
}
|
||||
},
|
||||
{
|
||||
'block_id': 'opportunity_name',
|
||||
'type': 'input',
|
||||
'label': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Opportunity Name'
|
||||
},
|
||||
'element': {
|
||||
'action_id': 'opportunity_name',
|
||||
'type': 'plain_text_input'
|
||||
},
|
||||
'hint': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Name of the opportunity, ie. MD40, PS k8s advisory/consulting, CloudOne, etc'
|
||||
}
|
||||
},
|
||||
{
|
||||
'block_id': 'channel_to_post_to',
|
||||
'type': 'input',
|
||||
'label': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Select channel to notify'
|
||||
},
|
||||
'element': {
|
||||
'action_id': 'channel_to_post_to',
|
||||
'type': 'multi_channels_select',
|
||||
'max_selected_items': 1
|
||||
},
|
||||
'hint': {
|
||||
'type': 'plain_text',
|
||||
'text': 'Select the channel to notify when documents are generated'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=app/app.py
|
||||
export FLASK_DEBUG=0
|
||||
export FLASK_ENV=production
|
||||
flask $@
|
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=app/app.py
|
||||
export FLASK_DEBUG=1
|
||||
export FLASK_ENV=development
|
||||
flask run
|
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
BASE=data/gunicorn
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=app/app.py
|
||||
export FLASK_DEBUG=0
|
||||
export FLASK_ENV=production
|
||||
|
||||
mkdir -p $BASE
|
||||
|
||||
kill $(cat $BASE/gunicorn.pid) 2>&1
|
||||
|
||||
sleep 2
|
||||
|
||||
gunicorn \
|
||||
--bind 127.0.0.1:4000 "app.app:app" \
|
||||
--daemon \
|
||||
--log-file $BASE/gunicorn.log \
|
||||
--pid $BASE/gunicorn.pid \
|
||||
--access-logfile $BASE/access.log \
|
||||
--capture-output \
|
||||
--reload
|
||||
|
||||
sleep 2
|
||||
|
||||
echo "Starting gunicorn with pid $(cat $BASE/gunicorn.pid)"
|
@ -0,0 +1,4 @@
|
||||
SLACK_ACCESS_TOKEN=
|
||||
SLACK_SIGNING_SECRET=
|
||||
ZAPIER_WEBHOOK_submit_new_channel=
|
||||
ZAPIER_WEBHOOK_generate_documents=
|
@ -0,0 +1,7 @@
|
||||
Flask
|
||||
gunicorn
|
||||
slack_sdk
|
||||
python-slugify
|
||||
google-api-python-client
|
||||
google-auth-httplib2
|
||||
google-auth-oauthlib
|
Loading…
Reference in New Issue