Skip to content
This repository was archived by the owner on Jun 19, 2019. It is now read-only.

Commit 78ae14a

Browse files
Merge pull request #18 from AllenAnthes/master
Migration to Events API
2 parents b0222ae + 0b30a2e commit 78ae14a

15 files changed

+734
-76
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
credentials.json
44
creds.py
55

6+
67
# remove pycharm ide files
78
.idea/
89

.travis.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
language: python
2+
sudo: false
3+
os:
4+
- linux
5+
6+
python:
7+
- "3.6"
8+
9+
install:
10+
- pip install -r requirements.txt
11+
12+
script:
13+
- py.test

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
worker: python run.py
File renamed without changes.

archived/working_simple_bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import websocket
77
from urllib3 import disable_warnings, exceptions # allow to disable InsecureRequestWarning, not sure if needed
88

9-
from src.creds import TOKEN # locally saved file "creds.py" this is added to .gitignore
10-
from src.message import new_join
9+
# from src.creds import TOKEN # locally saved file "creds.py" this is added to .gitignore
10+
from archived.message import new_join
1111
from utils.log_manager import setup_logging
1212

1313
# any file can get instance of logger.

requirements.txt

376 Bytes
Binary file not shown.

run.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from src.app import run_bot
3-
3+
from src.flask_endpoint import start_server
44

55
if __name__ == '__main__':
6-
run_bot(delay=1)
6+
# run_bot(delay=1)
7+
start_server()

src/airtable_handling.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import json
2+
from pprint import pprint
3+
4+
import requests
5+
from airtable import Airtable
6+
from decouple import config
7+
8+
BASE_KEY = config('PERSONAL_AIRTABLE_BASE_KEY')
9+
API_KEY = config('PERSONAL_AIRTABLE_TOKEN')
10+
TABLE_NAME = 'Mentor Request'
11+
12+
13+
14+
def get_table(table):
15+
airtable = Airtable(BASE_KEY, table, api_key=API_KEY)
16+
res = airtable.get_all()
17+
pprint(res)
18+
19+
20+
21+
22+
def test():
23+
params = {
24+
'fields': {
25+
'Slack User': 'Allen2',
26+
'Email': '[email protected]',
27+
'Service': ['recBxmDasLXwmVB78'],
28+
'Skillsets': ["Web (Frontend Development)"],
29+
'Additional Details': 'Details'}
30+
}
31+
32+
headers = {
33+
'authorization': "Bearer " + API_KEY
34+
}
35+
res = requests.post("https://api.airtable.com/v0/app2p4KgQKx35WoCm/Mentor%20Request", json=params,
36+
headers=headers)
37+
print(res.content)
38+
39+
40+
if __name__ == '__main__':
41+
# test()
42+
get_table('Services')
43+
44+
45+
services_records = {
46+
'General Guidance - Slack Chat': 'recBxmDasLXwmVB78',
47+
'General Guidance - Voice Chat': 'recDyu4PMbPl7Ti58',
48+
'Pair Programming': 'recHCFAO9uNSy1WDs',
49+
'Code Review': 'recUK55xJXOfAaYNb',
50+
'Resume Review': 'recXZzUduWfaxWvSF',
51+
'Mock Interview': 'recdY4XLeN1CPz1l8'
52+
}

src/app.py

Lines changed: 184 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,223 @@
1+
import json
12
import logging
23
import time
34
from slackclient import SlackClient
45
from utils.log_manager import setup_logging
5-
from src.creds import TOKEN, PROXY
6+
from decouple import config
67
import traceback
8+
from pprint import pprint
9+
import requests
10+
11+
from src.help_menu import HELP_MENU_RESPONSES
12+
from src.messages import *
13+
14+
# from src.airtable_handling import airtable
715

816
logger = logging.getLogger(__name__)
917
new_event_logger = logging.getLogger(f'{__name__}.new_member')
1018
all_event_logger = logging.getLogger(f'{__name__}.all_events')
1119

