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