yong-purple_ruby 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|