12-
1320
# constants
14-
MESSAGE = (
15-
"Hi {real_name},\n\n Welcome to Operation Code! I'm a bot designed to help answer questions and get you on your way in our community.\n\n"
16-
"Please take a moment to review our <https://op.co.de/code-of-conduct|Code of Conduct.>\n\n"
17-
"Our goal here at Operation Code is to get veterans and their families started on the path to a career in programming. "
18-
"We do that through providing you with scholarships, mentoring, career development opportunities, conference tickets, and more!\n\n"
19-
"You're currently in Slack, a chat application that serves as the hub of Operation Code. "
20-
"If you're currently visiting us via your browser, Slack provides a stand alone program to make staying in touch even more convenient. "
21-
"You can download it <https://slack.com/downloads|here.>\n\n"
22-
"Want to make your first change to a program right now? "
23-
"All active Operation Code Projects are located on our source control repository. "
24-
"Our projects can be viewed on <https://github.com/OperationCode/START_HERE|Github.>")
25-
26-
PROXY = PROXY if PROXY else None
21+
PROXY = config('PROXY', default=None)
22+
23+
TOKEN = config('PERSONAL_APP_TOKEN')
24+
COMMUNITY_CHANNEL = config('PERSONAL_PRIVATE_CHANNEL')
25+
26+
# TOKEN = config('OPCODE_APP_TOKEN')
27+
# COMMUNITY_CHANNEL = config('OPCODE_REWRITE_CHANNEL')
28+
# PROJECTS_CHANNEL = config('OPCODE_OC_PROJECTS_CHANNEL')
29+
# COMMUNITY_CHANNEL = config('OPCODE_COMMUNITY_ID')
30+
# COMMUNITY_CHANNEL = config('OPCODE_BOT_TESTING_CHANNEL')
31+
32+
"""Airtable configs"""
33+
AIRTABLE_BASE_KEY = config('PERSONAL_AIRTABLE_BASE_KEY')
34+
AIRTABLE_API_KEY = config('PERSONAL_AIRTABLE_TOKEN')
35+
AIRTABLE_TABLE_NAME = 'Mentor Request'
36+
2737
slack_client = SlackClient(TOKEN, proxies=PROXY)
2838

2939

30-
def build_message(message_template, **kwargs):
40+
# TODO: Do something with all of the return values here
41+
42+
def build_message(message_template: str, **kwargs: dict) -> str:
3143
return message_template.format(**kwargs)
3244

3345

34-
def event_handler(event_dict):
46+
def event_handler(event_dict: dict) -> None:
47+
"""
48+
Handles routing all of the received subscribed events to the correct method
49+
:param event_dict:
50+
"""
3551
all_event_logger.info(event_dict)
3652
if event_dict['type'] == 'team_join':
3753
new_event_logger.info('New member event recieved')
3854
new_member(event_dict)
3955

40-
if event_dict['type'] == 'presence_change':
41-
all_event_logger.info('User {} changed state to {}'.format(user_name_from_id(event_dict['user']), event_dict['presence']))
42-
43-
# can be used for development to trigger the event instead of the team_join
44-
if event_dict['type'] == 'message' and 'user' in event_dict.keys():
45-
46-
# Will need to be removed. Currently for testing
47-
logger.info('Message event')
48-
if event_dict['type'] == 'message' and 'user' in event_dict.keys() and event_dict['text'] == 'test4611':
56+
""" Trigger for testing team_join event """
57+
if event_dict['type'] == 'message' and 'user' in event_dict.keys() and event_dict['text'] == 'testgreet':
4958
event_dict['user'] = {'id': event_dict['user']}
5059
new_member(event_dict)
5160

5261

