satoriformat.{c,h}: Add message parsing support

Sun, 10 Aug 2025 23:03:27 +0800

author
Gong Zhile <gongzl@stu.hebust.edu.cn>
date
Sun, 10 Aug 2025 23:03:27 +0800
changeset 2
efafd19ab2fe
parent 1
98bcf06036b8
child 3
33a7b189a2c6

satoriformat.{c,h}: Add message parsing support

meson.build file | annotate | diff | comparison | revisions
satoriformat.c file | annotate | diff | comparison | revisions
satoriformat.h file | annotate | diff | comparison | revisions
--- a/meson.build	Sat Aug 09 00:19:03 2025 +0800
+++ b/meson.build	Sun Aug 10 23:03:27 2025 +0800
@@ -3,6 +3,7 @@
 libpurple_dep = dependency('purple-3')
 libsoup_dep = dependency('libsoup-3.0')
 libjson_dep = dependency('json-glib-1.0')
+libpango_dep = dependency('pango')
 
 PURPLE_PLUGINDIR = libpurple_dep.get_pkgconfig_variable('plugindir')
 PURPLE_SATORI_SRCS = [
@@ -12,6 +13,7 @@
   'purplesatoriprotocolcontacts.c',
   'purplesatoriprotocolconversation.c',
   'satoriapi.c',
+  'satoriformat.c',
 ]
 
 library('libpurplesatori', PURPLE_SATORI_SRCS,
@@ -20,7 +22,7 @@
         '-DG_LOG_DOMAIN="Purple-Satori"',
         '-DGETTEXT_PACKAGE="pidgin3"'
     ],
-    dependencies : [libpurple_dep, libsoup_dep, libjson_dep],
+    dependencies : [libpurple_dep, libsoup_dep, libjson_dep, libpango_dep],
     name_prefix : '',
     install : true,
     install_dir : PURPLE_PLUGINDIR)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/satoriformat.c	Sun Aug 10 23:03:27 2025 +0800
