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 CHANGED
@@ -3,7 +3,7 @@ require 'hoe'
3
3
 
4
4
  EXT = "ext/ruburple_ext.#{Hoe::DLEXT}"
5
5
 
6
- Hoe.new('purple_ruby', '0.1.1') do |p|
6
+ Hoe.new('purple_ruby', '0.2.0') do |p|
7
7
  p.author = 'yong'
8
8
  p.email = 'yong@intridea.com'
9
9
  p.url = 'http://www.intridea.com'
@@ -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
- first_space = data.index(' ')
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} #{user} #{message}\n"
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 im_hanlder;
107
- static VALUE signed_on_hanlder;
108
- static GHashTable* hash_table;
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
- VALUE *args = g_new(VALUE, 3);
114
- args[0] = rb_str_new2(purple_account_get_username(purple_conversation_get_account(conv)));
115
- args[1] = rb_str_new2(who);
116
- args[2] = rb_str_new2(message);
117
- rb_funcall2(im_hanlder, rb_intern("call"), 3, args);
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 ui_init(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
- * This should initialize the UI components for all the modules. Here we
147
- * just initialize the UI for conversations.
148
- */
149
- purple_conversations_set_ui_ops(&conv_uiops);
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
- ui_init,
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
- static void
167
- sighandler(int sig)
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
- im_hanlder = rb_block_proc();
208
- return im_hanlder;
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(&notify_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)signed_on_hanlder, rb_intern("call"), 1, args);
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
- signed_on_hanlder = rb_block_proc();
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 signed_on_hanlder;
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
- //printf("recv %d %d\n", socket, i);
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
- //printf("closed %d %d %s\n", socket, i, g_strerror(errno));
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, rb_intern("call"), 1, args);
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
- //printf("new connection: %d\n", client_socket);
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, "login", login, 3);
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
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yong-purple_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yong