62+
def help_menu_interaction(data: dict) -> None:
63+
"""
64+
Receives help menu selection from the user and dynamically updates
65+
displayed message
66+
:param data:
67+
"""
68+
69+
response = data['actions'][0]['value']
70+
71+
if response == 'suggestion':
72+
trigger_id = data['trigger_id']
73+
res = slack_client.api_call('dialog.open', trigger_id=trigger_id, dialog=SUGGESTION_MODAL)
74+
75+
# Disabled while airtable integration is still in development
76+
# elif response == 'mentor':
77+
# trigger_id = data['trigger_id']
78+
# res = slack_client.api_call('dialog.open', trigger_id=trigger_id, dialog=MENTOR_REQUEST_MODAL)
79+
# pprint(res)
80+
81+
else:
82+
params = {'text': HELP_MENU_RESPONSES[data['actions'][0]['value']],
83+
'channel': data['channel']['id'],
84+
'ts': data['message_ts'],
85+
'as_user': True
86+
}
87+
slack_client.api_call('chat.update', **params)
88+
89+
90+
def greeted_interaction(data: dict) -> dict:
91+
"""
92+
Handles the interactive message sent to the #community channel
93+
when a new member joins.
94+
95+
Displays the user that claimed the greeting along with the option
96+
to un-claim
97+
"""
98+
if data['actions'][0]['value'] == 'greeted':
99+
clicker = data['user']['id']
100+
params = {'text': data['original_message']['text'],
101+
"attachments": greeted_response_attachments(clicker),
102+
'channel': data['channel']['id'],
103+
'ts': data['message_ts'],
104+
'as_user': True
105+
}
106+
res = slack_client.api_call("chat.update", **params)
107+
return res
108+
elif data['actions'][0]['value'] == 'reset_greet':
109+
params = {'text': data['original_message']['text'],
110+
"attachments": needs_greet_button(),
111+
'channel': data['channel']['id'],
112+
'ts': data['message_ts'],
113+
'as_user': True
114+
}
115+
res = slack_client.api_call("chat.update", **params)
116+
117+
118+
def suggestion_submission(data: dict) -> None:
119+
"""
120+
Receives the event when a user submits a suggestion for a new help topic and
121+
posts it to the #community channel
122+
:param data:
123+
"""
124+
suggestion = data['submission']['suggestion']
125+
user_id = data['user']['id']
126+
message = f":exclamation:<@{user_id}> just submitted a suggestion for a help topic:exclamation:\n-- {suggestion}"
127+
res = slack_client.api_call('chat.postMessage', channel=COMMUNITY_CHANNEL, text=message)
128+
53129

54-
def new_member(event_dict):
130+
def mentor_submission(data):
131+
"""
132+
Parses the mentor request dialog form and pushes the data to Airtable.
133+
:param data:
134+
:return:
135+
"""
136+
137+
# Temporary hack. Change this to getting the record ID's from the table itself
138+
services_records = {
139+
'General Guidance - Slack Chat': 'recBxmDasLXwmVB78',
140+
'General Guidance - Voice Chat': 'recDyu4PMbPl7Ti58',
141+
'Pair Programming': 'recHCFAO9uNSy1WDs',
142+
'Code Review': 'recUK55xJXOfAaYNb',
143+
'Resume Review': 'recXZzUduWfaxWvSF',
144+
'Mock Interview': 'recdY4XLeN1CPz1l8'
145+
}
146+
147+
form = data['submission']
148+
params = {
149+
'fields': {
150+
'Slack User': form['Slack User'],
151+
'Email': form['Email'],
152+
'Service': [services_records[form['service']]],
153+
'Skillsets': [form['skillset']],
154+
'Additional Details': form['Additional Details']
155+
}
156+
}
157+
158+
headers = {
159+
'authorization': "Bearer " + AIRTABLE_API_KEY
160+
}
161+
res = requests.post(f"https://api.airtable.com/v0/{AIRTABLE_BASE_KEY}/{AIRTABLE_TABLE_NAME}", json=params,
162+
headers=headers)
163+
164+
165+
def new_member(event_dict: dict) -> None:
166+
"""
167+
Invoked when a new user joins and a team_join event is received.
168+
DMs the new user with the welcome message and help menu as well as pings
169+
the #community channel with a new member notification
170+
:param event_dict:
171+
"""
55172
new_event_logger.info('Recieved json event: {}'.format(event_dict))
56173

