新增Slack适配器全套核心模块,包括消息处理流水线、会话管理、配置适配、权限控制等完整功能: 1. 新增语音、视觉相关的TTS和图像分析导出接口 2. 实现消息预处理、路由、线程上下文处理的完整流水线 3. 新增账号管理、缓存机制、房间上下文提取功能 4. 支持Webhook和Socket Mode两种事件接收方式 5. 实现权限白名单、审批配对、自动状态管理功能 6. 新增配置迁移、作用域校验、重连策略等辅助模块
115 lines
3.3 KiB
Python
115 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, UTC, timedelta
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class PollOption:
|
|
index: int
|
|
text: str
|
|
votes: set[str] = field(default_factory=set)
|
|
|
|
|
|
@dataclass
|
|
class Poll:
|
|
poll_id: str
|
|
question: str
|
|
options: list[PollOption]
|
|
chat_id: str
|
|
message_ts: str
|
|
created_by: str = ""
|
|
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
closed_at: datetime | None = None
|
|
|
|
@property
|
|
def is_closed(self) -> bool:
|
|
return self.closed_at is not None
|
|
|
|
@property
|
|
def total_votes(self) -> int:
|
|
return sum(len(opt.votes) for opt in self.options)
|
|
|
|
def vote(self, option_index: int, user_id: str) -> bool:
|
|
for opt in self.options:
|
|
opt.votes.discard(user_id)
|
|
if 0 <= option_index < len(self.options):
|
|
self.options[option_index].votes.add(user_id)
|
|
return True
|
|
return False
|
|
|
|
def results(self) -> dict[str, Any]:
|
|
option_results = [
|
|
{
|
|
"index": opt.index,
|
|
"text": opt.text,
|
|
"count": len(opt.votes),
|
|
}
|
|
for opt in self.options
|
|
]
|
|
return {
|
|
"poll_id": self.poll_id,
|
|
"question": self.question,
|
|
"options": option_results,
|
|
"total_votes": self.total_votes,
|
|
"is_closed": self.is_closed,
|
|
"chat_id": self.chat_id,
|
|
}
|
|
|
|
|
|
class PollManager:
|
|
def __init__(self, ttl_seconds: float = 86400.0):
|
|
self._polls: dict[str, Poll] = {}
|
|
self._ttl_seconds = ttl_seconds
|
|
|
|
def create_poll(
|
|
self, question: str, options: list[str], chat_id: str, message_ts: str, created_by: str = ""
|
|
) -> Poll:
|
|
poll_id = uuid.uuid4().hex[:12]
|
|
poll_options = [PollOption(index=i, text=opt) for i, opt in enumerate(options)]
|
|
poll = Poll(
|
|
poll_id=poll_id,
|
|
question=question,
|
|
options=poll_options,
|
|
chat_id=chat_id,
|
|
message_ts=message_ts,
|
|
created_by=created_by,
|
|
)
|
|
self._polls[poll_id] = poll
|
|
self._cleanup_expired()
|
|
return poll
|
|
|
|
def get_poll(self, poll_id: str) -> Poll | None:
|
|
return self._polls.get(poll_id)
|
|
|
|
def vote_poll(self, poll_id: str, option_index: int, user_id: str) -> bool:
|
|
poll = self._polls.get(poll_id)
|
|
if not poll or poll.is_closed:
|
|
return False
|
|
return poll.vote(option_index, user_id)
|
|
|
|
def close_poll(self, poll_id: str) -> bool:
|
|
poll = self._polls.get(poll_id)
|
|
if not poll or poll.is_closed:
|
|
return False
|
|
poll.closed_at = datetime.now(UTC)
|
|
return True
|
|
|
|
def list_polls(self, chat_id: str | None = None) -> list[dict[str, Any]]:
|
|
polls = self._polls.values()
|
|
if chat_id:
|
|
polls = [p for p in polls if p.chat_id == chat_id]
|
|
return [p.results() for p in polls if not p.is_closed]
|
|
|
|
def _cleanup_expired(self) -> None:
|
|
now = datetime.now(UTC)
|
|
expired = [
|
|
pid
|
|
for pid, poll in self._polls.items()
|
|
if poll.is_closed and now - poll.closed_at > timedelta(seconds=self._ttl_seconds)
|
|
]
|
|
for pid in expired:
|
|
self._polls.pop(pid, None)
|