ForcePilot/backend/package/yuxi/channels/adapters/slack/poll_manager.py
Kris df1a7c7bca refactor(slack-adapter): 整理导入顺序并修复格式问题
本次提交包含多个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. 新增多种系统事件处理逻辑
2026-05-13 16:14:38 +08:00

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)