Various improvement, Support configuration from UI default tip

Sun, 10 Aug 2025 23:53:22 +0800

author
Gong Zhile <gongzl@stu.hebust.edu.cn>
date
Sun, 10 Aug 2025 23:53:22 +0800
changeset 3
33a7b189a2c6
parent 2
efafd19ab2fe

Various improvement, Support configuration from UI

purplesatoriconnection.c file | annotate | diff | comparison | revisions
purplesatoriconnection.h file | annotate | diff | comparison | revisions
purplesatoriprotocol.c file | annotate | diff | comparison | revisions
purplesatoriprotocolconversation.c file | annotate | diff | comparison | revisions
satoriapi.c file | annotate | diff | comparison | revisions
satoriapi.h file | annotate | diff | comparison | revisions
satorimessage.h file | annotate | diff | comparison | revisions
--- a/purplesatoriconnection.c	Sun Aug 10 23:03:27 2025 +0800
+++ b/purplesatoriconnection.c	Sun Aug 10 23:53:22 2025 +0800
@@ -30,9 +30,11 @@
 #include <time.h>
 
 #include "purplesatoriconnection.h"
+#include "pango/pango-attributes.h"
 #include "purplesatoriprotocolcontacts.h"
 #include "satorimessage.h"
 #include "satoritypes.h"
