1
+ import json
1
2
import logging
2
3
import time
3
4
from slackclient import SlackClient
4
5
from utils .log_manager import setup_logging
5
- from src . creds import TOKEN , PROXY
6
+ from decouple import config
6
7
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
7
15
8
16
logger = logging .getLogger (__name__ )
9
17
new_event_logger = logging .getLogger (f'{ __name__ } .new_member' )
10
18
all_event_logger = logging .getLogger (f'{ __name__ } .all_events' )
11
19
12
-
13
20
# 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
+
27
37
slack_client = SlackClient (TOKEN , proxies = PROXY )
28
38
29
39
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 :
31
43
return message_template .format (** kwargs )
32
44
33
45
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
+ """
35
51
all_event_logger .info (event_dict )
36
52
if event_dict ['type' ] == 'team_join' :
37
53
new_event_logger .info ('New member event recieved' )
38
54
new_member (event_dict )
39
55
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' :
49
58
event_dict ['user' ] = {'id' : event_dict ['user' ]}
50
59
new_member (event_dict )
51
60
52
61
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
+
53
129
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
+ """
55
172
new_event_logger .info ('Recieved json event: {}' .format (event_dict ))
56
173
57
174
user_id = event_dict ['user' ]['id' ]
58
- # user_id = event_dict['user']
59
175
logging .info ('team_join message' )
60
176
61
- custom_message = build_message (MESSAGE ,
62
- real_name = user_name_from_id (user_id ))
177
+ real_name = user_name_from_id (user_id )
63
178
179
+ custom_message = MESSAGE .format (real_name = real_name )
64
180
65
- new_event_logger .info ('Built message: {}' .format (event_dict ))
181
+ new_event_logger .info ('Built message: {}' .format (custom_message ))
66
182
response = slack_client .api_call ('chat.postMessage' ,
67
183
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: {} \n Response2: {}' .format (response , r2 ))
74
201
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 ))
76
203
77
204
78
- def parse_slack_output (slack_rtm_output ) :
205
+ def parse_slack_output (slack_rtm_output : list ) -> None :
79
206
"""
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
83
209
"""
84
210
for output in slack_rtm_output :
85
211
# process a single item in list at a time
86
212
event_handler (output )
87
213
88
214
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
+ """
91
221
response = slack_client .api_call ('users.info' , user = user_id )
92
222
93
223
if response ['user' ]['real_name' ]:
@@ -97,14 +227,21 @@ def user_name_from_id(user_id):
97
227
else :
98
228
return 'New Member'
99
229
230
+
100
231
def join_channels ():
232
+ """
233
+ Utility function for joining channels. Move to utils?
234
+ """
101
235
response = slack_client .api_call ('channels.join' , name = 'general' )
102
236
print (response )
103
237
104
238
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
+ """
108
245
setup_logging ()
109
246
if slack_client .rtm_connect ():
110
247
print (f"StarterBot connected and running with a { delay } second delay" )
0 commit comments