1+ import os
2+ import json
3+ import requests
4+ from datetime import datetime
5+
6+ # Get configuration from environment variables
7+ SLACK_TOKEN = os .environ .get ("SLACK_TOKEN" )
8+ CHANNEL_ID = os .environ .get ("CHANNEL_ID" )
9+ ROTATION_FILE = "rotation_state.json"
10+
11+ def get_channel_members ():
12+ """Get all members in the channel, excluding specific users."""
13+ url = "https://slack.com/api/conversations.members"
14+ headers = {"Authorization" : f"Bearer { SLACK_TOKEN } " }
15+ params = {"channel" : CHANNEL_ID }
16+
17+ response = requests .get (url , headers = headers , params = params )
18+ data = response .json ()
19+
20+ if not data .get ("ok" ):
21+ print (f"Error getting channel members: { data .get ('error' )} " )
22+ return []
23+
24+ # Get all members
25+ all_members = data .get ("members" , [])
26+
27+ # Get excluded users from environment variable
28+ excluded_users_str = os .environ .get ("EXCLUDED_USERS" , "" )
29+ excluded_users = [user .strip () for user in excluded_users_str .split ("," ) if user .strip ()]
30+
31+ # Filter out excluded users
32+ eligible_members = [user for user in all_members if user not in excluded_users ]
33+
34+ print (f"Found { len (eligible_members )} eligible members out of { len (all_members )} total members" )
35+ return eligible_members
36+
137def get_rotation_state ():
238 """Get current rotation state from file or initialize if not exists."""
339 try :
@@ -19,3 +55,128 @@ def save_rotation_state(state):
1955 os .system ('git add rotation_state.json' )
2056 os .system ('git commit -m "Update rotation state [skip ci]"' )
2157 os .system (f'git push https://{ os .environ .get ("GITHUB_TOKEN" )} @github.com/{ os .environ .get ("GITHUB_REPOSITORY" )} .git HEAD:main' )
58+
59+ def get_user_info (user_id ):
60+ """Get user information from Slack."""
61+ url = "https://slack.com/api/users.info"
62+ headers = {"Authorization" : f"Bearer { SLACK_TOKEN } " }
63+ params = {"user" : user_id }
64+
65+ response = requests .get (url , headers = headers , params = params )
66+ data = response .json ()
67+
68+ if not data .get ("ok" ):
69+ print (f"Error getting user info: { data .get ('error' )} " )
70+ return {}
71+
72+ return data .get ("user" , {}).get ("profile" , {})
73+
74+ def send_message (channel , text ):
75+ """Send a message to a Slack channel."""
76+ url = "https://slack.com/api/chat.postMessage"
77+ headers = {
78+ "Authorization" : f"Bearer { SLACK_TOKEN } " ,
79+ "Content-Type" : "application/json"
80+ }
81+ payload = {
82+ "channel" : channel ,
83+ "text" : text
84+ }
85+
86+ response = requests .post (url , headers = headers , json = payload )
87+ if not response .json ().get ("ok" ):
88+ print (f"Error sending message: { response .json ().get ('error' )} " )
89+
90+ def update_user_status (user_id ):
91+ """Update user's status to indicate they're the monitor."""
92+ url = "https://slack.com/api/users.profile.set"
93+ headers = {
94+ "Authorization" : f"Bearer { SLACK_TOKEN } " ,
95+ "Content-Type" : "application/json"
96+ }
97+
98+ # Set an emoji and status text
99+ payload = {
100+ "user" : user_id ,
101+ "profile" : {
102+ "status_text" : "Slack Monitor This Week" ,
103+ "status_emoji" : ":eyes:" ,
104+ "status_expiration" : 0 # Won't expire automatically
105+ }
106+ }
107+
108+ response = requests .post (url , headers = headers , json = payload )
109+ if not response .json ().get ("ok" ):
110+ print (f"Error updating status: { response .json ().get ('error' )} " )
111+
112+ def update_monitor_alias (new_monitor_id ):
113+ """Update the bot's profile to indicate current monitor."""
114+ user_info = get_user_info (new_monitor_id )
115+ user_name = user_info .get ("real_name" , "Unknown User" )
116+
117+ # Set the bot's profile to reference the current monitor
118+ url = "https://slack.com/api/users.profile.set"
119+ headers = {
120+ "Authorization" : f"Bearer { SLACK_TOKEN } " ,
121+ "Content-Type" : "application/json"
122+ }
123+
124+ payload = {
125+ "profile" : {
126+ "real_name" : f"PatternFly Slack Monitor ({ user_name } )" ,
127+ "status_text" : f"Currently: { user_name } " ,
128+ "status_emoji" : ":eyes:" ,
129+ }
130+ }
131+
132+ response = requests .post (url , headers = headers , json = payload )
133+ if not response .json ().get ("ok" ):
134+ print (f"Error updating bot profile: { response .json ().get ('error' )} " )
135+
136+ # Add message in channel about how to tag the current monitor
137+ message = f"<@{ new_monitor_id } > is this week's slack-monitor! You can use @patternfly-slack-monitor to tag them."
138+ send_message (CHANNEL_ID , message )
139+
140+ def notify_new_monitor (user_id ):
141+ """Send notification to channel about new monitor."""
142+ message = (
143+ f"<@{ user_id } > has been assigned as this week's slack-monitor! "
144+ f"They'll be responsible for monitoring the slack channels according to the document pinned to the top of this channel."
145+ )
146+ send_message (CHANNEL_ID , message )
147+
148+ def assign_slack_monitor ():
149+ """Main function to assign a new slack monitor."""
150+ print (f"Running slack monitor rotation on { datetime .now ()} " )
151+
152+ # Get all channel members
153+ members = get_channel_members ()
154+ if not members :
155+ print ("No eligible members found in channel" )
156+ return
157+
158+ # Get current rotation state
159+ state = get_rotation_state ()
160+
161+ # Select next member
162+ next_index = (state ["last_index" ] + 1 ) % len (members )
163+ next_user = members [next_index ]
164+
165+ # Update rotation state
166+ state ["last_index" ] = next_index
167+ state ["last_user" ] = next_user
168+ save_rotation_state (state )
169+
170+ # Send message to channel
171+ notify_new_monitor (next_user )
172+
173+ # Update user status (optional)
174+ update_user_status (next_user )
175+
176+ # Update bot profile for alias functionality
177+ update_monitor_alias (next_user )
178+
179+ print (f"Successfully assigned { next_user } as the new slack monitor" )
180+
181+ if __name__ == "__main__" :
182+ assign_slack_monitor ()
0 commit comments