Files
handbook/docker/packages/slack/src/slack_mock/tools/dms.py
T
2026-06-24 14:16:58 -07:00

137 lines
4.8 KiB
Python

"""Direct message tool handlers."""
from __future__ import annotations
import re
from typing import Any
from slack_mock.models import SlackChannel, SlackMessage, SlackMessageType, SlackState
from slack_mock.state import (
add_message,
generate_mpim_channel_id,
generate_timestamp,
get_bot_user_id,
get_state,
get_user,
mutate_state,
)
from slack_mock.tools.common import (
channel_payload,
create_channel_object,
empty_message,
is_direct_conversation,
message_payload,
model_dump,
now_seconds,
)
def open_dm(user_id: str) -> dict[str, Any]:
return _open_dm(user_id)
def _open_dm(user_id: str) -> dict[str, Any]:
state = get_state()
user = get_user(user_id)
if user is None:
return {"ok": False, "error": "user_not_found", "channel": {}}
for channel in state.channels.values():
if channel.is_im and channel.user == user_id:
return {"ok": True, "channel": channel_payload(channel)}
def _open(state: SlackState) -> SlackChannel:
now = now_seconds()
channel_id = f"D{str(now)[-6:]}{re.sub(r'[^a-zA-Z0-9]', '', user_id)}"
channel = create_channel_object(channel_id, user.name, is_private=True, is_im=True, user_id=user_id)
state.channels[channel_id] = channel
state.messages[channel_id] = []
return channel
channel = mutate_state(_open)
return {"ok": True, "channel": channel_payload(channel)}
def _mpim_members(user_ids: list[str]) -> tuple[list[str], list[str]]:
unique_user_ids = list(dict.fromkeys(user_ids))
bot_user_id = get_bot_user_id()
other_user_ids = sorted(user_id for user_id in unique_user_ids if user_id != bot_user_id)
return [bot_user_id, *other_user_ids], other_user_ids
def _mpim_name(other_user_ids: list[str]) -> str:
state = get_state()
names = [state.users[user_id].name for user_id in other_user_ids]
normalized_names = [re.sub(r"[^a-z0-9_-]+", "-", name.casefold()).strip("-") for name in names]
return f"mpdm-{'--'.join(normalized_names)}-1"
def open_mpim(user_ids: list[str]) -> dict[str, Any]:
return _open_mpim(user_ids)
def _open_mpim(user_ids: list[str]) -> dict[str, Any]:
state = get_state()
member_ids, other_user_ids = _mpim_members(user_ids)
if len(other_user_ids) < 2:
return {"ok": False, "error": "not_enough_users", "channel": {}}
missing_user_ids = [user_id for user_id in other_user_ids if user_id not in state.users]
if missing_user_ids:
return {"ok": False, "error": "user_not_found", "channel": {}}
member_set = set(member_ids)
channel_name = _mpim_name(other_user_ids)
for channel in state.channels.values():
if not channel.is_mpim:
continue
if channel.members and set(channel.members) == member_set:
return {"ok": True, "channel": channel_payload(channel)}
if channel.members is None and channel.name == channel_name:
return {"ok": True, "channel": channel_payload(channel)}
def _open(state: SlackState) -> SlackChannel:
channel_id = generate_mpim_channel_id()
channel = create_channel_object(
channel_id,
channel_name,
is_private=True,
is_mpim=True,
members=member_ids,
)
state.channels[channel_id] = channel
state.messages[channel_id] = []
return channel
channel = mutate_state(_open)
return {"ok": True, "channel": channel_payload(channel)}
def list_dms(limit: int = 20) -> dict[str, Any]:
dms = sorted(
(channel for channel in get_state().channels.values() if is_direct_conversation(channel)),
key=lambda channel: channel.updated,
reverse=True,
)
return {"ok": True, "channels": model_dump(dms[: limit or 20])}
def send_dm(user_id: str, text: str) -> dict[str, Any]:
opened = _open_dm(user_id)
if not opened.get("ok"):
return {"ok": False, "error": opened.get("error"), "channel": "", "ts": "", "message": empty_message()}
channel_id = opened["channel"]["id"]
ts = generate_timestamp()
message = SlackMessage(type=SlackMessageType.MESSAGE, user=get_bot_user_id(), text=text, ts=ts, team="T_MOCK")
add_message(channel_id, message)
return {"ok": True, "channel": channel_id, "ts": ts, "message": message_payload(message)}
def send_mpim(user_ids: list[str], text: str) -> dict[str, Any]:
opened = _open_mpim(user_ids)
if not opened.get("ok"):
return {"ok": False, "error": opened.get("error"), "channel": "", "ts": "", "message": empty_message()}
channel_id = opened["channel"]["id"]
ts = generate_timestamp()
message = SlackMessage(type=SlackMessageType.MESSAGE, user=get_bot_user_id(), text=text, ts=ts, team="T_MOCK")
add_message(channel_id, message)
return {"ok": True, "channel": channel_id, "ts": ts, "message": message_payload(message)}