Add support for reactions and redactions

This commit is contained in:
Tulir Asokan 2019-05-14 18:31:43 +03:00
parent 76f65eb9d3
commit be564dd6f4
2 changed files with 47 additions and 7 deletions

View File

@ -20,11 +20,11 @@ import html
from mautrix.client import Client from mautrix.client import Client
from mautrix.types import (Event, StateEvent, EventID, UserID, FileInfo, MessageType, from mautrix.types import (Event, StateEvent, EventID, UserID, FileInfo, MessageType,
MediaMessageEventContent) MediaMessageEventContent, EventType, GenericEvent, RedactionEvent)
from mautrix.client.api.types.event.message import media_reply_fallback_body_map from mautrix.client.api.types.event.message import media_reply_fallback_body_map
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
from maubot import Plugin, MessageEvent from maubot import Plugin, MessageEvent
from maubot.handlers import command from maubot.handlers import command, event
from .db import make_tables, Karma, Version from .db import make_tables, Karma, Version
@ -56,6 +56,11 @@ def sha1(val: str) -> str:
return hashlib.sha1(val.encode("utf-8")).hexdigest() return hashlib.sha1(val.encode("utf-8")).hexdigest()
def reaction_key(evt: GenericEvent) -> None:
relates_to = evt.content.get("m.relates_to", None) or {}
return relates_to.get("key") if relates_to.get("rel_type", None) == "m.annotation" else None
class KarmaBot(Plugin): class KarmaBot(Plugin):
karma_t: Type[Karma] karma_t: Type[Karma]
version: Type[Version] version: Type[Version]
@ -85,6 +90,29 @@ class KarmaBot(Plugin):
def downvote(self, evt: MessageEvent, _: Tuple[str]) -> Awaitable[None]: def downvote(self, evt: MessageEvent, _: Tuple[str]) -> Awaitable[None]:
return self._vote(evt, evt.content.get_reply_to(), -1) return self._vote(evt, evt.content.get_reply_to(), -1)
@command.passive(regex=UPVOTE_EMOJI, field=reaction_key,
event_type=EventType.find("m.reaction"), msgtypes=None)
def upvote_react(self, evt: GenericEvent, key: Tuple[str]) -> Awaitable[None]:
try:
return self._vote(evt, evt.content["m.relates_to"]["event_id"], 1)
except KeyError:
pass
@command.passive(regex=DOWNVOTE_EMOJI, field=reaction_key,
event_type=EventType.find("m.reaction"), msgtypes=None)
def downvote_react(self, evt: GenericEvent, key: Tuple[str]) -> Awaitable[None]:
try:
return self._vote(evt, evt.content["m.relates_to"]["event_id"], -1)
except KeyError:
pass
@event.on(EventType.ROOM_REDACTION)
async def redact(self, evt: RedactionEvent) -> None:
karma = self.karma_t.get_by_given_from(evt.redacts)
if karma:
self.log.debug(f"Deleting {karma} due to redaction by {evt.sender}.")
karma.delete()
@karma.subcommand("stats", help="View global karma statistics") @karma.subcommand("stats", help="View global karma statistics")
async def karma_stats(self, evt: MessageEvent) -> None: async def karma_stats(self, evt: MessageEvent) -> None:
await evt.reply("Not yet implemented :(") await evt.reply("Not yet implemented :(")
@ -181,18 +209,18 @@ class KarmaBot(Plugin):
return return
in_filter = evt.sender in self.config["filter"] in_filter = evt.sender in self.config["filter"]
if self.config["democracy"] == in_filter or sha1(evt.sender) in self.config["opt_out"]: if self.config["democracy"] == in_filter or sha1(evt.sender) in self.config["opt_out"]:
if self.config["errors.filtered_users"]: if self.config["errors.filtered_users"] and isinstance(evt, MessageEvent):
await evt.reply("Sorry, you're not allowed to vote.") await evt.reply("Sorry, you're not allowed to vote.")
return return
if self.karma_t.is_vote_event(target): if self.karma_t.is_vote_event(target):
if self.config["errors.vote_on_vote"]: if self.config["errors.vote_on_vote"] and isinstance(evt, MessageEvent):
await evt.reply("Sorry, you can't vote on votes.") await evt.reply("Sorry, you can't vote on votes.")
return return
karma_target = await self.client.get_event(evt.room_id, target) karma_target = await self.client.get_event(evt.room_id, target)
if not karma_target: if not karma_target:
return return
if karma_target.sender == evt.sender and value > 0: if karma_target.sender == evt.sender and value > 0:
if self.config["errors.upvote_self"]: if self.config["errors.upvote_self"] and isinstance(evt, MessageEvent):
await evt.reply("Hey! You can't upvote yourself!") await evt.reply("Hey! You can't upvote yourself!")
return return
karma_id = dict(given_to=karma_target.sender, given_by=evt.sender, given_in=evt.room_id, karma_id = dict(given_to=karma_target.sender, given_by=evt.sender, given_in=evt.room_id,
@ -203,7 +231,7 @@ class KarmaBot(Plugin):
existing = self.karma_t.get(**karma_id) existing = self.karma_t.get(**karma_id)
if existing is not None: if existing is not None:
if existing.value == value: if existing.value == value:
if self.config["errors.already_voted"]: if self.config["errors.already_voted"] and isinstance(evt, MessageEvent):
await evt.reply(f"You already {self._sign(value)}'d that message.") await evt.reply(f"You already {self._sign(value)}'d that message.")
return return
existing.update(new_value=value) existing.update(new_value=value)
@ -211,7 +239,8 @@ class KarmaBot(Plugin):
karma = self.karma_t(**karma_id, given_from=evt.event_id, value=value, karma = self.karma_t(**karma_id, given_from=evt.event_id, value=value,
content=self._parse_content(karma_target) if not anonymize else "") content=self._parse_content(karma_target) if not anonymize else "")
karma.insert() karma.insert()
await evt.mark_read() if isinstance(evt, MessageEvent):
await evt.mark_read()
def _denotify(self, mxid: UserID) -> str: def _denotify(self, mxid: UserID) -> str:
localpart, _ = self.client.parse_mxid(mxid) localpart, _ = self.client.parse_mxid(mxid)

View File

@ -148,6 +148,17 @@ class Karma:
return cls(given_to=given_to, given_by=given_by, given_in=given_in, given_for=given_for, return cls(given_to=given_to, given_by=given_by, given_in=given_in, given_for=given_for,
given_from=given_from, given_at=given_at, value=value, content=content) given_from=given_from, given_at=given_at, value=value, content=content)
@classmethod
def get_by_given_from(cls, given_from: EventID) -> Optional['Karma']:
rows = cls.db.execute(cls.t.select().where(cls.c.given_from == given_from))
try:
(given_to, given_by, given_in, given_for,
given_from, given_at, value, content) = next(rows)
except StopIteration:
return None
return cls(given_to=given_to, given_by=given_by, given_in=given_in, given_for=given_for,
given_from=given_from, given_at=given_at, value=value, content=content)
def delete(self) -> None: def delete(self) -> None:
self.db.execute(self.t.delete().where(and_( self.db.execute(self.t.delete().where(and_(
self.c.given_to == self.given_to, self.c.given_by == self.given_by, self.c.given_to == self.given_to, self.c.given_by == self.given_by,