purple_ruby 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -0
- data/Manifest.txt +9 -0
- data/README.txt +53 -0
- data/Rakefile +11 -0
- data/examples/purplegw_example.rb +94 -0
- data/ext/account.c +162 -0
- data/ext/extconf.rb +6 -0
- data/ext/purple_ruby.c +799 -0
- data/ext/reconnect.c +166 -0
- metadata +75 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
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,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
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(¬ify_ops);
|
414
|
+
set_callback(¬ify_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(¬ify_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
|
+
|