ForcePilot/backend/package/yuxi/channels/adapters/mattermost/poll_handler.py
Kris 002d601d1b feat(mattermost): 实现完整的 Mattermost 适配器模块
新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
2026-05-12 00:46:12 +08:00

129 lines
3.8 KiB
Python

from __future__ import annotations
import time
from collections import defaultdict
from typing import Any
class PollResultTracker:
"""投票结果跟踪和聚合。"""
def __init__(self):
self._polls: dict[str, dict[str, Any]] = {}
def init_poll(
self,
poll_id: str,
question: str,
options: list[str],
post_id: str = "",
chat_id: str = "",
) -> None:
self._polls[poll_id] = {
"poll_id": poll_id,
"question": question,
"options": options,
"post_id": post_id,
"chat_id": chat_id,
"votes": defaultdict(set),
"voters": {},
"created_at": time.monotonic(),
"ended": False,
}
def record_vote(self, poll_id: str, option: str, user_id: str) -> bool:
poll = self._polls.get(poll_id)
if not poll or poll["ended"]:
return False
for existing_opt, voters in poll["votes"].items():
if user_id in voters:
voters.discard(user_id)
poll["votes"][option].add(user_id)
poll["voters"][user_id] = option
return True
def end_poll(self, poll_id: str) -> dict | None:
poll = self._polls.get(poll_id)
if not poll:
return None
poll["ended"] = True
return poll
def get_results(self, poll_id: str) -> dict | None:
poll = self._polls.get(poll_id)
if not poll:
return None
results = {"question": poll["question"], "options": {}}
for option in poll["options"]:
results["options"][option] = len(poll["votes"].get(option, set()))
results["total_votes"] = len(poll["voters"])
results["ended"] = poll["ended"]
return results
def format_results_message(self, poll_id: str) -> str:
results = self.get_results(poll_id)
if not results:
return "未找到投票"
max_opt_len = max((len(opt) for opt in results["options"]), default=0)
lines = [f"**📊 投票结果: {results['question']}**\n"]
total = max(results["total_votes"], 1)
for option, count in results["options"].items():
bar_len = max(1, int(20 * count / total))
bar = "" * bar_len + "" * (20 - bar_len)
lines.append(f"{option.ljust(max_opt_len)} [{bar}] {count}")
lines.append(f"\n总票数: {results['total_votes']}")
if results["ended"]:
lines.append("\n此投票已结束")
return "\n".join(lines)
_tracker = PollResultTracker()
def get_poll_tracker() -> PollResultTracker:
return _tracker
async def handle_poll_completion(
adapter: Any,
poll_id: str,
chat_id: str,
) -> dict:
tracker = get_poll_tracker()
poll = tracker.end_poll(poll_id)
if not poll:
return {"error": "Poll not found"}
results_msg = tracker.format_results_message(poll_id)
if adapter and hasattr(adapter, "send"):
from yuxi.channels.models import ChannelIdentity, ChannelResponse
channel_type = getattr(adapter, "channel_type", None)
channel_id = getattr(adapter, "channel_id", "mattermost")
response = ChannelResponse(
identity=ChannelIdentity(
channel_id=channel_id,
channel_type=channel_type,
channel_user_id="system",
channel_chat_id=chat_id or poll.get("chat_id", ""),
),
content=results_msg,
reply_to_message_id=poll.get("post_id", ""),
metadata={"poll_result": True, "poll_id": poll_id},
)
result = await adapter.send(response)
return {"success": result.success, "message_id": result.message_id}
return {"results_message": results_msg}