2026-05-12 00:48:57 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResolvedSlackAccount(BaseModel):
|
|
|
|
|
account_id: str = "default"
|
|
|
|
|
enabled: bool = True
|
|
|
|
|
name: str = ""
|
|
|
|
|
|
|
|
|
|
bot_token: str = ""
|
|
|
|
|
app_token: str = ""
|
|
|
|
|
user_token: str = ""
|
|
|
|
|
|
|
|
|
|
bot_token_source: str = "none"
|
|
|
|
|
app_token_source: str = "none"
|
|
|
|
|
user_token_source: str = "none"
|
|
|
|
|
|
|
|
|
|
bot_user_id: str = ""
|
|
|
|
|
bot_id: str = ""
|
|
|
|
|
team: str = ""
|
|
|
|
|
team_id: str = ""
|
|
|
|
|
|
|
|
|
|
dm_policy: str = "allowlist"
|
|
|
|
|
group_policy: str = "allowlist"
|
|
|
|
|
require_mention: bool = False
|
|
|
|
|
allow_from: set[str] = set()
|
|
|
|
|
|
|
|
|
|
mode: str = "socket"
|
|
|
|
|
signing_secret: str = ""
|
|
|
|
|
|
|
|
|
|
text_chunk_limit: int = 8000
|
|
|
|
|
max_media_size_mb: int = 100
|
|
|
|
|
thread_history_scope: str = "channel"
|
|
|
|
|
thread_inherit_parent: bool = False
|
|
|
|
|
thread_require_explicit_mention: bool = False
|
|
|
|
|
|
2026-05-13 16:14:38 +08:00
|
|
|
show_configured: bool = True
|
|
|
|
|
quickstart_allow_from: list[str] = []
|
|
|
|
|
force_account_binding: bool = False
|
|
|
|
|
prefer_session_lookup: bool = False
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SlackChannelMeta(BaseModel):
|
|
|
|
|
channel_id: str = ""
|
|
|
|
|
channel_name: str = ""
|
|
|
|
|
is_im: bool = False
|
|
|
|
|
is_channel: bool = False
|
|
|
|
|
is_group: bool = False
|
|
|
|
|
is_private: bool = False
|
|
|
|
|
show_configured: bool = True
|
|
|
|
|
quickstart_allow_from: list[str] = []
|
|
|
|
|
force_account_binding: bool = False
|
|
|
|
|
prefer_session_lookup: bool = False
|
|
|
|
|
|
2026-05-12 00:48:57 +08:00
|
|
|
model_config = ConfigDict(extra="allow")
|
2026-05-13 16:14:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class SlackTokenSelector:
|
|
|
|
|
WRITE_SCOPES = frozenset(
|
|
|
|
|
{
|
|
|
|
|
"chat:write",
|
|
|
|
|
"chat:write.customize",
|
|
|
|
|
"chat:update",
|
|
|
|
|
"chat:delete",
|
|
|
|
|
"reactions:write",
|
|
|
|
|
"files:write",
|
|
|
|
|
"users.profile:write",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def select_for_operation(
|
|
|
|
|
account: ResolvedSlackAccount,
|
|
|
|
|
operation: str = "write",
|
|
|
|
|
) -> str:
|
|
|
|
|
if operation == "write":
|
|
|
|
|
return account.bot_token or account.user_token
|
|
|
|
|
if account.bot_token:
|
|
|
|
|
return account.bot_token
|
|
|
|
|
return account.user_token or account.bot_token
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_token_for_operation(
|
|
|
|
|
account: ResolvedSlackAccount,
|
|
|
|
|
scopes_needed: list[str] | None = None,
|
|
|
|
|
) -> tuple[str, str]:
|
|
|
|
|
if scopes_needed and any(s in SlackTokenSelector.WRITE_SCOPES for s in scopes_needed):
|
|
|
|
|
if account.bot_token:
|
|
|
|
|
return account.bot_token, "bot"
|
|
|
|
|
return account.user_token, "user"
|
|
|
|
|
|
|
|
|
|
if account.bot_token:
|
|
|
|
|
return account.bot_token, "bot"
|
|
|
|
|
return account.user_token or account.bot_token, "user"
|