diff options
author | Trygve Aaberge <trygveaa@gmail.com> | 2023-01-14 06:48:11 +0100 |
---|---|---|
committer | Trygve Aaberge <trygveaa@gmail.com> | 2024-02-18 11:32:53 +0100 |
commit | 78e551804dfd1784f10c627dd4c8d019214b66a1 (patch) | |
tree | e41a030098c6a3d786c6a5160a94b9b2957769d7 | |
parent | eed3b0a919e3790c4383548e3d30c83070465e7d (diff) | |
download | wee-slack-78e551804dfd1784f10c627dd4c8d019214b66a1.tar.gz |
Split slack classes into multiple files
-rw-r--r-- | slack/api.py | 246 | ||||
-rw-r--r-- | slack/commands.py | 2 | ||||
-rw-r--r-- | slack/config.py | 2 | ||||
-rw-r--r-- | slack/http.py | 2 | ||||
-rw-r--r-- | slack/init.py | 4 | ||||
-rw-r--r-- | slack/log.py | 2 | ||||
-rw-r--r-- | slack/shared.py | 2 | ||||
-rw-r--r-- | slack/slack_conversation.py | 107 | ||||
-rw-r--r-- | slack/slack_message.py | 48 | ||||
-rw-r--r-- | slack/slack_user.py | 21 | ||||
-rw-r--r-- | slack/slack_workspace.py | 98 | ||||
-rw-r--r-- | slack/util.py | 2 |
12 files changed, 286 insertions, 250 deletions
diff --git a/slack/api.py b/slack/api.py deleted file mode 100644 index 2f4b738..0000000 --- a/slack/api.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import annotations - -import json -import re -import time -from contextlib import contextmanager -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import urlencode - -import weechat - -from slack.http import http_request -from slack.shared import shared -from slack.task import Future, create_task, gather -from slack.util import get_callback_name - -if TYPE_CHECKING: - from slack_api import SlackConversationInfoResponse - - -def get_conversation_from_buffer_pointer( - buffer_pointer: str, -) -> Optional[SlackConversation]: - for workspace in shared.workspaces.values(): - for conversation in workspace.conversations.values(): - if conversation.buffer_pointer == buffer_pointer: - return conversation - return None - - -class SlackApi: - def __init__(self, workspace: SlackWorkspace): - self.workspace = workspace - - def get_request_options(self): - return { - "useragent": f"wee_slack {shared.SCRIPT_VERSION}", - "httpheader": f"Authorization: Bearer {self.workspace.config.api_token.value}", - "cookie": self.workspace.config.api_cookies.value, - } - - async def fetch(self, method: str, params: Dict[str, Union[str, int]] = {}): - url = f"https://api.slack.com/api/{method}?{urlencode(params)}" - response = await http_request( - url, - self.get_request_options(), - self.workspace.config.slack_timeout.value * 1000, - ) - return json.loads(response) - - async def fetch_list( - self, - method: str, - list_key: str, - params: Dict[str, Union[str, int]] = {}, - pages: int = 1, # negative or 0 means all pages - ): - response = await self.fetch(method, params) - next_cursor = response.get("response_metadata", {}).get("next_cursor") - if pages != 1 and next_cursor and response["ok"]: - params["cursor"] = next_cursor - next_pages = await self.fetch_list(method, list_key, params, pages - 1) - response[list_key].extend(next_pages[list_key]) - return response - return response - - -class SlackWorkspace: - def __init__(self, name: str): - self.name = name - self.config = shared.config.create_workspace_config(self.name) - self.api = SlackApi(self) - self.is_connected = False - self.nick = "TODO" - # Maybe make private, so you have to use get_user? Maybe make get_user a getter, though don't know if that's a problem since it's async - self.users: Dict[str, Future[SlackUser]] = {} - self.conversations: Dict[str, SlackConversation] = {} - - async def connect(self): - # rtm_connect = await self.api.fetch("rtm.connect") - user_channels_response = await self.api.fetch_list( - "users.conversations", - "channels", - { - "exclude_archived": True, - # "types": "public_channel,private_channel,im", - "types": "public_channel", - "limit": 1000, - }, - -1, - ) - user_channels = user_channels_response["channels"] - - for channel in user_channels: - conversation = SlackConversation(self, channel["id"]) - self.conversations[channel["id"]] = conversation - create_task(conversation.init()) - - # print(rtm_connect) - # print([c["name"] for c in user_channels]) - self.is_connected = True - weechat.bar_item_update("input_text") - - async def create_user(self, id: str) -> SlackUser: - user = SlackUser(self, id) - await user.init() - return user - - async def get_user(self, id: str) -> SlackUser: - if id in self.users: - return await self.users[id] - self.users[id] = create_task(self.create_user(id)) - return await self.users[id] - - -class SlackUser: - def __init__(self, workspace: SlackWorkspace, id: str): - self.workspace = workspace - self.id = id - self.name: str - - @property - def api(self) -> SlackApi: - return self.workspace.api - - async def init(self): - info = await self.api.fetch("users.info", {"user": self.id}) - self.name = info["user"]["name"] - - -def buffer_input_cb(data: str, buffer: str, input_data: str) -> int: - weechat.prnt(buffer, "Text: %s" % input_data) - return weechat.WEECHAT_RC_OK - - -class SlackConversation: - def __init__(self, workspace: SlackWorkspace, id: str): - self.workspace = workspace - self.id = id - # TODO: buffer_pointer may be accessed by buffer_switch before it's initialized - self.buffer_pointer: str = "" - self.name: str - self.is_loading = False - self.history_filled = False - self.history_pending = False - - @property - def api(self) -> SlackApi: - return self.workspace.api - - @contextmanager - def loading(self): - self.is_loading = True - weechat.bar_item_update("input_text") - try: - yield - finally: - self.is_loading = False - weechat.bar_item_update("input_text") - - async def init(self): - with self.loading(): - info = await self.fetch_info() - if info["ok"] != True: - # TODO: Handle error - return - - info_channel = info["channel"] - if info_channel["is_im"] == True: - self.name = "IM" # TODO - elif info_channel["is_mpim"] == True: - self.name = "MPIM" # TODO - else: - self.name = info_channel["name"] - - self.buffer_pointer = weechat.buffer_new( - self.name, get_callback_name(buffer_input_cb), "", "", "" - ) - weechat.buffer_set(self.buffer_pointer, "localvar_set_nick", "nick") - - async def fetch_info(self) -> SlackConversationInfoResponse: - with self.loading(): - info = await self.api.fetch("conversations.info", {"channel": self.id}) - return info - - async def fill_history(self): - if self.history_filled or self.history_pending: - return - - with self.loading(): - self.history_pending = True - - history = await self.api.fetch( - "conversations.history", {"channel": self.id} - ) - start = time.time() - - messages = [SlackMessage(self, message) for message in history["messages"]] - messages_rendered = await gather( - *(message.render_message() for message in messages) - ) - - for rendered in reversed(messages_rendered): - weechat.prnt(self.buffer_pointer, rendered) - - print(f"history w/o fetch took: {time.time() - start}") - self.history_filled = True - self.history_pending = False - - -class SlackMessage: - def __init__(self, conversation: SlackConversation, message_json: Any): - self.conversation = conversation - self.ts = message_json["ts"] - self.message_json = message_json - - @property - def workspace(self) -> SlackWorkspace: - return self.conversation.workspace - - @property - def api(self) -> SlackApi: - return self.workspace.api - - async def render_message(self): - message = await self.unfurl_refs(self.message_json["text"]) - if "user" in self.message_json: - user = await self.workspace.get_user(self.message_json["user"]) - prefix = user.name - else: - prefix = "bot" - - return f"{prefix}\t{message}" - - async def unfurl_refs(self, message: str): - re_user = re.compile("<@([^>]+)>") - user_ids: List[str] = re_user.findall(message) - users_list = await gather( - *(self.workspace.get_user(user_id) for user_id in user_ids) - ) - users = dict(zip(user_ids, users_list)) - - def unfurl_user(user_id: str): - return "@" + users[user_id].name - - return re_user.sub(lambda match: unfurl_user(match.group(1)), message) diff --git a/slack/commands.py b/slack/commands.py index 24ee99f..44d1d13 100644 --- a/slack/commands.py +++ b/slack/commands.py @@ -7,9 +7,9 @@ from typing import Any, Callable, Dict, List, Optional, Tuple import weechat -from slack.api import SlackWorkspace from slack.log import print_error from slack.shared import shared +from slack.slack_workspace import SlackWorkspace from slack.task import create_task from slack.util import get_callback_name, with_color from slack.weechat_config import WeeChatOption diff --git a/slack/config.py b/slack/config.py index b2e2c07..7228ed8 100644 --- a/slack/config.py +++ b/slack/config.py @@ -4,9 +4,9 @@ from typing import Union import weechat -from slack.api import SlackWorkspace from slack.log import print_error from slack.shared import shared +from slack.slack_workspace import SlackWorkspace from slack.util import get_callback_name from slack.weechat_config import ( WeeChatColor, diff --git a/slack/http.py b/slack/http.py index ca99526..7ec661e 100644 --- a/slack/http.py +++ b/slack/http.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import resource from io import StringIO diff --git a/slack/init.py b/slack/init.py index b126a24..e039d9c 100644 --- a/slack/init.py +++ b/slack/init.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import weechat -from slack.api import get_conversation_from_buffer_pointer from slack.commands import register_commands from slack.config import SlackConfig from slack.shared import shared +from slack.slack_conversation import get_conversation_from_buffer_pointer from slack.task import create_task, sleep from slack.util import get_callback_name, with_color diff --git a/slack/log.py b/slack/log.py index 2c8a679..552727d 100644 --- a/slack/log.py +++ b/slack/log.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import IntEnum import weechat diff --git a/slack/shared.py b/slack/shared.py index eab2c92..1e9b2aa 100644 --- a/slack/shared.py +++ b/slack/shared.py @@ -4,8 +4,8 @@ from collections import defaultdict from typing import TYPE_CHECKING, Any, Dict, List if TYPE_CHECKING: - from slack.api import SlackWorkspace from slack.config import SlackConfig + from slack.slack_workspace import SlackWorkspace from slack.task import Future, Task diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py new file mode 100644 index 0000000..16dc8ec --- /dev/null +++ b/slack/slack_conversation.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import time +from contextlib import contextmanager +from typing import TYPE_CHECKING, Optional + +import weechat + +from slack.shared import shared +from slack.slack_message import SlackMessage +from slack.task import gather +from slack.util import get_callback_name + +if TYPE_CHECKING: + from slack_api import SlackConversationInfoResponse + + from slack.slack_workspace import SlackApi, SlackWorkspace + + +def get_conversation_from_buffer_pointer( + buffer_pointer: str, +) -> Optional[SlackConversation]: + for workspace in shared.workspaces.values(): + for conversation in workspace.conversations.values(): + if conversation.buffer_pointer == buffer_pointer: + return conversation + return None + + +def buffer_input_cb(data: str, buffer: str, input_data: str) -> int: + weechat.prnt(buffer, "Text: %s" % input_data) + return weechat.WEECHAT_RC_OK + + +class SlackConversation: + def __init__(self, workspace: SlackWorkspace, id: str): + self.workspace = workspace + self.id = id + # TODO: buffer_pointer may be accessed by buffer_switch before it's initialized + self.buffer_pointer: str = "" + self.name: str + self.is_loading = False + self.history_filled = False + self.history_pending = False + + @property + def api(self) -> SlackApi: + return self.workspace.api + + @contextmanager + def loading(self): + self.is_loading = True + weechat.bar_item_update("input_text") + try: + yield + finally: + self.is_loading = False + weechat.bar_item_update("input_text") + + async def init(self): + with self.loading(): + info = await self.fetch_info() + if info["ok"] != True: + # TODO: Handle error + return + + info_channel = info["channel"] + if info_channel["is_im"] == True: + self.name = "IM" # TODO + elif info_channel["is_mpim"] == True: + self.name = "MPIM" # TODO + else: + self.name = info_channel["name"] + + self.buffer_pointer = weechat.buffer_new( + self.name, get_callback_name(buffer_input_cb), "", "", "" + ) + weechat.buffer_set(self.buffer_pointer, "localvar_set_nick", "nick") + + async def fetch_info(self) -> SlackConversationInfoResponse: + with self.loading(): + info = await self.api.fetch("conversations.info", {"channel": self.id}) + return info + + async def fill_history(self): + if self.history_filled or self.history_pending: + return + + with self.loading(): + self.history_pending = True + + history = await self.api.fetch( + "conversations.history", {"channel": self.id} + ) + start = time.time() + + messages = [SlackMessage(self, message) for message in history["messages"]] + messages_rendered = await gather( + *(message.render_message() for message in messages) + ) + + for rendered in reversed(messages_rendered): + weechat.prnt(self.buffer_pointer, rendered) + + print(f"history w/o fetch took: {time.time() - start}") + self.history_filled = True + self.history_pending = False diff --git a/slack/slack_message.py b/slack/slack_message.py new file mode 100644 index 0000000..d90ddb8 --- /dev/null +++ b/slack/slack_message.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import re +from typing import TYPE_CHECKING, Any, List + +from slack.task import gather + +if TYPE_CHECKING: + from slack.slack_conversation import SlackConversation + from slack.slack_workspace import SlackApi, SlackWorkspace + + +class SlackMessage: + def __init__(self, conversation: SlackConversation, message_json: Any): + self.conversation = conversation + self.ts = message_json["ts"] + self.message_json = message_json + + @property + def workspace(self) -> SlackWorkspace: + return self.conversation.workspace + + @property + def api(self) -> SlackApi: + return self.workspace.api + + async def render_message(self): + message = await self.unfurl_refs(self.message_json["text"]) + if "user" in self.message_json: + user = await self.workspace.get_user(self.message_json["user"]) + prefix = user.name + else: + prefix = "bot" + + return f"{prefix}\t{message}" + + async def unfurl_refs(self, message: str): + re_user = re.compile("<@([^>]+)>") + user_ids: List[str] = re_user.findall(message) + users_list = await gather( + *(self.workspace.get_user(user_id) for user_id in user_ids) + ) + users = dict(zip(user_ids, users_list)) + + def unfurl_user(user_id: str): + return "@" + users[user_id].name + + return re_user.sub(lambda match: unfurl_user(match.group(1)), message) diff --git a/slack/slack_user.py b/slack/slack_user.py new file mode 100644 index 0000000..50540f8 --- /dev/null +++ b/slack/slack_user.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from slack.slack_workspace import SlackApi, SlackWorkspace + + +class SlackUser: + def __init__(self, workspace: SlackWorkspace, id: str): + self.workspace = workspace + self.id = id + self.name: str + + @property + def api(self) -> SlackApi: + return self.workspace.api + + async def init(self): + info = await self.api.fetch("users.info", {"user": self.id}) + self.name = info["user"]["name"] diff --git a/slack/slack_workspace.py b/slack/slack_workspace.py new file mode 100644 index 0000000..df99f94 --- /dev/null +++ b/slack/slack_workspace.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +import json +from typing import Dict, Union +from urllib.parse import urlencode + +import weechat + +from slack.http import http_request +from slack.shared import shared +from slack.slack_conversation import SlackConversation +from slack.slack_user import SlackUser +from slack.task import Future, create_task + + +class SlackApi: + def __init__(self, workspace: SlackWorkspace): + self.workspace = workspace + + def get_request_options(self): + return { + "useragent": f"wee_slack {shared.SCRIPT_VERSION}", + "httpheader": f"Authorization: Bearer {self.workspace.config.api_token.value}", + "cookie": self.workspace.config.api_cookies.value, + } + + async def fetch(self, method: str, params: Dict[str, Union[str, int]] = {}): + url = f"https://api.slack.com/api/{method}?{urlencode(params)}" + response = await http_request( + url, + self.get_request_options(), + self.workspace.config.slack_timeout.value * 1000, + ) + return json.loads(response) + + async def fetch_list( + self, + method: str, + list_key: str, + params: Dict[str, Union[str, int]] = {}, + pages: int = 1, # negative or 0 means all pages + ): + response = await self.fetch(method, params) + next_cursor = response.get("response_metadata", {}).get("next_cursor") + if pages != 1 and next_cursor and response["ok"]: + params["cursor"] = next_cursor + next_pages = await self.fetch_list(method, list_key, params, pages - 1) + response[list_key].extend(next_pages[list_key]) + return response + return response + + +class SlackWorkspace: + def __init__(self, name: str): + self.name = name + self.config = shared.config.create_workspace_config(self.name) + self.api = SlackApi(self) + self.is_connected = False + self.nick = "TODO" + # Maybe make private, so you have to use get_user? Maybe make get_user a getter, though don't know if that's a problem since it's async + self.users: Dict[str, Future[SlackUser]] = {} + self.conversations: Dict[str, SlackConversation] = {} + + async def connect(self): + # rtm_connect = await self.api.fetch("rtm.connect") + user_channels_response = await self.api.fetch_list( + "users.conversations", + "channels", + { + "exclude_archived": True, + # "types": "public_channel,private_channel,im", + "types": "public_channel", + "limit": 1000, + }, + -1, + ) + user_channels = user_channels_response["channels"] + + for channel in user_channels: + conversation = SlackConversation(self, channel["id"]) + self.conversations[channel["id"]] = conversation + create_task(conversation.init()) + + # print(rtm_connect) + # print([c["name"] for c in user_channels]) + self.is_connected = True + weechat.bar_item_update("input_text") + + async def create_user(self, id: str) -> SlackUser: + user = SlackUser(self, id) + await user.init() + return user + + async def get_user(self, id: str) -> SlackUser: + if id in self.users: + return await self.users[id] + self.users[id] = create_task(self.create_user(id)) + return await self.users[id] diff --git a/slack/util.py b/slack/util.py index 112f060..b5b5bb2 100644 --- a/slack/util.py +++ b/slack/util.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable, Dict, Union import weechat |