Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0444.py @ 4071:4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 11:49:51 +0200 |
parents | sat/plugins/plugin_xep_0444.py@c23cad65ae99 |
children | 04cdcb3fd713 |
comparison
equal
deleted
inserted
replaced
4070:d10748475025 | 4071:4b842c1fb686 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia plugin for XEP-0444 | |
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 from typing import List, Iterable | |
20 from copy import deepcopy | |
21 | |
22 from twisted.words.protocols.jabber import jid, xmlstream | |
23 from twisted.words.xish import domish | |
24 from twisted.internet import defer | |
25 from wokkel import disco, iwokkel | |
26 from zope.interface import implementer | |
27 | |
28 from libervia.backend.core.constants import Const as C | |
29 from libervia.backend.core.i18n import _ | |
30 from libervia.backend.core.log import getLogger | |
31 from libervia.backend.core import exceptions | |
32 from libervia.backend.core.core_types import SatXMPPEntity | |
33 from libervia.backend.memory.sqla_mapping import History | |
34 | |
35 log = getLogger(__name__) | |
36 | |
37 PLUGIN_INFO = { | |
38 C.PI_NAME: "Message Reactions", | |
39 C.PI_IMPORT_NAME: "XEP-0444", | |
40 C.PI_TYPE: C.PLUG_TYPE_XEP, | |
41 C.PI_MODES: C.PLUG_MODE_BOTH, | |
42 C.PI_PROTOCOLS: ["XEP-0444"], | |
43 C.PI_DEPENDENCIES: ["XEP-0334"], | |
44 C.PI_MAIN: "XEP_0444", | |
45 C.PI_HANDLER: "yes", | |
46 C.PI_DESCRIPTION: _("""Message Reactions implementation"""), | |
47 } | |
48 | |
49 NS_REACTIONS = "urn:xmpp:reactions:0" | |
50 | |
51 | |
52 class XEP_0444: | |
53 | |
54 def __init__(self, host): | |
55 log.info(_("Message Reactions initialization")) | |
56 host.register_namespace("reactions", NS_REACTIONS) | |
57 self.host = host | |
58 self._h = host.plugins["XEP-0334"] | |
59 host.bridge.add_method( | |
60 "message_reactions_set", | |
61 ".plugin", | |
62 in_sign="ssas", | |
63 out_sign="", | |
64 method=self._reactions_set, | |
65 async_=True, | |
66 ) | |
67 host.trigger.add("message_received", self._message_received_trigger) | |
68 | |
69 def get_handler(self, client): | |
70 return XEP_0444_Handler() | |
71 | |
72 async def _message_received_trigger( | |
73 self, | |
74 client: SatXMPPEntity, | |
75 message_elt: domish.Element, | |
76 post_treat: defer.Deferred | |
77 ) -> bool: | |
78 return True | |
79 | |
80 def _reactions_set(self, message_id: str, profile: str, reactions: List[str]) -> None: | |
81 client = self.host.get_client(profile) | |
82 return defer.ensureDeferred( | |
83 self.set_reactions(client, message_id) | |
84 ) | |
85 | |
86 def send_reactions( | |
87 self, | |
88 client: SatXMPPEntity, | |
89 dest_jid: jid.JID, | |
90 message_id: str, | |
91 reactions: Iterable[str] | |
92 ) -> None: | |
93 """Send the <message> stanza containing the reactions | |
94 | |
95 @param dest_jid: recipient of the reaction | |
96 @param message_id: either <origin-id> or message's ID | |
97 see https://xmpp.org/extensions/xep-0444.html#business-id | |
98 """ | |
99 message_elt = domish.Element((None, "message")) | |
100 message_elt["from"] = client.jid.full() | |
101 message_elt["to"] = dest_jid.full() | |
102 reactions_elt = message_elt.addElement((NS_REACTIONS, "reactions")) | |
103 reactions_elt["id"] = message_id | |
104 for r in set(reactions): | |
105 reactions_elt.addElement("reaction", content=r) | |
106 self._h.add_hint_elements(message_elt, [self._h.HINT_STORE]) | |
107 client.send(message_elt) | |
108 | |
109 async def add_reactions_to_history( | |
110 self, | |
111 history: History, | |
112 from_jid: jid.JID, | |
113 reactions: Iterable[str] | |
114 ) -> None: | |
115 """Update History instance with given reactions | |
116 | |
117 @param history: storage History instance | |
118 will be updated in DB | |
119 "summary" field of history.extra["reactions"] will also be updated | |
120 @param from_jid: author of the reactions | |
121 @param reactions: list of reactions | |
122 """ | |
123 history.extra = deepcopy(history.extra) if history.extra else {} | |
124 h_reactions = history.extra.setdefault("reactions", {}) | |
125 # reactions mapped by originating JID | |
126 by_jid = h_reactions.setdefault("by_jid", {}) | |
127 # reactions are sorted to in summary to keep a consistent order | |
128 h_reactions["by_jid"][from_jid.userhost()] = sorted(list(set(reactions))) | |
129 h_reactions["summary"] = sorted(list(set().union(*by_jid.values()))) | |
130 await self.host.memory.storage.session_add(history) | |
131 | |
132 async def set_reactions( | |
133 self, | |
134 client: SatXMPPEntity, | |
135 message_id: str, | |
136 reactions: Iterable[str] | |
137 ) -> None: | |
138 """Set and replace reactions to a message | |
139 | |
140 @param message_id: internal ID of the message | |
141 @param rections: lsit of emojis to used to react to the message | |
142 use empty list to remove all reactions | |
143 """ | |
144 if not message_id: | |
145 raise ValueError("message_id can't be empty") | |
146 history = await self.host.memory.storage.get( | |
147 client, History, History.uid, message_id, | |
148 joined_loads=[History.messages, History.subjects] | |
149 ) | |
150 if history is None: | |
151 raise exceptions.NotFound( | |
152 f"message to retract not found in database ({message_id})" | |
153 ) | |
154 mess_id = history.origin_id or history.stanza_id | |
155 if not mess_id: | |
156 raise exceptions.DataError( | |
157 "target message has neither origin-id nor message-id, we can't send a " | |
158 "reaction" | |
159 ) | |
160 await self.add_reactions_to_history(history, client.jid, reactions) | |
161 self.send_reactions(client, history.dest_jid, mess_id, reactions) | |
162 | |
163 | |
164 @implementer(iwokkel.IDisco) | |
165 class XEP_0444_Handler(xmlstream.XMPPHandler): | |
166 | |
167 def getDiscoInfo(self, requestor, service, nodeIdentifier=""): | |
168 return [disco.DiscoFeature(NS_REACTIONS)] | |
169 | |
170 def getDiscoItems(self, requestor, service, nodeIdentifier=""): | |
171 return [] |