diff -r 000000000000 -r cc7c1f9d20f7 purplesatoriconnection.c
--- /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 .
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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);
+}