本次提交包含多个Slack适配器相关的代码优化: 1. 统一多个文件中datetime和UTC的导入顺序 2. 调整collection.abc导入的参数顺序 3. 修复normalizer.py的文件末尾空行问题 4. 重新排序blocks.py中的函数导入 5. 调整directory_config.py中的函数顺序 6. 重构http_handler中的channel_manager调用方式 7. 新增Slack原生流探测逻辑和相关状态管理 8. 扩展消息动作分类和默认配置 9. 新增大量Slack消息块构建工具函数 10. 大幅重构__init__.py的导出内容,整理导入顺序 11. 为adapter新增熔断机制、缓存持久化和更多API方法 12. 新增多种系统事件处理逻辑
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 UTC, datetime, 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)
|