purple_ruby 0.6.2

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/History.txt ADDED
@@ -0,0 +1,2 @@
1
+ == 0.1.0
2
+
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ ext/extconf.rb
2
+ ext/purple_ruby.c
3
+ ext/reconnect.c
4
+ ext/account.c
5
+ examples/purplegw_example.rb
6
+ Manifest.txt
7
+ History.txt
8
+ README.txt
9
+ Rakefile
data/README.txt ADDED
@@ -0,0 +1,53 @@
1
+ == OVERVIEW
2
+
3
+ purple_ruby is a ruby gem to write servers that send and recive IM messages. It uses libpurple ( http://developer.pidgin.im/wiki/WhatIsLibpurple ) and therforce supports all protocols that Pidgin/Adium supports (MSN/Gtalk/Yahoo/AIM/ICQ etc).
4
+
5
+ For MSN, we recommend msn-pecan ( http://code.google.com/p/msn-pecan/ ), which is more more stable than official MSN plugin.
6
+
7
+ Please check examples/purplegw_example.rb for details. Bascially you just tell it what to do when an IM was received, and there is an embedded tcp 'proxy' which allows you send IM messages.
8
+
9
+ Why not "ruburple"? I have used ruburple ( http://rubyforge.org/projects/ruburple ), but found it blocks a lot. libpurple needs to run its own event loop which interferes with ruby's green thread model. Ruburple's author has done lots of hard work to workaround the problem ( http://rubyforge.org/pipermail/ruburple-development/2007-June/000005.html ), but it does not work well.
10
+
11
+ == INSTALLATION
12
+
13
+ Ubuntu:
14
+ ---------------
15
+ sudo apt-get install libpurple0 libpurple-dev
16
+ gem sources -a http://gems.github.com (you only have to do this once)
17
+ sudo gem install yong-purple_ruby
18
+
19
+ Redhat/Centos
20
+ ---------------
21
+ wget -O /etc/yum.repos.d/pidgin.repo http://rpm.pidgin.im/centos/pidgin.repo
22
+ yum -y install glib2-devel libpurple-devel
23
+ gem sources -a http://gems.github.com (you only have to do this once)
24
+ sudo gem install yong-purple_ruby
25
+
26
+ OSX:
27
+ ----
28
+ sudo port -d selfupdate
29
+ sudo port install gnutls
30
+ (wait forever....)
31
+ sudo port install nss
32
+ (wait forever....)
33
+ wget http://downloads.sourceforge.net/pidgin/pidgin-2.5.7.tar.bz2
34
+ tar xvjf pidgin-2.5.7.tar.bz2
35
+ cd pidgin-2.5.7
36
+ ./configure --disable-gtkui --disable-screensaver --disable-consoleui --disable-sm --disable-perl --disable-tk --disable-tcl --disable-gstreamer --disable-schemas-install --disable-gestures --disable-cap --disable-gevolution --disable-gtkspell --disable-startup-notification --disable-avahi --disable-nm --disable-dbus --disable-meanwhile
37
+ make
38
+ (wait forever...)
39
+ sudo make install
40
+
41
+ edit your ~/.bash_profile and add this line
42
+ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
43
+
44
+ gem sources -a http://gems.github.com (you only have to do this once)
45
+ sudo gem install yong-purple_ruby
46
+
47
+ == Copyright
48
+
49
+ purple_ruby is Copyright (c) 2009 Xue Yong Zhi and Intridea, Inc. ( http://intridea.com ), released under the GPL License.
50
+
51
+
52
+
53
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+
2
+ EXT = "ext/ruburple_ext.#{Config::CONFIG['DLEXT']}"
3
+
4
+ task :default => EXT
5
+
6
+ file EXT => ["ext/extconf.rb", "ext/purple_ruby.c"] do
7
+ Dir.chdir "ext" do
8
+ ruby "extconf.rb"
9
+ sh "make"
10
+ end
11
+ end
@@ -0,0 +1,94 @@
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 'hpricot'
14
+ require 'socket'
15
+ require File.expand_path(File.join(File.dirname(__FILE__), '../ext/purple_ruby'))
16
+
17
+ class PurpleGWExample
18
+ SERVER_IP = "127.0.0.1"
19
+ SERVER_PORT = 9877
20
+
21
+ def start configs
22
+ PurpleRuby.init false #use 'true' if you want to see the debug messages
23
+
24
+ puts "Available protocols:", PurpleRuby.list_protocols
25
+
26
+ accounts = {}
27
+ configs.each {|config|
28
+ puts "logging in #{config[:username]} (#{config[:protocol]})..."
29
+ account = PurpleRuby.login(config[:protocol], config[:username], config[:password])
30
+ accounts[config[:protocol]] = account
31
+ }
32
+
33
+ #handle incoming im messages
34
+ PurpleRuby.watch_incoming_im do |acc, sender, message|
35
+ sender = sender[0...sender.index('/')] if sender.index('/') #discard anything after '/'
36
+ text = (Hpricot(message)).to_plain_text
37
+ puts "recv: #{acc.username}, #{sender}, #{text}"
38
+ end
39
+
40
+ PurpleRuby.watch_signed_on_event do |acc|
41
+ puts "signed on: #{acc.username}"
42
+ end
43
+
44
+ PurpleRuby.watch_connection_error do |acc, type, description|
45
+ puts "connection_error: #{acc.username} #{type} #{description}"
46
+ true #'true': auto-reconnect; 'false': do nothing
47
+ end
48
+
49
+ #request can be: 'SSL Certificate Verification' etc
50
+ PurpleRuby.watch_request do |title, primary, secondary, who|
51
+ puts "request: #{title}, #{primary}, #{secondary}, #{who}"
52
+ true #'true': accept a request; 'false': ignore a request
53
+ end
54
+
55
+ #request for authorization when someone adds this account to their buddy list
56
+ PurpleRuby.watch_new_buddy do |acc, remote_user, message|
57
+ puts "new buddy: #{acc.username} #{remote_user} #{message}"
58
+ true #'true': accept; 'false': deny
59
+ end
60
+
61
+ PurpleRuby.watch_notify_message do |type, title, primary, secondary|
62
+ puts "notification: #{type}, #{title}, #{primary}, #{secondary}"
63
+ end
64
+
65
+ #listen a tcp port, parse incoming data and send it out.
66
+ #We assume the incoming data is in the following format (separated by comma):
67
+ #<protocol>,<user>,<message>
68
+ PurpleRuby.watch_incoming_ipc(SERVER_IP, SERVER_PORT) do |data|
69
+ protocol, user, message = data.split(",").collect{|x| x.chomp.strip}
70
+ puts "send: #{protocol},#{user},#{message}"
71
+ puts accounts[protocol].send_im(user, message)
72
+ end
73
+
74
+ PurpleRuby.main_loop_run
75
+ end
76
+
77
+ def self.deliver(protocol, to_users, message)
78
+ to_users = [to_users] unless to_users.is_a?(Array)
79
+ to_users.each do |user|
80
+ t = TCPSocket.new(SERVER_IP, SERVER_PORT)
81
+ t.print "#{protocol},#{user},#{message}"
82
+ t.close
83
+ end
84
+ end
85
+ end
86
+
87
+ if ARGV.length >= 3
88
+ configs = []
89
+ configs << {:protocol => ARGV[0], :username => ARGV[1], :password => ARGV[2]}
90
+ configs << {:protocol => ARGV[3], :username => ARGV[4], :password => ARGV[5]} if ARGV.length >= 6
91
+ #add more accounts here if you like
92
+ PurpleGWExample.new.start configs
93
+ end
94
+
data/ext/account.c ADDED
@@ -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
+
data/ext/extconf.rb ADDED
@@ -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')
data/ext/purple_ruby.c ADDED
@@ -0,0 +1,799 @@
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/conversation.h>
22
+ #include <libpurple/core.h>
23
+ #include <libpurple/debug.h>
24
+ #include <libpurple/cipher.h>
25
+ #include <libpurple/eventloop.h>
26
+ #include <libpurple/ft.h>
27
+ #include <libpurple/log.h>
28
+ #include <libpurple/notify.h>
29
+ #include <libpurple/prefs.h>
30
+ #include <libpurple/prpl.h>
31
+ #include <libpurple/pounce.h>
32
+ #include <libpurple/request.h>
33
+ #include <libpurple/savedstatuses.h>
34
+ #include <libpurple/sound.h>
35
+ #include <libpurple/status.h>
36
+ #include <libpurple/util.h>
37
+ #include <libpurple/whiteboard.h>
38
+ #include <libpurple/network.h>
39
+
40
+ #include <ruby.h>
41
+ #include <errno.h>
42
+ #include <signal.h>
43
+ #include <stdarg.h>
44
+ #include <unistd.h>
45
+ #include <netinet/in.h>
46
+ #include <sys/socket.h>
47
+ #include <arpa/inet.h>
48
+ #include <fcntl.h>
49
+
50
+ #define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
51
+ #define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
52
+
53
+ typedef struct _PurpleGLibIOClosure {
54
+ PurpleInputFunction function;
55
+ guint result;
56
+ gpointer data;
57
+ } PurpleGLibIOClosure;
58
+
59
+ static void purple_glib_io_destroy(gpointer data)
60
+ {
61
+ g_free(data);
62
+ }
63
+
64
+ static gboolean purple_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
65
+ {
66
+ PurpleGLibIOClosure *closure = data;
67
+ PurpleInputCondition purple_cond = 0;
68
+
69
+ if (condition & PURPLE_GLIB_READ_COND)
70
+ purple_cond |= PURPLE_INPUT_READ;
71
+ if (condition & PURPLE_GLIB_WRITE_COND)
72
+ purple_cond |= PURPLE_INPUT_WRITE;
73
+
74
+ closure->function(closure->data, g_io_channel_unix_get_fd(source),
75
+ purple_cond);
76
+
77
+ return TRUE;
78
+ }
79
+
80
+ static guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function,
81
+ gpointer data)
82
+ {
83
+ PurpleGLibIOClosure *closure = g_new0(PurpleGLibIOClosure, 1);
84
+ GIOChannel *channel;
85
+ GIOCondition cond = 0;
86
+
87
+ closure->function = function;
88
+ closure->data = data;
89
+
90
+ if (condition & PURPLE_INPUT_READ)
91
+ cond |= PURPLE_GLIB_READ_COND;
92
+ if (condition & PURPLE_INPUT_WRITE)
93
+ cond |= PURPLE_GLIB_WRITE_COND;
94
+
95
+ channel = g_io_channel_unix_new(fd);
96
+ closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
97
+ purple_glib_io_invoke, closure, purple_glib_io_destroy);
98
+
99
+ g_io_channel_unref(channel);
100
+ return closure->result;
101
+ }
102
+
103
+ static PurpleEventLoopUiOps glib_eventloops =
104
+ {
105
+ g_timeout_add,
106
+ g_source_remove,
107
+ glib_input_add,
108
+ g_source_remove,
109
+ NULL,
110
+ #if GLIB_CHECK_VERSION(2,14,0)
111
+ g_timeout_add_seconds,
112
+ #else
113
+ NULL,
114
+ #endif
115
+
116
+ /* padding */
117
+ NULL,
118
+ NULL,
119
+ NULL
120
+ };
121
+
122
+ static VALUE cPurpleRuby;
123
+ VALUE cAccount;
124
+ const char* UI_ID = "purplegw";
125
+ static GMainLoop *main_loop = NULL;
126
+ static GHashTable* data_hash_table = NULL;
127
+ static GHashTable* fd_hash_table = NULL;
128
+ ID CALL;
129
+ extern PurpleAccountUiOps account_ops;
130
+
131
+ static VALUE im_handler = Qnil;
132
+ static VALUE signed_on_handler = Qnil;
133
+ static VALUE connection_error_handler = Qnil;
134
+ static VALUE notify_message_handler = Qnil;
135
+ static VALUE request_handler = Qnil;
136
+ static VALUE ipc_handler = Qnil;
137
+ static VALUE timer_handler = Qnil;
138
+ guint timer_timeout = 0;
139
+ VALUE new_buddy_handler = Qnil;
140
+
141
+ extern void
142
+ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
143
+ const char *text);
144
+
145
+ extern void finch_connections_init();
146
+
147
+ VALUE inspect_rb_obj(VALUE obj)
148
+ {
149
+ return rb_funcall(obj, rb_intern("inspect"), 0, 0);
150
+ }
151
+
152
+ void set_callback(VALUE* handler, const char* handler_name)
153
+ {
154
+ if (!rb_block_given_p()) {
155
+ rb_raise(rb_eArgError, "%s: no block", handler_name);
156
+ }
157
+
158
+ if (Qnil != *handler) {
159
+ rb_raise(rb_eArgError, "%s should only be assigned once", handler_name);
160
+ }
161
+
162
+ *handler = rb_block_proc();
163
+ /*
164
+ * If you create a Ruby object from C and store it in a C global variable without
165
+ * exporting it to Ruby, you must at least tell the garbage collector about it,
166
+ * lest ye be reaped inadvertently:
167
+ */
168
+ rb_global_variable(handler);
169
+
170
+ if (rb_obj_class(*handler) != rb_cProc) {
171
+ rb_raise(rb_eTypeError, "%s got unexpected value: %s", handler_name,
172
+ RSTRING(inspect_rb_obj(*handler))->ptr);
173
+ }
174
+ }
175
+
176
+ void check_callback(VALUE handler, const char* handler_name){
177
+ if (rb_obj_class(handler) != rb_cProc) {
178
+ rb_raise(rb_eTypeError, "%s has unexpected value: %s",
179
+ handler_name,
180
+ RSTRING(inspect_rb_obj(handler))->ptr);
181
+ }
182
+ }
183
+
184
+ void report_disconnect(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
185
+ {
186
+ if (Qnil != connection_error_handler) {
187
+ VALUE args[3];
188
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(gc));
189
+ args[1] = INT2FIX(reason);
190
+ args[2] = rb_str_new2(text);
191
+ check_callback(connection_error_handler, "connection_error_handler");
192
+ VALUE v = rb_funcall2(connection_error_handler, CALL, 3, args);
193
+
194
+ if (v != Qnil && v != Qfalse) {
195
+ finch_connection_report_disconnect(gc, reason, text);
196
+ }
197
+ }
198
+ }
199
+
200
+ static void* notify_message(PurpleNotifyMsgType type,
201
+ const char *title,
202
+ const char *primary,
203
+ const char *secondary)
204
+ {
205
+ if (notify_message_handler != Qnil) {
206
+ VALUE args[4];
207
+ args[0] = INT2FIX(type);
208
+ args[1] = rb_str_new2(NULL == title ? "" : title);
209
+ args[2] = rb_str_new2(NULL == primary ? "" : primary);
210
+ args[3] = rb_str_new2(NULL == secondary ? "" : secondary);
211
+ check_callback(notify_message_handler, "notify_message_handler");
212
+ rb_funcall2(notify_message_handler, CALL, 4, args);
213
+ }
214
+
215
+ return NULL;
216
+ }
217
+
218
+ static void write_conv(PurpleConversation *conv, const char *who, const char *alias,
219
+ const char *message, PurpleMessageFlags flags, time_t mtime)
220
+ {
221
+ if (im_handler != Qnil) {
222
+ PurpleAccount* account = purple_conversation_get_account(conv);
223
+ if (strcmp(purple_account_get_protocol_id(account), "prpl-msn") == 0 &&
224
+ (strstr(message, "Message could not be sent") != NULL ||
225
+ strstr(message, "Message was not sent") != NULL ||
226
+ strstr(message, "Message may have not been sent") != NULL
227
+ )
228
+ ) {
229
+ /* I have seen error like 'msn: Connection error from Switchboard server'.
230
+ * In that case, libpurple will notify user with two regular im message.
231
+ * The first message is an error message, the second one is the original message that failed to send.
232
+ */
233
+ notify_message(PURPLE_CONNECTION_ERROR_NETWORK_ERROR, message, purple_account_get_protocol_id(account), who);
234
+ } else {
235
+ VALUE args[3];
236
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
237
+ args[1] = rb_str_new2(who);
238
+ args[2] = rb_str_new2(message);
239
+ check_callback(im_handler, "im_handler");
240
+ rb_funcall2(im_handler, CALL, 3, args);
241
+ }
242
+ }
243
+ }
244
+
245
+ static PurpleConversationUiOps conv_uiops =
246
+ {
247
+ NULL, /* create_conversation */
248
+ NULL, /* destroy_conversation */
249
+ NULL, /* write_chat */
250
+ NULL, /* write_im */
251
+ write_conv, /* write_conv */
252
+ NULL, /* chat_add_users */
253
+ NULL, /* chat_rename_user */
254
+ NULL, /* chat_remove_users */
255
+ NULL, /* chat_update_user */
256
+ NULL, /* present */
257
+ NULL, /* has_focus */
258
+ NULL, /* custom_smiley_add */
259
+ NULL, /* custom_smiley_write */
260
+ NULL, /* custom_smiley_close */
261
+ NULL, /* send_confirm */
262
+ NULL,
263
+ NULL,
264
+ NULL,
265
+ NULL
266
+ };
267
+
268
+ static PurpleConnectionUiOps connection_ops =
269
+ {
270
+ NULL, /* connect_progress */
271
+ NULL, /* connected */
272
+ NULL, /* disconnected */
273
+ NULL, /* notice */
274
+ NULL,
275
+ NULL, /* network_connected */
276
+ NULL, /* network_disconnected */
277
+ report_disconnect,
278
+ NULL,
279
+ NULL,
280
+ NULL
281
+ };
282
+
283
+ static void* request_action(const char *title, const char *primary, const char *secondary,
284
+ int default_action,
285
+ PurpleAccount *account,
286
+ const char *who,
287
+ PurpleConversation *conv,
288
+ void *user_data,
289
+ size_t action_count,
290
+ va_list actions)
291
+ {
292
+ if (request_handler != Qnil) {
293
+ VALUE args[4];
294
+ args[0] = rb_str_new2(NULL == title ? "" : title);
295
+ args[1] = rb_str_new2(NULL == primary ? "" : primary);
296
+ args[2] = rb_str_new2(NULL == secondary ? "" : secondary);
297
+ args[3] = rb_str_new2(NULL == who ? "" : who);
298
+ check_callback(request_handler, "request_handler");
299
+ VALUE v = rb_funcall2(request_handler, CALL, 4, args);
300
+
301
+ if (v != Qnil && v != Qfalse) {
302
+ /*const char *text =*/ va_arg(actions, const char *);
303
+ GCallback ok_cb = va_arg(actions, GCallback);
304
+ ((PurpleRequestActionCb)ok_cb)(user_data, default_action);
305
+ }
306
+ }
307
+
308
+ return NULL;
309
+ }
310
+
311
+ static PurpleRequestUiOps request_ops =
312
+ {
313
+ NULL, /*request_input*/
314
+ NULL, /*request_choice*/
315
+ request_action, /*request_action*/
316
+ NULL, /*request_fields*/
317
+ NULL, /*request_file*/
318
+ NULL, /*close_request*/
319
+ NULL, /*request_folder*/
320
+ NULL,
321
+ NULL,
322
+ NULL,
323
+ NULL
324
+ };
325
+
326
+ static PurpleNotifyUiOps notify_ops =
327
+ {
328
+ notify_message, /*notify_message*/
329
+ NULL, /*notify_email*/
330
+ NULL, /*notify_emails*/
331
+ NULL, /*notify_formatted*/
332
+ NULL, /*notify_searchresults*/
333
+ NULL, /*notify_searchresults_new_rows*/
334
+ NULL, /*notify_userinfo*/
335
+ NULL, /*notify_uri*/
336
+ NULL, /*close_notify*/
337
+ NULL,
338
+ NULL,
339
+ NULL,
340
+ NULL,
341
+ };
342
+
343
+ static PurpleCoreUiOps core_uiops =
344
+ {
345
+ NULL,
346
+ NULL,
347
+ NULL,
348
+ NULL,
349
+
350
+ /* padding */
351
+ NULL,
352
+ NULL,
353
+ NULL,
354
+ NULL
355
+ };
356
+
357
+ //I have tried to detect Ctrl-C using ruby's trap method,
358
+ //but it does not work as expected: it can not detect Ctrl-C
359
+ //until a network event occurs
360
+ static void sighandler(int sig)
361
+ {
362
+ switch (sig) {
363
+ case SIGINT:
364
+ g_main_loop_quit(main_loop);
365
+ break;
366
+ }
367
+ }
368
+
369
+ static VALUE init(VALUE self, VALUE debug, VALUE path)
370
+ {
371
+ signal(SIGCHLD, SIG_IGN);
372
+ signal(SIGPIPE, SIG_IGN);
373
+ signal(SIGINT, sighandler);
374
+
375
+ data_hash_table = g_hash_table_new(NULL, NULL);
376
+ fd_hash_table = g_hash_table_new(NULL, NULL);
377
+
378
+ purple_debug_set_enabled((debug == Qnil || debug == Qfalse) ? FALSE : TRUE);
379
+
380
+ if (path != Qnil) {
381
+ purple_util_set_user_dir(RSTRING(path)->ptr);
382
+ }
383
+
384
+ purple_core_set_ui_ops(&core_uiops);
385
+ purple_eventloop_set_ui_ops(&glib_eventloops);
386
+
387
+ if (!purple_core_init(UI_ID)) {
388
+ rb_raise(rb_eRuntimeError, "libpurple initialization failed");
389
+ }
390
+
391
+ /* Create and load the buddylist. */
392
+ purple_set_blist(purple_blist_new());
393
+ purple_blist_load();
394
+
395
+ /* Load the preferences. */
396
+ purple_prefs_load();
397
+
398
+ /* Load the pounces. */
399
+ purple_pounces_load();
400
+
401
+ return Qnil;
402
+ }
403
+
404
+ static VALUE watch_incoming_im(VALUE self)
405
+ {
406
+ purple_conversations_set_ui_ops(&conv_uiops);
407
+ set_callback(&im_handler, "im_handler");
408
+ return im_handler;
409
+ }
410
+
411
+ static VALUE watch_notify_message(VALUE self)
412
+ {
413
+ purple_notify_set_ui_ops(&notify_ops);
414
+ set_callback(&notify_message_handler, "notify_message_handler");
415
+ return notify_message_handler;
416
+ }
417
+
418
+ static VALUE watch_request(VALUE self)
419
+ {
420
+ purple_request_set_ui_ops(&request_ops);
421
+ set_callback(&request_handler, "request_handler");
422
+ return request_handler;
423
+ }
424
+
425
+ static VALUE watch_new_buddy(VALUE self)
426
+ {
427
+ purple_accounts_set_ui_ops(&account_ops);
428
+ set_callback(&new_buddy_handler, "new_buddy_handler");
429
+ return new_buddy_handler;
430
+ }
431
+
432
+ static void signed_on(PurpleConnection* connection)
433
+ {
434
+ VALUE args[1];
435
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(connection));
436
+ check_callback(signed_on_handler, "signed_on_handler");
437
+ rb_funcall2(signed_on_handler, CALL, 1, args);
438
+ }
439
+
440
+ static VALUE watch_signed_on_event(VALUE self)
441
+ {
442
+ set_callback(&signed_on_handler, "signed_on_handler");
443
+ int handle;
444
+ purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
445
+ PURPLE_CALLBACK(signed_on), NULL);
446
+ return signed_on_handler;
447
+ }
448
+
449
+ static VALUE watch_connection_error(VALUE self)
450
+ {
451
+ finch_connections_init();
452
+ purple_connections_set_ui_ops(&connection_ops);
453
+
454
+ set_callback(&connection_error_handler, "connection_error_handler");
455
+
456
+ /*int handle;
457
+ purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
458
+ PURPLE_CALLBACK(connection_error), NULL);*/
459
+ return connection_error_handler;
460
+ }
461
+
462
+ static void _read_socket_handler(gpointer notused, int socket, PurpleInputCondition condition)
463
+ {
464
+ char message[4096] = {0};
465
+ int i = recv(socket, message, sizeof(message) - 1, 0);
466
+ if (i > 0) {
467
+ purple_debug_info("purple_ruby", "recv %d: %d\n", socket, i);
468
+
469
+ gpointer str = g_hash_table_lookup(data_hash_table, (gpointer)socket);
470
+ if (NULL == str) rb_raise(rb_eRuntimeError, "can not find socket: %d", socket);
471
+ rb_str_append((VALUE)str, rb_str_new2(message));
472
+ } else {
473
+ purple_debug_info("purple_ruby", "close connection %d: %d %d\n", socket, i, errno);
474
+
475
+ gpointer str = g_hash_table_lookup(data_hash_table, (gpointer)socket);
476
+ if (NULL == str) {
477
+ purple_debug_warning("purple_ruby", "can not find socket in data_hash_table %d\n", socket);
478
+ return;
479
+ }
480
+
481
+ gpointer purple_fd = g_hash_table_lookup(fd_hash_table, (gpointer)socket);
482
+ if (NULL == purple_fd) {
483
+ purple_debug_warning("purple_ruby", "can not find socket in fd_hash_table %d\n", socket);
484
+ return;
485
+ }
486
+
487
+ g_hash_table_remove(fd_hash_table, (gpointer)socket);
488
+ g_hash_table_remove(data_hash_table, (gpointer)socket);
489
+ purple_input_remove((guint)purple_fd);
490
+ close(socket);
491
+
492
+ VALUE args[1];
493
+ args[0] = (VALUE)str;
494
+ check_callback(ipc_handler, "ipc_handler");
495
+ rb_funcall2(ipc_handler, CALL, 1, args);
496
+ }
497
+ }
498
+
499
+ static void _accept_socket_handler(gpointer notused, int server_socket, PurpleInputCondition condition)
500
+ {
501
+ /* Check that it is a read condition */
502
+ if (condition != PURPLE_INPUT_READ)
503
+ return;
504
+
505
+ struct sockaddr_in their_addr; /* connector's address information */
506
+ socklen_t sin_size = sizeof(struct sockaddr);
507
+ int client_socket;
508
+ if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
509
+ purple_debug_warning("purple_ruby", "failed to accept %d: %d\n", client_socket, errno);
510
+ return;
511
+ }
512
+
513
+ int flags = fcntl(client_socket, F_GETFL);
514
+ fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
515
+ #ifndef _WIN32
516
+ fcntl(client_socket, F_SETFD, FD_CLOEXEC);
517
+ #endif
518
+
519
+ purple_debug_info("purple_ruby", "new connection: %d\n", client_socket);
520
+
521
+ guint purple_fd = purple_input_add(client_socket, PURPLE_INPUT_READ, _read_socket_handler, NULL);
522
+
523
+ g_hash_table_insert(data_hash_table, (gpointer)client_socket, (gpointer)rb_str_new2(""));
524
+ g_hash_table_insert(fd_hash_table, (gpointer)client_socket, (gpointer)purple_fd);
525
+ }
526
+
527
+ static VALUE watch_incoming_ipc(VALUE self, VALUE serverip, VALUE port)
528
+ {
529
+ struct sockaddr_in my_addr;
530
+ int soc;
531
+ int on = 1;
532
+
533
+ /* Open a listening socket for incoming conversations */
534
+ if ((soc = socket(PF_INET, SOCK_STREAM, 0)) < 0)
535
+ {
536
+ rb_raise(rb_eRuntimeError, "Cannot open socket: %s\n", g_strerror(errno));
537
+ return Qnil;
538
+ }
539
+
540
+ if (setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
541
+ {
542
+ rb_raise(rb_eRuntimeError, "SO_REUSEADDR failed: %s\n", g_strerror(errno));
543
+ return Qnil;
544
+ }
545
+
546
+ memset(&my_addr, 0, sizeof(struct sockaddr_in));
547
+ my_addr.sin_family = AF_INET;
548
+ my_addr.sin_addr.s_addr = inet_addr(RSTRING(serverip)->ptr);
549
+ my_addr.sin_port = htons(FIX2INT(port));
550
+ if (bind(soc, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0)
551
+ {
552
+ rb_raise(rb_eRuntimeError, "Unable to bind to port %d: %s\n", (int)FIX2INT(port), g_strerror(errno));
553
+ return Qnil;
554
+ }
555
+
556
+ /* Attempt to listen on the bound socket */
557
+ if (listen(soc, 10) != 0)
558
+ {
559
+ rb_raise(rb_eRuntimeError, "Cannot listen on socket: %s\n", g_strerror(errno));
560
+ return Qnil;
561
+ }
562
+
563
+ set_callback(&ipc_handler, "ipc_handler");
564
+
565
+ /* Open a watcher in the socket we have just opened */
566
+ purple_input_add(soc, PURPLE_INPUT_READ, _accept_socket_handler, NULL);
567
+
568
+ return port;
569
+ }
570
+
571
+ static gboolean
572
+ do_timeout(gpointer data)
573
+ {
574
+ VALUE handler = data;
575
+ check_callback(handler, "timer_handler");
576
+ VALUE v = rb_funcall(handler, CALL, 0, 0);
577
+ return (v == Qtrue);
578
+ }
579
+
580
+ static VALUE watch_timer(VALUE self, VALUE delay)
581
+ {
582
+ set_callback(&timer_handler, "timer_handler");
583
+ if (timer_timeout != 0)
584
+ g_source_remove(timer_timeout);
585
+ timer_timeout = g_timeout_add(delay, do_timeout, timer_handler);
586
+ return delay;
587
+ }
588
+
589
+ static VALUE login(VALUE self, VALUE protocol, VALUE username, VALUE password)
590
+ {
591
+ PurpleAccount* account = purple_account_new(RSTRING(username)->ptr, RSTRING(protocol)->ptr);
592
+ if (NULL == account || NULL == account->presence) {
593
+ rb_raise(rb_eRuntimeError, "No able to create account: %s", RSTRING(protocol)->ptr);
594
+ }
595
+ purple_account_set_password(account, RSTRING(password)->ptr);
596
+ purple_account_set_remember_password(account, TRUE);
597
+ purple_account_set_enabled(account, UI_ID, TRUE);
598
+ PurpleSavedStatus *status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
599
+ purple_savedstatus_activate(status);
600
+
601
+ return Data_Wrap_Struct(cAccount, NULL, NULL, account);
602
+ }
603
+
604
+ static VALUE main_loop_run(VALUE self)
605
+ {
606
+ main_loop = g_main_loop_new(NULL, FALSE);
607
+ g_main_loop_run(main_loop);
608
+
609
+ #ifdef DEBUG_MEM_LEAK
610
+ printf("QUIT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
611
+ purple_core_quit();
612
+ if (im_handler == Qnil) rb_gc_unregister_address(&im_handler);
613
+ if (signed_on_handler == Qnil) rb_gc_unregister_address(&signed_on_handler);
614
+ if (connection_error_handler == Qnil) rb_gc_unregister_address(&connection_error_handler);
615
+ if (notify_message_handler == Qnil) rb_gc_unregister_address(&notify_message_handler);
616
+ if (request_handler == Qnil) rb_gc_unregister_address(&request_handler);
617
+ if (ipc_handler == Qnil) rb_gc_unregister_address(&ipc_handler);
618
+ if (timer_timeout != 0) g_source_remove(timer_timeout);
619
+ if (timer_handler == Qnil) rb_gc_unregister_address(&timer_handler);
620
+ if (new_buddy_handler == Qnil) rb_gc_unregister_address(&new_buddy_handler);
621
+ rb_gc_start();
622
+ #endif
623
+
624
+ return Qnil;
625
+ }
626
+
627
+ static VALUE main_loop_stop(VALUE self)
628
+ {
629
+ g_main_loop_quit(main_loop);
630
+ return Qnil;
631
+ }
632
+
633
+ static VALUE send_im(VALUE self, VALUE name, VALUE message)
634
+ {
635
+ PurpleAccount *account;
636
+ Data_Get_Struct(self, PurpleAccount, account);
637
+
638
+ if (purple_account_is_connected(account)) {
639
+ int i = serv_send_im(purple_account_get_connection(account), RSTRING(name)->ptr, RSTRING(message)->ptr, 0);
640
+ return INT2FIX(i);
641
+ } else {
642
+ return Qnil;
643
+ }
644
+ }
645
+
646
+ static VALUE username(VALUE self)
647
+ {
648
+ PurpleAccount *account;
649
+ Data_Get_Struct(self, PurpleAccount, account);
650
+ return rb_str_new2(purple_account_get_username(account));
651
+ }
652
+
653
+ static VALUE protocol_id(VALUE self)
654
+ {
655
+ PurpleAccount *account;
656
+ Data_Get_Struct(self, PurpleAccount, account);
657
+ return rb_str_new2(purple_account_get_protocol_id(account));
658
+ }
659
+
660
+ static VALUE protocol_name(VALUE self)
661
+ {
662
+ PurpleAccount *account;
663
+ Data_Get_Struct(self, PurpleAccount, account);
664
+ return rb_str_new2(purple_account_get_protocol_name(account));
665
+ }
666
+
667
+ static VALUE get_bool_setting(VALUE self, VALUE name, VALUE default_value)
668
+ {
669
+ PurpleAccount *account;
670
+ Data_Get_Struct(self, PurpleAccount, account);
671
+ gboolean value = purple_account_get_bool(account, RSTRING(name)->ptr,
672
+ (default_value == Qfalse || default_value == Qnil) ? FALSE : TRUE);
673
+ return (TRUE == value) ? Qtrue : Qfalse;
674
+ }
675
+
676
+ static VALUE get_string_setting(VALUE self, VALUE name, VALUE default_value)
677
+ {
678
+ PurpleAccount *account;
679
+ Data_Get_Struct(self, PurpleAccount, account);
680
+ const char* value = purple_account_get_string(account, RSTRING(name)->ptr, RSTRING(default_value)->ptr);
681
+ return (NULL == value) ? Qnil : rb_str_new2(value);
682
+ }
683
+
684
+ static VALUE list_protocols(VALUE self)
685
+ {
686
+ VALUE array = rb_ary_new();
687
+
688
+ GList *iter = purple_plugins_get_protocols();
689
+ int i;
690
+ for (i = 0; iter; iter = iter->next) {
691
+ PurplePlugin *plugin = iter->data;
692
+ PurplePluginInfo *info = plugin->info;
693
+ if (info && info->name) {
694
+ char s[256];
695
+ snprintf(s, sizeof(s) -1, "%s %s", info->id, info->name);
696
+ rb_ary_push(array, rb_str_new2(s));
697
+ }
698
+ }
699
+
700
+ return array;
701
+ }
702
+
703
+ static VALUE add_buddy(VALUE self, VALUE buddy)
704
+ {
705
+ PurpleAccount *account;
706
+ Data_Get_Struct(self, PurpleAccount, account);
707
+
708
+ PurpleBuddy* pb = purple_buddy_new(account, RSTRING(buddy)->ptr, NULL);
709
+
710
+ char* group = _("Buddies");
711
+ PurpleGroup* grp = purple_find_group(group);
712
+ if (!grp)
713
+ {
714
+ grp = purple_group_new(group);
715
+ purple_blist_add_group(grp, NULL);
716
+ }
717
+
718
+ purple_blist_add_buddy(pb, NULL, grp, NULL);
719
+ purple_account_add_buddy(account, pb);
720
+ return Qtrue;
721
+ }
722
+
723
+ static VALUE remove_buddy(VALUE self, VALUE buddy)
724
+ {
725
+ PurpleAccount *account;
726
+ Data_Get_Struct(self, PurpleAccount, account);
727
+
728
+ PurpleBuddy* pb = purple_find_buddy(account, RSTRING(buddy)->ptr);
729
+ if (NULL == pb) {
730
+ rb_raise(rb_eRuntimeError, "Failed to remove buddy for %s : %s does not exist", purple_account_get_username(account), RSTRING(buddy)->ptr);
731
+ }
732
+
733
+ char* group = _("Buddies");
734
+ PurpleGroup* grp = purple_find_group(group);
735
+ if (!grp)
736
+ {
737
+ grp = purple_group_new(group);
738
+ purple_blist_add_group(grp, NULL);
739
+ }
740
+
741
+ purple_blist_remove_buddy(pb);
742
+ purple_account_remove_buddy(account, pb, grp);
743
+ return Qtrue;
744
+ }
745
+
746
+ static VALUE has_buddy(VALUE self, VALUE buddy)
747
+ {
748
+ PurpleAccount *account;
749
+ Data_Get_Struct(self, PurpleAccount, account);
750
+ if (purple_find_buddy(account, RSTRING(buddy)->ptr) != NULL) {
751
+ return Qtrue;
752
+ } else {
753
+ return Qfalse;
754
+ }
755
+ }
756
+
757
+ static VALUE acc_delete(VALUE self)
758
+ {
759
+ PurpleAccount *account;
760
+ Data_Get_Struct(self, PurpleAccount, account);
761
+ purple_accounts_delete(account);
762
+ return Qnil;
763
+ }
764
+
765
+ void Init_purple_ruby()
766
+ {
767
+ CALL = rb_intern("call");
768
+
769
+ cPurpleRuby = rb_define_class("PurpleRuby", rb_cObject);
770
+ rb_define_singleton_method(cPurpleRuby, "init", init, 1);
771
+ rb_define_singleton_method(cPurpleRuby, "list_protocols", list_protocols, 0);
772
+ rb_define_singleton_method(cPurpleRuby, "watch_signed_on_event", watch_signed_on_event, 0);
773
+ rb_define_singleton_method(cPurpleRuby, "watch_connection_error", watch_connection_error, 0);
774
+ rb_define_singleton_method(cPurpleRuby, "watch_incoming_im", watch_incoming_im, 0);
775
+ rb_define_singleton_method(cPurpleRuby, "watch_notify_message", watch_notify_message, 0);
776
+ rb_define_singleton_method(cPurpleRuby, "watch_request", watch_request, 0);
777
+ rb_define_singleton_method(cPurpleRuby, "watch_new_buddy", watch_new_buddy, 0);
778
+ rb_define_singleton_method(cPurpleRuby, "watch_incoming_ipc", watch_incoming_ipc, 2);
779
+ rb_define_singleton_method(cPurpleRuby, "watch_timer", watch_timer, 1);
780
+ rb_define_singleton_method(cPurpleRuby, "login", login, 3);
781
+ rb_define_singleton_method(cPurpleRuby, "main_loop_run", main_loop_run, 0);
782
+ rb_define_singleton_method(cPurpleRuby, "main_loop_stop", main_loop_stop, 0);
783
+
784
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_ERROR", INT2NUM(PURPLE_NOTIFY_MSG_ERROR));
785
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_WARNING", INT2NUM(PURPLE_NOTIFY_MSG_WARNING));
786
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_INFO", INT2NUM(PURPLE_NOTIFY_MSG_INFO));
787
+
788
+ cAccount = rb_define_class_under(cPurpleRuby, "Account", rb_cObject);
789
+ rb_define_method(cAccount, "send_im", send_im, 2);
790
+ rb_define_method(cAccount, "username", username, 0);
791
+ rb_define_method(cAccount, "protocol_id", protocol_id, 0);
792
+ rb_define_method(cAccount, "protocol_name", protocol_name, 0);
793
+ rb_define_method(cAccount, "get_bool_setting", get_bool_setting, 2);
794
+ rb_define_method(cAccount, "get_string_setting", get_string_setting, 2);
795
+ rb_define_method(cAccount, "add_buddy", add_buddy, 1);
796
+ rb_define_method(cAccount, "remove_buddy", remove_buddy, 1);
797
+ rb_define_method(cAccount, "has_buddy?", has_buddy, 1);
798
+ rb_define_method(cAccount, "delete", acc_delete, 0);
799
+ }
data/ext/reconnect.c ADDED
@@ -0,0 +1,166 @@
1
+ /*
2
+ * adopted from finch's gntconn.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
+ extern const char* UI_ID;
47
+
48
+ #define INITIAL_RECON_DELAY_MIN 8000
49
+ #define INITIAL_RECON_DELAY_MAX 60000
50
+
51
+ #define MAX_RECON_DELAY 600000
52
+
53
+ typedef struct {
54
+ int delay;
55
+ guint timeout;
56
+ } FinchAutoRecon;
57
+
58
+ /**
59
+ * Contains accounts that are auto-reconnecting.
60
+ * The key is a pointer to the PurpleAccount and the
61
+ * value is a pointer to a FinchAutoRecon.
62
+ */
63
+ static GHashTable *hash = NULL;
64
+
65
+ static void
66
+ free_auto_recon(gpointer data)
67
+ {
68
+ FinchAutoRecon *info = data;
69
+
70
+ if (info->timeout != 0)
71
+ g_source_remove(info->timeout);
72
+
73
+ g_free(info);
74
+ }
75
+
76
+ static gboolean
77
+ do_signon(gpointer data)
78
+ {
79
+ PurpleAccount *account = data;
80
+ FinchAutoRecon *info;
81
+ PurpleStatus *status;
82
+
83
+ purple_debug_info("autorecon", "do_signon called\n");
84
+ g_return_val_if_fail(account != NULL, FALSE);
85
+ info = g_hash_table_lookup(hash, account);
86
+
87
+ if (info)
88
+ info->timeout = 0;
89
+
90
+ status = purple_account_get_active_status(account);
91
+ if (purple_status_is_online(status))
92
+ {
93
+ purple_debug_info("autorecon", "calling purple_account_connect\n");
94
+ purple_account_connect(account);
95
+ purple_debug_info("autorecon", "done calling purple_account_connect\n");
96
+ }
97
+
98
+ return FALSE;
99
+ }
100
+
101
+ static gboolean
102
+ enable_account(gpointer data)
103
+ {
104
+ PurpleAccount *account = data;
105
+ FinchAutoRecon *info;
106
+
107
+ purple_debug_info("autorecon", "enable_account called\n");
108
+ g_return_val_if_fail(account != NULL, FALSE);
109
+ info = g_hash_table_lookup(hash, account);
110
+
111
+ if (info)
112
+ info->timeout = 0;
113
+
114
+ purple_account_set_enabled(account, UI_ID, TRUE);
115
+
116
+ return FALSE;
117
+ }
118
+
119
+ void
120
+ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
121
+ const char *text)
122
+ {
123
+ PurpleAccount *account = purple_connection_get_account(gc);
124
+ FinchAutoRecon *info = g_hash_table_lookup(hash, account);
125
+
126
+ if (info == NULL) {
127
+ info = g_new0(FinchAutoRecon, 1);
128
+ g_hash_table_insert(hash, account, info);
129
+ info->delay = g_random_int_range(INITIAL_RECON_DELAY_MIN, INITIAL_RECON_DELAY_MAX);
130
+ } else {
131
+ info->delay = MIN(2 * info->delay, MAX_RECON_DELAY);
132
+ if (info->timeout != 0)
133
+ g_source_remove(info->timeout);
134
+ }
135
+
136
+ if (!purple_connection_error_is_fatal(reason)) {
137
+ info->timeout = g_timeout_add(info->delay, do_signon, account);
138
+ } else {
139
+ info->timeout = g_timeout_add(info->delay, enable_account, account);
140
+ }
141
+ }
142
+
143
+ static void
144
+ account_removed_cb(PurpleAccount *account, gpointer user_data)
145
+ {
146
+ g_hash_table_remove(hash, account);
147
+ }
148
+
149
+ static void *
150
+ finch_connection_get_handle(void)
151
+ {
152
+ static int handle;
153
+
154
+ return &handle;
155
+ }
156
+
157
+ void finch_connections_init()
158
+ {
159
+ hash = g_hash_table_new_full(
160
+ g_direct_hash, g_direct_equal,
161
+ NULL, free_auto_recon);
162
+
163
+ purple_signal_connect(purple_accounts_get_handle(), "account-removed",
164
+ finch_connection_get_handle(),
165
+ PURPLE_CALLBACK(account_removed_cb), NULL);
166
+ }
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: purple_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.2
5
+ platform: ruby
6
+ authors:
7
+ - yong
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-01 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ version:
25
+ description: A ruby gem to write server that sends and recives IM messages
26
+ email: yong@intridea.com
27
+ executables: []
28
+
29
+ extensions:
30
+ - ext/extconf.rb
31
+ extra_rdoc_files:
32
+ - Manifest.txt
33
+ - History.txt
34
+ - README.txt
35
+ files:
36
+ - ext/extconf.rb
37
+ - ext/purple_ruby.c
38
+ - ext/reconnect.c
39
+ - ext/account.c
40
+ - examples/purplegw_example.rb
41
+ - Manifest.txt
42
+ - History.txt
43
+ - README.txt
44
+ - Rakefile
45
+ has_rdoc: true
46
+ homepage: http://www.intridea.com
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README.txt
53
+ require_paths:
54
+ - ext
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: purplegw_ruby
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: A ruby gem to write server that sends and recives IM messages
74
+ test_files: []
75
+