component Email gateway: Convert mailing list to pubsub nodes:

- `Credentials` is now a Pydantic based model.
- Mailing list related emails are now detected hand saved as pubsub items, using XEP-0277
  blog items, and creating suitable nodes. The ID of the mailing list is used for root
  node.
- Mailing list items are currently restricted to recipient associated JID.
- Words in square bracket in title `[Like][That]` are removed and converted to blog
  categories.
- Method to convert between MbData and email (in both directions) have been created.

rel 462
This commit is contained in:
Goffi 2025-08-03 23:45:48 +02:00
commit e1828b3672
2 changed files with 540 additions and 66 deletions

View file

@ -33,6 +33,7 @@ from typing import (
Set,
Tuple,
Union,
cast,
overload,
)
from urllib import parse
@ -59,6 +60,7 @@ from libervia.backend.core.i18n import _
from libervia.backend.core.log import getLogger
from libervia.backend.memory import persistent
from libervia.backend.memory.sqla_mapping import History, SubscriptionState
from libervia.backend.plugins.plugin_xep_0277 import XEP_0277
from libervia.backend.tools import utils
from libervia.backend.tools.common import data_format, tls, uri
from libervia.backend.tools.common.async_utils import async_lru
@ -157,7 +159,7 @@ class APGateway:
self._p = host.plugins["XEP-0060"]
self._a = host.plugins["XEP-0084"]
self._e = host.plugins["XEP-0106"]
self._m = host.plugins["XEP-0277"]
self._m = cast(XEP_0277, host.plugins["XEP-0277"])
self._v = host.plugins["XEP-0292"]
self._refs = host.plugins["XEP-0372"]
self._r = host.plugins["XEP-0424"]
@ -526,6 +528,7 @@ class APGateway:
for now, only handle XMPP items to convert to AP
"""
assert self.client is not None
url_type, url_args = self.parse_apurl(url)
if url_type == TYPE_ITEM:
try:
@ -2228,6 +2231,7 @@ class APGateway:
@param public: if True, the activity will be addressed to public namespace
@return: actor_id of the entity deleting the item, activity to send
"""
assert self.client is not None
if node is None:
node = self._m.namespace
@ -2763,7 +2767,7 @@ class APGateway:
create_kwargs={"subscribed": True},
)
else:
# it is a root item (i.e. not a reply to an other item)
# it is a root item (i.e. not a reply to another item)
create = node == self._events.namespace
cached_node = await self.host.memory.storage.get_pubsub_node(
client, service, node, with_subscriptions=True, create=create

View file

@ -16,7 +16,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional, Tuple, List, Dict, Any, Union
from typing import Optional, Tuple, List, Dict, Any
from urllib.parse import urlparse
from pathlib import Path
from base64 import b64encode
@ -25,7 +25,7 @@ import tempfile
from twisted.internet import defer, threads
from twisted.words.protocols.jabber import jid, error
from twisted.words.xish import domish
from wokkel import rsm, pubsub, disco
from wokkel import rsm, pubsub
from libervia.backend.core.i18n import _
from libervia.backend.core import exceptions
@ -265,7 +265,7 @@ class APPubsubService(rsm.PubSubService):
avatar_data = await self.get_avatar_data(client, requestor_actor_id, ap_account)
return self.apg._a.build_item_metadata_elt(avatar_data)
def _blocking_b_6_4_encode_avatar(self, avatar_data: Dict[str, Any]) -> None:
def _blocking_b64encode_avatar(self, avatar_data: Dict[str, Any]) -> None:
with avatar_data["path"].open("rb") as f:
avatar_data["base64"] = b64encode(f.read()).decode()
@ -287,7 +287,7 @@ class APPubsubService(rsm.PubSubService):
)
if "base64" not in avatar_data:
await threads.deferToThread(
self._blocking_b_6_4_encode_avatar, avatar_data
self._blocking_b64encode_avatar, avatar_data
)
else:
if len(itemIdentifiers) > 1:
@ -300,7 +300,7 @@ class APPubsubService(rsm.PubSubService):
if cache_data is None:
raise error.StanzaError("item-not-found")
avatar_data = {"cache_uid": item_id, "path": cache_data["path"]}
await threads.deferToThread(self._blocking_b_6_4_encode_avatar, avatar_data)
await threads.deferToThread(self._blocking_b64encode_avatar, avatar_data)
return self.apg._a.build_item_data_elt(avatar_data)