+#include "satoriformat.h"
 #include "satoriapi.h"
 
 struct _PurpleSatoriConnection {
@@ -64,14 +66,22 @@
 
 	const gchar *id = json_object_get_string_member_with_default(
 		msg_obj, "id", NULL);
-	const gchar *text = json_object_get_string_member_with_default(
+	const gchar *html = json_object_get_string_member_with_default(
 		msg_obj, "content", "Invalid Message");
 
 	time_t created_at = json_object_get_int_member_with_default(
 		msg_obj, "created_at", 0) / 1000; /* timestamp in mS */
 
-	PurpleMessage *message = purple_message_new(mbr, text);
+	PangoAttrList *attrs = pango_attr_list_new();
+	GString *text = NULL;
+	satori_format_html_to_purple(conversation, html, &text, attrs);
+
+	PurpleMessage *message = purple_message_new(mbr, text->str);
 	if (id) purple_message_set_id(message, id);
+	purple_message_set_attributes(message, attrs);
+
+	pango_attr_list_unref(attrs);
+	g_free(text);
 
 	if (created_at) {
 		GDateTime *ts = g_date_time_new_from_unix_local(created_at);
@@ -112,8 +122,6 @@
 	gsize sz;
         const gchar *ptr = g_bytes_get_data(message, &sz);
 
-        /* g_print("Received text data: %s\n", ptr); */
-
 	JsonParser *parser = json_parser_new();
 	if (!json_parser_load_from_data(parser, ptr, sz, NULL)) {
 		purple_debug_warning("satori", "bad json received from ws");
@@ -144,6 +152,10 @@
 		purple_contact_info_set_display_name(ci, user.nick
 						     ? user.nick : user.name);
 
+		PurplePresence *presence = purple_contact_info_get_presence(ci);
+		purple_presence_set_primitive(presence,
+					      PURPLE_PRESENCE_PRIMITIVE_AVAILABLE);
+
 		satori_refresh_buddy_contacts(con, NULL);
 		satori_refresh_conversations(con, NULL);
 		break;
@@ -221,20 +233,25 @@
 				 G_GNUC_UNUSED GError **error)
 {
 	PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(connection);
+	PurpleAccount *acc = purple_connection_get_account(connection);
 
 	if (con->wscon)
 		g_object_unref(con->wscon);
 
-	SoupMessage *svmsg = satori_message_new("GET", PURPLE_SATORI_WSURL);
+	gchar *wsurl = \
+	  g_strdup_printf("%s://%s%s/events",
+			  purple_account_get_bool(acc, "https", FALSE) ? "wss" : "ws",
+			  purple_account_get_string(acc, "host", "127.0.0.1:5600"),
+			  purple_account_get_string(acc, "path", "/v1"));
+
+	SoupMessage *svmsg = soup_message_new("GET", wsurl);
 	soup_session_websocket_connect_async(
 		con->session, svmsg,
 		NULL, NULL, 0, NULL,
 		(GAsyncReadyCallback) satori_ws_on_connection,
 		con);
 
-	/* purple_account_connected(account); */
-	/* purple_satori_contacts_load(account); */
-
+	g_free(wsurl);
 	return TRUE;
 }
 
--- a/purplesatoriconnection.h	Sun Aug 10 23:03:27 2025 +0800
+++ b/purplesatoriconnection.h	Sun Aug 10 23:53:22 2025 +0800
@@ -22,21 +22,39 @@
 #include <glib.h>
 #include <purple.h>
 #include <libsoup/soup.h>
+#include <libsoup/soup-types.h>
 
 G_BEGIN_DECLS
 
-#define PURPLE_SATORI_HOST	"192.168.1.154:5600"
-#define PURPLE_SATORI_WSURL	"ws://" PURPLE_SATORI_HOST "/v1/events"
-
-#define PURPLE_SATORI_PLATFORM	"QQ"
-#define PURPLE_SATORI_USER_ID	"3751531667"
-
 #define PURPLE_SATORI_TYPE_CONNECTION (purple_satori_connection_get_type())
 G_DECLARE_FINAL_TYPE(PurpleSatoriConnection, purple_satori_connection, PURPLE_SATORI,
                      CONNECTION, PurpleConnection)
 
 G_GNUC_INTERNAL void purple_satori_connection_register(GPluginNativePlugin *plugin);
 
+static inline SoupMessage *
+SATORI_ENDPOINT(PurpleSatoriConnection *con, const gchar *p) {
+	PurpleAccount *acc =						\
+		purple_connection_get_account(PURPLE_CONNECTION(con));
+
+	gchar *url =				\
+	  g_strdup_printf("%s://%s%s%s",
+			  purple_account_get_bool(acc, "https", FALSE) ? "https" : "http",
+			  purple_account_get_string(acc, "host", "127.0.0.1:5600"),
+			  purple_account_get_string(acc, "path", "/v1"), p);
+
+	SoupMessage *msg = soup_message_new("POST", url);
+	SoupMessageHeaders *headers = soup_message_get_request_headers(msg);
+
+	soup_message_headers_append(headers, "Satori-Platform",
+				    purple_account_get_string(acc, "platform", ""));
+	soup_message_headers_append(headers, "Satori-User-ID",
+				    purple_account_get_username(acc));
+
+	g_free(url);
+	return msg;
+}
+
 void
 purple_satori_connection_send_and_read_async(PurpleSatoriConnection	*con,
 					     SoupMessage		*msg,
--- a/purplesatoriprotocol.c	Sun Aug 10 23:03:27 2025 +0800
+++ b/purplesatoriprotocol.c	Sun Aug 10 23:53:22 2025 +0800
@@ -20,6 +20,7 @@
 
 #include "purplesatoriprotocol.h"
 
+#include "glib.h"
 #include "purplesatoriconnection.h"
 #include "purplesatoriprotocolcontacts.h"
 #include "purplesatoriprotocolconversation.h"
@@ -48,6 +49,34 @@
 
 }
 
+static GList *
+purple_satori_protocol_get_account_options(G_GNUC_UNUSED PurpleProtocol *protocol)
+{
+	PurpleAccountOption *option = NULL;
+	GList *options = NULL;
+
+	option = purple_account_option_string_new(_("Satori Platform"), "platform",
+						  "QQ");
+	options = g_list_append(options, option);
+
+	option = purple_account_option_bool_new(_("Secure Connection (HTTPS)"),
+					       "https", FALSE);
+	options = g_list_append(options, option);
+
+	option = purple_account_option_string_new(_("API Host"),
+	                                          "host", "127.0.0.1:5600");
+	options = g_list_append(options, option);
+
+	option = purple_account_option_string_new(_("API Path"), "path", "/v1"),
+	options = g_list_append(options, option);
+
+	option = purple_account_option_string_new(_("API Token"), "token", NULL);
+	purple_account_option_string_set_masked(option, TRUE);
+	options = g_list_append(options, option);
+
+	return options;
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -73,6 +102,7 @@
 purple_satori_protocol_class_init(PurpleSatoriProtocolClass *klass) {
 	PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
 
+	protocol_class->get_account_options = purple_satori_protocol_get_account_options;
 	protocol_class->create_connection = purple_satori_protocol_create_connection;
 }
 
--- a/purplesatoriprotocolconversation.c	Sun Aug 10 23:03:27 2025 +0800
+++ b/purplesatoriprotocolconversation.c	Sun Aug 10 23:53:22 2025 +0800
@@ -20,39 +20,13 @@
 
 #include "purplesatoriprotocolconversation.h"
 
+#include "glib.h"
 #include "purplesatoriconnection.h"
 #include "purplesatoriplugin.h"
 #include "purplesatoriprotocol.h"
 #include "satoriapi.h"
 #include "satoritypes.h"
 
-typedef struct {
-	PurpleConversation *conversation;
-	PurpleMessage *message;
-} PurpleSatoriProtocolIMInfo;
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-purple_satori_protocol_im_info_free(PurpleSatoriProtocolIMInfo *info) {
-	g_clear_object(&info->conversation);
-	g_clear_object(&info->message);
-	g_free(info);
-}
-
-/******************************************************************************
- * Callbacks
- *****************************************************************************/
-static gboolean
-purple_satori_protocol_echo_im_cb(gpointer data) {
-	PurpleSatoriProtocolIMInfo *info = data;
-
-	purple_conversation_write_message(info->conversation, info->message);
-
-	return G_SOURCE_REMOVE;
-}
-
 /******************************************************************************
  * PurpleProtocolConversation Implementation
  *****************************************************************************/
@@ -60,7 +34,7 @@
 purple_satori_protocol_get_create_conversation_details(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
 						       G_GNUC_UNUSED PurpleAccount *account)
 {
-	return purple_create_conversation_details_new(0);
+	return purple_create_conversation_details_new(2);
 }
 
 static void
@@ -128,113 +102,28 @@
 }
 
 static void
-purple_satori_protocol_conversation_leave_conversation_async(PurpleProtocolConversation *protocol,
-                                                           G_GNUC_UNUSED PurpleConversation *conversation,
-                                                           GCancellable *cancellable,
-                                                           GAsyncReadyCallback callback,
-                                                           gpointer data)
-{
-	GTask *task = NULL;
-
-	task = g_task_new(protocol, cancellable, callback, data);
-	g_task_set_source_tag(task,
-	                      purple_satori_protocol_conversation_leave_conversation_async);
-
-	g_task_return_boolean(task, TRUE);
-	g_clear_object(&task);
-}
-
-static gboolean
-purple_satori_protocol_conversation_leave_conversation_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
-                                                            GAsyncResult *result,
-                                                            GError **error)
-{
-	gpointer tag = purple_satori_protocol_conversation_leave_conversation_async;
-
-	g_return_val_if_fail(g_async_result_is_tagged(result, tag), FALSE);
-
-	return g_task_propagate_boolean(G_TASK(result), error);
-}
-
-static void
-purple_satori_protocol_send_message_async(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
-                                        PurpleConversation *conversation,
-                                        PurpleMessage *message,
-                                        GCancellable *cancellable,
-                                        GAsyncReadyCallback callback,
-                                        gpointer data)
+purple_satori_protocol_send_message_async(PurpleProtocolConversation *protocol,
+					  PurpleConversation *conversation,
+					  PurpleMessage *message,
+					  GCancellable *cancellable,
+					  GAsyncReadyCallback callback,
+					  gpointer data)
 {
-	GTask *task = NULL;
-
-	if(purple_conversation_is_dm(conversation)) {
-		PurpleAccount *account = NULL;
-		PurpleContact *contact = NULL;
-		PurpleContactInfo *contact_info = NULL;
-		PurpleContactManager *manager = NULL;
-		PurpleConversationMember *member = NULL;
-		PurpleConversationMembers *members = NULL;
-
-		account = purple_conversation_get_account(conversation);
-		members = purple_conversation_get_members(conversation);
-
-		manager = purple_contact_manager_get_default();
-
-		/* Check if this dm is with echo. */
-		contact = purple_contact_manager_find_with_id(manager, account,
-		                                              "echo");
-		contact_info = PURPLE_CONTACT_INFO(contact);
-		member = purple_conversation_members_find_member(members, contact_info);
-		if(PURPLE_IS_CONVERSATION_MEMBER(member)) {
-			PurpleSatoriProtocolIMInfo *info = NULL;
-			const char *contents = purple_message_get_contents(message);
-
-			info = g_new(PurpleSatoriProtocolIMInfo, 1);
-			info->conversation = g_object_ref(conversation);
-			info->message = purple_message_new(member, contents);
-			purple_message_set_edited(info->message,
-			                          purple_message_get_edited(message));
-
-			g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
-			                purple_satori_protocol_echo_im_cb, info,
-			                (GDestroyNotify)purple_satori_protocol_im_info_free);
-		}
-
-		/* Check if this dm is with aegina. */
-		contact = purple_contact_manager_find_with_id(manager, account,
-		                                              "aegina");
-		contact_info = PURPLE_CONTACT_INFO(contact);
-		member = purple_conversation_members_find_member(members, contact_info);
-		if(PURPLE_IS_CONVERSATION_MEMBER(member)) {
-			PurpleSatoriProtocolIMInfo *info = g_new(PurpleSatoriProtocolIMInfo, 1);
-			PurpleConversationMember *author = purple_message_get_author(message);
-			PurpleContactInfo *author_info = NULL;
-			const char *contents = NULL;
-			const char *author_id = NULL;
-
-			author_info = purple_conversation_member_get_contact_info(author);
-			author_id = purple_contact_info_get_id(author_info);
-			if(purple_strequal(author_id, "hades")) {
-				contents = "🫥️";
-			} else {
-				/* TRANSLATORS: This is a reference to the Cap of Invisibility owned by
-				 * various Greek gods, such as Hades, as mentioned. */
-				contents = _("Don't tell Hades I have his Cap");
-			}
-
-			info->conversation = g_object_ref(conversation);
-			info->message = purple_message_new(member, contents);
-
-			g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, purple_satori_protocol_echo_im_cb,
-			                info, (GDestroyNotify)purple_satori_protocol_im_info_free);
-		}
-	}
+	PurpleSatoriConnection *con = \
+		PURPLE_SATORI_CONNECTION(purple_conversation_get_connection(conversation));
 
 	purple_conversation_write_message(conversation, message);
 
-	task = g_task_new(protocol, cancellable, callback, data);
-	g_task_return_boolean(task, TRUE);
+	GTask *task = g_task_new(protocol, cancellable, callback, data);
+	g_task_set_source_tag(task,
+			      purple_satori_protocol_send_message_async);
 
-	g_clear_object(&task);
+	gchar *content = \
+		g_markup_escape_text(purple_message_get_contents(message), -1);
+	satori_send_message(con, conversation, message, content, task);
+	g_free(content);
+
+	return;
 }
 
 static gboolean
