purplesatoriconnection.c

changeset 0
cc7c1f9d20f7
child 1
98bcf06036b8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/purplesatoriconnection.c	Fri Aug 08 09:46:55 2025 +0800
@@ -0,0 +1,229 @@
+/*
+ * 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 <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n-lib.h>
+#include <json-glib/json-glib.h>
+
+#include <gio/gio.h>
+#include <libsoup/soup.h>
+#include <libsoup/soup-session.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-websocket-connection.h>
+
+#include "purplesatoriconnection.h"
+#include "purplesatoriprotocolcontacts.h"
+#include "satorimessage.h"
+#include "satoritypes.h"
+#include "satoriapi.h"
+
+struct _PurpleSatoriConnection {
+	PurpleConnection	 parent;
+	SoupSession		*session;
+
+	SoupWebsocketConnection *wscon;
+	gboolean		 wsidented;
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleSatoriConnection, purple_satori_connection,
+                               PURPLE_TYPE_CONNECTION, G_TYPE_FLAG_FINAL, {});
+
+/******************************************************************************
+ * PurpleConnection WS Callbacks
+ *****************************************************************************/
+
+static void
+satori_ws_on_closed(SoupWebsocketConnection *wscon, PurpleSatoriConnection *data)
+{
+	PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(data);
+	PurpleAccount *acc = purple_connection_get_account(PURPLE_CONNECTION(con));
+	purple_account_disconnect(acc);
+
+	con->wscon = NULL;
+}
+
+static void
+satori_ws_on_message(SoupWebsocketConnection *wscon, gint type,
+		     GBytes *message, PurpleSatoriConnection *con)
+{
+	PurpleAccount *acc = purple_connection_get_account(
+		PURPLE_CONNECTION(con));
+
+	if (type != SOUP_WEBSOCKET_DATA_TEXT) {
+		purple_debug_warning("satori", "unexpected data recv from ws");
+		return;
+	}
+
+	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");
+		g_object_unref(parser);
+		return;
+	}
+
+	JsonObject *root = json_node_get_object(json_parser_get_root(parser));
+	JsonObject *body = json_object_get_object_member(root, "body");
+	SatoriWebsocketOpcode op = json_object_get_int_member(root, "op");
+
+	switch (op) {
+
+	case SATORI_WEBSOCKET_OP_READY:
+	{
+		purple_account_connected(acc);
+
+		JsonArray *logins = json_object_get_array_member(body, "logins");
+		JsonObject *user_obj = json_object_get_object_member(
+			json_array_get_object_element(logins, 0), "user");
+		SatoriUser user = { 0 };
+
+		if (!user_obj) break;
+		satori_user_from_json(user_obj, &user);
+
+		PurpleContactInfo *ci = purple_account_get_contact_info(acc);
+		purple_contact_info_set_id(ci, user.id);
+		purple_contact_info_set_display_name(ci, user.nick
+						     ? user.nick : user.name);
+
+		satori_refresh_buddy_contacts(con, NULL);
+		break;
+	}
+
+	default:		/* ignored */
+		break;
+
+	}
+
+	g_print("op = %d\n", op);
+
+	g_object_unref(parser);
+}
+
+static void
+satori_ws_on_connection(SoupSession *session, GAsyncResult *res, gpointer data)
+{
+	PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(data);
+	PurpleAccount *acc = purple_connection_get_account(PURPLE_CONNECTION(con));
+	GError *err = NULL;
+
+	con->wscon = soup_session_websocket_connect_finish(session, res, &err);
+	if (err) {
+		purple_account_disconnect_with_error(acc, err);
+		return;
+	}
+
+	g_signal_connect(con->wscon, "message",
+			 G_CALLBACK(satori_ws_on_message), con);
+	g_signal_connect(con->wscon, "closed",
+			 G_CALLBACK(satori_ws_on_closed), con);
+
+	GBytes *frame = satori_message_gen_ident(NULL, 0);
+	soup_websocket_connection_send_text(con->wscon,
+					    g_bytes_get_data(frame, NULL));
+	g_bytes_unref(frame);
+}
+
+/******************************************************************************
+ * PurpleConnection Implementation
+ *****************************************************************************/
+static gboolean
+purple_satori_connection_connect(PurpleConnection *connection,
+				 G_GNUC_UNUSED GError **error)
+{
+	PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(connection);
+
+	if (con->wscon)
+		g_object_unref(con->wscon);
+
+	SoupMessage *svmsg = satori_message_new("GET", PURPLE_SATORI_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); */
+
+	return TRUE;
+}
+
+static gboolean
+purple_satori_connection_disconnect(PurpleConnection *connection,
+				    G_GNUC_UNUSED GError **error)
+{
+	PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(connection);
+	if (!con->wscon) return TRUE;
+
+	g_signal_handlers_disconnect_by_data(con->wscon, con);
+	soup_websocket_connection_close(con->wscon,
+					SOUP_WEBSOCKET_CLOSE_NO_STATUS, NULL);
+	con->wscon = NULL;
+	return TRUE;
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_satori_connection_init(PurpleSatoriConnection *connection) {
+	connection->session = soup_session_new();
+	connection->wscon   = NULL;
+}
+
+static void
+purple_satori_connection_class_finalize(G_GNUC_UNUSED PurpleSatoriConnectionClass *klass) {
+}
+
+static void
+purple_satori_connection_class_init(PurpleSatoriConnectionClass *klass) {
+	PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass);
+
+	connection_class->connect = purple_satori_connection_connect;
+	connection_class->disconnect = purple_satori_connection_disconnect;
+}
+
+/******************************************************************************
+ * Internal API
+ *****************************************************************************/
+void
+purple_satori_connection_register(GPluginNativePlugin *plugin) {
+	purple_satori_connection_register_type(G_TYPE_MODULE(plugin));
+}
+
+/******************************************************************************
+ * Public API Implementation
+ *****************************************************************************/
+
+void
+purple_satori_connection_send_and_read_async(PurpleSatoriConnection	*con,
+					     SoupMessage		*msg,
+					     int			 io_priority,
+					     GCancellable		*cancellable,
+					     GAsyncReadyCallback	 callback,
+					     gpointer			 user_data)
+{
+	soup_session_send_and_read_async(con->session, msg, io_priority,
+					 cancellable, callback, user_data);
+}

mercurial