57174
user_id = event_dict['user']['id']
58-
# user_id = event_dict['user']
59175
logging.info('team_join message')
60176

61-
custom_message = build_message(MESSAGE,
62-
real_name=user_name_from_id(user_id))
177+
real_name = user_name_from_id(user_id)
63178

179+
custom_message = MESSAGE.format(real_name=real_name)
64180

65-
new_event_logger.info('Built message: {}'.format(event_dict))
181+
new_event_logger.info('Built message: {}'.format(custom_message))
66182
response = slack_client.api_call('chat.postMessage',
67183
channel=user_id,
68-
text=custom_message,
69-
as_user=True)
70-
71-
72-
if response['ok'] == 'true':
73-
new_event_logger.info('New Member Slack response: {}'.format(response))
184+
# channel=COMMUNITY_CHANNEL, # testing option
185+
as_user=True, # Currently not working. DM comes from my account
186+
text=custom_message)
187+
188+
r2 = slack_client.api_call('chat.postMessage',
189+
channel=user_id,
190+
# channel=COMMUNITY_CHANNEL, # testing option
191+
as_user=True,
192+
**HELP_MENU)
193+
194+
# Notify #community
195+
text = f":tada: <@{user_id}> has joined the Slack team :tada:"
196+
slack_client.api_call('chat.postMessage', channel=COMMUNITY_CHANNEL,
197+
text=text, attachments=needs_greet_button())
198+
199+
if response['ok'] and r2['ok']:
200+
new_event_logger.info('New Member Slack response: Response 1: {} \nResponse2: {}'.format(response, r2))
74201
else:
75-
new_event_logger.error('FAILED -- Message to new member returned error: {}'.format(response))
202+
new_event_logger.error('FAILED -- Message to new member returned error: {}\n{}'.format(response, r2))
76203

77204

78-
def parse_slack_output(slack_rtm_output):
205+
def parse_slack_output(slack_rtm_output: list) -> None:
79206
"""
80-
The Slack Real Time Messaging API is an events firehose.
81-
This parsing function returns None unless a message
82-
is directed at the Bot, based on its ID.
207+
Method for parsing slack events when using the RTM API instead
208+
of the Events/App APIs
83209
"""
84210
for output in slack_rtm_output:
85211
# process a single item in list at a time
86212
event_handler(output)
87213

88214

89-
def user_name_from_id(user_id):
90-
# get detailed user info
215+
def user_name_from_id(user_id: str) -> str:
216+
"""
217+
Queries the Slack workspace for the users real name
218+
to personalize messages. Prioritizes real_name -> name -> 'New Member'
219+
:param user_id:
220+
"""
91221
response = slack_client.api_call('users.info', user=user_id)
92222

93223
if response['user']['real_name']:
@@ -97,14 +227,21 @@ def user_name_from_id(user_id):
97227
else:
98228
return 'New Member'
99229

230+
100231
def join_channels():
232+
"""
233+
Utility function for joining channels. Move to utils?
234+
"""
101235
response = slack_client.api_call('channels.join', name='general')
102236
print(response)
103237

104238

105-
106-
# set the defalt to a 1 second delay
107-
def run_bot(delay=1):
239+
def run_bot(delay: int = 1) -> None:
240+
"""
241+
Runs the bot using the Slack Real Time Messaging API.
242+
**Doesn't provide events or interactive functionality
243+
:param delay:
244+
"""
108245
setup_logging()
109246
if slack_client.rtm_connect():
110247
print(f"StarterBot connected and running with a {delay} second delay")

0 commit comments

Comments
 (0)