@@ -256,11 +145,6 @@
 	iface->create_conversation_finish =
 		purple_satori_protocol_create_conversation_finish;
 
-	iface->leave_conversation_async =
-		purple_satori_protocol_conversation_leave_conversation_async;
-	iface->leave_conversation_finish =
-		purple_satori_protocol_conversation_leave_conversation_finish;
-
 	iface->send_message_async = purple_satori_protocol_send_message_async;
 	iface->send_message_finish = purple_satori_protocol_send_message_finish;
 }
--- a/satoriapi.c	Sun Aug 10 23:03:27 2025 +0800
+++ b/satoriapi.c	Sun Aug 10 23:53:22 2025 +0800
@@ -25,6 +25,7 @@
 
 #include "purplesatoriconnection.h"
 #include "purplesatoriplugin.h"
+#include "purplesatoriprotocolconversation.h"
 #include "satorimessage.h"
 #include "satoritypes.h"
 #include "satoriapi.h"
@@ -153,9 +154,12 @@
 		found = FALSE;
 	}
 
-	purple_contact_info_set_display_name(
-		PURPLE_CONTACT_INFO(contact),
-		user->nick ? user->nick : user->name);
+	if (user->nick)
+		purple_contact_info_set_display_name(
+			PURPLE_CONTACT_INFO(contact), user->nick);
+	else if (user->name)
+		purple_contact_info_set_display_name(
+			PURPLE_CONTACT_INFO(contact), user->name);
 
 	PurplePresence *presence = purple_contact_info_get_presence(
 		PURPLE_CONTACT_INFO(contact));
