新增 Slack 渠道扩展,支持在 Yuxi 平台中集成 Slack 团队协作平台。 包含以下功能模块: - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - monitor: 渠道状态监控 - status: 会话状态管理 - actions: 交互动作处理 - interactive: 交互式消息 - commands: 斜杠指令 - threading: 线程管理 - mentions: @提及 - constants: 常量定义 - types: 类型定义
732 lines
27 KiB
Python
732 lines
27 KiB
Python
import asyncio
|
|
import logging
|
|
import random
|
|
|
|
from yuxi.channel.extensions.slack.constants import (
|
|
SLACK_TEXT_LIMIT,
|
|
SLACK_RETRY_MAX_ATTEMPTS,
|
|
SLACK_RETRY_BASE_DELAY,
|
|
SLACK_RETRY_MAX_DELAY,
|
|
)
|
|
from yuxi.channel.extensions.slack.errors import (
|
|
SlackError,
|
|
SlackErrorCode,
|
|
is_retryable_error,
|
|
)
|
|
from yuxi.channel.extensions.slack.config import SlackConfigAdapter
|
|
from yuxi.channel.extensions.slack.format import chunk_text
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SlackOutbound:
|
|
delivery_mode = "direct"
|
|
chunker_mode = "length"
|
|
text_chunk_limit = SLACK_TEXT_LIMIT
|
|
poll_max_options = None
|
|
supports_poll_duration_seconds = False
|
|
supports_anonymous_polls = False
|
|
extract_markdown_images = True
|
|
presentation_capabilities = None
|
|
delivery_capabilities = None
|
|
|
|
def chunker(self, text: str, limit: int, ctx: object | None = None) -> list[str]:
|
|
return chunk_text(text, limit)
|
|
|
|
async def _get_client(self, account_id: str | None, config: dict | None = None):
|
|
try:
|
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
except ImportError:
|
|
raise SlackError(SlackErrorCode.CONFIG_ERROR, "slack_sdk is not installed")
|
|
|
|
adapter = SlackConfigAdapter()
|
|
account = await adapter.resolve_account(account_id or "default", config)
|
|
|
|
client = AsyncWebClient(token=account["bot_token"])
|
|
return client, account
|
|
|
|
async def _retry_with_backoff(self, operation, max_attempts=None, account=None):
|
|
try:
|
|
from slack_sdk.errors import SlackApiError
|
|
except ImportError:
|
|
return await operation()
|
|
|
|
max_attempts = max_attempts or SLACK_RETRY_MAX_ATTEMPTS
|
|
base_delay = SLACK_RETRY_BASE_DELAY
|
|
max_delay = SLACK_RETRY_MAX_DELAY
|
|
|
|
for attempt in range(max_attempts):
|
|
try:
|
|
return await operation()
|
|
except SlackApiError as e:
|
|
error_code = e.response.get("error", "unknown")
|
|
|
|
if error_code == "ratelimited":
|
|
retry_after = float(e.response.get("headers", {}).get("Retry-After", base_delay))
|
|
if attempt < max_attempts - 1:
|
|
logger.warning(
|
|
"Slack rate limited, waiting %.1fs (attempt %d/%d)",
|
|
retry_after,
|
|
attempt + 1,
|
|
max_attempts,
|
|
)
|
|
await asyncio.sleep(retry_after)
|
|
continue
|
|
raise SlackError(
|
|
SlackErrorCode.RATE_LIMITED,
|
|
f"Rate limit exceeded after {max_attempts} attempts",
|
|
) from e
|
|
|
|
if not is_retryable_error(error_code):
|
|
try:
|
|
code = SlackErrorCode(error_code)
|
|
except ValueError:
|
|
code = SlackErrorCode.UNKNOWN
|
|
raise SlackError(code, str(e)) from e
|
|
|
|
if attempt < max_attempts - 1:
|
|
delay = min(
|
|
base_delay * (2**attempt) + random.uniform(0, 1),
|
|
max_delay,
|
|
)
|
|
logger.warning(
|
|
"Slack API error '%s', retrying in %.1fs (attempt %d/%d)",
|
|
error_code,
|
|
delay,
|
|
attempt + 1,
|
|
max_attempts,
|
|
)
|
|
await asyncio.sleep(delay)
|
|
else:
|
|
raise SlackError(
|
|
SlackErrorCode.SEND_FAILED,
|
|
f"Max retries ({max_attempts}) exceeded: {error_code}",
|
|
) from e
|
|
except (ConnectionError, TimeoutError) as e:
|
|
if attempt < max_attempts - 1:
|
|
delay = min(
|
|
base_delay * (2**attempt) + random.uniform(0, 1),
|
|
max_delay,
|
|
)
|
|
logger.warning(
|
|
"Network error, retrying in %.1fs (attempt %d/%d)",
|
|
delay,
|
|
attempt + 1,
|
|
max_attempts,
|
|
)
|
|
await asyncio.sleep(delay)
|
|
else:
|
|
raise SlackError(
|
|
SlackErrorCode.NETWORK_ERROR,
|
|
f"Network error after {max_attempts} attempts: {e}",
|
|
) from e
|
|
|
|
async def send_text(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
reply_broadcast: bool = False,
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
thread_ts = thread_id or reply_to_id
|
|
|
|
async def do_send():
|
|
kwargs: dict = {
|
|
"channel": target_id,
|
|
"text": content,
|
|
"mrkdwn": True,
|
|
}
|
|
if thread_ts:
|
|
kwargs["thread_ts"] = thread_ts
|
|
if reply_broadcast and thread_ts:
|
|
kwargs["reply_broadcast"] = True
|
|
|
|
await client.chat_postMessage(**kwargs)
|
|
|
|
await self._retry_with_backoff(do_send)
|
|
|
|
async def send_media(
|
|
self,
|
|
target_id: str,
|
|
media_url: str,
|
|
media_type: str,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> None:
|
|
import aiohttp
|
|
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(media_url) as resp:
|
|
data = await resp.read()
|
|
content_type = resp.content_type or "application/octet-stream"
|
|
|
|
filename = media_url.rsplit("/", 1)[-1].split("?")[0] or "media"
|
|
file_size = len(data)
|
|
thread_ts = thread_id or reply_to_id
|
|
|
|
async def do_upload():
|
|
upload_result = await client.files_getUploadURLExternal(
|
|
filename=filename,
|
|
length=file_size,
|
|
)
|
|
upload_url = upload_result["upload_url"]
|
|
file_id = upload_result["file_id"]
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.put(
|
|
upload_url,
|
|
data=data,
|
|
headers={"Content-Type": content_type},
|
|
) as resp:
|
|
resp.raise_for_status()
|
|
|
|
complete_kwargs: dict = {
|
|
"files": [{"id": file_id, "title": filename}],
|
|
"channel_id": target_id,
|
|
}
|
|
if thread_ts:
|
|
complete_kwargs["thread_ts"] = thread_ts
|
|
|
|
await client.files_completeUploadExternal(**complete_kwargs)
|
|
|
|
await self._retry_with_backoff(do_upload)
|
|
|
|
async def edit_message(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
content: str,
|
|
*,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> str | None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_edit():
|
|
result = await client.chat_update(
|
|
channel=target_id,
|
|
ts=message_id,
|
|
text=content,
|
|
mrkdwn=True,
|
|
)
|
|
return result.get("ts")
|
|
|
|
return await self._retry_with_backoff(do_edit)
|
|
|
|
async def delete_message(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_delete():
|
|
await client.chat_delete(
|
|
channel=target_id,
|
|
ts=message_id,
|
|
)
|
|
|
|
await self._retry_with_backoff(do_delete)
|
|
|
|
async def send_reaction(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
emoji: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_react():
|
|
await client.reactions_add(
|
|
channel=target_id,
|
|
name=emoji.strip(":"),
|
|
timestamp=message_id,
|
|
)
|
|
|
|
await self._retry_with_backoff(do_react)
|
|
|
|
async def remove_reaction(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
emoji: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_remove():
|
|
await client.reactions_remove(
|
|
channel=target_id,
|
|
name=emoji.strip(":"),
|
|
timestamp=message_id,
|
|
)
|
|
|
|
await self._retry_with_backoff(do_remove)
|
|
|
|
async def pin_message(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_pin():
|
|
await client.pins_add(
|
|
channel=target_id,
|
|
timestamp=message_id,
|
|
)
|
|
|
|
await self._retry_with_backoff(do_pin)
|
|
|
|
async def unpin_message(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_unpin():
|
|
await client.pins_remove(
|
|
channel=target_id,
|
|
timestamp=message_id,
|
|
)
|
|
|
|
await self._retry_with_backoff(do_unpin)
|
|
|
|
async def send_payload(self, ctx: object) -> object:
|
|
payload = getattr(ctx, "payload", None) or {}
|
|
target_id = getattr(ctx, "target_id", "")
|
|
blocks = payload.get("blocks", [])
|
|
text = payload.get("text", "")
|
|
thread_ts = getattr(ctx, "thread_ts", None)
|
|
account_id = getattr(ctx, "account_id", None)
|
|
|
|
if not target_id or not blocks:
|
|
return None
|
|
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_send():
|
|
kwargs: dict = {"channel": target_id, "blocks": blocks}
|
|
if text:
|
|
kwargs["text"] = text
|
|
if thread_ts:
|
|
kwargs["thread_ts"] = thread_ts
|
|
result = await client.chat_postMessage(**kwargs)
|
|
return result.get("ts")
|
|
|
|
return await self._retry_with_backoff(do_send)
|
|
|
|
async def get_channel_history(
|
|
self,
|
|
channel_id: str,
|
|
*,
|
|
limit: int = 10,
|
|
latest: str | None = None,
|
|
oldest: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
kwargs: dict = {"channel": channel_id, "limit": min(limit, 1000)}
|
|
if latest:
|
|
kwargs["latest"] = latest
|
|
if oldest:
|
|
kwargs["oldest"] = oldest
|
|
result = await client.conversations_history(**kwargs)
|
|
return result.get("messages", [])
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def get_thread_replies(
|
|
self,
|
|
channel_id: str,
|
|
thread_ts: str,
|
|
*,
|
|
limit: int = 10,
|
|
account_id: str | None = None,
|
|
) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
result = await client.conversations_replies(
|
|
channel=channel_id,
|
|
ts=thread_ts,
|
|
limit=min(limit, 1000),
|
|
)
|
|
return result.get("messages", [])
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def list_channels(
|
|
self,
|
|
*,
|
|
types: str = "public_channel,private_channel",
|
|
limit: int = 100,
|
|
cursor: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
kwargs: dict = {"types": types, "limit": min(limit, 1000)}
|
|
if cursor:
|
|
kwargs["cursor"] = cursor
|
|
result = await client.conversations_list(**kwargs)
|
|
return {
|
|
"channels": result.get("channels", []),
|
|
"next_cursor": result.get("response_metadata", {}).get("next_cursor", ""),
|
|
}
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def get_channel_info(self, channel_id: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
result = await client.conversations_info(channel=channel_id)
|
|
return result.get("channel", {})
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def get_channel_members(
|
|
self,
|
|
channel_id: str,
|
|
*,
|
|
limit: int = 100,
|
|
cursor: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
kwargs: dict = {"channel": channel_id, "limit": min(limit, 1000)}
|
|
if cursor:
|
|
kwargs["cursor"] = cursor
|
|
result = await client.conversations_members(**kwargs)
|
|
return {
|
|
"members": result.get("members", []),
|
|
"next_cursor": result.get("response_metadata", {}).get("next_cursor", ""),
|
|
}
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def get_permalink(
|
|
self,
|
|
channel_id: str,
|
|
message_ts: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> str | None:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
result = await client.chat_getPermalink(channel=channel_id, message_ts=message_ts)
|
|
return result.get("permalink")
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def schedule_message(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
post_at: int,
|
|
*,
|
|
thread_ts: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> str:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_schedule():
|
|
kwargs: dict = {
|
|
"channel": target_id,
|
|
"text": content,
|
|
"post_at": post_at,
|
|
"mrkdwn": True,
|
|
}
|
|
if thread_ts:
|
|
kwargs["thread_ts"] = thread_ts
|
|
result = await client.chat_scheduleMessage(**kwargs)
|
|
return result.get("scheduled_message_id")
|
|
|
|
return await self._retry_with_backoff(do_schedule)
|
|
|
|
async def get_file_info(self, file_id: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.files_info(file=file_id)
|
|
return result.get("file", {})
|
|
|
|
async def list_files(
|
|
self,
|
|
*,
|
|
channel_id: str | None = None,
|
|
count: int = 100,
|
|
page: int = 1,
|
|
account_id: str | None = None,
|
|
) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
kwargs: dict = {"count": min(count, 1000)}
|
|
if channel_id:
|
|
kwargs["channel"] = channel_id
|
|
kwargs["page"] = min(page, 100)
|
|
result = await client.files_list(**kwargs)
|
|
return result.get("files", [])
|
|
|
|
async def delete_file(self, file_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.files_delete(file=file_id)
|
|
|
|
async def get_reactions(self, channel_id: str, message_ts: str, *, account_id: str | None = None) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.reactions_get(channel=channel_id, timestamp=message_ts, full=True)
|
|
return result.get("message", {}).get("reactions", [])
|
|
|
|
async def list_pins(self, channel_id: str, *, account_id: str | None = None) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.pins_list(channel=channel_id)
|
|
return result.get("items", [])
|
|
|
|
async def list_users(self, *, cursor: str | None = None, limit: int = 100, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
|
|
async def do_fetch():
|
|
kwargs: dict = {"limit": min(limit, 1000)}
|
|
if cursor:
|
|
kwargs["cursor"] = cursor
|
|
result = await client.users_list(**kwargs)
|
|
return {
|
|
"members": result.get("members", []),
|
|
"next_cursor": result.get("response_metadata", {}).get("next_cursor", ""),
|
|
}
|
|
|
|
return await self._retry_with_backoff(do_fetch)
|
|
|
|
async def lookup_user_by_email(self, email: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.users_lookupByEmail(email=email)
|
|
return result.get("user", {})
|
|
|
|
async def get_user_presence(self, user_id: str, *, account_id: str | None = None) -> str:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.users_getPresence(user=user_id)
|
|
return result.get("presence", "unknown")
|
|
|
|
async def join_channel(self, channel_id: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_join(channel=channel_id)
|
|
return result.get("channel", {})
|
|
|
|
async def leave_channel(self, channel_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.conversations_leave(channel=channel_id)
|
|
|
|
async def invite_users(self, channel_id: str, users: list[str], *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_invite(channel=channel_id, users=",".join(users))
|
|
return result.get("channel", {})
|
|
|
|
async def set_channel_topic(self, channel_id: str, topic: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_setTopic(channel=channel_id, topic=topic)
|
|
return result.get("channel", {})
|
|
|
|
async def archive_channel(self, channel_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.conversations_archive(channel=channel_id)
|
|
|
|
async def unarchive_channel(self, channel_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.conversations_unarchive(channel=channel_id)
|
|
|
|
async def create_channel(self, name: str, *, is_private: bool = False, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_create(name=name, is_private=is_private)
|
|
return result.get("channel", {})
|
|
|
|
async def delete_scheduled_message(
|
|
self, channel_id: str, scheduled_message_id: str, *, account_id: str | None = None
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.chat_deleteScheduledMessage(channel=channel_id, scheduled_message_id=scheduled_message_id)
|
|
|
|
async def list_scheduled_messages(self, channel_id: str, *, account_id: str | None = None) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.chat_scheduledMessages_list(channel=channel_id)
|
|
return result.get("scheduled_messages", [])
|
|
|
|
async def unfurl_links(
|
|
self, channel_id: str, message_ts: str, unfurls: dict, *, account_id: str | None = None
|
|
) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.chat_unfurl(channel=channel_id, ts=message_ts, unfurls=unfurls)
|
|
|
|
async def send_me_message(self, channel_id: str, text: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.chat_meMessage(channel=channel_id, text=text)
|
|
|
|
async def get_user_profile(
|
|
self, user_id: str, *, include_labels: bool = False, account_id: str | None = None
|
|
) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.users_profile_get(user=user_id, include_labels=include_labels)
|
|
return result.get("profile", {})
|
|
|
|
async def set_user_profile(self, user_id: str, profile: dict, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.users_profile_set(user=user_id, profile=profile)
|
|
return result.get("profile", {})
|
|
|
|
async def get_user_identity(self, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.users_identity()
|
|
return {"user": result.get("user", {}), "team": result.get("team", {})}
|
|
|
|
async def list_user_conversations(
|
|
self,
|
|
*,
|
|
user_id: str | None = None,
|
|
types: str = "public_channel,private_channel,im,mpim",
|
|
limit: int = 100,
|
|
account_id: str | None = None,
|
|
) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
kwargs: dict = {"types": types, "limit": min(limit, 1000)}
|
|
if user_id:
|
|
kwargs["user"] = user_id
|
|
result = await client.users_conversations(**kwargs)
|
|
return {
|
|
"channels": result.get("channels", []),
|
|
"next_cursor": result.get("response_metadata", {}).get("next_cursor", ""),
|
|
}
|
|
|
|
async def set_user_presence(self, presence: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.users_setPresence(presence=presence)
|
|
|
|
async def get_dnd_info(self, user_id: str | None = None, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
kwargs: dict = {}
|
|
if user_id:
|
|
kwargs["user"] = user_id
|
|
result = await client.dnd_info(**kwargs)
|
|
return result.data
|
|
|
|
async def search_messages(
|
|
self, query: str, *, count: int = 20, page: int = 1, account_id: str | None = None
|
|
) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.search_messages(query=query, count=min(count, 100), page=min(page, 100))
|
|
return result.data
|
|
|
|
async def add_reminder(self, text: str, time: int | str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.reminders_add(text=text, time=time)
|
|
return result.get("reminder", {})
|
|
|
|
async def list_reminders(self, *, account_id: str | None = None) -> list[dict]:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.reminders_list()
|
|
return result.get("reminders", [])
|
|
|
|
async def complete_reminder(self, reminder_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.reminders_complete(reminder=reminder_id)
|
|
|
|
async def delete_reminder(self, reminder_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.reminders_delete(reminder=reminder_id)
|
|
|
|
async def add_star(self, channel_id: str, message_ts: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.stars_add(channel=channel_id, timestamp=message_ts)
|
|
|
|
async def remove_star(self, channel_id: str, message_ts: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.stars_remove(channel=channel_id, timestamp=message_ts)
|
|
|
|
async def list_stars(self, *, count: int = 100, page: int = 1, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.stars_list(count=min(count, 1000), page=min(page, 100))
|
|
return result.data
|
|
|
|
async def list_emoji(self, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.emoji_list()
|
|
return result.get("emoji", {})
|
|
|
|
async def kick_user(self, channel_id: str, user_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.conversations_kick(channel=channel_id, user=user_id)
|
|
|
|
async def set_channel_purpose(self, channel_id: str, purpose: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_setPurpose(channel=channel_id, purpose=purpose)
|
|
return result.get("channel", {})
|
|
|
|
async def rename_channel(self, channel_id: str, name: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_rename(channel=channel_id, name=name)
|
|
return result.get("channel", {})
|
|
|
|
async def open_dm(self, user_id: str, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.conversations_open(users=[user_id])
|
|
return result.get("channel", {})
|
|
|
|
async def close_conversation(self, channel_id: str, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.conversations_close(channel=channel_id)
|
|
|
|
async def open_modal(self, trigger_id: str, view: dict, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.views_open(trigger_id=trigger_id, view=view)
|
|
return result.get("view", {})
|
|
|
|
async def push_modal(self, trigger_id: str, view: dict, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.views_push(trigger_id=trigger_id, view=view)
|
|
return result.get("view", {})
|
|
|
|
async def update_modal(self, view_id: str, view: dict, *, account_id: str | None = None) -> dict:
|
|
client, account = await self._get_client(account_id)
|
|
result = await client.views_update(view_id=view_id, view=view)
|
|
return result.get("view", {})
|
|
|
|
async def publish_app_home(self, user_id: str, view: dict, *, account_id: str | None = None) -> None:
|
|
client, account = await self._get_client(account_id)
|
|
await client.views_publish(user_id=user_id, view=view)
|
|
|
|
async def send_poll(self, ctx: object) -> object:
|
|
return None
|
|
|
|
def sanitize_text(self, text: str, payload: object) -> str:
|
|
return text
|
|
|
|
def should_skip_plain_text_sanitization(self, payload: object) -> bool:
|
|
return False
|
|
|
|
def normalize_payload(self, payload, config, account_id=None):
|
|
return payload
|
|
|
|
def resolve_effective_text_chunk_limit(self, config, account_id=None, fallback_limit=None):
|
|
return fallback_limit or SLACK_TEXT_LIMIT
|