1+ import json
12import logging
23import time
34from slackclient import SlackClient
45from utils .log_manager import setup_logging
5- from src . creds import TOKEN , PROXY
6+ from decouple import config
67import 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
816logger = logging .getLogger (__name__ )
917new_event_logger = logging .getLogger (f'{ __name__ } .new_member' )
1018all_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+
2737slack_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: {} \n Response2: {}' .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+
100231def 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