@@ -252,8 +256,7 @@
 		JB_END_OBJ(data, b);
 	}
 
-	SoupMessage *msg = satori_message_new(
-		"POST", SATORI_ENDPOINT("/v1/friend.list"));
+	SoupMessage *msg = SATORI_ENDPOINT(con, "/friend.list");
 	soup_message_set_request_body_from_bytes(msg, "application/json", data);
 
 	purple_satori_connection_send_and_read_async(
@@ -337,8 +340,7 @@
 		JB_END_OBJ(data, b);
 	}
 
-	SoupMessage *msg = satori_message_new(
-		"POST", SATORI_ENDPOINT("/v1/channel.list"));
+	SoupMessage *msg = SATORI_ENDPOINT(con, "/channel.list");
 	soup_message_set_request_body_from_bytes(msg, "application/json", data);
 
 	purple_satori_connection_send_and_read_async(
@@ -412,8 +414,7 @@
 		JB_END_OBJ(data, b);
 	}
 
-	SoupMessage *msg = satori_message_new(
-		"POST", SATORI_ENDPOINT("/v1/guild.list"));
+	SoupMessage *msg = SATORI_ENDPOINT(con, "/guild.list");
 	soup_message_set_request_body_from_bytes(msg, "application/json", data);
 
 	purple_satori_connection_send_and_read_async(
@@ -425,6 +426,10 @@
 	g_bytes_unref(data);
 }
 
