99 lines
2.7 KiB
Python
99 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
from typing import Any
|
|
|
|
|
|
def build_reaction(emoji: str, target_event_id: str) -> dict[str, Any]:
|
|
return {
|
|
"m.relates_to": {
|
|
"rel_type": "m.annotation",
|
|
"event_id": target_event_id,
|
|
"key": emoji,
|
|
},
|
|
}
|
|
|
|
|
|
def extract_reaction(event: Any) -> dict[str, str] | None:
|
|
content = event.source.get("content", {}) if hasattr(event, "source") else {}
|
|
relates_to = content.get("m.relates_to", {})
|
|
if relates_to.get("rel_type") != "m.annotation":
|
|
return None
|
|
return {
|
|
"target_event_id": relates_to.get("event_id", ""),
|
|
"key": relates_to.get("key", ""),
|
|
}
|
|
|
|
|
|
def summarize_reactions(events: list[dict[str, Any]]) -> dict[str, dict]:
|
|
key_counter: Counter[str] = Counter()
|
|
user_keys: dict[str, set[str]] = {}
|
|
last_sender: dict[str, str] = {}
|
|
|
|
for evt in events:
|
|
sender = evt.get("sender", "")
|
|
key = evt.get("key", "")
|
|
target = evt.get("target_event_id", "")
|
|
|
|
if not key or not target:
|
|
continue
|
|
|
|
key_counter[key] += 1
|
|
|
|
if target not in user_keys:
|
|
user_keys[target] = set()
|
|
user_keys[target].add(sender)
|
|
|
|
last_sender[key] = sender
|
|
|
|
result: dict[str, dict] = {}
|
|
for target, users in user_keys.items():
|
|
result[target] = {
|
|
"total_reactions": sum(
|
|
count
|
|
for k, count in key_counter.items()
|
|
if any(e.get("target_event_id") == target and e.get("key") == k for e in events)
|
|
),
|
|
"unique_reactors": len(users),
|
|
"keys": [
|
|
{"key": k, "count": count}
|
|
for k, count in key_counter.items()
|
|
if any(e.get("target_event_id") == target and e.get("key") == k for e in events)
|
|
],
|
|
}
|
|
return result
|
|
|
|
|
|
_COMMON_EMOJI_MAP = {
|
|
"👍": "+1",
|
|
"👎": "-1",
|
|
"❤️": "heart",
|
|
"😄": "smile",
|
|
"🎉": "party",
|
|
"🚀": "rocket",
|
|
"👀": "eyes",
|
|
"✅": "check",
|
|
}
|
|
|
|
|
|
def select_reaction(available: list[dict[str, Any]], query: str) -> dict[str, Any] | None:
|
|
normalized = _COMMON_EMOJI_MAP.get(query, query).lower()
|
|
|
|
for entry in available:
|
|
key = entry.get("key", "")
|
|
if key == query or key.lower() == normalized:
|
|
return entry
|
|
|
|
return available[0] if available else None
|
|
|
|
|
|
def build_batch_remove(
|
|
client_reactions: list[dict[str, Any]],
|
|
target_event_id: str,
|
|
) -> list[dict[str, Any]]:
|
|
return [
|
|
build_reaction(r.get("key", ""), target_event_id)
|
|
for r in client_reactions
|
|
if r.get("target_event_id") == target_event_id
|
|
]
|