ndk/config.rb
#
# Copyright (c) 2004-2005 SASADA Koichi <ko1 at atdot.net>
#
# This program is free software with ABSOLUTELY NO WARRANTY.
# You can re-distribute and/or modify this program under
# the same terms of the Ruby's license.
#
#
# $Id$
# Create : K.S. 04/04/17 16:50:33
#
#
# You can check RCFILE with following command:
#
# ruby ndk_config.rb [RCFILE]
#
require 'uri'
require 'socket'
require 'kconv'
require 'ndk/logger'
module Nadoka
class NDK_ConfigBase
# system
# 0: quiet, 1: normal, 2: system, 3: debug
Loglevel = 2
Setting_name = nil
# client server
Client_server_port = 6667 # or nil (no listen)
Client_server_host = nil
Client_server_pass = 'NadokaPassWord' # or nil
Client_server_acl = nil
Client_server_ssl_cert_file = nil
Client_server_ssl_key_file = nil
ACL_Object = nil
#
Server_list = [
# { :host => '127.0.0.1', :port => 6667, :pass => nil }
]
Servers = []
Reconnect_delay = 150
Default_channels = []
Login_channels = []
#
User = ENV['USER'] || ENV['USERNAME'] || 'nadokatest'
Nick = 'ndkusr'
Hostname = Socket.gethostname
Servername = '*'
Realname = 'nadoka user'
Mode = nil
Away_Message = 'away'
Away_Nick = nil
Quit_Message = "Quit Nadoka #{::Nadoka::NDK_Version}"
#
Channel_info = {}
# log
Default_log = {
:file => '${setting_name}-${channel_name}/%y%m%d.log',
:time_format => '%H:%M:%S',
:message_format => {
'PRIVMSG' => '<{nick}> {msg}',
'NOTICE' => '{{nick}} {msg}',
'JOIN' => '+ {nick} ({prefix:user}@{prefix:host})',
'NICK' => '* {nick} -> {newnick}',
'QUIT' => '- {nick} (QUIT: {msg}) ({prefix:user}@{prefix:host})',
'PART' => '- {nick} (PART: {msg}) ({prefix:user}@{prefix:host})',
'KICK' => '- {nick} kicked by {kicker} ({msg})',
'MODE' => '* {nick} changed mode ({msg})',
'TOPIC' => '<{ch} TOPIC> {msg} (by {nick})',
'SYSTEM' => '[NDK] {orig}',
'OTHER' => '{orig}',
'SIMPLE' => '{orig}',
},
}
System_log = {
:file => '${setting_name}-system.log',
:time_format => '%y/%m/%d-%H:%M:%S',
:message_format => {
'PRIVMSG' => '{ch} <{nick}> {msg}',
'NOTICE' => '{ch} {{nick}} {msg}',
'JOIN' => '{ch} + {nick} ({prefix:user}@{prefix:host})',
'NICK' => '{ch} * {nick} -> {newnick}',
'QUIT' => '{ch} - {nick} (QUIT: {msg}) ({prefix:user}@{prefix:host})',
'PART' => '{ch} - {nick} (PART: {msg}) ({prefix:user}@{prefix:host})',
'KICK' => '{ch} - {nick} kicked by {kicker} ({msg})',
'MODE' => '{ch} * {nick} changed mode ({msg})',
'TOPIC' => '{ch} <{ch} TOPIC> {msg} (by {nick})',
'SYSTEM' => '[NDK] {orig}',
'OTHER' => nil,
'SIMPLE' => nil,
},
}
Debug_log = {
:io => $stdout,
:time_format => '%y/%m/%d-%H:%M:%S',
:message_format => System_log[:message_format],
}
Talk_log = {
:file => '${setting_name}-talk/%y%m%d.log',
:time_format => Default_log[:time_format],
:message_format => {
'PRIVMSG' => '[{sender} => {receiver}] {msg}',
'NOTICE' => '{{sender} -> {receiver}} {msg}',
}
}
System_Logwriter = nil
Debug_Logwriter = nil
Default_Logwriter = nil
Talk_Logwriter = nil
BackLog_Lines = 20
# file name encoding setting
# 'euc' or 'sjis' or 'jis' or 'utf8'
FilenameEncoding =
case RUBY_PLATFORM
when /mswin/, /cygwin/, /mingw/
'sjis'
else
if /UTF-?8/i =~ ENV['LANG']
'utf8'
else
'euc'
end
end
# dirs
Default_Plugins_dir = File.expand_path('../../plugins', __FILE__)
Plugins_dir = './plugins'
Log_dir = './log'
# bots
BotConfig = []
# filters
Privmsg_Filter = []
Notice_Filter = []
Primitive_Filters = {}
# ...
Privmsg_Filter_light = []
Nadoka_server_name = 'NadokaProgram'
def self.inherited subklass
ConfigClass << subklass
end
end
ConfigClass = [NDK_ConfigBase]
class NDK_Config
NDK_ConfigBase.constants.each{|e|
eval %Q{
def #{e.downcase}
@config['#{e.downcase}'.intern]
end
}
}
def initialize manager, rcfile = nil
@manager = manager
@bots = []
load_config(rcfile || './nadokarc')
end
attr_reader :config, :bots, :logger
def remove_previous_setting
# remove setting class
base_klass = ConfigClass.shift
while klass = ConfigClass.shift
Object.module_eval{
remove_const(klass.name)
}
end
ConfigClass.push(base_klass)
# clear required files
RequiredFiles.replace []
# remove current NadokaBot
Object.module_eval %q{
remove_const :NadokaBot
module NadokaBot
def self.included mod
Nadoka::NDK_Config::BotClasses['::' + mod.name.downcase] = mod
end
end
}
# clear bot class
BotClasses.each{|k, v|
Object.module_eval{
if /\:\:/ !~ k.to_s && const_defined?(v.name)
remove_const(v.name)
end
}
}
BotClasses.clear
# destruct bot instances
@bots.each{|bot|
bot.bot_destruct
}
@bots = []
GC.start
end
def load_bots
# for compatibility
return load_bots_old if @config[:botconfig].kind_of? Hash
@bots = @config[:botconfig].map{|bot|
if bot.kind_of? Hash
next nil if bot[:disable]
name = bot[:name]
cfg = bot
raise "No bot name specified. Check rcfile." unless name
else
name = bot
cfg = nil
end
load_botfile name.to_s.downcase
make_bot_instance name, cfg
}.compact
end
# for compatibility
def load_bots_old
(@config[:botfiles] + (@config[:defaultbotfiles]||[])).each{|file|
load_botfile file
}
@config[:botconfig].keys.each{|bk|
bkn = bk.to_s
bkni= bkn.intern
unless BotClasses.any?{|n, c| n == bkni}
if @config[:botfiles]
raise "No such BotClass: #{bkn}"
else
load_botfile "#{bkn.downcase}.nb"
end
end
}
@bots = BotClasses.map{|bkname, bk|
if @config[:botconfig].has_key? bkname
if (cfg = @config[:botconfig][bkname]).kind_of? Array
cfg.map{|c|
make_bot_instance bk, c
}
else
make_bot_instance bk, cfg
end
else
make_bot_instance bk, nil
end
}.flatten
end
def server_setting
if svrs = @config[:servers]
svl = []
svrs.each{|si|
ports = si[:port] || 6667
host = si[:host]
pass = si[:pass]
ssl_params = si[:ssl_params]
if ports.respond_to? :each
ports.each{|port|
svl << {:host => host, :port => port, :pass => pass, :ssl_params => ssl_params}
}
else
svl << {:host => host, :port => ports, :pass => pass, :ssl_params => ssl_params}
end
}
@config[:server_list] = svl
end
end
def make_logwriter log
return unless log
case log
when Hash
if log.has_key?(:logwriter)
return log[:logwriter]
elsif log.has_key?(:logwriterclass)
klass = log[:logwriterclass]
elsif log.has_key?(:io)
klass = IOLogWriter
elsif log.has_key?(:file)
klass = FileLogWriter
else
klass = FileLogWriter
end
opts = @config[:default_log].merge(log)
klass.new(self, opts)
when String
opts = @config[:default_log].dup
opts[:file] = log
FileLogWriter.new(self, opts)
when IO
opts = @config[:default_log].dup
opts[:io] = log
IOLogWriter.new(self, opts)
else
raise "Unknown LogWriter setting"
end
end
def make_default_logwriter
if @config[:default_log].kind_of? Hash
dl = @config[:default_log]
else
# defult_log must be Hash
dl = @config[:default_log]
@config[:default_log] = NDK_ConfigBase::Default_log.dup
end
@config[:default_logwriter] ||= make_logwriter(dl)
@config[:system_logwriter] ||= make_logwriter(@config[:system_log])
@config[:debug_logwriter] ||= make_logwriter(@config[:debug_log])
@config[:talk_logwriter] ||= make_logwriter(@config[:talk_log])
end
def channel_setting
# treat with channel information
if chs = @config[:channel_info]
dchs = []
lchs = []
cchs = {}
chs.each{|ch, setting|
ch = identical_channel_name(ch)
setting = {} unless setting.kind_of?(Hash)
if !setting[:timing] || setting[:timing] == :startup
dchs << ch
elsif setting[:timing] == :login
lchs << ch
end
# log writer
setting[:logwriter] ||= make_logwriter(setting[:log]) || @config[:default_logwriter]
cchs[ch] = setting
}
chs.replace cchs
@config[:default_channels] = dchs
@config[:login_channels] = lchs
end
end
def acl_setting
if @config[:client_server_acl] && !@config[:acl_object]
require 'drb/acl'
acl = @config[:client_server_acl].strip.split(/\s+/)
@config[:acl_object] = ACL.new(acl)
@logger.slog "ACL: #{acl.join(' ')}"
end
end
def load_config(rcfile)
load(rcfile) if rcfile
@config = {}
klass = ConfigClass.last
klass.ancestors[0..klass.ancestors.index(NDK_ConfigBase)].reverse_each{|kl|
kl.constants.each{|e|
@config[e.downcase.intern] = klass.const_get(e)
}
}
@config[:setting_name] ||= File.basename(@manager.rc).sub(/\.?rc$/, '')
if $NDK_Debug
@config[:loglevel] = 3
end
make_default_logwriter
@logger = NDK_Logger.new(@manager, self)
@logger.slog "load config: #{rcfile}"
server_setting
channel_setting
acl_setting
load_bots
end
def ch_config ch, key
channel_info[ch] && channel_info[ch][key]
end
def canonical_channel_name ch
ch = ch.toeuc.sub(/^\!.{5}/, '!')
identical_channel_name ch
end
def identical_channel_name ch
# use 4 gsub() because of the compatibility of RFC2813(3.2)
ch = ch.toeuc.downcase.tr('[]\\~', '{}|^').tojis
if ch.respond_to?(:force_encoding)
ch.force_encoding(Encoding::ASCII_8BIT)
end
ch
end
RName = { # ('&','#','+','!')
'#' => 'CS-',
'&' => 'CA-',
'+' => 'CP-',
'!' => 'CE-',
}
def make_logfilename tmpl, rch, cn
unless cn
cn = rch.sub(/^\!.{5}/, '!')
case @config[:filenameencoding].to_s.downcase[0]
when ?e # EUC
cn = cn.toeuc.downcase
when ?s # SJIS
cn = cn.tosjis.downcase
when ?u # utf-8
cn = cn.toutf8.downcase
else # JIS
cn = cn.toeuc.downcase.tojis
cn = URI.encode(cn)
end
# escape
cn = cn.sub(/^[\&\#\+\!]|/){|c|
RName[c]
}
cn = cn.tr("*:/", "__I")
end
# format
str = Time.now.strftime(tmpl)
str.gsub(/\$\{setting_name\}/, setting_name).
gsub(/\$\{channel_name\}|\{ch\}/, cn)
end
def log_format timefmt, msgfmts, msgobj
text = log_format_message(msgfmts, msgobj)
if timefmt && !msgobj[:nostamp]
text = "#{msgobj[:time].strftime(timefmt)} #{text}"
end
text
end
def log_format_message msgfmts, msgobj
type = msgobj[:type]
format = msgfmts.fetch(type, @config[:default_log][:message_format][type])
if format.kind_of? Proc
text = format.call(params)
elsif format
if format.respond_to?(:force_encoding)
format.force_encoding(Encoding::ASCII_8BIT)
end
text = format.gsub(/\{([a-z]+)\}|\{prefix\:([a-z]+)\}/){|key|
if $2
method = $2.intern
if msgobj[:orig].respond_to?(:prefix)
prefix = msgobj[:orig].prefix || ''
if prefix.respond_to?(:force_encoding)
prefix.force_encoding(Encoding::ASCII_8BIT)
end
/^(.+?)\!(.+?)@(.+)/ =~ prefix
case method
when :nick
$1
when :user
$2
when :host
$3
else
"!!unknown prefix attribute: #{method}!!"
end
end
else
if m = msgobj[$1.intern]
if m.respond_to?(:force_encoding)
m.dup.force_encoding(Encoding::ASCII_8BIT)
else
m
end
else
"!!unknown attribute: #{$1}!!"
end
end
}
else
text = msgobj[:orig].to_s
end
end
def make_bot_instance bk, cfg
bk = BotClasses[bk.to_s.downcase.intern] unless bk.kind_of? Class
bot = bk.new @manager, self, cfg || {}
@logger.slog "bot instance: #{bot.bot_state}"
bot
end
def load_botfile file
loaded = false
if @config[:plugins_dir].respond_to? :each
@config[:plugins_dir].each{|dir|
if load_file File.expand_path("#{file}.nb", dir)
loaded = true
break
end
}
else
loaded = load_file File.expand_path("#{file}.nb", @config[:plugins_dir])
end
unless loaded
raise "No such bot file: #{file}"
end
end
def load_file file
if FileTest.exist? file
Nadoka.require_bot file
true
else
false
end
end
RequiredFiles = []
BotClasses = {}
end
def self.require_bot file
return if NDK_Config::RequiredFiles.include? file
NDK_Config::RequiredFiles.push file
begin
ret = ::Kernel.load(file)
rescue
NDK_Config::RequiredFiles.pop
raise
end
ret
end
end
module NadokaBot
# empty module for bot namespace
# this module is reloadable
def self.included mod
Nadoka::NDK_Config::BotClasses['::' + mod.name.downcase] = mod
end
end
if $0 == __FILE__
require 'pp'
pp Nadoka::NDK_Config.new(nil, ARGV.shift)
end