+/******************************************************************************
+ * DM Creation
+ *****************************************************************************/
+
 typedef struct {
 	PurpleSatoriConnection *con;
 	SatoriUser user;
@@ -497,8 +502,7 @@
 		JB_END_OBJ(data, b);
 	}
 
-	SoupMessage *msg = satori_message_new(
-		"POST", SATORI_ENDPOINT("/v1/user.channel.create"));
+	SoupMessage *msg = SATORI_ENDPOINT(con, "/user.channel.create");
 	soup_message_set_request_body_from_bytes(msg, "application/json", data);
 
 	SatoriOnDmChannelData *dptr = g_new0(SatoriOnDmChannelData, 1);
@@ -515,3 +519,110 @@
 	g_object_unref(msg);
 	g_bytes_unref(data);
 }
+
+/******************************************************************************
+ * Message Routines
+ *****************************************************************************/
+
+typedef struct {
+	PurpleConversation *conversation;
+	PurpleMessage *message;
+	GTask *task;
+} SatoriSendMessageData;
+
+static void
+satori_on_message_sent(SoupSession *session,
+		       GAsyncResult *res,
+		       SatoriSendMessageData *dptr)
+{
+	GError *error = NULL;
+	GBytes *resp = soup_session_send_and_read_finish(session, res, &error);
+
+	if (error) {
+		purple_debug_error("satori",
+				   "create_dm_channel failed: %s",
+				   error->message);
+		if (resp)
+			g_bytes_unref(resp);
+
+		g_task_return_error(dptr->task, error);
+		goto cleanup;
+	}
+
+	gsize sz;
+	const gchar *ptr = g_bytes_get_data(resp, &sz);
+
+	JsonParser *parser = json_parser_new();
+	if (!json_parser_load_from_data(parser, ptr, sz, NULL)) {
+		purple_debug_warning("satori", "bad json received from api");
+		g_task_return_new_error_literal(dptr->task, PURPLE_SATORI_DOMAIN, 0,
+						"bad json received from api");
+		goto cleanup;
+	}
+
+	/* Initialize ID */
+
+	JsonArray *root = json_node_get_array(json_parser_get_root(parser));
+	JsonObject *msg_obj = json_array_get_object_element(root, 0);
+	const gchar *id = json_object_get_string_member_with_default(
+		msg_obj, "id", NULL);
+
+	if (!id) {
+		g_task_return_new_error_literal(dptr->task, PURPLE_SATORI_DOMAIN, 0,
+						"message not found");
+		goto cleanup;
+	}
+
+	purple_message_set_id(dptr->message, id);
+	purple_message_set_delivered(dptr->message, TRUE);
+
+	/* Initialize created_time */
+
+	time_t created_at = json_object_get_int_member_with_default(
+		msg_obj, "created_at", 0) / 1000; /* timestamp in mS */
+
+	if (created_at) {
+		GDateTime *ts = g_date_time_new_from_unix_local(created_at);
+		purple_message_set_delivered_at(dptr->message, ts);
+		g_date_time_unref(ts);
+	}
+
+	g_task_return_boolean(dptr->task, TRUE);
+	g_object_unref(parser);
+cleanup:
+	g_clear_object(&dptr->task);
+	g_free(dptr);
+}
+
+void
+satori_send_message(PurpleSatoriConnection *con,
+		    PurpleConversation *conversation,
+		    PurpleMessage *message,
+		    const gchar *content,
+		    GTask *task)
+{
+	GBytes *data = NULL;
+
+	{
+		JB_BEGIN_OBJ(b);
+		JBA(b, "channel_id", purple_conversation_get_id(conversation));
+		JBA(b, "content", content);
+		JB_END_OBJ(data, b);
+	}
+
+	SoupMessage *msg = SATORI_ENDPOINT(con, "/message.create");
+	soup_message_set_request_body_from_bytes(msg, "application/json", data);
+
+	SatoriSendMessageData *dptr = g_new0(SatoriSendMessageData, 1);
+	dptr->conversation = conversation;
+	dptr->message = message;
+	dptr->task = task;
+
+	purple_satori_connection_send_and_read_async(
+		con, msg, 0, NULL,
+		(GAsyncReadyCallback) satori_on_message_sent,
+		dptr);
+
+	g_object_unref(msg);
+	g_bytes_unref(data);
+}
--- a/satoriapi.h	Sun Aug 10 23:03:27 2025 +0800
+++ b/satoriapi.h	Sun Aug 10 23:53:22 2025 +0800
@@ -30,5 +30,7 @@
 void satori_refresh_buddy_contacts(PurpleSatoriConnection *con, const gchar *next);
 void satori_refresh_conversations(PurpleSatoriConnection *con, const gchar *next);
 void satori_create_dm_channel(PurpleSatoriConnection *con, SatoriUser *user, GTask *task);
