def post_msg(channel_or_user_id: str, text) -> None: logging.info(f"Posting to {channel_or_user_id}: {text}") SLACK_CLIENT.api_call( "chat.postMessage", channel=channel_or_user_id, text=text, link_names=True, # convert # and @ in links as_user=True, unfurl_links=False, unfurl_media=False, )
def parse_next_msg(): """Parse next message posted on slack for actions to do by bot""" msg = SLACK_CLIENT.rtm_read() if not msg: return None msg = msg[0] user_id = msg.get("user") channel_id = msg.get("channel") text = msg.get("text") # handle events first event_type = msg.get("type") # 1. if new channel auto-join bot if event_type == "channel_created": bot_joins_new_channel(msg["channel"]["id"]) return None # 2. if a new user joins send a welcome msg if event_type == "team_join": # return the message to apply the karma change # https://api.slack.com/methods/users.info welcome_msg = AUTOMATED_COMMANDS["welcome"]( user_id["id"]) # new user joining post_msg(user_id["id"], welcome_msg) # return Message object to handle karma in main return Message(user_id=KARMABOT_ID, channel_id=GENERAL_CHANNEL, text=welcome_msg) # end events # not sure but sometimes we get dicts? if (not isinstance(channel_id, str) or not isinstance(user_id, str) or not isinstance(text, str)): return None # ignore anything karma bot says! if user_id == KARMABOT_ID: return None # text replacements on first matching word in text # TODO: maybe this should replace all instances in text message ... text_replace_output = text and perform_text_replacements(text) if text_replace_output: post_msg(channel_id, text_replace_output) # if we recognize a valid bot command post its output, done # DM's = channels start with a 'D' / channel can be dict?! private = channel_id and channel_id.startswith("D") cmd_output = perform_bot_cmd(msg, private) if cmd_output: post_msg(channel_id, cmd_output) return None if not channel_id or not text: return None # Returns a message for karma processing return Message(user_id=user_id, channel_id=channel_id, text=text)
def get_channel_name(channel_id: str) -> str: channel_info: dict = SLACK_CLIENT.api_call("channels.info", channel=channel_id) # Private channels and direct messages cannot be resolved via api if not channel_info["ok"]: return "Unknown or Private" channel_name = channel_info["channel"]["name"] return channel_name
def test_slack_team_join(mock_slack_rtm_read_team_join, mock_slack_api_call): user_id = SLACK_CLIENT.rtm_read()[0].get("user")["id"] welcome_user(user_id) actual = parse_next_msg() assert actual.user_id == KARMABOT_ID assert actual.channel_id == GENERAL_CHANNEL assert user_id in actual.text assert "Introduce yourself in #general if you like" in actual.text
def get_bot_id(): BOT_NAME = "karmabot" api_call = SLACK_CLIENT.api_call("users.list") if not api_call.get("ok"): error = api_call.get("error", "none") print(f"Could not get users.list, error: {error}") sys.exit(1) users = api_call.get("members") for user in users: if "name" in user and user.get("name") == BOT_NAME: print(f"Bot ID for {user['name']} is {user.get('id')}")
def _create_karma_user(self, user_id): user_info = SLACK_CLIENT.api_call("users.info", user=user_id) error = user_info.get("error") if error is not None: logging.info(f"Cannot get user info for {user_id} - error: {error}") raise GetUserInfoException slack_id = user_info["user"]["id"] username = get_available_username(user_info) new_user = KarmaUser(user_id=slack_id, username=username) self.session.add(new_user) self.session.commit() logging.info(f"Created new KarmaUser: {repr(new_user)}") return new_user
def get_recommended_channels(**kwargs): """Show some of our Community's favorite channels you can join see https://api.slack.com/methods/channels.list as well as https://api.slack.com/methods/channels.info for API info """ _, text = kwargs.get("user"), kwargs.get("text") potential_channels: Channel = [] msg = MSG_BEGIN nr_channels = text.split()[2] if len( text.split()) >= 3 else DEFAULT_NR_CHANNELS if isinstance(nr_channels, str): nr_channels = (int(nr_channels) if nr_channels.isnumeric() else DEFAULT_NR_CHANNELS) # retrieve channel list response: Dict = SLACK_CLIENT.api_call("channels.list", exclude_archived=True, exclude_members=True) if not response["ok"]: logging.error( f'Error for API call "channels.list": {response["error"]}') return "I am truly sorry but something went wrong ;(" channels: List[Dict] = response["channels"] # retrieve channel info for each channel in channel list # only consider channels that are not the general channel, that are not private and that have at least one message for channel in channels: channel_is_potential = (channel["is_channel"] and not channel["is_general"] and not channel["is_private"]) if channel_is_potential: # we have to stick with channel.info, also it could be # that the latest message is a bot or join message # but channels.history is not allowed for bots. # However, it seems that in the future, Slack will update the bot permissions # see: https://api.slack.com/methods/channels.history response: Dict = SLACK_CLIENT.api_call("channels.info", channel=channel["id"]) if not response["ok"]: logging.error( f'Error for API call "channel.info": {response["error"]}') return "I am truly sorry but something went wrong ;(" channel_info: Dict = response["channel"] if channel_info.get("latest", None): potential_channels.append( Channel( channel["id"], channel["name"], channel_info["purpose"]["value"], channel["num_members"], float(channel_info["latest"]["ts"]), channel_info["latest"].get("subtype"), )) # now weight channels and return message potential_channels = sorted( ((calc_channel_score(chan), chan) for chan in potential_channels), reverse=True) msg = MSG_BEGIN + "\n".join((MSG_LINE.format( channel=channel.name, member_count=channel.num_members, time_since_last_post=humanize.naturaltime( seconds_since_last_post(channel)), purpose=channel.purpose or "<Invest today and get an awesome description!>", ) for score, channel in potential_channels[:nr_channels] if score > 0)) return msg
def test_slack_rtm_read(mock_slack_rtm_read_msg): event = SLACK_CLIENT.rtm_read() assert event[0]["type"] == "message" assert event[0]["user"] == "ABC123" assert event[0]["text"] == "Hi everybody"
def test_ignore_message_subtypes(mock_slack_api_call, frozen_now): latest_ignored = SLACK_CLIENT.api_call("channels.info", channel="SOMEJOINS") all_ignored = SLACK_CLIENT.api_call("channels.info", channel="ONLYJOINS") assert _channel_score(latest_ignored) > 0 assert _channel_score(all_ignored) == 0
def test_channel_score(mock_slack_api_call, frozen_now): most_recent = SLACK_CLIENT.api_call("channels.info", channel="CHANNEL42") less_recent = SLACK_CLIENT.api_call("channels.info", channel="CHANNEL43") assert _channel_score(most_recent) > _channel_score(less_recent)
def check_connection(): # Slack Real Time Messaging API - https://api.slack.com/rtm if not SLACK_CLIENT.rtm_connect(): logging.error("Connection Failed, invalid token?") sys.exit(1)
def test_get_available_username(mock_slack_api_call, test_user_id, expected): user_info = SLACK_CLIENT.api_call("users.info", user=test_user_id) assert get_available_username(user_info) == expected