From 788ca73d866af17510055850d6306282f2d5cf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Tue, 28 Mar 2023 21:47:24 +0200 Subject: [PATCH] WIP: implement setting circle avatar missing: actually doing the thing, we get forbidden back because the role doesn't propagate to groups.$SNIKKET_DOMAIN Fixes #49. --- snikket_web/admin.py | 24 +++++++++++++++ snikket_web/prosodyclient.py | 20 +++++++++++++ snikket_web/templates/admin_edit_circle.html | 13 ++++++-- snikket_web/xmpputil.py | 31 ++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/snikket_web/admin.py b/snikket_web/admin.py index 05fe166..87a76c7 100644 --- a/snikket_web/admin.py +++ b/snikket_web/admin.py @@ -28,6 +28,7 @@ from . import prosodyclient, _version from .infra import client, circle_name, BaseForm +from .user import EAVATARTOOBIG bp = Blueprint("admin", __name__, url_prefix="/admin") @@ -443,6 +444,10 @@ class EditCircleForm(BaseForm): validators=[wtforms.validators.InputRequired()], ) + avatar = wtforms.FileField( + _l("Avatar") + ) + user_to_add = wtforms.SelectField( _l("Select user"), validate_choice=False, @@ -466,6 +471,8 @@ class EditCircleForm(BaseForm): @bp.route("/circle/", methods=["GET", "POST"]) @client.require_admin_session() async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]: + max_avatar_size = current_app.config["MAX_AVATAR_SIZE"] + async with client.authenticated_session() as session: try: circle = await client.get_group_by_id( @@ -511,6 +518,20 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]: id_, new_name=form.name.data, ) + file_info = (await request.files).get(form.avatar.name) + if file_info is not None: + mimetype = file_info.mimetype + data = file_info.stream.read() + if len(data) > max_avatar_size: + form.avatar.errors.append(EAVATARTOOBIG) + ok = False + elif len(data) > 0: + print("setting muc avatar") + await client.set_muc_avatar( + circle.muc_jid, + data, + mimetype, + ) await flash( _("Circle data updated"), "success", @@ -550,6 +571,9 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]: form=form, circle_members=circle_members, invite_form=invite_form, + max_avatar_size=max_avatar_size, + avatar_too_big_warning_header=_l("Error"), + avatar_too_big_warning=EAVATARTOOBIG, ) diff --git a/snikket_web/prosodyclient.py b/snikket_web/prosodyclient.py index d00ddd5..3157eef 100644 --- a/snikket_web/prosodyclient.py +++ b/snikket_web/prosodyclient.py @@ -1246,3 +1246,23 @@ async def post_announcement( json=payload) as resp: self._raise_error_from_response(resp) resp.raise_for_status() + + @autosession + async def set_muc_avatar( + self, + muc_jid: str, + data: bytes, + mimetype: str, + *, + session: aiohttp.ClientSession, + ): + xmpputil.extract_iq_reply( + await self._xml_iq_call( + session, + xmpputil.make_muc_avatar_set_request( + muc_jid, + data, + mimetype, + ), + ) + ) diff --git a/snikket_web/templates/admin_edit_circle.html b/snikket_web/templates/admin_edit_circle.html index b158688..1c6b94a 100644 --- a/snikket_web/templates/admin_edit_circle.html +++ b/snikket_web/templates/admin_edit_circle.html @@ -1,12 +1,12 @@ {% extends "admin_app.html" %} -{% from "library.j2" import form_button, standard_button, value_or_hint, custom_form_button, clipboard_button, icon %} +{% from "library.j2" import form_button, standard_button, value_or_hint, custom_form_button, clipboard_button, icon, avatar with context %} {% block head_lead %} {{ super() }} {% include "copy-snippet.html" %} {% endblock %} {% block content %}

{% trans circle_name=(target_circle | circle_name) %}Edit circle {{ circle_name }}{% endtrans %}

-
+ {{- form.csrf_token -}} {%- if target_circle.id_ == "default" -%} {#- -#} @@ -39,6 +39,15 @@

{% trans %}Circle information{% endtrans %}

{% trans %}This circle has no group chat associated.{% endtrans %}

{%- endif -%} +

+ {{ form.avatar.label }} +
+ {{ form.avatar(accept="image/png", + data_maxsize=max_avatar_size, + data_warning_header=avatar_too_big_warning_header, + data_maxsize_warning=avatar_too_big_warning) }} +
+
{%- call standard_button("back", url_for(".circles"), class="tertiary") -%} {% trans %}Return to circle list{% endtrans %} diff --git a/snikket_web/xmpputil.py b/snikket_web/xmpputil.py index f60956a..a56b271 100644 --- a/snikket_web/xmpputil.py +++ b/snikket_web/xmpputil.py @@ -49,6 +49,8 @@ FORM_NODE_CONFIG = "http://jabber.org/protocol/pubsub#node_config" FORM_FIELD_PUBSUB_ACCESS_MODEL = "pubsub#access_model" +NS_VCARD_TEMP = "vcard-temp" + SimpleJID = typing.Tuple[typing.Optional[str], str, typing.Optional[str]] T = typing.TypeVar("T") @@ -226,6 +228,35 @@ def make_avatar_metadata_set_request( return req +def make_muc_avatar_set_request( + to: str, + data: bytes, + mimetype: str, + ) -> ET.Element: + req = ET.Element("iq", type="set", to=to) + vcard = ET.SubElement( + req, + "vCard", + xmlns=NS_VCARD_TEMP, + ) + photo_el = ET.SubElement( + vcard, + "PHOTO", + xmlns=NS_VCARD_TEMP, + ) + ET.SubElement( + photo_el, + "BINVAL", + xmlns=NS_VCARD_TEMP, + ).text = base64.b64encode(data).decode("ascii") + ET.SubElement( + photo_el, + "TYPE", + xmlns=NS_VCARD_TEMP, + ).text = mimetype + return req + + def _require_child(t: ET.Element, tag: str) -> ET.Element: el = t.find(tag) if el is None: