yong-purple_ruby 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +1 -1
- data/examples/purplegw_example.rb +13 -8
- data/ext/purple_ruby.c +224 -33
- metadata +1 -1
data/Rakefile
CHANGED
@@ -36,22 +36,27 @@ class PurpleGWExample
|
|
36
36
|
puts "recv: #{receiver}, #{sender}, #{text}"
|
37
37
|
end
|
38
38
|
|
39
|
-
#TODO detect login failure
|
40
39
|
PurpleRuby.watch_signed_on_event do |acc|
|
41
40
|
puts "signed on: #{acc.username}"
|
42
41
|
end
|
43
42
|
|
43
|
+
PurpleRuby.watch_connection_error do |acc, type, description|
|
44
|
+
raise "connection_error: #{acc.username} #{type} #{description}"
|
45
|
+
end
|
46
|
+
|
47
|
+
#request can be: 'SSL Certificate Verification' etc
|
48
|
+
PurpleRuby.watch_request do |title, primary, secondary, who|
|
49
|
+
puts "request: #{title}, #{primary}, #{secondary}, #{who}"
|
50
|
+
true #'true': accept a request; 'false': ignore a request
|
51
|
+
end
|
52
|
+
|
44
53
|
#listen a tcp port, parse incoming data and send it out.
|
45
54
|
#We assume the incoming data is in the following format:
|
46
55
|
#<protocol> <user> <message>
|
47
56
|
PurpleRuby.watch_incoming_ipc(SERVER_IP, SERVER_PORT) do |data|
|
48
|
-
|
49
|
-
second_space = data.index(' ', first_space + 1)
|
50
|
-
protocol = data[0...first_space]
|
51
|
-
user = data[(first_space+1)...second_space]
|
52
|
-
message = data[(second_space+1)...-1]
|
57
|
+
protocol, user, message = data.split(",").collect{|x| x.chomp.strip}
|
53
58
|
puts "send: #{protocol}, #{user}, #{message}"
|
54
|
-
accounts[protocol].send_im(user, message)
|
59
|
+
puts accounts[protocol].send_im(user, message)
|
55
60
|
end
|
56
61
|
|
57
62
|
PurpleRuby.main_loop_run
|
@@ -61,7 +66,7 @@ class PurpleGWExample
|
|
61
66
|
to_users = [to_users] unless to_users.is_a?(Array)
|
62
67
|
to_users.each do |user|
|
63
68
|
t = TCPSocket.new(SERVER_IP, SERVER_PORT)
|
64
|
-
t.print "#{protocol}
|
69
|
+
t.print "#{protocol},#{user},#{message}"
|
65
70
|
t.close
|
66
71
|
end
|
67
72
|
end
|
data/ext/purple_ruby.c
CHANGED
@@ -22,6 +22,7 @@
|
|
22
22
|
#include <errno.h>
|
23
23
|
#include <signal.h>
|
24
24
|
#include <stdarg.h>
|
25
|
+
#include <unistd.h>
|
25
26
|
#include <netinet/in.h>
|
26
27
|
#include <sys/socket.h>
|
27
28
|
#include <arpa/inet.h>
|
@@ -102,19 +103,51 @@ static PurpleEventLoopUiOps glib_eventloops =
|
|
102
103
|
static VALUE cPurpleRuby;
|
103
104
|
static VALUE cAccount;
|
104
105
|
static char* UI_ID = "purplegw";
|
105
|
-
static GMainLoop *main_loop;
|
106
|
-
static VALUE
|
107
|
-
static VALUE
|
108
|
-
static
|
106
|
+
static GMainLoop *main_loop = NULL;
|
107
|
+
static VALUE im_handler = Qnil;
|
108
|
+
static VALUE signed_on_handler = Qnil;
|
109
|
+
static VALUE connection_error_handler = Qnil;
|
110
|
+
static VALUE notify_message_handler = Qnil;
|
111
|
+
static VALUE request_handler = Qnil;
|
112
|
+
static GHashTable* hash_table = NULL;
|
113
|
+
static ID CALL;
|
114
|
+
|
115
|
+
static void connection_error(PurpleConnection* connection,
|
116
|
+
PurpleConnectionError type,
|
117
|
+
const gchar *description,
|
118
|
+
gpointer unused)
|
119
|
+
{
|
120
|
+
if (Qnil != connection_error_handler) {
|
121
|
+
VALUE *args = g_new(VALUE, 3);
|
122
|
+
args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(connection));
|
123
|
+
args[1] = INT2FIX(type);
|
124
|
+
args[2] = rb_str_new2(description);
|
125
|
+
rb_funcall2((VALUE)connection_error_handler, CALL, 3, args);
|
126
|
+
g_free(args);
|
127
|
+
}
|
128
|
+
}
|
109
129
|
|
110
130
|
static void write_conv(PurpleConversation *conv, const char *who, const char *alias,
|
111
131
|
const char *message, PurpleMessageFlags flags, time_t mtime)
|
112
132
|
{
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
133
|
+
if (im_handler != Qnil) {
|
134
|
+
PurpleAccount* account = purple_conversation_get_account(conv);
|
135
|
+
if (strcmp(purple_account_get_protocol_id(account), "prpl-msn") == 0 &&
|
136
|
+
strstr(message, "Message could not be sent") != NULL) {
|
137
|
+
/* I have seen error like 'msn: Connection error from Switchboard server'.
|
138
|
+
* In that case, libpurple will notify user with two regular im message.
|
139
|
+
* The first message is an error message, the second one is the original message that failed to send.
|
140
|
+
*/
|
141
|
+
connection_error(purple_account_get_connection(account), PURPLE_CONNECTION_ERROR_NETWORK_ERROR, message, NULL);
|
142
|
+
} else {
|
143
|
+
VALUE *args = g_new(VALUE, 3);
|
144
|
+
args[0] = rb_str_new2(purple_account_get_username(account));
|
145
|
+
args[1] = rb_str_new2(who);
|
146
|
+
args[2] = rb_str_new2(message);
|
147
|
+
rb_funcall2(im_handler, CALL, 3, args);
|
148
|
+
g_free(args);
|
149
|
+
}
|
150
|
+
}
|
118
151
|
}
|
119
152
|
|
120
153
|
static PurpleConversationUiOps conv_uiops =
|
@@ -140,20 +173,110 @@ static PurpleConversationUiOps conv_uiops =
|
|
140
173
|
NULL
|
141
174
|
};
|
142
175
|
|
143
|
-
static void
|
176
|
+
static void report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
|
177
|
+
{
|
178
|
+
connection_error(gc, reason, text, NULL);
|
179
|
+
}
|
180
|
+
|
181
|
+
static PurpleConnectionUiOps connection_ops =
|
182
|
+
{
|
183
|
+
NULL, /* connect_progress */
|
184
|
+
NULL, /* connected */
|
185
|
+
NULL, /* disconnected */
|
186
|
+
NULL, /* notice */
|
187
|
+
NULL,
|
188
|
+
NULL, /* network_connected */
|
189
|
+
NULL, /* network_disconnected */
|
190
|
+
report_disconnect_reason,
|
191
|
+
NULL,
|
192
|
+
NULL,
|
193
|
+
NULL
|
194
|
+
};
|
195
|
+
|
196
|
+
static void* notify_message(PurpleNotifyMsgType type,
|
197
|
+
const char *title,
|
198
|
+
const char *primary,
|
199
|
+
const char *secondary)
|
200
|
+
{
|
201
|
+
if (notify_message_handler != Qnil) {
|
202
|
+
VALUE *args = g_new(VALUE, 4);
|
203
|
+
args[0] = INT2FIX(type);
|
204
|
+
args[1] = rb_str_new2(NULL == title ? "" : title);
|
205
|
+
args[2] = rb_str_new2(NULL == primary ? "" : primary);
|
206
|
+
args[3] = rb_str_new2(NULL == secondary ? "" : secondary);
|
207
|
+
rb_funcall2(notify_message_handler, CALL, 4, args);
|
208
|
+
g_free(args);
|
209
|
+
}
|
210
|
+
|
211
|
+
return NULL;
|
212
|
+
}
|
213
|
+
|
214
|
+
static void* request_action(const char *title, const char *primary, const char *secondary,
|
215
|
+
int default_action,
|
216
|
+
PurpleAccount *account,
|
217
|
+
const char *who,
|
218
|
+
PurpleConversation *conv,
|
219
|
+
void *user_data,
|
220
|
+
size_t action_count,
|
221
|
+
va_list actions)
|
144
222
|
{
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
223
|
+
if (request_handler != Qnil) {
|
224
|
+
VALUE *args = g_new(VALUE, 4);
|
225
|
+
args[0] = rb_str_new2(NULL == title ? "" : title);
|
226
|
+
args[1] = rb_str_new2(NULL == primary ? "" : primary);
|
227
|
+
args[2] = rb_str_new2(NULL == secondary ? "" : secondary);
|
228
|
+
args[3] = rb_str_new2(NULL == who ? "" : who);
|
229
|
+
VALUE v = rb_funcall2(request_handler, CALL, 4, args);
|
230
|
+
|
231
|
+
if (v != Qnil && v != Qfalse) {
|
232
|
+
const char *text = va_arg(actions, const char *);
|
233
|
+
GCallback ok_cb = va_arg(actions, GCallback);
|
234
|
+
((PurpleRequestActionCb)ok_cb)(user_data, default_action);
|
235
|
+
}
|
236
|
+
|
237
|
+
g_free(args);
|
238
|
+
}
|
239
|
+
|
240
|
+
return NULL;
|
150
241
|
}
|
151
242
|
|
243
|
+
static PurpleRequestUiOps request_ops =
|
244
|
+
{
|
245
|
+
NULL, /*request_input*/
|
246
|
+
NULL, /*request_choice*/
|
247
|
+
request_action, /*request_action*/
|
248
|
+
NULL, /*request_fields*/
|
249
|
+
NULL, /*request_file*/
|
250
|
+
NULL, /*close_request*/
|
251
|
+
NULL, /*request_folder*/
|
252
|
+
NULL,
|
253
|
+
NULL,
|
254
|
+
NULL,
|
255
|
+
NULL
|
256
|
+
};
|
257
|
+
|
258
|
+
static PurpleNotifyUiOps notify_ops =
|
259
|
+
{
|
260
|
+
notify_message, /*notify_message*/
|
261
|
+
NULL, /*notify_email*/
|
262
|
+
NULL, /*notify_emails*/
|
263
|
+
NULL, /*notify_formatted*/
|
264
|
+
NULL, /*notify_searchresults*/
|
265
|
+
NULL, /*notify_searchresults_new_rows*/
|
266
|
+
NULL, /*notify_userinfo*/
|
267
|
+
NULL, /*notify_uri*/
|
268
|
+
NULL, /*close_notify*/
|
269
|
+
NULL,
|
270
|
+
NULL,
|
271
|
+
NULL,
|
272
|
+
NULL,
|
273
|
+
};
|
274
|
+
|
152
275
|
static PurpleCoreUiOps core_uiops =
|
153
276
|
{
|
154
277
|
NULL,
|
155
278
|
NULL,
|
156
|
-
|
279
|
+
NULL,
|
157
280
|
NULL,
|
158
281
|
|
159
282
|
/* padding */
|
@@ -163,8 +286,10 @@ static PurpleCoreUiOps core_uiops =
|
|
163
286
|
NULL
|
164
287
|
};
|
165
288
|
|
166
|
-
|
167
|
-
|
289
|
+
//I have tried to detect Ctrl-C using ruby's trap method,
|
290
|
+
//but it does not work as expected: it can not detect Ctrl-C
|
291
|
+
//until a network event occurs
|
292
|
+
static void sighandler(int sig)
|
168
293
|
{
|
169
294
|
switch (sig) {
|
170
295
|
case SIGINT:
|
@@ -173,7 +298,6 @@ sighandler(int sig)
|
|
173
298
|
}
|
174
299
|
}
|
175
300
|
|
176
|
-
|
177
301
|
static VALUE init(VALUE self, VALUE debug)
|
178
302
|
{
|
179
303
|
signal(SIGPIPE, SIG_IGN);
|
@@ -204,24 +328,51 @@ static VALUE init(VALUE self, VALUE debug)
|
|
204
328
|
|
205
329
|
static VALUE watch_incoming_im(VALUE self)
|
206
330
|
{
|
207
|
-
|
208
|
-
|
331
|
+
purple_conversations_set_ui_ops(&conv_uiops);
|
332
|
+
im_handler = rb_block_proc();
|
333
|
+
return im_handler;
|
334
|
+
}
|
335
|
+
|
336
|
+
static VALUE watch_notify_message(VALUE self)
|
337
|
+
{
|
338
|
+
purple_notify_set_ui_ops(¬ify_ops);
|
339
|
+
notify_message_handler = rb_block_proc();
|
340
|
+
return notify_message_handler;
|
341
|
+
}
|
342
|
+
|
343
|
+
static VALUE watch_request(VALUE self)
|
344
|
+
{
|
345
|
+
purple_request_set_ui_ops(&request_ops);
|
346
|
+
request_handler = rb_block_proc();
|
347
|
+
return request_handler;
|
209
348
|
}
|
210
349
|
|
211
350
|
static void signed_on(PurpleConnection* connection)
|
212
351
|
{
|
213
352
|
VALUE *args = g_new(VALUE, 1);
|
214
353
|
args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(connection));
|
215
|
-
rb_funcall2((VALUE)
|
354
|
+
rb_funcall2((VALUE)signed_on_handler, CALL, 1, args);
|
355
|
+
g_free(args);
|
216
356
|
}
|
217
357
|
|
218
358
|
static VALUE watch_signed_on_event(VALUE self)
|
219
359
|
{
|
220
|
-
|
360
|
+
signed_on_handler = rb_block_proc();
|
221
361
|
int handle;
|
222
362
|
purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
|
223
363
|
PURPLE_CALLBACK(signed_on), NULL);
|
224
|
-
return
|
364
|
+
return signed_on_handler;
|
365
|
+
}
|
366
|
+
|
367
|
+
static VALUE watch_connection_error(VALUE self)
|
368
|
+
{
|
369
|
+
purple_connections_set_ui_ops(&connection_ops);
|
370
|
+
|
371
|
+
connection_error_handler = rb_block_proc();
|
372
|
+
int handle;
|
373
|
+
purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
|
374
|
+
PURPLE_CALLBACK(connection_error), NULL);
|
375
|
+
return connection_error_handler;
|
225
376
|
}
|
226
377
|
|
227
378
|
static void _read_socket_handler(gpointer data, int socket, PurpleInputCondition condition)
|
@@ -229,24 +380,26 @@ static void _read_socket_handler(gpointer data, int socket, PurpleInputCondition
|
|
229
380
|
char message[4096] = {0};
|
230
381
|
int i = recv(socket, message, sizeof(message) - 1, 0);
|
231
382
|
if (i > 0) {
|
232
|
-
|
383
|
+
purple_debug_info("purple_ruby", "recv %d: %d\n", socket, i);
|
233
384
|
|
234
385
|
VALUE str = (VALUE)g_hash_table_lookup(hash_table, (gpointer)socket);
|
235
386
|
if (NULL == str) rb_raise(rb_eRuntimeError, "can not find socket: %d", socket);
|
236
387
|
rb_str_append(str, rb_str_new2(message));
|
237
388
|
} else {
|
238
|
-
|
389
|
+
purple_debug_info("purple_ruby", "close connection %d: %d %d\n", socket, i, errno);
|
390
|
+
|
391
|
+
close(socket);
|
392
|
+
purple_input_remove(socket);
|
239
393
|
|
240
394
|
VALUE str = (VALUE)g_hash_table_lookup(hash_table, (gpointer)socket);
|
241
395
|
if (NULL == str) return;
|
242
396
|
|
243
|
-
close(socket);
|
244
|
-
purple_input_remove(socket);
|
245
397
|
g_hash_table_remove(hash_table, (gpointer)socket);
|
246
398
|
|
247
399
|
VALUE *args = g_new(VALUE, 1);
|
248
400
|
args[0] = str;
|
249
|
-
rb_funcall2((VALUE)data,
|
401
|
+
rb_funcall2((VALUE)data, CALL, 1, args);
|
402
|
+
g_free(args);
|
250
403
|
}
|
251
404
|
}
|
252
405
|
|
@@ -260,6 +413,7 @@ static void _accept_socket_handler(gpointer data, int server_socket, PurpleInput
|
|
260
413
|
socklen_t sin_size = sizeof(struct sockaddr);
|
261
414
|
int client_socket;
|
262
415
|
if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
|
416
|
+
purple_debug_warning("purple_ruby", "failed to accept %d: %d\n", client_socket, errno);
|
263
417
|
return;
|
264
418
|
}
|
265
419
|
|
@@ -269,7 +423,7 @@ static void _accept_socket_handler(gpointer data, int server_socket, PurpleInput
|
|
269
423
|
fcntl(client_socket, F_SETFD, FD_CLOEXEC);
|
270
424
|
#endif
|
271
425
|
|
272
|
-
|
426
|
+
purple_debug_info("purple_ruby", "new connection: %d\n", client_socket);
|
273
427
|
|
274
428
|
g_hash_table_insert(hash_table, (gpointer)client_socket, (gpointer)rb_str_new2(""));
|
275
429
|
|
@@ -316,6 +470,9 @@ static VALUE watch_incoming_ipc(VALUE self, VALUE serverip, VALUE port)
|
|
316
470
|
static VALUE login(VALUE self, VALUE protocol, VALUE username, VALUE password)
|
317
471
|
{
|
318
472
|
PurpleAccount* account = purple_account_new(RSTRING(username)->ptr, RSTRING(protocol)->ptr);
|
473
|
+
if (NULL == account || NULL == account->presence) {
|
474
|
+
rb_raise(rb_eRuntimeError, "No able to create account: %s", RSTRING(protocol)->ptr);
|
475
|
+
}
|
319
476
|
purple_account_set_password(account, RSTRING(password)->ptr);
|
320
477
|
purple_account_set_enabled(account, UI_ID, TRUE);
|
321
478
|
PurpleSavedStatus *status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
|
@@ -381,6 +538,8 @@ static VALUE add_buddy(VALUE self, VALUE buddy)
|
|
381
538
|
PurpleAccount *account;
|
382
539
|
Data_Get_Struct(self, PurpleAccount, account);
|
383
540
|
|
541
|
+
PurpleBuddy* pb = purple_buddy_new(account, RSTRING(buddy)->ptr, NULL);
|
542
|
+
|
384
543
|
char* group = _("Buddies");
|
385
544
|
PurpleGroup* grp = purple_find_group(group);
|
386
545
|
if (!grp)
|
@@ -388,13 +547,35 @@ static VALUE add_buddy(VALUE self, VALUE buddy)
|
|
388
547
|
grp = purple_group_new(group);
|
389
548
|
purple_blist_add_group(grp, NULL);
|
390
549
|
}
|
391
|
-
|
392
|
-
PurpleBuddy* pb = purple_buddy_new(account, RSTRING(buddy)->ptr, NULL);
|
550
|
+
|
393
551
|
purple_blist_add_buddy(pb, NULL, grp, NULL);
|
394
552
|
purple_account_add_buddy(account, pb);
|
395
553
|
return Qtrue;
|
396
554
|
}
|
397
555
|
|
556
|
+
static VALUE remove_buddy(VALUE self, VALUE buddy)
|
557
|
+
{
|
558
|
+
PurpleAccount *account;
|
559
|
+
Data_Get_Struct(self, PurpleAccount, account);
|
560
|
+
|
561
|
+
PurpleBuddy* pb = purple_find_buddy(account, RSTRING(buddy)->ptr);
|
562
|
+
if (NULL == pb) {
|
563
|
+
rb_raise(rb_eRuntimeError, "Failed to remove buddy for %s : %s does not exist", purple_account_get_username(account), RSTRING(buddy)->ptr);
|
564
|
+
}
|
565
|
+
|
566
|
+
char* group = _("Buddies");
|
567
|
+
PurpleGroup* grp = purple_find_group(group);
|
568
|
+
if (!grp)
|
569
|
+
{
|
570
|
+
grp = purple_group_new(group);
|
571
|
+
purple_blist_add_group(grp, NULL);
|
572
|
+
}
|
573
|
+
|
574
|
+
purple_blist_remove_buddy(pb);
|
575
|
+
purple_account_remove_buddy(account, pb, grp);
|
576
|
+
return Qtrue;
|
577
|
+
}
|
578
|
+
|
398
579
|
static VALUE has_buddy(VALUE self, VALUE buddy)
|
399
580
|
{
|
400
581
|
PurpleAccount *account;
|
@@ -408,19 +589,29 @@ static VALUE has_buddy(VALUE self, VALUE buddy)
|
|
408
589
|
|
409
590
|
void Init_purple_ruby()
|
410
591
|
{
|
592
|
+
CALL = rb_intern("call");
|
593
|
+
|
411
594
|
cPurpleRuby = rb_define_class("PurpleRuby", rb_cObject);
|
412
595
|
rb_define_singleton_method(cPurpleRuby, "init", init, 1);
|
413
596
|
rb_define_singleton_method(cPurpleRuby, "list_protocols", list_protocols, 0);
|
414
597
|
rb_define_singleton_method(cPurpleRuby, "watch_signed_on_event", watch_signed_on_event, 0);
|
598
|
+
rb_define_singleton_method(cPurpleRuby, "watch_connection_error", watch_connection_error, 0);
|
415
599
|
rb_define_singleton_method(cPurpleRuby, "watch_incoming_im", watch_incoming_im, 0);
|
416
|
-
rb_define_singleton_method(cPurpleRuby, "
|
600
|
+
rb_define_singleton_method(cPurpleRuby, "watch_notify_message", watch_notify_message, 0);
|
601
|
+
rb_define_singleton_method(cPurpleRuby, "watch_request", watch_request, 0);
|
417
602
|
rb_define_singleton_method(cPurpleRuby, "watch_incoming_ipc", watch_incoming_ipc, 2);
|
603
|
+
rb_define_singleton_method(cPurpleRuby, "login", login, 3);
|
418
604
|
rb_define_singleton_method(cPurpleRuby, "main_loop_run", main_loop_run, 0);
|
419
605
|
rb_define_singleton_method(cPurpleRuby, "main_loop_stop", main_loop_stop, 0);
|
420
606
|
|
607
|
+
rb_define_const(cPurpleRuby, "NOTIFY_MSG_ERROR", INT2NUM(PURPLE_NOTIFY_MSG_ERROR));
|
608
|
+
rb_define_const(cPurpleRuby, "NOTIFY_MSG_WARNING", INT2NUM(PURPLE_NOTIFY_MSG_WARNING));
|
609
|
+
rb_define_const(cPurpleRuby, "NOTIFY_MSG_INFO", INT2NUM(PURPLE_NOTIFY_MSG_INFO));
|
610
|
+
|
421
611
|
cAccount = rb_define_class_under(cPurpleRuby, "Account", rb_cObject);
|
422
612
|
rb_define_method(cAccount, "send_im", send_im, 2);
|
423
613
|
rb_define_method(cAccount, "username", username, 0);
|
424
614
|
rb_define_method(cAccount, "add_buddy", add_buddy, 1);
|
615
|
+
rb_define_method(cAccount, "remove_buddy", remove_buddy, 1);
|
425
616
|
rb_define_method(cAccount, "has_buddy?", has_buddy, 1);
|
426
617
|
}
|