tunecore-announcer 0.0.1
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/.idea/encodings.xml +5 -0
- data/.idea/inspectionProfiles/Project_Default.xml +6 -0
- data/.idea/inspectionProfiles/profiles_settings.xml +8 -0
- data/.idea/misc.xml +48 -0
- data/.idea/modules.xml +9 -0
- data/.idea/tunecore-announcer.iml +12 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +316 -0
- data/History.txt +4 -0
- data/Manifest.txt +24 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +48 -0
- data/Rakefile +28 -0
- data/bin/announcer +79 -0
- data/lib/tunecore-announcer/logger.rb +14 -0
- data/lib/tunecore-announcer/utils.rb +14 -0
- data/lib/tunecore-announcer/xmpp_connection.rb +163 -0
- data/lib/tunecore-announcer.rb +14 -0
- data/lib/xmpp4r-simple/xmpp4r-simple.rb +528 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_helper.rb +3 -0
- data/test/test_tunecore-announcer.rb +11 -0
- data.tar.gz.sig +0 -0
- metadata +122 -0
- metadata.gz.sig +3 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'xmpp4r'
|
|
3
|
+
require 'xmpp4r/vcard'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TunecoreAnnouncer::XmppConnection
|
|
7
|
+
include Singleton
|
|
8
|
+
|
|
9
|
+
attr_reader :server
|
|
10
|
+
attr_reader :name
|
|
11
|
+
attr_reader :port
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@logger = TunecoreAnnouncer::Logger.instance
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def connect(opt)
|
|
18
|
+
@options = opt
|
|
19
|
+
@server = opt.jabber_server
|
|
20
|
+
@name = "#{opt.name_prefix}-#{TunecoreAnnouncer::Utils.random_word(2)}"
|
|
21
|
+
@password = opt.jabber_password
|
|
22
|
+
admin_jids = opt.admin_jids
|
|
23
|
+
|
|
24
|
+
register
|
|
25
|
+
@logger.info "Connected to XMPP server"
|
|
26
|
+
#@jabber.add(admin_jids)
|
|
27
|
+
add_admins
|
|
28
|
+
#@logger.debug "Added #{admin_jid} to roster"
|
|
29
|
+
@logger.debug "Launching listener"
|
|
30
|
+
admins(opt.hello_mesg) if opt.hello_mesg
|
|
31
|
+
set_vcard
|
|
32
|
+
listen
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def disconnect
|
|
37
|
+
admins "Shutting down ... Sayonara!"
|
|
38
|
+
deregister
|
|
39
|
+
@jabber.disconnect
|
|
40
|
+
@listener_thread.join
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def admins(mesg)
|
|
44
|
+
@options.admin_jids.each do |admin_jid|
|
|
45
|
+
@jabber.deliver admin_jid, mesg
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def status(presence, mesg)
|
|
50
|
+
pres = Jabber::Presence::new
|
|
51
|
+
set_avatar(pres) unless @options.avatar_file.nil?
|
|
52
|
+
pres.set_status mesg
|
|
53
|
+
@logger.debug "Setting presence: #{pres.to_s}, #{mesg}"
|
|
54
|
+
@jabber.send!(pres)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def register
|
|
61
|
+
connection_string = "#{@name}@#{@server}"
|
|
62
|
+
@logger.debug "Registering #{connection_string}"
|
|
63
|
+
begin
|
|
64
|
+
@jabber = Jabber::Simple.register(connection_string, @password)
|
|
65
|
+
rescue => e
|
|
66
|
+
@logger.fatal "XMPP connection refused: #{e.message}\n#{e.backtrace}"
|
|
67
|
+
puts "XMPP connection refused: #{e.inspect}\n#{e.backtrace}"
|
|
68
|
+
exit
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def deregister
|
|
73
|
+
@logger.debug "Removing registration"
|
|
74
|
+
client = @jabber.client
|
|
75
|
+
client.remove_registration
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def listen
|
|
80
|
+
@listener_thread = Thread.new{
|
|
81
|
+
@logger.debug "Listening for XMPP messages (connected=#{@jabber.connected?})"
|
|
82
|
+
while @jabber.connected? do
|
|
83
|
+
#@logger.debug "Polling for Jabber mesg"
|
|
84
|
+
@jabber.received_messages.each do |message|
|
|
85
|
+
process_incoming message
|
|
86
|
+
sleep 1
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
@listener_thread.run
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def process_incoming(message)
|
|
94
|
+
@logger.debug "Jabber msg from #{message.from}: #{message.body}"
|
|
95
|
+
case message.body
|
|
96
|
+
when "uptime"
|
|
97
|
+
@jabber.deliver(message.from, `uptime`)
|
|
98
|
+
when "ps"
|
|
99
|
+
@jabber.deliver(message.from, `ps -ef`)
|
|
100
|
+
when "hostname"
|
|
101
|
+
@jabber.deliver(message.from, `hostname`)
|
|
102
|
+
else
|
|
103
|
+
@jabber.deliver(message.from, "Sorry, I don't know #{message.body}")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def set_vcard
|
|
108
|
+
# set vcard info
|
|
109
|
+
@avatar_sha1 = nil # this gets used later on
|
|
110
|
+
vcard = Jabber::Vcard::IqVcard.new
|
|
111
|
+
vcard["FN"] = @name # full name
|
|
112
|
+
vcard["NICKNAME"] = @name # nickname
|
|
113
|
+
# buddy icon stuff
|
|
114
|
+
unless @options.avatar_file.nil?
|
|
115
|
+
vcard["PHOTO/TYPE"] = "image/png"
|
|
116
|
+
# open buddy icon/avatar image file
|
|
117
|
+
image_file = File.new(@options.avatar_file, "r")
|
|
118
|
+
# Base64 encode the file contents
|
|
119
|
+
image_b64 = Base64.b64encode(image_file.read())
|
|
120
|
+
# process sha1 hash of photo contents
|
|
121
|
+
# this is used by the presence setting
|
|
122
|
+
image_file.rewind # must rewind the file to the beginning
|
|
123
|
+
@avatar_sha1 = Digest::SHA1.hexdigest(image_file.read())
|
|
124
|
+
vcard["PHOTO/BINVAL"] = image_b64 # set the BINVAL to the Base64 encoded contents of our image
|
|
125
|
+
end
|
|
126
|
+
begin
|
|
127
|
+
# create a vcard helper and immediately set the vcard info
|
|
128
|
+
@logger.info "Setting Vcard"
|
|
129
|
+
vcard_helper = Jabber::Vcard::Helper.new(@jabber.client).set(vcard)
|
|
130
|
+
rescue
|
|
131
|
+
@logger.error "Vcard operation timed out."
|
|
132
|
+
end
|
|
133
|
+
return true
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def set_avatar(pres)
|
|
137
|
+
if not @avatar_sha1.nil?
|
|
138
|
+
# send the sha1 hash of the avatar to the server
|
|
139
|
+
# as per the RFC http://www.xmpp.org/extensions/xep-0153.html
|
|
140
|
+
x = REXML::Element::new("x")
|
|
141
|
+
x.add_namespace('vcard-temp:x:update')
|
|
142
|
+
photo = REXML::Element::new("photo")
|
|
143
|
+
# this is the avatar hash as computed in the vcard thread above
|
|
144
|
+
avatar_hash = REXML::Text.new(@avatar_sha1)
|
|
145
|
+
# add text to photo
|
|
146
|
+
photo.add(avatar_hash)
|
|
147
|
+
# add photo to x
|
|
148
|
+
x.add(photo)
|
|
149
|
+
# add x to presence
|
|
150
|
+
pres.add_element(x)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def add_admins
|
|
155
|
+
@options.admin_jids.each do |jid|
|
|
156
|
+
@jabber.add jid
|
|
157
|
+
@logger.debug "Added admin #{jid}"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
|
3
|
+
|
|
4
|
+
module TunecoreAnnouncer
|
|
5
|
+
VERSION = '0.0.1'
|
|
6
|
+
|
|
7
|
+
require "optparse"
|
|
8
|
+
require 'ostruct'
|
|
9
|
+
require 'rubygems'
|
|
10
|
+
require 'xmpp4r-simple/xmpp4r-simple'
|
|
11
|
+
require 'tunecore-announcer/utils'
|
|
12
|
+
require 'tunecore-announcer/logger'
|
|
13
|
+
require 'tunecore-announcer/xmpp_connection'
|
|
14
|
+
end
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# Jabber::Simple - An extremely easy-to-use Jabber client library.
|
|
2
|
+
# Copyright 2006 Blaine Cook <blaine@obvious.com>, Obvious Corp.
|
|
3
|
+
#
|
|
4
|
+
# Jabber::Simple 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
|
+
# Jabber::Simple 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 Jabber::Simple; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
require 'rubygems'
|
|
19
|
+
require 'xmpp4r'
|
|
20
|
+
require 'xmpp4r/roster'
|
|
21
|
+
require 'xmpp4r/vcard'
|
|
22
|
+
|
|
23
|
+
module Jabber
|
|
24
|
+
|
|
25
|
+
class ConnectionError < StandardError #:nodoc:
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class RegistrationError < StandardError #:nodoc:
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Contact #:nodoc:
|
|
32
|
+
|
|
33
|
+
include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
|
|
34
|
+
|
|
35
|
+
def initialize(client, jid)
|
|
36
|
+
@jid = jid.respond_to?(:resource) ? jid : JID.new(jid)
|
|
37
|
+
@client = client
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inspect
|
|
41
|
+
"Jabber::Contact #{jid.to_s}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def subscribed?
|
|
45
|
+
[:to, :both].include?(subscription)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def subscription
|
|
49
|
+
roster_item && roster_item.subscription
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def ask_for_authorization!
|
|
53
|
+
subscription_request = Presence.new.set_type(:subscribe)
|
|
54
|
+
subscription_request.to = jid
|
|
55
|
+
client.send!(subscription_request)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def unsubscribe!
|
|
59
|
+
unsubscription_request = Presence.new.set_type(:unsubscribe)
|
|
60
|
+
unsubscription_request.to = jid
|
|
61
|
+
client.send!(unsubscription_request)
|
|
62
|
+
client.send!(unsubscription_request.set_type(:unsubscribed))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def jid(bare=true)
|
|
66
|
+
bare ? @jid.strip : @jid
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def roster_item
|
|
72
|
+
client.roster.items[jid]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def client
|
|
76
|
+
@client
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class Simple
|
|
81
|
+
|
|
82
|
+
include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
|
|
83
|
+
|
|
84
|
+
def self.register(jid, password, status = nil, status_message = "Available")
|
|
85
|
+
new(jid, password, status, status_message, true)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Create a new Jabber::Simple client. You will be automatically connected
|
|
89
|
+
# to the Jabber server and your status message will be set to the string
|
|
90
|
+
# passed in as the status_message argument.
|
|
91
|
+
#
|
|
92
|
+
# jabber = Jabber::Simple.new("me@example.com", "password", "Chat with me - Please!")
|
|
93
|
+
def initialize(jid, password, status = nil, status_message = "Available", register = false)
|
|
94
|
+
@jid = jid
|
|
95
|
+
@password = password
|
|
96
|
+
@disconnected = false
|
|
97
|
+
register!(password) if @register = register
|
|
98
|
+
status(status, status_message)
|
|
99
|
+
start_deferred_delivery_thread
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def inspect #:nodoc:
|
|
103
|
+
"Jabber::Simple #{@jid}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Send a message to jabber user jid.
|
|
107
|
+
#
|
|
108
|
+
# Valid message types are:
|
|
109
|
+
#
|
|
110
|
+
# * :normal (default): a normal message.
|
|
111
|
+
# * :chat: a one-to-one chat message.
|
|
112
|
+
# * :groupchat: a group-chat message.
|
|
113
|
+
# * :headline: a "headline" message.
|
|
114
|
+
# * :error: an error message.
|
|
115
|
+
#
|
|
116
|
+
# If the recipient is not in your contacts list, the message will be queued
|
|
117
|
+
# for later delivery, and the Contact will be automatically asked for
|
|
118
|
+
# authorization (see Jabber::Simple#add).
|
|
119
|
+
#
|
|
120
|
+
# message should be a string or a valid Jabber::Message object. In either case,
|
|
121
|
+
# the message recipient will be set to jid.
|
|
122
|
+
def deliver(jid, message, type=:chat)
|
|
123
|
+
contacts(jid) do |friend|
|
|
124
|
+
unless subscribed_to? friend
|
|
125
|
+
add(friend.jid)
|
|
126
|
+
return deliver_deferred(friend.jid, message, type)
|
|
127
|
+
end
|
|
128
|
+
if message.kind_of?(Jabber::Message)
|
|
129
|
+
msg = message
|
|
130
|
+
msg.to = friend.jid
|
|
131
|
+
else
|
|
132
|
+
msg = Message.new(friend.jid)
|
|
133
|
+
msg.type = type
|
|
134
|
+
msg.body = message
|
|
135
|
+
end
|
|
136
|
+
send!(msg)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Set your presence, with a message.
|
|
141
|
+
#
|
|
142
|
+
# Available values for presence are:
|
|
143
|
+
#
|
|
144
|
+
# * nil: online.
|
|
145
|
+
# * :chat: free for chat.
|
|
146
|
+
# * :away: away from the computer.
|
|
147
|
+
# * :dnd: do not disturb.
|
|
148
|
+
# * :xa: extended away.
|
|
149
|
+
#
|
|
150
|
+
# It's not possible to set an offline status - to do that, disconnect! :-)
|
|
151
|
+
def status(presence, message)
|
|
152
|
+
@presence = presence
|
|
153
|
+
@status_message = message
|
|
154
|
+
stat_msg = Presence.new(@presence, @status_message)
|
|
155
|
+
send!(stat_msg)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Ask the users specified by jids for authorization (i.e., ask them to add
|
|
159
|
+
# you to their contact list). If you are already in the user's contact list,
|
|
160
|
+
# add() will not attempt to re-request authorization. In order to force
|
|
161
|
+
# re-authorization, first remove() the user, then re-add them.
|
|
162
|
+
#
|
|
163
|
+
# Example usage:
|
|
164
|
+
#
|
|
165
|
+
# jabber_simple.add("friend@friendosaurus.com")
|
|
166
|
+
#
|
|
167
|
+
# Because the authorization process might take a few seconds, or might
|
|
168
|
+
# never happen depending on when (and if) the user accepts your
|
|
169
|
+
# request, results are placed in the Jabber::Simple#new_subscriptions queue.
|
|
170
|
+
def add(*jids)
|
|
171
|
+
contacts(*jids) do |friend|
|
|
172
|
+
next if subscribed_to? friend
|
|
173
|
+
friend.ask_for_authorization!
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Remove the jabber users specified by jids from the contact list.
|
|
178
|
+
def remove(*jids)
|
|
179
|
+
contacts(*jids) do |unfriend|
|
|
180
|
+
unfriend.unsubscribe!
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Returns true if this Jabber account is subscribed to status updates for
|
|
185
|
+
# the jabber user jid, false otherwise.
|
|
186
|
+
def subscribed_to?(jid)
|
|
187
|
+
contacts(jid) do |contact|
|
|
188
|
+
return contact.subscribed?
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# If contacts is a single contact, returns a Jabber::Contact object
|
|
193
|
+
# representing that user; if contacts is an array, returns an array of
|
|
194
|
+
# Jabber::Contact objects.
|
|
195
|
+
#
|
|
196
|
+
# When called with a block, contacts will yield each Jabber::Contact object
|
|
197
|
+
# in turn. This is mainly used internally, but exposed as an utility
|
|
198
|
+
# function.
|
|
199
|
+
def contacts(*contacts, &block)
|
|
200
|
+
@contacts ||= {}
|
|
201
|
+
contakts = []
|
|
202
|
+
contacts.each do |contact|
|
|
203
|
+
jid = contact.to_s
|
|
204
|
+
unless @contacts[jid]
|
|
205
|
+
@contacts[jid] = contact.respond_to?(:ask_for_authorization!) ? contact : Contact.new(self, contact)
|
|
206
|
+
end
|
|
207
|
+
yield @contacts[jid] if block_given?
|
|
208
|
+
contakts << @contacts[jid]
|
|
209
|
+
end
|
|
210
|
+
contakts.size > 1 ? contakts : contakts.first
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns true if the Jabber client is connected to the Jabber server,
|
|
214
|
+
# false otherwise.
|
|
215
|
+
def connected?
|
|
216
|
+
@client ||= nil
|
|
217
|
+
connected = @client.respond_to?(:is_connected?) && @client.is_connected?
|
|
218
|
+
return connected
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Returns an array of messages received since the last time
|
|
222
|
+
# received_messages was called. Passing a block will yield each message in
|
|
223
|
+
# turn, allowing you to break part-way through processing (especially
|
|
224
|
+
# useful when your message handling code is not thread-safe (e.g.,
|
|
225
|
+
# ActiveRecord).
|
|
226
|
+
#
|
|
227
|
+
# e.g.:
|
|
228
|
+
#
|
|
229
|
+
# jabber.received_messages do |message|
|
|
230
|
+
# puts "Received message from #{message.from}: #{message.body}"
|
|
231
|
+
# end
|
|
232
|
+
def received_messages(&block)
|
|
233
|
+
dequeue(:received_messages, &block)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Returns true if there are unprocessed received messages waiting in the
|
|
237
|
+
# queue, false otherwise.
|
|
238
|
+
def received_messages?
|
|
239
|
+
!queue(:received_messages).empty?
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Returns an array of presence updates received since the last time
|
|
243
|
+
# presence_updates was called. Passing a block will yield each update in
|
|
244
|
+
# turn, allowing you to break part-way through processing (especially
|
|
245
|
+
# useful when your presence handling code is not thread-safe (e.g.,
|
|
246
|
+
# ActiveRecord).
|
|
247
|
+
#
|
|
248
|
+
# e.g.:
|
|
249
|
+
#
|
|
250
|
+
# jabber.presence_updates do |friend, new_presence|
|
|
251
|
+
# puts "Received presence update from #{friend}: #{new_presence}"
|
|
252
|
+
# end
|
|
253
|
+
def presence_updates(&block)
|
|
254
|
+
updates = []
|
|
255
|
+
@presence_mutex.synchronize do
|
|
256
|
+
dequeue(:presence_updates) do |friend|
|
|
257
|
+
presence = @presence_updates[friend]
|
|
258
|
+
next unless presence
|
|
259
|
+
new_update = [friend, presence[0], presence[1]]
|
|
260
|
+
yield new_update if block_given?
|
|
261
|
+
updates << new_update
|
|
262
|
+
@presence_updates.delete(friend)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
return updates
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Returns true if there are unprocessed presence updates waiting in the
|
|
269
|
+
# queue, false otherwise.
|
|
270
|
+
def presence_updates?
|
|
271
|
+
!queue(:presence_updates).empty?
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Returns an array of subscription notifications received since the last
|
|
275
|
+
# time new_subscriptions was called. Passing a block will yield each update
|
|
276
|
+
# in turn, allowing you to break part-way through processing (especially
|
|
277
|
+
# useful when your subscription handling code is not thread-safe (e.g.,
|
|
278
|
+
# ActiveRecord).
|
|
279
|
+
#
|
|
280
|
+
# e.g.:
|
|
281
|
+
#
|
|
282
|
+
# jabber.new_subscriptions do |friend, presence|
|
|
283
|
+
# puts "Received presence update from #{friend.to_s}: #{presence}"
|
|
284
|
+
# end
|
|
285
|
+
def new_subscriptions(&block)
|
|
286
|
+
dequeue(:new_subscriptions, &block)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Returns true if there are unprocessed presence updates waiting in the
|
|
290
|
+
# queue, false otherwise.
|
|
291
|
+
def new_subscriptions?
|
|
292
|
+
!queue(:new_subscriptions).empty?
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Returns an array of subscription notifications received since the last
|
|
296
|
+
# time subscription_requests was called. Passing a block will yield each update
|
|
297
|
+
# in turn, allowing you to break part-way through processing (especially
|
|
298
|
+
# useful when your subscription handling code is not thread-safe (e.g.,
|
|
299
|
+
# ActiveRecord).
|
|
300
|
+
#
|
|
301
|
+
# e.g.:
|
|
302
|
+
#
|
|
303
|
+
# jabber.subscription_requests do |friend, presence|
|
|
304
|
+
# puts "Received presence update from #{friend.to_s}: #{presence}"
|
|
305
|
+
# end
|
|
306
|
+
def subscription_requests(&block)
|
|
307
|
+
dequeue(:subscription_requests, &block)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Returns true if auto-accept subscriptions (friend requests) is enabled
|
|
311
|
+
# (default), false otherwise.
|
|
312
|
+
def accept_subscriptions?
|
|
313
|
+
@accept_subscriptions = true if @accept_subscriptions.nil?
|
|
314
|
+
@accept_subscriptions
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Change whether or not subscriptions (friend requests) are automatically accepted.
|
|
318
|
+
def accept_subscriptions=(accept_status)
|
|
319
|
+
@accept_subscriptions = accept_status
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Direct access to the underlying Roster helper.
|
|
323
|
+
def roster
|
|
324
|
+
return @roster if @roster
|
|
325
|
+
self.roster = Roster::Helper.new(client)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Direct access to the underlying Jabber client.
|
|
329
|
+
def client
|
|
330
|
+
connect!() unless connected?
|
|
331
|
+
@client
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def send!(msg)
|
|
335
|
+
attempt! {
|
|
336
|
+
client.send(msg)
|
|
337
|
+
}
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def register!(password)
|
|
341
|
+
attempt! {
|
|
342
|
+
begin
|
|
343
|
+
client.register(password)
|
|
344
|
+
@register = false
|
|
345
|
+
disconnect
|
|
346
|
+
reconnect
|
|
347
|
+
rescue => e
|
|
348
|
+
error_msg = "Error registering: #{e.error.text}\n\n"
|
|
349
|
+
if e.error.type == :modify
|
|
350
|
+
error_msg += "Accepted registration information:\n"
|
|
351
|
+
instructions, fields = client.register_info
|
|
352
|
+
fields.each { |info|
|
|
353
|
+
error_msg += "* #{info}\n"
|
|
354
|
+
}
|
|
355
|
+
error_msg += "(#{instructions})"
|
|
356
|
+
end
|
|
357
|
+
raise RegistrationError, error_msg
|
|
358
|
+
end
|
|
359
|
+
}
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Send a Jabber stanza over-the-wire.
|
|
363
|
+
def attempt!
|
|
364
|
+
attempts = 0
|
|
365
|
+
begin
|
|
366
|
+
attempts += 1
|
|
367
|
+
yield
|
|
368
|
+
rescue Errno::EPIPE, IOError => e
|
|
369
|
+
sleep 1
|
|
370
|
+
disconnect
|
|
371
|
+
reconnect
|
|
372
|
+
retry unless attempts > 3
|
|
373
|
+
raise e
|
|
374
|
+
rescue Errno::ECONNRESET => e
|
|
375
|
+
sleep (attempts^2) * 60 + 60
|
|
376
|
+
disconnect
|
|
377
|
+
reconnect
|
|
378
|
+
retry unless attempts > 3
|
|
379
|
+
raise e
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Use this to force the client to reconnect after a force_disconnect.
|
|
384
|
+
def reconnect
|
|
385
|
+
@disconnected = false
|
|
386
|
+
connect!
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Use this to force the client to disconnect and not automatically
|
|
390
|
+
# reconnect.
|
|
391
|
+
def disconnect
|
|
392
|
+
disconnect!
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Queue messages for delivery once a user has accepted our authorization
|
|
396
|
+
# request. Works in conjunction with the deferred delivery thread.
|
|
397
|
+
#
|
|
398
|
+
# You can use this method if you want to manually add friends and still
|
|
399
|
+
# have the message queued for later delivery.
|
|
400
|
+
def deliver_deferred(jid, message, type)
|
|
401
|
+
msg = {:to => jid, :message => message, :type => type}
|
|
402
|
+
queue(:pending_messages) << [msg]
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
private
|
|
406
|
+
|
|
407
|
+
def client=(client)
|
|
408
|
+
self.roster = nil # ensure we clear the roster, since that's now associated with a different client.
|
|
409
|
+
@client = client
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def roster=(new_roster)
|
|
413
|
+
@roster = new_roster
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def connect!
|
|
417
|
+
raise ConnectionError, "Connections are disabled - use Jabber::Simple::force_connect() to reconnect." if @disconnected
|
|
418
|
+
# Pre-connect
|
|
419
|
+
@connect_mutex ||= Mutex.new
|
|
420
|
+
|
|
421
|
+
# don't try to connect if another thread is already connecting.
|
|
422
|
+
return if @connect_mutex.locked?
|
|
423
|
+
|
|
424
|
+
@connect_mutex.lock
|
|
425
|
+
disconnect!(false) if connected?
|
|
426
|
+
|
|
427
|
+
# Connect
|
|
428
|
+
jid = JID.new(@jid)
|
|
429
|
+
my_client = Client.new(@jid)
|
|
430
|
+
my_client.connect
|
|
431
|
+
my_client.auth(@password) unless @register
|
|
432
|
+
self.client = my_client
|
|
433
|
+
|
|
434
|
+
# Post-connect
|
|
435
|
+
register_default_callbacks
|
|
436
|
+
status(@presence, @status_message) unless @register
|
|
437
|
+
@connect_mutex.unlock
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def disconnect!(auto_reconnect = true)
|
|
441
|
+
if client.respond_to?(:is_connected?) && client.is_connected?
|
|
442
|
+
begin
|
|
443
|
+
client.close
|
|
444
|
+
rescue Errno::EPIPE, IOError => e
|
|
445
|
+
# probably should log this.
|
|
446
|
+
nil
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
client = nil
|
|
450
|
+
@disconnected = auto_reconnect
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def register_default_callbacks
|
|
454
|
+
client.add_message_callback do |message|
|
|
455
|
+
queue(:received_messages) << message unless message.body.nil?
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
roster.add_subscription_callback do |roster_item, presence|
|
|
459
|
+
if presence.type == :subscribed
|
|
460
|
+
queue(:new_subscriptions) << [roster_item, presence]
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
roster.add_subscription_request_callback do |roster_item, presence|
|
|
465
|
+
if accept_subscriptions?
|
|
466
|
+
roster.accept_subscription(presence.from)
|
|
467
|
+
else
|
|
468
|
+
queue(:subscription_requests) << [roster_item, presence]
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
@presence_updates = {}
|
|
473
|
+
@presence_mutex = Mutex.new
|
|
474
|
+
roster.add_presence_callback do |roster_item, old_presence, new_presence|
|
|
475
|
+
simple_jid = roster_item.jid.strip.to_s
|
|
476
|
+
presence = case new_presence.type
|
|
477
|
+
when nil: new_presence.show || :online
|
|
478
|
+
when :unavailable: :unavailable
|
|
479
|
+
else
|
|
480
|
+
nil
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
if presence && @presence_updates[simple_jid] != presence
|
|
484
|
+
queue(:presence_updates) << simple_jid
|
|
485
|
+
@presence_mutex.synchronize { @presence_updates[simple_jid] = [presence, new_presence.status] }
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# This thread facilitates the delivery of messages to users who haven't yet
|
|
491
|
+
# accepted an invitation from us. When we attempt to deliver a message, if
|
|
492
|
+
# the user hasn't subscribed, we place the message in a queue for later
|
|
493
|
+
# delivery. Once a user has accepted our authorization request, we deliver
|
|
494
|
+
# any messages that have been queued up in the meantime.
|
|
495
|
+
def start_deferred_delivery_thread #:nodoc:
|
|
496
|
+
Thread.new {
|
|
497
|
+
loop {
|
|
498
|
+
messages = [queue(:pending_messages).pop].flatten
|
|
499
|
+
messages.each do |message|
|
|
500
|
+
if subscribed_to?(message[:to])
|
|
501
|
+
deliver(message[:to], message[:message], message[:type])
|
|
502
|
+
else
|
|
503
|
+
queue(:pending_messages) << message
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def queue(queue)
|
|
511
|
+
@queues ||= Hash.new { |h,k| h[k] = Queue.new }
|
|
512
|
+
@queues[queue]
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def dequeue(queue, non_blocking = true, max_items = 100, &block)
|
|
516
|
+
queue_items = []
|
|
517
|
+
max_items.times do
|
|
518
|
+
queue_item = queue(queue).pop(non_blocking) rescue nil
|
|
519
|
+
break if queue_item.nil?
|
|
520
|
+
queue_items << queue_item
|
|
521
|
+
yield queue_item if block_given?
|
|
522
|
+
end
|
|
523
|
+
queue_items
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
true
|
data/script/console
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# File: script/console
|
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
|
4
|
+
|
|
5
|
+
libs = " -r irb/completion"
|
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/tunecore-announcer.rb'}"
|
|
9
|
+
puts "Loading tunecore-announcer gem"
|
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|