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 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