dcu-purple_ruby 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ #
2
+ #Example Usage:
3
+ #
4
+ #Start the daemon and receive IM:
5
+ #$ruby examples/purplegw_example.rb prpl-msn user@hotmail.com password prpl-jabber user@gmail.com password
6
+ #
7
+ #Send im:
8
+ #$ irb
9
+ #irb(main):001:0> require 'examples/purplegw_example'
10
+ #irb(main):007:0> PurpleGWExample.deliver 'prpl-jabber', 'friend@gmail.com', 'hello worlds!'
11
+ #
12
+
13
+ require 'socket'
14
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/purple_ruby'))
15
+
16
+ class PurpleGWExample
17
+ SERVER_IP = "127.0.0.1"
18
+ SERVER_PORT = 9877
19
+
20
+ def start configs
21
+ Purple.init :debug => false, :user_dir => File.dirname(__FILE__) #use 'true' if you want to see the debug messages
22
+
23
+ puts "Available protocols:", Purple.list_protocols
24
+
25
+ accounts = {}
26
+ configs.each {|config|
27
+ puts "logging in #{config[:username]} (#{config[:protocol]})..."
28
+ account = Purple.login(config[:protocol], config[:username], config[:password])
29
+ accounts[config[:protocol]] = account
30
+ }
31
+
32
+ #handle incoming im messages
33
+ Purple.watch_incoming_im do |acc, sender, message|
34
+ sender = sender[0...sender.index('/')] if sender.index('/') #discard anything after '/'
35
+ puts "recv: #{acc.username}, #{sender}, #{message}"
36
+ end
37
+
38
+ Purple.watch_signed_on_event do |acc|
39
+ puts "signed on: #{acc.username}"
40
+ end
41
+
42
+ Purple.watch_connection_error do |acc, type, description|
43
+ puts "connection_error: #{acc.username} #{type} #{description}"
44
+ true #'true': auto-reconnect; 'false': do nothing
45
+ end
46
+
47
+ #request can be: 'SSL Certificate Verification' etc
48
+ Purple.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
+
53
+ #request for authorization when someone adds this account to their buddy list
54
+ Purple.watch_new_buddy do |acc, remote_user, message|
55
+ puts "new buddy: #{acc.username} #{remote_user} #{message}"
56
+ true #'true': accept; 'false': deny
57
+ end
58
+
59
+ Purple.watch_notify_message do |type, title, primary, secondary|
60
+ puts "notification: #{type}, #{title}, #{primary}, #{secondary}"
61
+ end
62
+
63
+ #listen a tcp port, parse incoming data and send it out.
64
+ #We assume the incoming data is in the following format (separated by comma):
65
+ #<protocol>,<user>,<message>
66
+ Purple.watch_incoming_ipc(SERVER_IP, SERVER_PORT) do |data|
67
+ protocol, user, message = data.split(",").collect{|x| x.chomp.strip}
68
+ puts "send: #{protocol},#{user},#{message}"
69
+ puts accounts[protocol].send_im(user, message)
70
+ end
71
+
72
+ Purple.main_loop_run
73
+ end
74
+
75
+ def self.deliver(protocol, to_users, message)
76
+ to_users = [to_users] unless to_users.is_a?(Array)
77
+ to_users.each do |user|
78
+ t = TCPSocket.new(SERVER_IP, SERVER_PORT)
79
+ t.print "#{protocol},#{user},#{message}"
80
+ t.close
81
+ end
82
+ end
83
+ end
84
+
85
+ if ARGV.length >= 3
86
+ configs = []
87
+ configs << {:protocol => ARGV[0], :username => ARGV[1], :password => ARGV[2]}
88
+ configs << {:protocol => ARGV[3], :username => ARGV[4], :password => ARGV[5]} if ARGV.length >= 6
89
+ #add more accounts here if you like
90
+ PurpleGWExample.new.start configs
91
+ end
92
+
@@ -0,0 +1,162 @@
1
+ /*
2
+ * adopted from finch's gntaccount.c
3
+ */
4
+
5
+ /* finch
6
+ *
7
+ * Finch is the legal property of its developers, whose names are too numerous
8
+ * to list here. Please refer to the COPYRIGHT file distributed with this
9
+ * source distribution.
10
+ *
11
+ * This program is free software; you can redistribute it and/or modify
12
+ * it under the terms of the GNU General Public License as published by
13
+ * the Free Software Foundation; either version 2 of the License, or
14
+ * (at your option) any later version.
15
+ *
16
+ * This program is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24
+ */
25
+
26
+ #include <libpurple/account.h>
27
+ #include <libpurple/conversation.h>
28
+ #include <libpurple/core.h>
29
+ #include <libpurple/debug.h>
30
+ #include <libpurple/cipher.h>
31
+ #include <libpurple/eventloop.h>
32
+ #include <libpurple/ft.h>
33
+ #include <libpurple/log.h>
34
+ #include <libpurple/notify.h>
35
+ #include <libpurple/prefs.h>
36
+ #include <libpurple/prpl.h>
37
+ #include <libpurple/pounce.h>
38
+ #include <libpurple/request.h>
39
+ #include <libpurple/savedstatuses.h>
40
+ #include <libpurple/sound.h>
41
+ #include <libpurple/status.h>
42
+ #include <libpurple/util.h>
43
+ #include <libpurple/whiteboard.h>
44
+ #include <libpurple/network.h>
45
+
46
+ #include <ruby.h>
47
+
48
+ extern ID CALL;
49
+ extern VALUE cAccount;
50
+ extern VALUE new_buddy_handler;
51
+
52
+ extern VALUE check_callback(VALUE, const char*);
53
+
54
+ static char *
55
+ make_info(PurpleAccount *account, PurpleConnection *gc, const char *remote_user,
56
+ const char *id, const char *alias, const char *msg)
57
+ {
58
+ if (msg != NULL && *msg == '\0')
59
+ msg = NULL;
60
+
61
+ return g_strdup_printf(_("%s%s%s%s has made %s his or her buddy%s%s"),
62
+ remote_user,
63
+ (alias != NULL ? " (" : ""),
64
+ (alias != NULL ? alias : ""),
65
+ (alias != NULL ? ")" : ""),
66
+ (id != NULL
67
+ ? id
68
+ : (purple_connection_get_display_name(gc) != NULL
69
+ ? purple_connection_get_display_name(gc)
70
+ : purple_account_get_username(account))),
71
+ (msg != NULL ? ": " : "."),
72
+ (msg != NULL ? msg : ""));
73
+ }
74
+
75
+ static void
76
+ notify_added(PurpleAccount *account, const char *remote_user,
77
+ const char *id, const char *alias,
78
+ const char *msg)
79
+ {
80
+ char *buffer;
81
+ PurpleConnection *gc;
82
+
83
+ gc = purple_account_get_connection(account);
84
+
85
+ buffer = make_info(account, gc, remote_user, id, alias, msg);
86
+
87
+ purple_notify_info(NULL, NULL, buffer, NULL);
88
+
89
+ g_free(buffer);
90
+ }
91
+
92
+ static void
93
+ request_add(PurpleAccount *account, const char *remote_user,
94
+ const char *id, const char *alias,
95
+ const char *message)
96
+ {
97
+ if (new_buddy_handler != Qnil) {
98
+ VALUE args[3];
99
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
100
+ args[1] = rb_str_new2(NULL == remote_user ? "" : remote_user);
101
+ args[2] = rb_str_new2(NULL == message ? "" : message);
102
+ check_callback(new_buddy_handler, "new_buddy_handler");
103
+ VALUE v = rb_funcall2(new_buddy_handler, CALL, 3, args);
104
+
105
+ if (v != Qnil && v != Qfalse) {
106
+ PurpleConnection *gc = purple_account_get_connection(account);
107
+ if (g_list_find(purple_connections_get_all(), gc))
108
+ {
109
+ purple_blist_request_add_buddy(account, remote_user,
110
+ NULL, alias);
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ static void *request_authorize(PurpleAccount *account,
117
+ const char *remote_user,
118
+ const char *id,
119
+ const char *alias,
120
+ const char *message,
121
+ gboolean on_list,
122
+ PurpleAccountRequestAuthorizationCb auth_cb,
123
+ PurpleAccountRequestAuthorizationCb deny_cb,
124
+ void *user_data)
125
+ {
126
+ if (new_buddy_handler != Qnil) {
127
+ VALUE args[3];
128
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
129
+ args[1] = rb_str_new2(NULL == remote_user ? "" : remote_user);
130
+ args[2] = rb_str_new2(NULL == message ? "" : message);
131
+ VALUE v = rb_funcall2((VALUE)new_buddy_handler, CALL, 3, args);
132
+
133
+ if (v != Qnil && v != Qfalse) {
134
+ auth_cb(user_data);
135
+ purple_blist_request_add_buddy(account, remote_user, NULL, alias);
136
+ } else {
137
+ deny_cb(user_data);
138
+ }
139
+ }
140
+
141
+ return NULL;
142
+ }
143
+
144
+ static void
145
+ request_close(void *uihandle)
146
+ {
147
+ purple_request_close(PURPLE_REQUEST_ACTION, uihandle);
148
+ }
149
+
150
+ PurpleAccountUiOps account_ops =
151
+ {
152
+ notify_added,
153
+ NULL,
154
+ request_add,
155
+ request_authorize,
156
+ request_close,
157
+ NULL,
158
+ NULL,
159
+ NULL,
160
+ NULL
161
+ };
162
+
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+ $CFLAGS = "#{ENV['CFLAGS']} -Wall -O3 -g"
3
+ pkg_config 'purple'
4
+ pkg_config 'glib-2.0'
5
+ pkg_config 'gthread-2.0'
6
+ create_makefile('purple_ruby_ext')
@@ -0,0 +1,934 @@
1
+ /*
2
+ * Author: yong@intridea.com
3
+ *
4
+ * This program is free software; you can redistribute it and/or modify
5
+ * it under the terms of the GNU General Public License as published by
6
+ * the Free Software Foundation; either version 2 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
17
+ *
18
+ */
19
+
20
+ #include <libpurple/account.h>
21
+ #include <libpurple/accountopt.h>
22
+ #include <libpurple/conversation.h>
23
+ #include <libpurple/core.h>
24
+ #include <libpurple/debug.h>
25
+ #include <libpurple/cipher.h>
26
+ #include <libpurple/eventloop.h>
27
+ #include <libpurple/ft.h>
28
+ #include <libpurple/log.h>
29
+ #include <libpurple/notify.h>
30
+ #include <libpurple/prefs.h>
31
+ #include <libpurple/prpl.h>
32
+ #include <libpurple/pounce.h>
33
+ #include <libpurple/request.h>
34
+ #include <libpurple/savedstatuses.h>
35
+ #include <libpurple/sound.h>
36
+ #include <libpurple/status.h>
37
+ #include <libpurple/util.h>
38
+ #include <libpurple/whiteboard.h>
39
+ #include <libpurple/network.h>
40
+
41
+ #include <ruby.h>
42
+ #include <errno.h>
43
+ #include <signal.h>
44
+ #include <stdarg.h>
45
+ #include <unistd.h>
46
+ #include <netinet/in.h>
47
+ #include <sys/socket.h>
48
+ #include <arpa/inet.h>
49
+ #include <fcntl.h>
50
+
51
+ #define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
52
+ #define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
53
+
54
+ typedef struct _PurpleGLibIOClosure {
55
+ PurpleInputFunction function;
56
+ guint result;
57
+ gpointer data;
58
+ } PurpleGLibIOClosure;
59
+
60
+ static void purple_glib_io_destroy(gpointer data)
61
+ {
62
+ g_free(data);
63
+ }
64
+
65
+ static gboolean purple_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
66
+ {
67
+ PurpleGLibIOClosure *closure = data;
68
+ PurpleInputCondition purple_cond = 0;
69
+
70
+ if (condition & PURPLE_GLIB_READ_COND)
71
+ purple_cond |= PURPLE_INPUT_READ;
72
+ if (condition & PURPLE_GLIB_WRITE_COND)
73
+ purple_cond |= PURPLE_INPUT_WRITE;
74
+
75
+ closure->function(closure->data, g_io_channel_unix_get_fd(source),
76
+ purple_cond);
77
+
78
+ return TRUE;
79
+ }
80
+
81
+ static guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function,
82
+ gpointer data)
83
+ {
84
+ PurpleGLibIOClosure *closure = g_new0(PurpleGLibIOClosure, 1);
85
+ GIOChannel *channel;
86
+ GIOCondition cond = 0;
87
+
88
+ closure->function = function;
89
+ closure->data = data;
90
+
91
+ if (condition & PURPLE_INPUT_READ)
92
+ cond |= PURPLE_GLIB_READ_COND;
93
+ if (condition & PURPLE_INPUT_WRITE)
94
+ cond |= PURPLE_GLIB_WRITE_COND;
95
+
96
+ channel = g_io_channel_unix_new(fd);
97
+ closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
98
+ purple_glib_io_invoke, closure, purple_glib_io_destroy);
99
+
100
+ g_io_channel_unref(channel);
101
+ return closure->result;
102
+ }
103
+
104
+ static PurpleEventLoopUiOps glib_eventloops =
105
+ {
106
+ g_timeout_add,
107
+ g_source_remove,
108
+ glib_input_add,
109
+ g_source_remove,
110
+ NULL,
111
+ #if GLIB_CHECK_VERSION(2,14,0)
112
+ g_timeout_add_seconds,
113
+ #else
114
+ NULL,
115
+ #endif
116
+
117
+ /* padding */
118
+ NULL,
119
+ NULL,
120
+ NULL
121
+ };
122
+
123
+ static VALUE cPurpleRuby, cProtocol;
124
+ VALUE cAccount;
125
+ const char* UI_ID = "purplegw";
126
+ static GMainLoop *main_loop = NULL;
127
+ static GHashTable* data_hash_table = NULL;
128
+ static GHashTable* fd_hash_table = NULL;
129
+ ID CALL, USER_DIR, DEBUG;
130
+ extern PurpleAccountUiOps account_ops;
131
+
132
+ static VALUE im_handler = Qnil;
133
+ static VALUE signed_on_handler = Qnil;
134
+ static VALUE connection_error_handler = Qnil;
135
+ static VALUE notify_message_handler = Qnil;
136
+ static VALUE request_handler = Qnil;
137
+ static VALUE ipc_handler = Qnil;
138
+ static VALUE timer_handler = Qnil;
139
+ guint timer_timeout = 0;
140
+ VALUE new_buddy_handler = Qnil;
141
+
142
+ extern void
143
+ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
144
+ const char *text);
145
+
146
+ extern void finch_connections_init();
147
+
148
+ VALUE inspect_rb_obj(VALUE obj)
149
+ {
150
+ return rb_funcall(obj, rb_intern("inspect"), 0, 0);
151
+ }
152
+
153
+ void set_callback(VALUE* handler, const char* handler_name)
154
+ {
155
+ if (!rb_block_given_p()) {
156
+ rb_raise(rb_eArgError, "%s: no block", handler_name);
157
+ }
158
+
159
+ if (Qnil != *handler) {
160
+ rb_raise(rb_eArgError, "%s should only be assigned once", handler_name);
161
+ }
162
+
163
+ *handler = rb_block_proc();
164
+ /*
165
+ * If you create a Ruby object from C and store it in a C global variable without
166
+ * exporting it to Ruby, you must at least tell the garbage collector about it,
167
+ * lest ye be reaped inadvertently:
168
+ */
169
+ rb_global_variable(handler);
170
+
171
+ if (rb_obj_class(*handler) != rb_cProc) {
172
+ rb_raise(rb_eTypeError, "%s got unexpected value: %s", handler_name,
173
+ RSTRING(inspect_rb_obj(*handler))->ptr);
174
+ }
175
+ }
176
+
177
+ void check_callback(VALUE handler, const char* handler_name){
178
+ if (rb_obj_class(handler) != rb_cProc) {
179
+ rb_raise(rb_eTypeError, "%s has unexpected value: %s",
180
+ handler_name,
181
+ RSTRING(inspect_rb_obj(handler))->ptr);
182
+ }
183
+ }
184
+
185
+ void report_disconnect(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
186
+ {
187
+ if (Qnil != connection_error_handler) {
188
+ VALUE args[3];
189
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(gc));
190
+ args[1] = INT2FIX(reason);
191
+ args[2] = rb_str_new2(text);
192
+ check_callback(connection_error_handler, "connection_error_handler");
193
+ VALUE v = rb_funcall2(connection_error_handler, CALL, 3, args);
194
+
195
+ if (v != Qnil && v != Qfalse) {
196
+ finch_connection_report_disconnect(gc, reason, text);
197
+ }
198
+ }
199
+ }
200
+
201
+ static void* notify_message(PurpleNotifyMsgType type,
202
+ const char *title,
203
+ const char *primary,
204
+ const char *secondary)
205
+ {
206
+ if (notify_message_handler != Qnil) {
207
+ VALUE args[4];
208
+ args[0] = INT2FIX(type);
209
+ args[1] = rb_str_new2(NULL == title ? "" : title);
210
+ args[2] = rb_str_new2(NULL == primary ? "" : primary);
211
+ args[3] = rb_str_new2(NULL == secondary ? "" : secondary);
212
+ check_callback(notify_message_handler, "notify_message_handler");
213
+ rb_funcall2(notify_message_handler, CALL, 4, args);
214
+ }
215
+
216
+ return NULL;
217
+ }
218
+
219
+ static void write_conv(PurpleConversation *conv, const char *who, const char *alias,
220
+ const char *message, PurpleMessageFlags flags, time_t mtime)
221
+ {
222
+ if (im_handler != Qnil) {
223
+ PurpleAccount* account = purple_conversation_get_account(conv);
224
+ if (strcmp(purple_account_get_protocol_id(account), "prpl-msn") == 0 &&
225
+ (strstr(message, "Message could not be sent") != NULL ||
226
+ strstr(message, "Message was not sent") != NULL ||
227
+ strstr(message, "Message may have not been sent") != NULL
228
+ )
229
+ ) {
230
+ /* I have seen error like 'msn: Connection error from Switchboard server'.
231
+ * In that case, libpurple will notify user with two regular im message.
232
+ * The first message is an error message, the second one is the original message that failed to send.
233
+ */
234
+ notify_message(PURPLE_CONNECTION_ERROR_NETWORK_ERROR, message, purple_account_get_protocol_id(account), who);
235
+ } else {
236
+ VALUE args[3];
237
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
238
+ args[1] = rb_str_new2(who);
239
+ args[2] = rb_str_new2(message);
240
+ check_callback(im_handler, "im_handler");
241
+ rb_funcall2(im_handler, CALL, 3, args);
242
+ }
243
+ }
244
+ }
245
+
246
+ static PurpleConversationUiOps conv_uiops =
247
+ {
248
+ NULL, /* create_conversation */
249
+ NULL, /* destroy_conversation */
250
+ NULL, /* write_chat */
251
+ NULL, /* write_im */
252
+ write_conv, /* write_conv */
253
+ NULL, /* chat_add_users */
254
+ NULL, /* chat_rename_user */
255
+ NULL, /* chat_remove_users */
256
+ NULL, /* chat_update_user */
257
+ NULL, /* present */
258
+ NULL, /* has_focus */
259
+ NULL, /* custom_smiley_add */
260
+ NULL, /* custom_smiley_write */
261
+ NULL, /* custom_smiley_close */
262
+ NULL, /* send_confirm */
263
+ NULL,
264
+ NULL,
265
+ NULL,
266
+ NULL
267
+ };
268
+
269
+ static PurpleConnectionUiOps connection_ops =
270
+ {
271
+ NULL, /* connect_progress */
272
+ NULL, /* connected */
273
+ NULL, /* disconnected */
274
+ NULL, /* notice */
275
+ NULL,
276
+ NULL, /* network_connected */
277
+ NULL, /* network_disconnected */
278
+ report_disconnect,
279
+ NULL,
280
+ NULL,
281
+ NULL
282
+ };
283
+
284
+ static void* request_action(const char *title, const char *primary, const char *secondary,
285
+ int default_action,
286
+ PurpleAccount *account,
287
+ const char *who,
288
+ PurpleConversation *conv,
289
+ void *user_data,
290
+ size_t action_count,
291
+ va_list actions)
292
+ {
293
+ if (request_handler != Qnil) {
294
+ VALUE args[4];
295
+ args[0] = rb_str_new2(NULL == title ? "" : title);
296
+ args[1] = rb_str_new2(NULL == primary ? "" : primary);
297
+ args[2] = rb_str_new2(NULL == secondary ? "" : secondary);
298
+ args[3] = rb_str_new2(NULL == who ? "" : who);
299
+ check_callback(request_handler, "request_handler");
300
+ VALUE v = rb_funcall2(request_handler, CALL, 4, args);
301
+
302
+ if (v != Qnil && v != Qfalse) {
303
+ /*const char *text =*/ va_arg(actions, const char *);
304
+ GCallback ok_cb = va_arg(actions, GCallback);
305
+ ((PurpleRequestActionCb)ok_cb)(user_data, default_action);
306
+ }
307
+ }
308
+
309
+ return NULL;
310
+ }
311
+
312
+ static PurpleRequestUiOps request_ops =
313
+ {
314
+ NULL, /*request_input*/
315
+ NULL, /*request_choice*/
316
+ request_action, /*request_action*/
317
+ NULL, /*request_fields*/
318
+ NULL, /*request_file*/
319
+ NULL, /*close_request*/
320
+ NULL, /*request_folder*/
321
+ NULL,
322
+ NULL,
323
+ NULL,
324
+ NULL
325
+ };
326
+
327
+ static PurpleNotifyUiOps notify_ops =
328
+ {
329
+ notify_message, /*notify_message*/
330
+ NULL, /*notify_email*/
331
+ NULL, /*notify_emails*/
332
+ NULL, /*notify_formatted*/
333
+ NULL, /*notify_searchresults*/
334
+ NULL, /*notify_searchresults_new_rows*/
335
+ NULL, /*notify_userinfo*/
336
+ NULL, /*notify_uri*/
337
+ NULL, /*close_notify*/
338
+ NULL,
339
+ NULL,
340
+ NULL,
341
+ NULL,
342
+ };
343
+
344
+ static PurpleCoreUiOps core_uiops =
345
+ {
346
+ NULL,
347
+ NULL,
348
+ NULL,
349
+ NULL,
350
+
351
+ /* padding */
352
+ NULL,
353
+ NULL,
354
+ NULL,
355
+ NULL
356
+ };
357
+
358
+ //I have tried to detect Ctrl-C using ruby's trap method,
359
+ //but it does not work as expected: it can not detect Ctrl-C
360
+ //until a network event occurs
361
+ static void sighandler(int sig)
362
+ {
363
+ switch (sig) {
364
+ case SIGINT:
365
+ g_main_loop_quit(main_loop);
366
+ break;
367
+ }
368
+ }
369
+
370
+ static VALUE init(int argc, VALUE *argv, VALUE self)
371
+ {
372
+ VALUE debug, path, settings;
373
+
374
+ rb_scan_args(argc, argv, "01", &settings);
375
+
376
+ if (argc == 0) {
377
+ debug = Qnil;
378
+ path = Qnil;
379
+ }
380
+ else {
381
+ settings = rb_convert_type(settings, T_HASH, "Hash", "to_hash");
382
+
383
+ debug = rb_hash_aref(settings, ID2SYM(DEBUG));
384
+ path = rb_hash_aref(settings, ID2SYM(USER_DIR));
385
+ }
386
+
387
+ signal(SIGCHLD, SIG_IGN);
388
+ signal(SIGPIPE, SIG_IGN);
389
+ signal(SIGINT, sighandler);
390
+
391
+ data_hash_table = g_hash_table_new(NULL, NULL);
392
+ fd_hash_table = g_hash_table_new(NULL, NULL);
393
+
394
+ purple_debug_set_enabled(RTEST(debug) ? TRUE : FALSE);
395
+
396
+ if (path != Qnil) {
397
+ purple_util_set_user_dir(StringValueCStr(path));
398
+ }
399
+
400
+ purple_core_set_ui_ops(&core_uiops);
401
+ purple_eventloop_set_ui_ops(&glib_eventloops);
402
+
403
+ if (!purple_core_init(UI_ID)) {
404
+ rb_raise(rb_eRuntimeError, "libpurple initialization failed");
405
+ }
406
+
407
+ /* Create and load the buddylist. */
408
+ purple_set_blist(purple_blist_new());
409
+ purple_blist_load();
410
+
411
+ /* Load the preferences. */
412
+ purple_prefs_load();
413
+
414
+ /* Load the pounces. */
415
+ purple_pounces_load();
416
+
417
+ return Qnil;
418
+ }
419
+
420
+ static VALUE watch_incoming_im(VALUE self)
421
+ {
422
+ purple_conversations_set_ui_ops(&conv_uiops);
423
+ set_callback(&im_handler, "im_handler");
424
+ return im_handler;
425
+ }
426
+
427
+ static VALUE watch_notify_message(VALUE self)
428
+ {
429
+ purple_notify_set_ui_ops(&notify_ops);
430
+ set_callback(&notify_message_handler, "notify_message_handler");
431
+ return notify_message_handler;
432
+ }
433
+
434
+ static VALUE watch_request(VALUE self)
435
+ {
436
+ purple_request_set_ui_ops(&request_ops);
437
+ set_callback(&request_handler, "request_handler");
438
+ return request_handler;
439
+ }
440
+
441
+ static VALUE watch_new_buddy(VALUE self)
442
+ {
443
+ purple_accounts_set_ui_ops(&account_ops);
444
+ set_callback(&new_buddy_handler, "new_buddy_handler");
445
+ return new_buddy_handler;
446
+ }
447
+
448
+ static void signed_on(PurpleConnection* connection)
449
+ {
450
+ VALUE args[1];
451
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(connection));
452
+ check_callback(signed_on_handler, "signed_on_handler");
453
+ rb_funcall2(signed_on_handler, CALL, 1, args);
454
+ }
455
+
456
+ static VALUE watch_signed_on_event(VALUE self)
457
+ {
458
+ set_callback(&signed_on_handler, "signed_on_handler");
459
+ int handle;
460
+ purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
461
+ PURPLE_CALLBACK(signed_on), NULL);
462
+ return signed_on_handler;
463
+ }
464
+
465
+ static VALUE watch_connection_error(VALUE self)
466
+ {
467
+ finch_connections_init();
468
+ purple_connections_set_ui_ops(&connection_ops);
469
+
470
+ set_callback(&connection_error_handler, "connection_error_handler");
471
+
472
+ /*int handle;
473
+ purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
474
+ PURPLE_CALLBACK(connection_error), NULL);*/
475
+ return connection_error_handler;
476
+ }
477
+
478
+ static void _read_socket_handler(gpointer notused, int socket, PurpleInputCondition condition)
479
+ {
480
+ char message[4096] = {0};
481
+ int i = recv(socket, message, sizeof(message) - 1, 0);
482
+ if (i > 0) {
483
+ purple_debug_info("purple_ruby", "recv %d: %d\n", socket, i);
484
+
485
+ gpointer str = g_hash_table_lookup(data_hash_table, (gpointer)socket);
486
+ if (NULL == str) rb_raise(rb_eRuntimeError, "can not find socket: %d", socket);
487
+ rb_str_append((VALUE)str, rb_str_new2(message));
488
+ } else {
489
+ purple_debug_info("purple_ruby", "close connection %d: %d %d\n", socket, i, errno);
490
+
491
+ gpointer str = g_hash_table_lookup(data_hash_table, (gpointer)socket);
492
+ if (NULL == str) {
493
+ purple_debug_warning("purple_ruby", "can not find socket in data_hash_table %d\n", socket);
494
+ return;
495
+ }
496
+
497
+ gpointer purple_fd = g_hash_table_lookup(fd_hash_table, (gpointer)socket);
498
+ if (NULL == purple_fd) {
499
+ purple_debug_warning("purple_ruby", "can not find socket in fd_hash_table %d\n", socket);
500
+ return;
501
+ }
502
+
503
+ g_hash_table_remove(fd_hash_table, (gpointer)socket);
504
+ g_hash_table_remove(data_hash_table, (gpointer)socket);
505
+ purple_input_remove((guint)purple_fd);
506
+ close(socket);
507
+
508
+ VALUE args[1];
509
+ args[0] = (VALUE)str;
510
+ check_callback(ipc_handler, "ipc_handler");
511
+ rb_funcall2(ipc_handler, CALL, 1, args);
512
+ }
513
+ }
514
+
515
+ static void _accept_socket_handler(gpointer notused, int server_socket, PurpleInputCondition condition)
516
+ {
517
+ /* Check that it is a read condition */
518
+ if (condition != PURPLE_INPUT_READ)
519
+ return;
520
+
521
+ struct sockaddr_in their_addr; /* connector's address information */
522
+ socklen_t sin_size = sizeof(struct sockaddr);
523
+ int client_socket;
524
+ if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
525
+ purple_debug_warning("purple_ruby", "failed to accept %d: %d\n", client_socket, errno);
526
+ return;
527
+ }
528
+
529
+ int flags = fcntl(client_socket, F_GETFL);
530
+ fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
531
+ #ifndef _WIN32
532
+ fcntl(client_socket, F_SETFD, FD_CLOEXEC);
533
+ #endif
534
+
535
+ purple_debug_info("purple_ruby", "new connection: %d\n", client_socket);
536
+
537
+ guint purple_fd = purple_input_add(client_socket, PURPLE_INPUT_READ, _read_socket_handler, NULL);
538
+
539
+ g_hash_table_insert(data_hash_table, (gpointer)client_socket, (gpointer)rb_str_new2(""));
540
+ g_hash_table_insert(fd_hash_table, (gpointer)client_socket, (gpointer)purple_fd);
541
+ }
542
+
543
+ static VALUE watch_incoming_ipc(VALUE self, VALUE serverip, VALUE port)
544
+ {
545
+ struct sockaddr_in my_addr;
546
+ int soc;
547
+ int on = 1;
548
+
549
+ /* Open a listening socket for incoming conversations */
550
+ if ((soc = socket(PF_INET, SOCK_STREAM, 0)) < 0)
551
+ {
552
+ rb_raise(rb_eRuntimeError, "Cannot open socket: %s\n", g_strerror(errno));
553
+ return Qnil;
554
+ }
555
+
556
+ if (setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
557
+ {
558
+ rb_raise(rb_eRuntimeError, "SO_REUSEADDR failed: %s\n", g_strerror(errno));
559
+ return Qnil;
560
+ }
561
+
562
+ memset(&my_addr, 0, sizeof(struct sockaddr_in));
563
+ my_addr.sin_family = AF_INET;
564
+ my_addr.sin_addr.s_addr = inet_addr(StringValueCStr(serverip));
565
+ my_addr.sin_port = htons(FIX2INT(port));
566
+ if (bind(soc, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0)
567
+ {
568
+ rb_raise(rb_eRuntimeError, "Unable to bind to port %d: %s\n", (int)FIX2INT(port), g_strerror(errno));
569
+ return Qnil;
570
+ }
571
+
572
+ /* Attempt to listen on the bound socket */
573
+ if (listen(soc, 10) != 0)
574
+ {
575
+ rb_raise(rb_eRuntimeError, "Cannot listen on socket: %s\n", g_strerror(errno));
576
+ return Qnil;
577
+ }
578
+
579
+ set_callback(&ipc_handler, "ipc_handler");
580
+
581
+ /* Open a watcher in the socket we have just opened */
582
+ purple_input_add(soc, PURPLE_INPUT_READ, _accept_socket_handler, NULL);
583
+
584
+ return port;
585
+ }
586
+
587
+ static gboolean
588
+ do_timeout(gpointer data)
589
+ {
590
+ VALUE handler = data;
591
+ check_callback(handler, "timer_handler");
592
+ VALUE v = rb_funcall(handler, CALL, 0, 0);
593
+ return (v == Qtrue);
594
+ }
595
+
596
+ static VALUE watch_timer(VALUE self, VALUE delay)
597
+ {
598
+ set_callback(&timer_handler, "timer_handler");
599
+ if (timer_timeout != 0)
600
+ g_source_remove(timer_timeout);
601
+ timer_timeout = g_timeout_add(delay, do_timeout, timer_handler);
602
+ return delay;
603
+ }
604
+
605
+ static VALUE login(VALUE self, VALUE protocol, VALUE username, VALUE password)
606
+ {
607
+ PurpleAccount* account = purple_account_new(StringValueCStr(username), StringValueCStr(protocol));
608
+ if (NULL == account || NULL == account->presence) {
609
+ rb_raise(rb_eRuntimeError, "No able to create account: %s", StringValueCStr(protocol));
610
+ }
611
+
612
+ purple_account_set_password(account, StringValueCStr(password));
613
+ purple_account_set_remember_password(account, TRUE);
614
+ purple_account_set_enabled(account, UI_ID, TRUE);
615
+ PurpleSavedStatus *status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
616
+ purple_savedstatus_activate(status);
617
+
618
+ return Data_Wrap_Struct(cAccount, NULL, NULL, account);
619
+ }
620
+
621
+ static VALUE main_loop_run(VALUE self)
622
+ {
623
+ main_loop = g_main_loop_new(NULL, FALSE);
624
+ g_main_loop_run(main_loop);
625
+
626
+ #ifdef DEBUG_MEM_LEAK
627
+ printf("QUIT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
628
+ purple_core_quit();
629
+ if (im_handler == Qnil) rb_gc_unregister_address(&im_handler);
630
+ if (signed_on_handler == Qnil) rb_gc_unregister_address(&signed_on_handler);
631
+ if (connection_error_handler == Qnil) rb_gc_unregister_address(&connection_error_handler);
632
+ if (notify_message_handler == Qnil) rb_gc_unregister_address(&notify_message_handler);
633
+ if (request_handler == Qnil) rb_gc_unregister_address(&request_handler);
634
+ if (ipc_handler == Qnil) rb_gc_unregister_address(&ipc_handler);
635
+ if (timer_timeout != 0) g_source_remove(timer_timeout);
636
+ if (timer_handler == Qnil) rb_gc_unregister_address(&timer_handler);
637
+ if (new_buddy_handler == Qnil) rb_gc_unregister_address(&new_buddy_handler);
638
+ rb_gc_start();
639
+ #endif
640
+
641
+ return Qnil;
642
+ }
643
+
644
+ static VALUE main_loop_stop(VALUE self)
645
+ {
646
+ g_main_loop_quit(main_loop);
647
+ return Qnil;
648
+ }
649
+
650
+ static VALUE send_im(VALUE self, VALUE name, VALUE message)
651
+ {
652
+ PurpleAccount *account;
653
+ Data_Get_Struct(self, PurpleAccount, account);
654
+
655
+ if (purple_account_is_connected(account)) {
656
+ int i = serv_send_im(purple_account_get_connection(account), StringValueCStr(name), StringValueCStr(message), 0);
657
+ return INT2FIX(i);
658
+ } else {
659
+ return Qnil;
660
+ }
661
+ }
662
+
663
+ static VALUE username(VALUE self)
664
+ {
665
+ PurpleAccount *account;
666
+ Data_Get_Struct(self, PurpleAccount, account);
667
+ return rb_str_new2(purple_account_get_username(account));
668
+ }
669
+
670
+ static VALUE protocol_id(VALUE self)
671
+ {
672
+ PurpleAccount *account;
673
+ Data_Get_Struct(self, PurpleAccount, account);
674
+ return rb_str_new2(purple_account_get_protocol_id(account));
675
+ }
676
+
677
+ static VALUE protocol_name(VALUE self)
678
+ {
679
+ PurpleAccount *account;
680
+ Data_Get_Struct(self, PurpleAccount, account);
681
+ return rb_str_new2(purple_account_get_protocol_name(account));
682
+ }
683
+
684
+ static VALUE get_bool_setting(VALUE self, VALUE name, VALUE default_value)
685
+ {
686
+ PurpleAccount *account;
687
+ Data_Get_Struct(self, PurpleAccount, account);
688
+ gboolean value = purple_account_get_bool(account, StringValueCStr(name), RTEST(default_value) ? TRUE : FALSE);
689
+ return (TRUE == value) ? Qtrue : Qfalse;
690
+ }
691
+
692
+ static VALUE set_bool_setting(VALUE self, VALUE name, VALUE value)
693
+ {
694
+ PurpleAccount *account;
695
+ Data_Get_Struct(self, PurpleAccount, account);
696
+ purple_account_set_bool(account, StringValueCStr(name), RTEST(value) ? TRUE : FALSE);
697
+ return value;
698
+ }
699
+
700
+ static VALUE get_int_setting(VALUE self, VALUE name, VALUE default_value)
701
+ {
702
+ PurpleAccount *account;
703
+ Data_Get_Struct(self, PurpleAccount, account);
704
+ return INT2FIX(purple_account_get_int(account, StringValueCStr(name), FIX2INT(default_value)));
705
+ }
706
+
707
+ static VALUE set_int_setting(VALUE self, VALUE name, VALUE value)
708
+ {
709
+ PurpleAccount *account;
710
+ Data_Get_Struct(self, PurpleAccount, account);
711
+ purple_account_set_int(account, StringValueCStr(name), FIX2INT(value));
712
+ return value;
713
+ }
714
+
715
+ static VALUE get_string_setting(VALUE self, VALUE name, VALUE default_value)
716
+ {
717
+ PurpleAccount *account;
718
+ Data_Get_Struct(self, PurpleAccount, account);
719
+ const char* value = purple_account_get_string(account, StringValueCStr(name), StringValueCStr(default_value));
720
+ return (NULL == value) ? Qnil : rb_str_new2(value);
721
+ }
722
+
723
+ static VALUE set_string_setting(VALUE self, VALUE name, VALUE value)
724
+ {
725
+ PurpleAccount *account;
726
+ Data_Get_Struct(self, PurpleAccount, account);
727
+ purple_account_set_string(account, StringValueCStr(name), StringValueCStr(value));
728
+ return value;
729
+ }
730
+
731
+ static VALUE list_protocols(VALUE self)
732
+ {
733
+ VALUE array = rb_ary_new();
734
+
735
+ GList *iter = purple_plugins_get_protocols();
736
+ int i;
737
+ for (i = 0; iter; iter = iter->next) {
738
+ VALUE protocol = Data_Wrap_Struct(cProtocol, NULL, NULL, iter->data);
739
+ rb_ary_push(array, protocol);
740
+ }
741
+
742
+ return array;
743
+ }
744
+
745
+ static VALUE add_buddy(VALUE self, VALUE buddy)
746
+ {
747
+ PurpleAccount *account;
748
+ Data_Get_Struct(self, PurpleAccount, account);
749
+
750
+ PurpleBuddy* pb = purple_buddy_new(account, StringValueCStr(buddy), NULL);
751
+
752
+ char* group = _("Buddies");
753
+ PurpleGroup* grp = purple_find_group(group);
754
+ if (!grp)
755
+ {
756
+ grp = purple_group_new(group);
757
+ purple_blist_add_group(grp, NULL);
758
+ }
759
+
760
+ purple_blist_add_buddy(pb, NULL, grp, NULL);
761
+ purple_account_add_buddy(account, pb);
762
+ return Qtrue;
763
+ }
764
+
765
+ static VALUE remove_buddy(VALUE self, VALUE buddy)
766
+ {
767
+ PurpleAccount *account;
768
+ Data_Get_Struct(self, PurpleAccount, account);
769
+
770
+ PurpleBuddy* pb = purple_find_buddy(account, StringValueCStr(buddy));
771
+ if (NULL == pb) {
772
+ rb_raise(rb_eRuntimeError, "Failed to remove buddy for %s : %s does not exist", purple_account_get_username(account), StringValueCStr(buddy));
773
+ }
774
+
775
+ char* group = _("Buddies");
776
+ PurpleGroup* grp = purple_find_group(group);
777
+ if (!grp)
778
+ {
779
+ grp = purple_group_new(group);
780
+ purple_blist_add_group(grp, NULL);
781
+ }
782
+
783
+ purple_blist_remove_buddy(pb);
784
+ purple_account_remove_buddy(account, pb, grp);
785
+
786
+ return Qtrue;
787
+ }
788
+
789
+ static VALUE has_buddy(VALUE self, VALUE buddy)
790
+ {
791
+ PurpleAccount *account;
792
+ Data_Get_Struct(self, PurpleAccount, account);
793
+ if (purple_find_buddy(account, StringValueCStr(buddy)) != NULL) {
794
+ return Qtrue;
795
+ } else {
796
+ return Qfalse;
797
+ }
798
+ }
799
+
800
+ static VALUE acc_delete(VALUE self)
801
+ {
802
+ PurpleAccount *account;
803
+ Data_Get_Struct(self, PurpleAccount, account);
804
+ purple_accounts_delete(account);
805
+ return Qnil;
806
+ }
807
+
808
+ static VALUE protocol_to_s(VALUE self)
809
+ {
810
+ PurplePlugin *protocol;
811
+ PurplePluginInfo *info;
812
+
813
+ Data_Get_Struct(self, PurplePlugin, protocol);
814
+ info = protocol->info;
815
+ if (info && info->name) {
816
+ char s[256];
817
+ snprintf(s, sizeof(s) -1, "%s %s", info->id, info->name);
818
+ return rb_str_new2(s);
819
+ }
820
+
821
+ return Qnil;
822
+ }
823
+
824
+ static VALUE protocol_get_id(VALUE self)
825
+ {
826
+ PurplePlugin *protocol;
827
+ PurplePluginInfo *info;
828
+
829
+ Data_Get_Struct(self, PurplePlugin, protocol);
830
+ info = protocol->info;
831
+ return info && info->id ? rb_str_new2(info->id) : Qnil;
832
+ }
833
+
834
+ static VALUE protocol_get_name(VALUE self)
835
+ {
836
+ PurplePlugin *protocol;
837
+ PurplePluginInfo *info;
838
+
839
+ Data_Get_Struct(self, PurplePlugin, protocol);
840
+ info = protocol->info;
841
+ return info && info->name ? rb_str_new2(info->name) : Qnil;
842
+ }
843
+
844
+ static VALUE protocol_get_default_options(VALUE self)
845
+ {
846
+ PurplePlugin *protocol;
847
+ PurplePluginProtocolInfo *prpl_info;
848
+ PurpleAccountOption *opt;
849
+ GList *opts;
850
+ const char *str_val;
851
+ VALUE h = rb_hash_new(), key, val;
852
+
853
+ Data_Get_Struct(self, PurplePlugin, protocol);
854
+ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(protocol);
855
+ opts = prpl_info->protocol_options;
856
+ for (; opts; opts = opts->next) {
857
+ opt = (PurpleAccountOption *)opts->data;
858
+ key = rb_str_new2(opt->pref_name);
859
+ switch (opt->type)
860
+ {
861
+ case PURPLE_PREF_BOOLEAN:
862
+ val = purple_account_option_get_default_bool(opt) ? Qtrue : Qfalse;
863
+ break;
864
+ case PURPLE_PREF_INT:
865
+ val = INT2FIX(purple_account_option_get_default_int(opt));
866
+ break;
867
+ case PURPLE_PREF_STRING:
868
+ str_val = purple_account_option_get_default_string(opt);
869
+ val = str_val == NULL ? rb_str_new2("") : rb_str_new2(str_val);
870
+ break;
871
+ case PURPLE_PREF_STRING_LIST:
872
+ str_val = rb_str_new2(purple_account_option_get_default_list_value(opt));
873
+ val = str_val == NULL ? rb_str_new2("") : rb_str_new2(str_val);
874
+ break;
875
+ default:
876
+ key = Qnil;
877
+ }
878
+
879
+ if (key != Qnil)
880
+ rb_hash_aset(h, key, val);
881
+ }
882
+
883
+ return h;
884
+ }
885
+
886
+ void Init_purple_ruby_ext()
887
+ {
888
+ CALL = rb_intern("call");
889
+ DEBUG = rb_intern("debug");
890
+ USER_DIR = rb_intern("user_dir");
891
+
892
+ cPurpleRuby = rb_define_class("PurpleRuby", rb_cObject);
893
+ rb_define_singleton_method(cPurpleRuby, "init", init, -1);
894
+ rb_define_singleton_method(cPurpleRuby, "list_protocols", list_protocols, 0);
895
+ rb_define_singleton_method(cPurpleRuby, "watch_signed_on_event", watch_signed_on_event, 0);
896
+ rb_define_singleton_method(cPurpleRuby, "watch_connection_error", watch_connection_error, 0);
897
+ rb_define_singleton_method(cPurpleRuby, "watch_incoming_im", watch_incoming_im, 0);
898
+ rb_define_singleton_method(cPurpleRuby, "watch_notify_message", watch_notify_message, 0);
899
+ rb_define_singleton_method(cPurpleRuby, "watch_request", watch_request, 0);
900
+ rb_define_singleton_method(cPurpleRuby, "watch_new_buddy", watch_new_buddy, 0);
901
+ rb_define_singleton_method(cPurpleRuby, "watch_incoming_ipc", watch_incoming_ipc, 2);
902
+ rb_define_singleton_method(cPurpleRuby, "watch_timer", watch_timer, 1);
903
+ rb_define_singleton_method(cPurpleRuby, "login", login, 3);
904
+ rb_define_singleton_method(cPurpleRuby, "main_loop_run", main_loop_run, 0);
905
+ rb_define_singleton_method(cPurpleRuby, "main_loop_stop", main_loop_stop, 0);
906
+
907
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_ERROR", INT2NUM(PURPLE_NOTIFY_MSG_ERROR));
908
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_WARNING", INT2NUM(PURPLE_NOTIFY_MSG_WARNING));
909
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_INFO", INT2NUM(PURPLE_NOTIFY_MSG_INFO));
910
+
911
+ cAccount = rb_define_class_under(cPurpleRuby, "Account", rb_cObject);
912
+ rb_define_method(cAccount, "send_im", send_im, 2);
913
+ rb_define_method(cAccount, "username", username, 0);
914
+ rb_define_method(cAccount, "protocol_id", protocol_id, 0);
915
+ rb_define_method(cAccount, "protocol_name", protocol_name, 0);
916
+ rb_define_method(cAccount, "get_bool_setting", get_bool_setting, 2);
917
+ rb_define_method(cAccount, "set_bool_setting", set_bool_setting, 2);
918
+ rb_define_method(cAccount, "get_int_setting", get_int_setting, 2);
919
+ rb_define_method(cAccount, "set_int_setting", set_int_setting, 2);
920
+ rb_define_method(cAccount, "get_string_setting", get_string_setting, 2);
921
+ rb_define_method(cAccount, "set_string_setting", set_string_setting, 2);
922
+ rb_define_method(cAccount, "add_buddy", add_buddy, 1);
923
+ rb_define_method(cAccount, "remove_buddy", remove_buddy, 1);
924
+ rb_define_method(cAccount, "has_buddy?", has_buddy, 1);
925
+ rb_define_method(cAccount, "delete", acc_delete, 0);
926
+
927
+ cProtocol = rb_define_class_under(cPurpleRuby, "Protocol", rb_cObject);
928
+ rb_define_method(cProtocol, "default_options", protocol_get_default_options, 0);
929
+ rb_define_method(cProtocol, "id", protocol_get_id, 0);
930
+ rb_define_method(cProtocol, "name", protocol_get_name, 0);
931
+ rb_define_method(cProtocol, "to_str", protocol_to_s, 0);
932
+ rb_define_method(cProtocol, "to_s", protocol_to_s, 0);
933
+
934
+ }