diff options
-rw-r--r-- | slack/slack_conversation.py | 16 | ||||
-rw-r--r-- | slack/slack_emoji.py | 29 | ||||
-rw-r--r-- | slack/slack_message.py | 30 | ||||
-rw-r--r-- | slack/slack_user.py | 18 | ||||
-rw-r--r-- | slack/slack_workspace.py | 7 | ||||
-rw-r--r-- | typings/slack_api/slack_users_info.pyi | 2 | ||||
-rw-r--r-- | typings/slack_rtm/slack_rtm_message.pyi | 9 |
7 files changed, 78 insertions, 33 deletions
diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py index 257796a..351d24d 100644 --- a/slack/slack_conversation.py +++ b/slack/slack_conversation.py @@ -188,6 +188,11 @@ class SlackConversation(SlackBuffer): def muted(self) -> bool: return self.id in self.workspace.muted_channels + @property + def im_user_id(self) -> Optional[str]: + if self.type == "im": + return self._info.get("user") + async def sort_key(self) -> str: type_sort_key = { "channel": 0, @@ -248,9 +253,14 @@ class SlackConversation(SlackBuffer): return True return False - def buffer_title(self) -> str: + async def buffer_title(self) -> str: # TODO: unfurl and apply styles - return unhtmlescape(self._info.get("topic", {}).get("value", "")) + topic = unhtmlescape(self._info.get("topic", {}).get("value", "")) + if self.im_user_id: + user = await self.workspace.users[self.im_user_id] + status = f"{user.status_emoji} {user.status_text}".strip() + return " | ".join(part for part in [status, topic] if part) + return topic async def set_topic(self, title: str): if "topic" not in self._info: @@ -269,7 +279,7 @@ class SlackConversation(SlackBuffer): return name, { "short_name": short_name, - "title": self.buffer_title(), + "title": await self.buffer_title(), "input_multiline": "1", "nicklist": "0" if self.type == "im" else "1", "nicklist_display_groups": "0", diff --git a/slack/slack_emoji.py b/slack/slack_emoji.py index d6247c3..e3d898b 100644 --- a/slack/slack_emoji.py +++ b/slack/slack_emoji.py @@ -2,15 +2,16 @@ from __future__ import annotations import json import os -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Dict, Optional import weechat from slack.error import store_and_format_exception from slack.log import print_error +from slack.shared import shared if TYPE_CHECKING: - from typing_extensions import NotRequired, TypedDict + from typing_extensions import NotRequired, TypedDict, assert_never class EmojiSkinVariation(TypedDict): name: str @@ -58,3 +59,27 @@ def load_standard_emojis() -> Dict[str, Emoji]: except Exception as e: print_error(f"couldn't read weemoji.json: {store_and_format_exception(e)}") return {} + + +def get_emoji(emoji_name: str, skin_tone: Optional[int] = None) -> str: + emoji_name_with_colons = f":{emoji_name}:" + if shared.config.look.render_emoji_as.value == "name": + return emoji_name_with_colons + + emoji_item = shared.standard_emojis.get(emoji_name) + if emoji_item is None: + return emoji_name_with_colons + + skin_tone_item = ( + emoji_item.get("skinVariations", {}).get(str(skin_tone)) if skin_tone else None + ) + emoji_unicode = ( + skin_tone_item["unicode"] if skin_tone_item else emoji_item["unicode"] + ) + + if shared.config.look.render_emoji_as.value == "emoji": + return emoji_unicode + elif shared.config.look.render_emoji_as.value == "both": + return f"{emoji_unicode}({emoji_name_with_colons})" + else: + assert_never(shared.config.look.render_emoji_as.value) diff --git a/slack/slack_message.py b/slack/slack_message.py index 438166f..2ac30e4 100644 --- a/slack/slack_message.py +++ b/slack/slack_message.py @@ -18,6 +18,7 @@ from slack.error import ( from slack.log import print_error from slack.python_compatibility import removeprefix, removesuffix from slack.shared import shared +from slack.slack_emoji import get_emoji from slack.slack_user import SlackBot, SlackUser, format_bot_nick, nick_color from slack.task import gather from slack.util import htmlescape, intersperse, unhtmlescape, with_color @@ -796,31 +797,6 @@ class SlackMessage: else: yield item - def _get_emoji(self, emoji_name: str, skin_tone: Optional[int] = None) -> str: - emoji_name_with_colons = f":{emoji_name}:" - if shared.config.look.render_emoji_as.value == "name": - return emoji_name_with_colons - - emoji_item = shared.standard_emojis.get(emoji_name) - if emoji_item is None: - return emoji_name_with_colons - - skin_tone_item = ( - emoji_item.get("skinVariations", {}).get(str(skin_tone)) - if skin_tone - else None - ) - emoji_unicode = ( - skin_tone_item["unicode"] if skin_tone_item else emoji_item["unicode"] - ) - - if shared.config.look.render_emoji_as.value == "emoji": - return emoji_unicode - elif shared.config.look.render_emoji_as.value == "both": - return f"{emoji_unicode}({emoji_name_with_colons})" - else: - assert_never(shared.config.look.render_emoji_as.value) - async def _create_reaction_string(self, reaction: SlackMessageReaction) -> str: if self.conversation.display_reaction_nicks(): users = await gather( @@ -832,7 +808,7 @@ class SlackMessage: users_str = "" reaction_string = ( - f"{self._get_emoji(reaction['name'])}{len(reaction['users'])}{users_str}" + f"{get_emoji(reaction['name'])}{len(reaction['users'])}{users_str}" ) if self.workspace.my_user.id in reaction["users"]: @@ -1063,7 +1039,7 @@ class SlackMessage: else: return element["url"] elif element["type"] == "emoji": - return self._get_emoji(element["name"], element.get("skin_tone")) + return get_emoji(element["name"], element.get("skin_tone")) elif element["type"] == "channel": return PendingMessageItem(self, "conversation", element["channel_id"]) elif element["type"] == "user": diff --git a/slack/slack_user.py b/slack/slack_user.py index 4e2ceae..f081f1c 100644 --- a/slack/slack_user.py +++ b/slack/slack_user.py @@ -6,6 +6,7 @@ import weechat from slack.error import SlackError from slack.shared import shared +from slack.slack_emoji import get_emoji from slack.util import with_color if TYPE_CHECKING: @@ -71,6 +72,17 @@ class SlackUser: def is_external(self) -> bool: return self._info["profile"]["team"] != self.workspace.id + @property + def status_text(self) -> str: + return self._info["profile"].get("status_text", "") or "" + + @property + def status_emoji(self) -> str: + status_emoji = self._info["profile"].get("status_emoji") + if not status_emoji: + return "" + return get_emoji(status_emoji.strip(":")) + def nick(self, colorize: bool = False, only_nick: bool = False) -> str: nick = self._name_without_spaces() @@ -93,6 +105,12 @@ class SlackUser: return nick_color(self._name_without_spaces()) + def update_info_json(self, info_json: SlackUserInfo): + self._info.update(info_json) # pyright: ignore [reportGeneralTypeIssues] + self._rendered_prefix = None + self._rendered_message = None + self._parsed_message = None + class SlackBot: def __init__(self, workspace: SlackWorkspace, info: SlackBotInfo): diff --git a/slack/slack_workspace.py b/slack/slack_workspace.py index bde1b4d..f2cb33a 100644 --- a/slack/slack_workspace.py +++ b/slack/slack_workspace.py @@ -350,6 +350,13 @@ class SlackWorkspace: if channel: await channel.update_buffer_props() return + elif data["type"] == "user_status_changed": + user = await self.users[data["user"]["id"]] + user.update_info_json(data["user"]) + for conversation in self.open_conversations.values(): + if conversation.im_user_id == user.id: + await conversation.update_buffer_props() + return elif data["type"] == "reaction_added" or data["type"] == "reaction_removed": channel_id = data["item"]["channel"] elif ( diff --git a/typings/slack_api/slack_users_info.pyi b/typings/slack_api/slack_users_info.pyi index a6a67d9..cd493ed 100644 --- a/typings/slack_api/slack_users_info.pyi +++ b/typings/slack_api/slack_users_info.pyi @@ -101,7 +101,7 @@ class SlackUserInfoPerson(SlackUserInfoCommon): profile: SlackProfilePerson is_bot: Literal[False] is_stranger: NotRequired[bool] - has_2fa: bool + has_2fa: NotRequired[bool] @final class SlackUserInfoBot(SlackUserInfoCommon): diff --git a/typings/slack_rtm/slack_rtm_message.pyi b/typings/slack_rtm/slack_rtm_message.pyi index efb9e6b..fccb6da 100644 --- a/typings/slack_rtm/slack_rtm_message.pyi +++ b/typings/slack_rtm/slack_rtm_message.pyi @@ -17,6 +17,7 @@ from slack_api.slack_conversations_history import ( SlackMessageWithFiles, ) from slack_api.slack_conversations_replies import SlackMessageThreadCommon +from slack_api.slack_users_info import SlackUserInfoPerson from typing_extensions import Literal, NotRequired, TypedDict, final class SlackRtmHello(TypedDict): @@ -252,6 +253,13 @@ class SlackPrefChange(TypedDict): name: str value: str +@final +class SlackUserStatusChanged(TypedDict): + type: Literal["user_status_changed"] + user: SlackUserInfoPerson + cache_ts: str + event_ts: str + SlackMessageRtm = ( SlackMessageStandardRtm | SlackMessageMeRtm @@ -288,4 +296,5 @@ SlackRtmMessage = ( | SlackShRoomUpdate | SlackUserTyping | SlackPrefChange + | SlackUserStatusChanged ) |