purplesatoriconnection.c

changeset 0
cc7c1f9d20f7
child 1
98bcf06036b8
equal deleted inserted replaced
-1:000000000000 0:cc7c1f9d20f7
1 /*
2 * Purple Satori Plugin - Satori Protocol Plugin for Purple3
3 * Copyright (C) 2025 Gong Zhile
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
17 */
18
19 #include <glib.h>
20 #include <glib-object.h>
21 #include <glib/gi18n-lib.h>
22 #include <json-glib/json-glib.h>
23
24 #include <gio/gio.h>
25 #include <libsoup/soup.h>
26 #include <libsoup/soup-session.h>
27 #include <libsoup/soup-message.h>
28 #include <libsoup/soup-types.h>
29 #include <libsoup/soup-websocket-connection.h>
30
31 #include "purplesatoriconnection.h"
32 #include "purplesatoriprotocolcontacts.h"
33 #include "satorimessage.h"
34 #include "satoritypes.h"
35 #include "satoriapi.h"
36
37 struct _PurpleSatoriConnection {
38 PurpleConnection parent;
39 SoupSession *session;
40
41 SoupWebsocketConnection *wscon;
42 gboolean wsidented;
43 };
44
45 G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleSatoriConnection, purple_satori_connection,
46 PURPLE_TYPE_CONNECTION, G_TYPE_FLAG_FINAL, {});
47
48 /******************************************************************************
49 * PurpleConnection WS Callbacks
50 *****************************************************************************/
51
52 static void
53 satori_ws_on_closed(SoupWebsocketConnection *wscon, PurpleSatoriConnection *data)
54 {
55 PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(data);
56 PurpleAccount *acc = purple_connection_get_account(PURPLE_CONNECTION(con));
57 purple_account_disconnect(acc);
58
59 con->wscon = NULL;
60 }
61
62 static void
63 satori_ws_on_message(SoupWebsocketConnection *wscon, gint type,
64 GBytes *message, PurpleSatoriConnection *con)
65 {
66 PurpleAccount *acc = purple_connection_get_account(
67 PURPLE_CONNECTION(con));
68
69 if (type != SOUP_WEBSOCKET_DATA_TEXT) {
70 purple_debug_warning("satori", "unexpected data recv from ws");
71 return;
72 }
73
74 gsize sz;
75 const gchar *ptr = g_bytes_get_data(message, &sz);
76
77 /* g_print("Received text data: %s\n", ptr); */
78
79 JsonParser *parser = json_parser_new();
80 if (!json_parser_load_from_data(parser, ptr, sz, NULL)) {
81 purple_debug_warning("satori", "bad json received from ws");
82 g_object_unref(parser);
83 return;
84 }
85
86 JsonObject *root = json_node_get_object(json_parser_get_root(parser));
87 JsonObject *body = json_object_get_object_member(root, "body");
88 SatoriWebsocketOpcode op = json_object_get_int_member(root, "op");
89
90 switch (op) {
91
92 case SATORI_WEBSOCKET_OP_READY:
93 {
94 purple_account_connected(acc);
95
96 JsonArray *logins = json_object_get_array_member(body, "logins");
97 JsonObject *user_obj = json_object_get_object_member(
98 json_array_get_object_element(logins, 0), "user");
99 SatoriUser user = { 0 };
100
101 if (!user_obj) break;
102 satori_user_from_json(user_obj, &user);
103
104 PurpleContactInfo *ci = purple_account_get_contact_info(acc);
105 purple_contact_info_set_id(ci, user.id);
106 purple_contact_info_set_display_name(ci, user.nick
107 ? user.nick : user.name);
108
109 satori_refresh_buddy_contacts(con, NULL);
110 break;
111 }
112
113 default: /* ignored */
114 break;
115
116 }
117
118 g_print("op = %d\n", op);
119
120 g_object_unref(parser);
121 }
122
123 static void
124 satori_ws_on_connection(SoupSession *session, GAsyncResult *res, gpointer data)
125 {
126 PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(data);
127 PurpleAccount *acc = purple_connection_get_account(PURPLE_CONNECTION(con));
128 GError *err = NULL;
129
130 con->wscon = soup_session_websocket_connect_finish(session, res, &err);
131 if (err) {
132 purple_account_disconnect_with_error(acc, err);
133 return;
134 }
135
136 g_signal_connect(con->wscon, "message",
137 G_CALLBACK(satori_ws_on_message), con);
138 g_signal_connect(con->wscon, "closed",
139 G_CALLBACK(satori_ws_on_closed), con);
140
141 GBytes *frame = satori_message_gen_ident(NULL, 0);
142 soup_websocket_connection_send_text(con->wscon,
143 g_bytes_get_data(frame, NULL));
144 g_bytes_unref(frame);
145 }
146
147 /******************************************************************************
148 * PurpleConnection Implementation
149 *****************************************************************************/
150 static gboolean
151 purple_satori_connection_connect(PurpleConnection *connection,
152 G_GNUC_UNUSED GError **error)
153 {
154 PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(connection);
155
156 if (con->wscon)
157 g_object_unref(con->wscon);
158
159 SoupMessage *svmsg = satori_message_new("GET", PURPLE_SATORI_WSURL);
160 soup_session_websocket_connect_async(
161 con->session, svmsg,
162 NULL, NULL, 0, NULL,
163 (GAsyncReadyCallback) satori_ws_on_connection,
164 con);
165
166 /* purple_account_connected(account); */
167 /* purple_satori_contacts_load(account); */
168
169 return TRUE;
170 }
171
172 static gboolean
173 purple_satori_connection_disconnect(PurpleConnection *connection,
174 G_GNUC_UNUSED GError **error)
175 {
176 PurpleSatoriConnection *con = PURPLE_SATORI_CONNECTION(connection);
177 if (!con->wscon) return TRUE;
178
179 g_signal_handlers_disconnect_by_data(con->wscon, con);
180 soup_websocket_connection_close(con->wscon,
181 SOUP_WEBSOCKET_CLOSE_NO_STATUS, NULL);
182 con->wscon = NULL;
183 return TRUE;
184 }
185
186 /******************************************************************************
187 * GObject Implementation
188 *****************************************************************************/
189 static void
190 purple_satori_connection_init(PurpleSatoriConnection *connection) {
191 connection->session = soup_session_new();
192 connection->wscon = NULL;
193 }
194
195 static void
196 purple_satori_connection_class_finalize(G_GNUC_UNUSED PurpleSatoriConnectionClass *klass) {
197 }
198
199 static void
200 purple_satori_connection_class_init(PurpleSatoriConnectionClass *klass) {
201 PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass);
202
203 connection_class->connect = purple_satori_connection_connect;
204 connection_class->disconnect = purple_satori_connection_disconnect;
205 }
206
207 /******************************************************************************
208 * Internal API
209 *****************************************************************************/
210 void
211 purple_satori_connection_register(GPluginNativePlugin *plugin) {
212 purple_satori_connection_register_type(G_TYPE_MODULE(plugin));
213 }
214
215 /******************************************************************************
216 * Public API Implementation
217 *****************************************************************************/
218
219 void
220 purple_satori_connection_send_and_read_async(PurpleSatoriConnection *con,
221 SoupMessage *msg,
222 int io_priority,
223 GCancellable *cancellable,
224 GAsyncReadyCallback callback,
225 gpointer user_data)
226 {
227 soup_session_send_and_read_async(con->session, msg, io_priority,
228 cancellable, callback, user_data);
229 }

mercurial