+void
+satori_send_message(PurpleSatoriConnection *con, PurpleConversation *conversation, PurpleMessage *message, const gchar *content, GTask *task);
 
 #endif	/* SATORI_API_H */
--- a/satorimessage.h	Sun Aug 10 23:03:27 2025 +0800
+++ b/satorimessage.h	Sun Aug 10 23:53:22 2025 +0800
@@ -28,8 +28,6 @@
 #include "satoritypes.h"
 #include "purplesatoriconnection.h"
 
-#define SATORI_ENDPOINT(path) "http://" PURPLE_SATORI_HOST path
-
 #define JBO(b) json_builder_begin_object(b)
 #define JEO(b) json_builder_end_object(b)
 #define JBSN(b, name) json_builder_set_member_name(b, name)
@@ -65,18 +63,6 @@
 		g_object_unref(b); \
 	} while (0)
 
-static inline SoupMessage *
-satori_message_new(const gchar *method, const gchar *url)
-{
-	SoupMessage *msg = soup_message_new(method, url);
-	SoupMessageHeaders *headers = soup_message_get_request_headers(msg);
-	soup_message_headers_append(headers, "Satori-Platform",
-				    PURPLE_SATORI_PLATFORM);
-	soup_message_headers_append(headers, "Satori-User-ID",
-				    PURPLE_SATORI_USER_ID);
-	return msg;
-}
-
 static inline GBytes *
 satori_message_gen_ident(const gchar *token, gint sn)
 {

mercurial