新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
129 lines
3.8 KiB
Python
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}
|