main
lza_menace 3 years ago
parent 4f47dd1429
commit 92fba1ac81

4
.gitignore vendored

@ -127,3 +127,7 @@ dmypy.json
# Pyre type checker
.pyre/
credentials.json
token.pickle

@ -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…
Cancel
Save