«
Previous
|
Next
»
Revision 3b07cbbc
Added by Marc Dequènes about 16 years ago
- ID 3b07cbbc426e471d7b58cde034f5ac38f588acfa
test.rb | ||
---|---|---|
#!/usr/bin/ruby -Ku
|
||
|
||
$KCODE = 'UTF8'
|
||
require 'jcode'
|
||
require 'singleton'
|
||
require 'yaml'
|
||
require 'ostruct'
|
||
require 'log4r'
|
||
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'
|
||
|
||
|
||
class Hash
|
||
def to_ostruct
|
||
data = self.dup
|
||
data.each_pair do |k, v|
|
||
data[k] = v.to_ostruct if v.is_a?(Hash)
|
||
end
|
||
OpenStruct.new(data)
|
||
end
|
||
end
|
||
|
||
class Object
|
||
def logger
|
||
CyborgHood::Logger.instance
|
||
end
|
||
|
||
def self.human_name
|
||
self.name.split("::").last
|
||
end
|
||
def human_name
|
||
self.class.human_name
|
||
end
|
||
end
|
||
|
||
module CyborgHood
|
||
|
||
PRODUCT = "CyborgHood"
|
||
VERSION = "0.0.1"
|
||
|
||
class Logger < Log4r::Logger
|
||
include Singleton
|
||
|
||
LOG_FORMAT = "[%5l - %d] %m"
|
||
|
||
def output_level(level)
|
||
l = case level
|
||
when :verbose
|
||
Log4r::DEBUG
|
||
when :quiet
|
||
Log4r::FATAL
|
||
else # normal
|
||
Log4r::WARN
|
||
end
|
||
@main_outputter.level = l
|
||
end
|
||
|
||
def log_to_file(filename)
|
||
file_outputter = Log4r::FileOutputter.new(filename, :filename => filename)
|
||
file_outputter.formatter = @default_formatter
|
||
self.outputters << file_outputter
|
||
end
|
||
|
||
private
|
||
|
||
def initialize
|
||
super(PRODUCT)
|
||
|
||
self.level = Log4r::DEBUG
|
||
@default_formatter = Log4r::PatternFormatter.new(:pattern => LOG_FORMAT)
|
||
@main_outputter = Log4r::Outputter.stdout
|
||
@main_outputter.formatter = @default_formatter
|
||
@main_outputter.level = Log4r::WARN
|
||
self.outputters = [@main_outputter]
|
||
end
|
||
end
|
||
|
||
class Config
|
||
include Singleton
|
||
|
||
CFG_DIR = './config/'
|
||
|
||
CONFFILE_GLOBAL = "cyborghood.conf"
|
||
CONFFILE_BOT = "cyborghood_%s.conf"
|
||
|
||
def self.load(name = nil)
|
||
# load all config parts
|
||
config = fetch_config
|
||
config = merge_configs(config, fetch_config(name)) unless name.nil?
|
||
|
||
# create config object for easier access
|
||
@@config = config.to_ostruct.freeze
|
||
end
|
||
|
||
def method_missing(sym, *args, &block)
|
||
begin
|
||
@@config.send sym, *args, &block
|
||
rescue
|
||
nil
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def self.fetch_config(name = nil)
|
||
begin
|
||
if name.nil?
|
||
filename = CONFFILE_GLOBAL
|
||
else
|
||
filename = sprintf(CONFFILE_BOT, name)
|
||
end
|
||
filepath = File.join(File.expand_path(CFG_DIR), filename)
|
||
str = File.read(filepath)
|
||
YAML.load(str)
|
||
rescue
|
||
logger.fatal "Problems fetching configuration file '" + filepath + "': " + $!
|
||
exit 1
|
||
end
|
||
end
|
||
|
||
def self.merge_configs(conf1, conf2)
|
||
new_conf = conf1.dup
|
||
conf2.each_pair do |k, v|
|
||
if conf1.has_key?(k) and conf1[k].is_a?(Hash) and v.is_a?(Hash)
|
||
new_conf[k] = merge_configs(conf1[k], v)
|
||
else
|
||
new_conf[k] = v
|
||
end
|
||
end
|
||
return new_conf
|
||
end
|
||
end
|
||
|
||
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
|
||
|
||
class Guard < Cyborg
|
||
ROOM_PARAMS = {
|
||
'muc#roomconfig_allowinvites' => 0,
|
||
'muc#roomconfig_changesubject' => 0,
|
||
'muc#roomconfig_membersonly' => 1,
|
||
'muc#roomconfig_publicroom' => 1,
|
||
'muc#roomconfig_persistentroom' => 1,
|
||
'muc#roomconfig_roomsecret' => 'moderators',
|
||
}
|
||
|
||
def initialize
|
||
super {
|
||
@in_room = true
|
||
}
|
||
end
|
||
|
||
def run
|
||
super do
|
||
logger.debug "COIN !"
|
||
p @muc.roster.collect{|k, v| [k, v.show] }
|
||
#sleep 3
|
||
Thread.stop
|
||
p "pompompom"
|
||
p @muc.roster.collect{|k, v| [k, v.show] }
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def on_missing_room
|
||
logger.info "CyborgHood HeadQuarters meeting room does not exist yet; creating..."
|
||
|
||
logger.debug "Fetching and checking room config form"
|
||
room_config_form = @muc.get_room_configuration
|
||
missing_params = ROOM_PARAMS.keys - room_config_form
|
||
unless missing_params.empty?
|
||
logger.fatal "CyborgHood HeadQuarters do not provide the following meeting room facilities: " + missing_params.join(", ")
|
||
quit(true)
|
||
end
|
||
|
||
logger.debug "Setting room configuration"
|
||
@muc.submit_room_configuration(ROOM_PARAMS)
|
||
end
|
||
|
||
def on_existing_room
|
||
logger.debug "Checking ownership"
|
||
unless @muc.owner?
|
||
logger.fatal "CyborgHood HeadQuarters meeting room has been taken over !!!"
|
||
quit(true)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
#Jabber::debug = true
|
||
|
||
bot = CyborgHood::Guard.new
|
||
|
||
trap('INT') do
|
||
bot.ask_to_stop
|
||
end
|
||
trap('TERM') do
|
||
bot.ask_to_stop
|
||
end
|
||
|
||
bot.run
|
||
|
Also available in: Unified diff
Initial version