root/lib/cyborghood/cyborg.rb @ 2520cf92
d32ee48a | Marc Dequenes | #--
|
|
# CyborgHood, a distributed system management software.
|
|||
e7315259 | Marc Dequènes (Duck) | # Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
|
d32ee48a | Marc Dequenes | #
|
|
# This program is free software: you can redistribute it and/or modify
|
|||
# it under the terms of the GNU General Public License as published by
|
|||
# the Free Software Foundation, either version 3 of the License, or
|
|||
# (at your option) any later version.
|
|||
#
|
|||
# This program is distributed in the hope that it will be useful,
|
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
# GNU General Public License for more details.
|
|||
#
|
|||
# You should have received a copy of the GNU General Public License
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#++
|
|||
3b07cbbc | duck | require 'xmpp4r'
|
|
require 'xmpp4r/presence'
|
|||
require 'xmpp4r/version/iq/version'
|
|||
require 'xmpp4r/version/helper/simpleresponder'
|
|||
require 'xmpp4r/caps'
|
|||
require 'xmpp4r/discovery'
|
|||
#require 'xmpp4r/vcard/helper/vcard'
|
|||
require 'xmpp4r/muc/helper/mucbrowser'
|
|||
require 'xmpp4r/muc/helper/simplemucclient'
|
|||
module CyborgHood
|
|||
class Cyborg
|
|||
attr_reader :jid, :room_jid, :chat_jid
|
|||
FEATURE_NS = 'http://duckcorp.org/protocol/cyborghood'
|
|||
def initialize()
|
|||
# load config
|
|||
Config.load(self.human_name.downcase)
|
|||
@config = Config.instance
|
|||
# setup logs
|
|||
unless @config.log.nil?
|
|||
logger.output_level(@config.log.console_level) unless @config.log.console_level.nil?
|
|||
logger.log_to_file(@config.log.file) unless @config.log.file.nil?
|
|||
end
|
|||
# prepare
|
|||
clear
|
|||
@jid = Jabber::JID.new(@config.xmpp.jid)
|
|||
@room_jid = Jabber::JID.new(@config.xmpp.muc).bare
|
|||
yield if block_given?
|
|||
logger.info "Bot '#{self.human_name}' loaded"
|
|||
end
|
|||
def run
|
|||
logger.info "Initializing bot..."
|
|||
begin
|
|||
@cl = Jabber::Client.new(@jid)
|
|||
@cl.allow_tls = true
|
|||
@cl.ssl_capath = @config.xmpp.ssl_capath unless @config.xmpp.ssl_capath == nil
|
|||
reconnect
|
|||
rescue Jabber::ServerError => e
|
|||
if e.error.type == :cancel and e.error.code == 409
|
|||
if @nick_index < 100
|
|||
retry
|
|||
else
|
|||
logger.fatal "Cannot connect to CyborgHood HeadQuarters because my Nickname range is full (maybe a security problem)"
|
|||
quit(true)
|
|||
end
|
|||
else
|
|||
logger.fatal "Cannot connect to CyborgHood HeadQuarters: " + e.error.text
|
|||
quit(true)
|
|||
end
|
|||
end
|
|||
@cl.on_exception { sleep 5; reconnect }
|
|||
logger.info "Bot ready for work"
|
|||
@run_thread = Thread.new { yield }
|
|||
@run_thread.join
|
|||
logger.info "Bot work is finished, going to sleep..."
|
|||
quit
|
|||
end
|
|||
def ask_to_stop
|
|||
@should_stop = true
|
|||
@run_thread.wakeup if @run_thread.stop?
|
|||
end
|
|||
protected
|
|||
def quit(fatal = false)
|
|||
if @cl
|
|||
@muc.exit if @muc and @muc.active?
|
|||
@cl.close
|
|||
end
|
|||
clear
|
|||
exit 1 if fatal
|
|||
end
|
|||
def advertise_presence(available)
|
|||
show = (available ? :chat : :dnd)
|
|||
@cl.send(Jabber::Presence.new.set_show(show))
|
|||
end
|
|||
def advertise_presence_in_room(available)
|
|||
show = (available ? :chat : :dnd)
|
|||
stanza = Jabber::Presence.new.set_show(show)
|
|||
stanza.from = @jid.to_s
|
|||
stanza.to = @chat_jid.to_s
|
|||
@cl.send(stanza)
|
|||
end
|
|||
def discover_features(jid)
|
|||
result = discover(jid)
|
|||
result.nil? ? [] : result.features
|
|||
end
|
|||
def room_members
|
|||
@muc.roster.keys.sort
|
|||
end
|
|||
def unnormalize_message(msg)
|
|||
msg2 = msg.dup
|
|||
REXML::DocType::DEFAULT_ENTITIES.each_value do |entity|
|
|||
msg2.gsub!("&#{entity.name};", entity.value)
|
|||
end
|
|||
msg2.gsub!( '&', '&' )
|
|||
return msg2
|
|||
end
|
|||
private
|
|||
def reconnect
|
|||
logger.debug "Connecting to the XMPP server..."
|
|||
@cl.connect
|
|||
@cl.auth(@config.xmpp.passwd)
|
|||
unless @cl.is_tls?
|
|||
logger.fatal "Connection to CyborgHood HeadQuarters is not encrypted; please configure TLS support !"
|
|||
quit(true)
|
|||
end
|
|||
# initial presence before being ready
|
|||
logger.debug "Advertising unavailable status"
|
|||
advertise_presence(false)
|
|||
# version
|
|||
logger.debug "Starting version responder"
|
|||
Jabber::Version::SimpleResponder.new(@cl, PRODUCT, VERSION, "")
|
|||
# advertise discovery info
|
|||
logger.debug "Starting capabilities responder"
|
|||
Jabber::Caps::Helper.new(@cl,
|
|||
[Jabber::Discovery::Identity.new('client', self.human_name, 'bot')],
|
|||
[Jabber::Discovery::Feature.new(FEATURE_NS)]
|
|||
)
|
|||
# enter MUC room
|
|||
knock_at_room_door unless @in_room
|
|||
enter_room
|
|||
# presence
|
|||
logger.debug "Advertising available status"
|
|||
advertise_presence(true)
|
|||
end
|
|||
def knock_at_room_door
|
|||
end
|
|||
def enter_room
|
|||
logger.debug "Discovering room component features"
|
|||
unless discover_features(@room_jid).include?(Jabber::MUC::XMUC.new.namespace)
|
|||
logger.fatal "CyborgHood HeadQuarters has no MUC support"
|
|||
quit(true)
|
|||
end
|
|||
logger.debug "Fetching room list"
|
|||
@muc_br = Jabber::MUC::MUCBrowser.new(@cl)
|
|||
room_list = @muc_br.muc_rooms(@room_jid.domain)
|
|||
@chat_jid = @room_jid.dup
|
|||
@chat_jid.resource = generate_available_nick()
|
|||
logger.debug "Joining room '#{@room_jid}' as '#{@chat_jid.resource}'..."
|
|||
@muc = Jabber::MUC::SimpleMUCClient.new(@cl)
|
|||
register_room_callbacks
|
|||
@muc.join(@chat_jid)
|
|||
if room_list.keys.include?(@room_jid.to_s)
|
|||
on_existing_room()
|
|||
else
|
|||
on_missing_room()
|
|||
end
|
|||
# presence
|
|||
logger.debug "Advertising available status in room"
|
|||
advertise_presence_in_room(true)
|
|||
@in_room = true
|
|||
end
|
|||
def on_missing_room
|
|||
logger.fatal "CyborgHood HeadQuarters has no meeting room"
|
|||
quit(true)
|
|||
end
|
|||
def on_existing_room
|
|||
end
|
|||
def register_room_callbacks
|
|||
end
|
|||
def discover(jid, node = nil)
|
|||
iq = Jabber::Iq.new(:get, jid.domain)
|
|||
iq.query = Jabber::Discovery::IqQueryDiscoInfo.new
|
|||
iq.query.node = node unless node.nil?
|
|||
result = nil
|
|||
@cl.send_with_id(iq) {|reply|
|
|||
result = reply.query
|
|||
}
|
|||
return result
|
|||
end
|
|||
def generate_available_nick
|
|||
@nick_index += 1
|
|||
self.human_name + "-" + @nick_index.to_s.rjust(2, "0")
|
|||
end
|
|||
def clear
|
|||
@cl = nil
|
|||
@muc_br = nil
|
|||
@muc = nil
|
|||
@chat_jid = nil
|
|||
@nick_index = 0
|
|||
@in_room = false
|
|||
@should_stop = false
|
|||
end
|
|||
end
|
|||
end
|