comparison libervia/backend/plugins/plugin_xep_0163.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_0163.py@524856bd7b19
children 0d7bb4df2343
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3
4 # SAT plugin for Personal Eventing Protocol (xep-0163)
5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from typing import Optional, Callable
21 from libervia.backend.core.i18n import _
22 from libervia.backend.core import exceptions
23 from libervia.backend.core.constants import Const as C
24 from libervia.backend.core.log import getLogger
25
26 from twisted.words.xish import domish
27
28 from wokkel import disco, pubsub
29 from wokkel.formats import Mood
30 from libervia.backend.tools.common import data_format
31
32
33 log = getLogger(__name__)
34
35 NS_USER_MOOD = "http://jabber.org/protocol/mood"
36
37 PLUGIN_INFO = {
38 C.PI_NAME: "Personal Eventing Protocol Plugin",
39 C.PI_IMPORT_NAME: "XEP-0163",
40 C.PI_TYPE: "XEP",
41 C.PI_MODES: C.PLUG_MODE_BOTH,
42 C.PI_PROTOCOLS: ["XEP-0163", "XEP-0107"],
43 C.PI_DEPENDENCIES: ["XEP-0060"],
44 C.PI_MAIN: "XEP_0163",
45 C.PI_HANDLER: "no",
46 C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol"""),
47 }
48
49
50 class XEP_0163(object):
51 def __init__(self, host):
52 log.info(_("PEP plugin initialization"))
53 self.host = host
54 self.pep_events = set()
55 self.pep_out_cb = {}
56 host.trigger.add("PubSub Disco Info", self.diso_info_trigger)
57 host.bridge.add_method(
58 "pep_send",
59 ".plugin",
60 in_sign="sa{ss}s",
61 out_sign="",
62 method=self.pep_send,
63 async_=True,
64 ) # args: type(MOOD, TUNE, etc), data, profile_key;
65 self.add_pep_event("MOOD", NS_USER_MOOD, self.user_mood_cb, self.send_mood)
66
67 def diso_info_trigger(self, disco_info, profile):
68 """Add info from managed PEP
69
70 @param disco_info: list of disco feature as returned by PubSub,
71 will be filled with PEP features
72 @param profile: profile we are handling
73 """
74 disco_info.extend(list(map(disco.DiscoFeature, self.pep_events)))
75 return True
76
77 def add_pep_event(
78 self,
79 event_type: Optional[str],
80 node: str,
81 in_callback: Callable,
82 out_callback: Optional[Callable] = None,
83 notify: bool = True
84 ) -> None:
85 """Add a Personal Eventing Protocol event manager
86
87 @param event_type: type of the event (stored uppercase),
88 only used when out_callback is set.
89 Can be MOOD, TUNE, etc.
90 @param node: namespace of the node (e.g. http://jabber.org/protocol/mood
91 for User Mood)
92 @param in_callback: method to call when this event occur
93 the callable will be called with (itemsEvent, profile) as arguments
94 @param out_callback: method to call when we want to publish this
95 event (must return a deferred)
96 the callable will be called when send_pep_event is called
97 @param notify: add autosubscribe (+notify) if True
98 """
99 if event_type and out_callback:
100 event_type = event_type.upper()
101 if event_type in self.pep_out_cb:
102 raise exceptions.ConflictError(
103 f"event_type {event_type!r} already exists"
104 )
105 self.pep_out_cb[event_type] = out_callback
106 self.pep_events.add(node)
107 if notify:
108 self.pep_events.add(node + "+notify")
109
110 def filter_pep_event(client, itemsEvent):
111 """Ignore messages which are not coming from PEP (i.e. a bare jid)
112
113 @param itemsEvent(pubsub.ItemsEvent): pubsub event
114 """
115 if not itemsEvent.sender.user or itemsEvent.sender.resource:
116 log.debug(
117 "ignoring non PEP event from {} (profile={})".format(
118 itemsEvent.sender.full(), client.profile
119 )
120 )
121 return
122 in_callback(itemsEvent, client.profile)
123
124 self.host.plugins["XEP-0060"].add_managed_node(node, items_cb=filter_pep_event)
125
126 def send_pep_event(self, node, data, profile):
127 """Publish the event data
128
129 @param node(unicode): node namespace
130 @param data: domish.Element to use as payload
131 @param profile: profile which send the data
132 """
133 client = self.host.get_client(profile)
134 item = pubsub.Item(payload=data)
135 return self.host.plugins["XEP-0060"].publish(client, None, node, [item])
136
137 def pep_send(self, event_type, data, profile_key=C.PROF_KEY_NONE):
138 """Send personal event after checking the data is alright
139
140 @param event_type: type of event (eg: MOOD, TUNE),
141 must be in self.pep_out_cb.keys()
142 @param data: dict of {string:string} of event_type dependant data
143 @param profile_key: profile who send the event
144 """
145 profile = self.host.memory.get_profile_name(profile_key)
146 if not profile:
147 log.error(
148 _("Trying to send personal event with an unknown profile key [%s]")
149 % profile_key
150 )
151 raise exceptions.ProfileUnknownError
152 if not event_type in list(self.pep_out_cb.keys()):
153 log.error(_("Trying to send personal event for an unknown type"))
154 raise exceptions.DataError("Type unknown")
155 return self.pep_out_cb[event_type](data, profile)
156
157 def user_mood_cb(self, itemsEvent, profile):
158 if not itemsEvent.items:
159 log.debug(_("No item found"))
160 return
161 try:
162 mood_elt = [
163 child for child in itemsEvent.items[0].elements() if child.name == "mood"
164 ][0]
165 except IndexError:
166 log.error(_("Can't find mood element in mood event"))
167 return
168 mood = Mood.fromXml(mood_elt)
169 if not mood:
170 log.debug(_("No mood found"))
171 return
172 self.host.bridge.ps_event(
173 C.PS_PEP,
174 itemsEvent.sender.full(),
175 itemsEvent.nodeIdentifier,
176 "MOOD",
177 data_format.serialise({"mood": mood.value or "", "text": mood.text or ""}),
178 profile,
179 )
180
181 def send_mood(self, data, profile):
182 """Send XEP-0107's User Mood
183
184 @param data: must include mood and text
185 @param profile: profile which send the mood"""
186 try:
187 value = data["mood"].lower()
188 text = data["text"] if "text" in data else ""
189 except KeyError:
190 raise exceptions.DataError("Mood data must contain at least 'mood' key")
191 mood = UserMood(value, text)
192 return self.send_pep_event(NS_USER_MOOD, mood, profile)
193
194
195 class UserMood(Mood, domish.Element):
196 """Improved wokkel Mood which is also a domish.Element"""
197
198 def __init__(self, value, text=None):
199 Mood.__init__(self, value, text)
200 domish.Element.__init__(self, (NS_USER_MOOD, "mood"))
201 self.addElement(value)
202 if text:
203 self.addElement("text", content=text)