@@ -0,0 +1,250 @@
+/*
+ * Purple Satori Plugin - Satori Protocol Plugin for Purple3
+ * Copyright (C) 2025 Gong Zhile
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "purplesatoriconnection.h"
+#include "satoriapi.h"
+#include <glib.h>
+#include <pango/pango-attributes.h>
+#include <string.h>
+
+typedef struct {
+	PangoAttribute *attr;
+	gint start;
+} SatoriHtmlTag;
+
+typedef struct {
+	PurpleConversation *conversation;
+	PangoAttrList *attrs;
+	GArray *tag_stack;
+	GString *out;
+} SatoriHtmlParseData;
+
+static SatoriHtmlTag *
+satori_html_push(SatoriHtmlParseData *data)
+{
+	SatoriHtmlTag t = { .attr = NULL, .start = data->out->len };
+	g_array_append_val(data->tag_stack, t);
+	return &g_array_index(data->tag_stack,
+			      SatoriHtmlTag,
+			      data->tag_stack->len - 1);
+}
+
+static void
+satori_html_pop(SatoriHtmlParseData *data)
+{
+	g_assert(data->tag_stack->len);
+
+	SatoriHtmlTag *top = &g_array_index(data->tag_stack,
+					    SatoriHtmlTag,
+					    data->tag_stack->len - 1);
+
+	if (top->attr) {
+		top->attr->start_index = top->start;
+		top->attr->end_index = data->out->len;
+		pango_attr_list_insert(data->attrs, top->attr);
+		top->attr = NULL;
+	}
+
+	g_array_remove_index(data->tag_stack, data->tag_stack->len - 1);
+}
+
+static void
+satori_format_html_start_element_cb(GMarkupParseContext	 *context,
+				    const gchar		 *element_name,
+				    const gchar		**attribute_names,
+				    const gchar		**attribute_values,
+				    gpointer		  user_data,
+				    GError		**error)
+{
+	SatoriHtmlParseData *dptr = user_data;
+	PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(
+		purple_conversation_get_connection(dptr->conversation));
+
+	SatoriHtmlTag *tag = satori_html_push(dptr);
+
+	if (g_str_equal(element_name, "quote")) {
+		g_string_append_printf(dptr->out, "> ");
+		tag->attr = pango_attr_background_new(
+			(guint16)(0.8 * 65535),
+			(guint16)(0.8 * 65535),
+			(guint16)(0.8 * 65535));
+		return;
+	} else if (g_str_equal(element_name, "at")) {
+		/* Mention Handling */
+
+		const gchar *id_value = NULL, *name_value = NULL, *type_value = NULL;
+		for (int i = 0; attribute_names[i]; i++) {
+			if (g_str_equal(attribute_names[i], "id"))
+				id_value = attribute_values[i];
+			else if (g_str_equal(attribute_names[i], "name"))
+				name_value = attribute_values[i];
+			else if (g_str_equal(attribute_names[i], "type"))
+				type_value = attribute_names[i];
+		}
+
+		if (!id_value && !name_value && !type_value)
+			return;
+
+		if (name_value && !id_value) {
+			g_string_append_printf(dptr->out, "@%s", name_value);
+		} else {
+			SatoriUser user = {
+				.id	= id_value,
+				.name	= name_value,
+				.nick	= name_value,
+			};
+
+			PurpleConversationMember *member =		\
+				purple_satori_add_conversation_member_from_user(
+					con, dptr->conversation, &user);
+			PurpleContactInfo *info = \
+				purple_conversation_member_get_contact_info(member);
+
+			const gchar *display_name = \
+				purple_contact_info_get_name_for_display(info);
+
+			g_string_append_printf(
+				dptr->out, "@%s",
+				(!display_name || !*display_name)
+				? user.id : display_name);
+		}
+
+		PurpleContactInfo *me =
+			purple_account_get_contact_info(
+				purple_conversation_get_account(dptr->conversation));
+
+		if (dptr->attrs) {
+			PangoAttribute *bg = NULL;
+			if (id_value &&
+			    g_str_equal(purple_contact_info_get_id(me), id_value))
+				bg = pango_attr_background_new(
+					(guint16)(100.0 / 255 * 65535),
+					(guint16)(149.0 / 255 * 65535),
+					(guint16)(237.0 / 255 * 65535));
+			else
+				bg = pango_attr_background_new(
+					(guint16)(240.0 / 255 * 65535),
+					(guint16)(248.0 / 255 * 65535),
+					(guint16)(255.0 / 255 * 65535));
+		        tag->attr = bg;
+		}
+
+		return;
+	} else if (g_str_equal(element_name, "img")) {
+		/* Image Handling */
+
+		GString *src_value = NULL;
+		for (int i = 0; attribute_names[i]; i++) {
+			if (g_str_equal(attribute_names[i], "src"))
+				src_value = g_string_new(attribute_values[i]);
+		}
+
+		if (!src_value) {
+			g_string_append_printf(dptr->out, "[Image] ");
+			return;
+		}
+
+		g_string_replace(src_value, "&amp;", "&", 0);
+		g_string_append_printf(dptr->out, "[Image: %s ] ", src_value->str);
+		g_string_free(src_value, TRUE);
+
+		return;
+	}
+}
+
+static void
+satori_format_html_end_element_cb(GMarkupParseContext *context,
+				  const gchar         *element_name,
+				  gpointer             user_data,
+				  GError             **error)
+{
+	SatoriHtmlParseData *dptr = user_data;
+
+	if (g_str_equal(element_name, "quote")) {
+		g_string_append_printf(dptr->out, "\n");
+	}
+
+        satori_html_pop(dptr);
+}
+
+static void
+satori_format_html_text_cb(GMarkupParseContext *context,
+			   const gchar         *text,
+			   gsize                text_len,
+			   gpointer             user_data,
+			   GError             **error)
+{
+	SatoriHtmlParseData *dptr = user_data;
+	g_string_append_len(dptr->out, text, text_len);
+}
+
+void
+satori_format_html_to_purple(PurpleConversation *conversation,
+			     const gchar *html,
+			     GString **out_content,
+			     G_GNUC_UNUSED PangoAttrList *attrs)
+{
+	gchar *wrapped_html = g_strdup_printf("<root>%s</root>", html);
+
+	GMarkupParser parser = {
+		.start_element = satori_format_html_start_element_cb,
+		.end_element   = satori_format_html_end_element_cb,
+		.text	       = satori_format_html_text_cb,
+		.passthrough   = NULL,
+		.error	       = NULL,
+	};
+
+	*out_content = g_string_new(NULL);
+
+	SatoriHtmlParseData dptr = {
+		.tag_stack = g_array_new(FALSE, FALSE, sizeof(SatoriHtmlTag)),
+		.conversation = conversation,
+		.out = *out_content,
+		.attrs = attrs,
+	};
+
+	GError *error = NULL;
+	GMarkupParseContext *context = \
+		g_markup_parse_context_new(&parser, 0, &dptr, NULL);
+
+	if (!g_markup_parse_context_parse(context, wrapped_html,
+					  strlen(wrapped_html), &error) ||
+	    !g_markup_parse_context_end_parse(context, &error)) {
+		gint bi = dptr.out->len, ei = 0;
+		g_string_append_printf(*out_content, "[PARSE FAILED: %s]",
+				       error ? error->message : "UNKOWN");
+		ei = dptr.out->len;
+
+		if (dptr.attrs) {
+			PangoAttribute *fg = \
+				pango_attr_foreground_new(65535, 0, 0);
+
+			fg->start_index = bi;
+			fg->end_index	= ei;
+			
+			pango_attr_list_insert(dptr.attrs, fg);
+
+		}
+
+		if (error)
+			g_error_free(error);
+	}
+
+	g_free(wrapped_html);
+	g_markup_parse_context_free(context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/satoriformat.h	Sun Aug 10 23:03:27 2025 +0800
@@ -0,0 +1,31 @@
+/*
+ * Purple Satori Plugin - Satori Protocol Plugin for Purple3
+ * Copyright (C) 2025 Gong Zhile
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef SATORI_FORMAT_H
+#define SATORI_FORMAT_H
+
+#include <glib.h>
+#include <purple.h>
+#include <pango/pango-attributes.h>
+
+void
+satori_format_html_to_purple(PurpleConversation *conversation,
+			     const gchar *html, GString **out_content,
+			     PangoAttrList *attrs);
+
+#endif	/* SATORI_FORMAT_H */

mercurial