nadoka 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.gitignore +5 -0
  2. data/ChangeLog.old +1553 -0
  3. data/Gemfile +4 -0
  4. data/README.org +31 -0
  5. data/Rakefile +1 -0
  6. data/bin/nadoka +13 -0
  7. data/lib/rss_check.rb +206 -0
  8. data/lib/tagparts.rb +206 -0
  9. data/nadoka.gemspec +29 -0
  10. data/nadoka.rb +123 -0
  11. data/nadokarc +267 -0
  12. data/ndk/bot.rb +241 -0
  13. data/ndk/client.rb +288 -0
  14. data/ndk/config.rb +571 -0
  15. data/ndk/error.rb +61 -0
  16. data/ndk/logger.rb +311 -0
  17. data/ndk/server.rb +784 -0
  18. data/ndk/server_state.rb +324 -0
  19. data/ndk/version.rb +44 -0
  20. data/plugins/autoawaybot.nb +66 -0
  21. data/plugins/autodumpbot.nb +227 -0
  22. data/plugins/autoop.nb +56 -0
  23. data/plugins/backlogbot.nb +88 -0
  24. data/plugins/checkbot.nb +64 -0
  25. data/plugins/cronbot.nb +20 -0
  26. data/plugins/dictbot.nb +53 -0
  27. data/plugins/drbcl.rb +39 -0
  28. data/plugins/drbot.nb +93 -0
  29. data/plugins/evalbot.nb +49 -0
  30. data/plugins/gonzuibot.nb +41 -0
  31. data/plugins/googlebot.nb +345 -0
  32. data/plugins/identifynickserv.nb +43 -0
  33. data/plugins/mailcheckbot.nb +0 -0
  34. data/plugins/marldiabot.nb +99 -0
  35. data/plugins/messagebot.nb +96 -0
  36. data/plugins/modemanager.nb +150 -0
  37. data/plugins/opensearchbot.nb +156 -0
  38. data/plugins/opshop.nb +23 -0
  39. data/plugins/pastebot.nb +46 -0
  40. data/plugins/roulettebot.nb +33 -0
  41. data/plugins/rss_checkbot.nb +121 -0
  42. data/plugins/samplebot.nb +24 -0
  43. data/plugins/sendpingbot.nb +17 -0
  44. data/plugins/shellbot.nb +59 -0
  45. data/plugins/sixamobot.nb +77 -0
  46. data/plugins/tenkibot.nb +111 -0
  47. data/plugins/timestampbot.nb +62 -0
  48. data/plugins/titlebot.nb +226 -0
  49. data/plugins/translatebot.nb +301 -0
  50. data/plugins/twitterbot.nb +138 -0
  51. data/plugins/weba.nb +209 -0
  52. data/plugins/xibot.nb +113 -0
  53. data/rice/irc.rb +780 -0
  54. metadata +102 -0
@@ -0,0 +1,61 @@
1
+ #
2
+ # Copyright (c) 2004-2005 SASADA Koichi <ko1 at atdot.net>
3
+ #
4
+ # This program is free software with ABSOLUTELY NO WARRANTY.
5
+ # You can re-distribute and/or modify this program under
6
+ # the same terms of the Ruby's license.
7
+ #
8
+ #
9
+ # $Id$
10
+ # Create : K.S. 04/04/20 23:57:17
11
+ #
12
+
13
+ module Nadoka
14
+
15
+ class NDK_Error < Exception
16
+ end
17
+
18
+ class NDK_QuitClient < NDK_Error
19
+ end
20
+
21
+ class NDK_BotBreak < NDK_Error
22
+ end
23
+
24
+ class NDK_BotSendCancel < NDK_Error
25
+ end
26
+
27
+ class NDK_QuitProgram < NDK_Error
28
+ end
29
+
30
+ class NDK_RestartProgram < NDK_Error
31
+ end
32
+
33
+ class NDK_ReconnectToServer < NDK_Error
34
+ end
35
+
36
+ class NDK_InvalidMessage < NDK_Error
37
+ end
38
+
39
+ ####
40
+ class NDK_FilterMessage_SendCancel < NDK_Error
41
+ end
42
+
43
+ class NDK_FilterMessage_Replace < NDK_Error
44
+ def initialize msg
45
+ @msg = msg
46
+ end
47
+ attr_reader :msg
48
+ end
49
+
50
+ class NDK_FilterMessage_OnlyBot < NDK_Error
51
+ end
52
+
53
+ class NDK_FilterMessage_OnlyLog < NDK_Error
54
+ end
55
+
56
+ class NDK_FilterMessage_BotAndLog < NDK_Error
57
+ end
58
+
59
+ end
60
+
61
+
@@ -0,0 +1,311 @@
1
+ #
2
+ # Copyright (c) 2004-2005 SASADA Koichi <ko1 at atdot.net>
3
+ #
4
+ # This program is free software with ABSOLUTELY NO WARRANTY.
5
+ # You can re-distribute and/or modify this program under
6
+ # the same terms of the Ruby's license.
7
+ #
8
+ #
9
+ # $Id$
10
+ # Create : K.S. 04/05/01 02:04:18
11
+
12
+ require 'kconv'
13
+ require 'fileutils'
14
+ require 'thread'
15
+
16
+ module Nadoka
17
+
18
+ class LogWriter
19
+ def initialize config, opts
20
+ @opts = opts
21
+ @config = config
22
+
23
+ @time_fmt = opts[:time_format]
24
+ @msg_fmts = opts[:message_format]
25
+ end
26
+
27
+ def write_log msg
28
+ raise "override me"
29
+ end
30
+
31
+ def log_format msgobj
32
+ @config.log_format @time_fmt, @msg_fmts, msgobj
33
+ end
34
+
35
+ def logging msgobj
36
+ msg = log_format(msgobj)
37
+ return if msg.empty?
38
+ write_log msg
39
+ end
40
+ end
41
+
42
+ class LogUnWriter < LogWriter
43
+ def logging _
44
+ end
45
+ end
46
+
47
+ class IOLogWriter < LogWriter
48
+ Lock = Mutex.new
49
+ def initialize config, opts
50
+ super
51
+ @io = opts[:io]
52
+ end
53
+
54
+ def write_log msg
55
+ Lock.synchronize{
56
+ @io.puts msg
57
+ }
58
+ end
59
+ end
60
+
61
+ class FileLogWriter < LogWriter
62
+ def initialize config, opts
63
+ super
64
+ @filename_fmt = opts[:file]
65
+ @channel_name_in_file_name = opts[:channel_name_in_file_name]
66
+ end
67
+
68
+ def logging msgobj
69
+ msg = log_format(msgobj)
70
+ return if msg.empty?
71
+ write_log_file make_logfilename(@filename_fmt, msgobj[:ch] || '', @channel_name_in_file_name), msg
72
+ end
73
+
74
+ def write_log_file basefile, msg
75
+ basedir = File.expand_path(@config.log_dir) + '/'
76
+ logfile = File.expand_path(basefile, basedir)
77
+ ldir = File.dirname(logfile) + '/'
78
+
79
+ if !FileTest.directory?(ldir)
80
+ raise "insecure directory: #{ldir} (pls check rc file.)" if /\A#{Regexp.quote(basedir)}/ !~ ldir
81
+ # make directory recursively
82
+ FileUtils.mkdir_p(ldir)
83
+ end
84
+
85
+ open(logfile, 'a'){|f|
86
+ f.flock(File::LOCK_EX)
87
+ f.puts msg
88
+ }
89
+ end
90
+
91
+ def make_logfilename tmpl, ch, cn
92
+ @config.make_logfilename tmpl, ch, cn
93
+ end
94
+ end
95
+
96
+ class NDK_Logger
97
+ class MessageStore
98
+ def initialize limit
99
+ @limit = limit
100
+ @pool = []
101
+ end
102
+
103
+ attr_reader :pool
104
+
105
+ def limit=(lim)
106
+ @limit = lim
107
+ end
108
+
109
+ def truncate
110
+ while @pool.size > @limit
111
+ @pool.shift
112
+ end
113
+ end
114
+
115
+ def push msgobj
116
+ truncate
117
+ @pool.push msgobj
118
+ end
119
+
120
+ def clear
121
+ @pool.clear
122
+ end
123
+ end
124
+
125
+ class MessageStoreByTime < MessageStore
126
+ def truncate
127
+ lim = Time.now.to_i - @limit
128
+ while true
129
+ if @pool[0][:time].to_i < lim
130
+ @pool.shift
131
+ else
132
+ break
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ class MessageStores
139
+ def initialize type, lim, config
140
+ @limit = lim
141
+ @class = type == :time ? MessageStoreByTime : MessageStore
142
+ @config = config
143
+ @pools = {}
144
+ end
145
+
146
+ attr_reader :pools
147
+
148
+ def push msgobj
149
+ ch = msgobj[:ccn]
150
+
151
+ unless pool = @pools[ch]
152
+ limit = (@config.channel_info[ch] && @config.channel_info[ch][:backloglines]) ||
153
+ @limit
154
+ @pools[ch] = pool = @class.new(limit)
155
+ end
156
+ pool.push msgobj
157
+ end
158
+
159
+ def each_channel_pool
160
+ @pools.each{|ch, store|
161
+ yield ch, store.pool
162
+ }
163
+ end
164
+ end
165
+
166
+
167
+ def initialize manager, config
168
+ @manager = manager
169
+ @config = config
170
+ @dlog = @config.debug_log
171
+ @message_stores = MessageStores.new(:size, @config.backlog_lines, @config)
172
+ end
173
+
174
+ attr_reader :message_stores
175
+
176
+ # debug message
177
+ def dlog msg
178
+ if @config.loglevel >= 3
179
+ msgobj = make_msgobj msg, 'DEBUG'
180
+ @config.debug_logwriter.logging msgobj
181
+ end
182
+ end
183
+ alias debug dlog
184
+
185
+ # system message
186
+ def slog msg, nostamp = false
187
+ msgobj = make_msgobj(msg, 'SYSTEM', nostamp)
188
+ if @config.loglevel >= 2
189
+ @config.system_logwriter.logging msgobj
190
+ @message_stores.push msgobj
191
+ end
192
+
193
+ str = @config.system_logwriter.log_format(msgobj)
194
+ @manager.send_to_clients Cmd.notice(@manager.state.nick, str) if @manager.state
195
+ dlog str
196
+ end
197
+
198
+ # channel message
199
+ def clog ch, msg, nostamp = false
200
+ clog_msgobj ch, make_msgobj(msg, 'SIMPLE', nostamp, ch)
201
+ end
202
+
203
+ # other irc log message
204
+ def olog msg
205
+ olog_msgobj make_msgobj(msg, 'OTHER')
206
+ end
207
+
208
+ #########################################
209
+ def make_msgobj msg, type = msg.command, nostamp = false, ch = nil
210
+ msgobj = {
211
+ :time => Time.now,
212
+ :type => type,
213
+ :orig => msg,
214
+ :nostamp => nostamp,
215
+ :ch => ch,
216
+ }
217
+
218
+ msgobj
219
+ end
220
+
221
+ def clog_msgobj ch, msgobj
222
+ if msgobj[:ccn] == :__talk__
223
+ logwriter = @config.talk_logwriter
224
+ else
225
+ logwriter = (@config.channel_info[ch] && @config.channel_info[ch][:logwriter]) ||
226
+ @config.default_logwriter
227
+ end
228
+
229
+ @message_stores.push msgobj
230
+ logwriter.logging msgobj
231
+ end
232
+
233
+ def olog_msgobj msgobj
234
+ if @config.loglevel >= 1
235
+ @config.system_logwriter.logging msgobj
236
+ @message_stores.push msgobj
237
+ end
238
+ end
239
+
240
+ # logging
241
+ def logging msg
242
+ user = @manager.nick_of(msg)
243
+ rch = msg.params[0]
244
+ ch_ = ch = @config.canonical_channel_name(rch)
245
+
246
+ msgobj = make_msgobj(msg)
247
+ msgobj[:ch] = rch # should be raw
248
+ msgobj[:ccn] = ch
249
+ msgobj[:nick] = user
250
+ msgobj[:msg] = msg.params[1]
251
+
252
+ case msg.command
253
+ when 'PRIVMSG', 'NOTICE', 'TOPIC', 'JOIN', 'PART'
254
+ unless /\A[\&\#\+\!]/ =~ ch # talk?
255
+ msgobj[:sender] = user
256
+ msgobj[:receiver] = rch
257
+ msgobj[:ccn] = :__talk__
258
+ end
259
+ clog_msgobj ch, msgobj
260
+
261
+ when 'NICK', 'QUIT'
262
+ # ignore. see below.
263
+
264
+ when 'MODE'
265
+ msgobj[:msg] = msg.params[1..-1].join(', ')
266
+
267
+ if @manager.state.current_channels[ch]
268
+ clog_msgobj ch, msgobj
269
+ else
270
+ olog_msgobj msgobj
271
+ end
272
+
273
+ when 'KICK'
274
+ msgobj[:kicker] = msg.params[1]
275
+ msgobj[:msg] = msg.params[2]
276
+ clog_msgobj ch, msgobj
277
+
278
+ when /^\d+/
279
+ # reply
280
+ str = msg.command + ' ' + msg.params.join(' ')
281
+ olog str
282
+
283
+ else
284
+ # other command
285
+ olog msg.to_s
286
+ end
287
+ end
288
+
289
+ def logging_nick ccn, rch, nick, newnick, msg
290
+ msgobj = make_msgobj(msg)
291
+ msgobj[:ch] = rch # should be raw
292
+ msgobj[:ccn] = ccn
293
+ msgobj[:nick] = nick
294
+ msgobj[:newnick] = newnick
295
+ clog_msgobj ccn, msgobj
296
+ end
297
+
298
+ def logging_quit ccn, rch, user, qmsg, msg
299
+ msgobj = make_msgobj(msg)
300
+ msgobj[:ch] = rch # should be raw
301
+ msgobj[:ccn] = ccn
302
+ msgobj[:nick] = user
303
+ msgobj[:msg] = qmsg
304
+ clog_msgobj ccn, msgobj
305
+ end
306
+
307
+ ###
308
+ end
309
+ end
310
+
311
+
@@ -0,0 +1,784 @@
1
+ #
2
+ # Copyright (c) 2004-2005 SASADA Koichi <ko1 at atdot.net>
3
+ #
4
+ # This program is free software with ABSOLUTELY NO WARRANTY.
5
+ # You can re-distribute and/or modify this program under
6
+ # the same terms of the Ruby's license.
7
+ #
8
+ #
9
+ # $Id$
10
+ # Create : K.S. 04/04/17 17:00:44
11
+ #
12
+
13
+ require 'rice/irc'
14
+ require 'ndk/error'
15
+ require 'ndk/config'
16
+ require 'ndk/server_state'
17
+ require 'ndk/client'
18
+
19
+ module Nadoka
20
+ Cmd = ::RICE::Command
21
+ Rpl = ::RICE::Reply
22
+
23
+ class NDK_Server
24
+ TimerIntervalSec = 60
25
+ MAX_PONG_FAIL = 5
26
+
27
+ def initialize rc
28
+ @rc = rc
29
+ @clients = []
30
+ @prev_timer = Time.now
31
+
32
+ @server_thread = nil
33
+ @clients_thread = nil
34
+
35
+ @state = nil
36
+
37
+ @state = NDK_State.new self
38
+ reload_config
39
+
40
+ @server = nil
41
+ @cserver = nil
42
+
43
+ @connected = false
44
+ @exitting = false
45
+
46
+ @pong_recieved = true
47
+ @pong_fail_count = 0
48
+
49
+ @isupport = {}
50
+
51
+ set_signal_trap
52
+ end
53
+ attr_reader :state, :connected, :rc
54
+ attr_reader :isupport
55
+
56
+ def client_count
57
+ @clients.size
58
+ end
59
+
60
+ def next_server_info
61
+ svinfo = @config.server_list.sort_by{rand}.shift
62
+ @config.server_list.push svinfo
63
+ [svinfo[:host], svinfo[:port], svinfo[:pass], svinfo[:ssl_params]]
64
+ end
65
+
66
+ def reload_config
67
+ @config.remove_previous_setting if defined?(@config)
68
+ @config = NDK_Config.new(self, @rc)
69
+
70
+ # reset logger
71
+ @logger = @config.logger
72
+ @state.logger = @logger
73
+ @state.config = @config
74
+ @clients.each{|c|
75
+ c.logger = @logger
76
+ }
77
+ end
78
+
79
+ def start_server_thread
80
+ @server_thread = Thread.new{
81
+ begin
82
+ @server = make_server()
83
+ @logger.slog "Server connection to #{@server.server}:#{@server.port}."
84
+ @pong_recieved = true
85
+
86
+ @server.start(1){|sv|
87
+ sv << Cmd.quit(@config.quit_message) if @config.quit_message
88
+ }
89
+
90
+ rescue RICE::Connection::Closed, SystemCallError, IOError
91
+ @connected = false
92
+ part_from_all_channels
93
+ @logger.slog "Connection closed by server. Trying to reconnect."
94
+
95
+ sleep @config.reconnect_delay
96
+ retry
97
+
98
+ rescue NDK_ReconnectToServer
99
+ @connected = false
100
+ part_from_all_channels
101
+
102
+ begin
103
+ @server.close if @server
104
+ rescue RICE::Connection::Closed, SystemCallError, IOError
105
+ end
106
+
107
+ @logger.slog "Reconnect request (no server response, or client request)."
108
+
109
+ sleep @config.reconnect_delay
110
+ retry
111
+
112
+ rescue Exception => e
113
+ ndk_error e
114
+ @clients_thread.kill if @clients_thread && @clients_thread.alive?
115
+ end
116
+ }
117
+ end
118
+
119
+ def make_server
120
+ host, port, @server_passwd, ssl_params = next_server_info
121
+ server = ::RICE::Connection.new(host, port, "\r\n", ssl_params)
122
+ server.regist{|rq, wq|
123
+ Thread.stop
124
+ @rq = rq
125
+ begin
126
+ @connected = false
127
+ server_main_proc
128
+ rescue Exception => e
129
+ ndk_error e
130
+ @server_thread.kill if @server_thread && @server_thread.alive?
131
+ @clients_thread.kill if @clients_thread && @clients_thread.alive?
132
+ ensure
133
+ @server.close
134
+ end
135
+ }
136
+ server
137
+ end
138
+
139
+ def server_main_proc
140
+ ## login
141
+
142
+ # send passwd
143
+ if @server_passwd
144
+ send_to_server Cmd.pass(@server_passwd)
145
+ end
146
+
147
+ # send nick
148
+ if @config.away_nick && client_count == 0
149
+ @state.original_nick = @config.nick
150
+ @state.nick = @config.away_nick
151
+ else
152
+ @state.nick = @config.nick
153
+ end
154
+ send_to_server Cmd.nick(@state.nick)
155
+
156
+ # send user info
157
+ send_to_server Cmd.user(@config.user,
158
+ @config.hostname, @config.servername,
159
+ @config.realname)
160
+
161
+ # wait welcome message
162
+ while q = recv_from_server
163
+ case q.command
164
+ when '001'
165
+ break
166
+ when '433'
167
+ # Nickname is already in use.
168
+ nick = @state.nick_succ(q.params[1])
169
+ @state.nick = nick
170
+ send_to_server Cmd.nick(nick)
171
+ when 'NOTICE'
172
+ # ignore
173
+ when 'ERROR'
174
+ msg = "Server login fail!(#{q})"
175
+ @server_thread.raise NDK_ReconnectToServer
176
+ else
177
+ msg = "Server login fail!(#{q})"
178
+ @logger.slog msg
179
+ raise msg
180
+ end
181
+ end
182
+
183
+ # change user mode
184
+ if @config.mode
185
+ send_to_server Cmd.mode(@state.nick, @config.mode)
186
+ end
187
+
188
+
189
+ # join to default channels
190
+ if @state.current_channels.size > 0
191
+ # if reconnect
192
+ @state.current_channels.each{|ch, chs|
193
+ join_to_channel ch
194
+ }
195
+ else
196
+ # default join process
197
+ @config.default_channels.each{|ch|
198
+ join_to_channel ch
199
+ }
200
+ end
201
+
202
+ @connected = true
203
+ @isupport = {}
204
+
205
+ ##
206
+ if @clients.size == 0
207
+ enter_away
208
+ end
209
+
210
+ invoke_event :invoke_bot, :server_connected
211
+
212
+ # loop
213
+ while q = recv_from_server
214
+
215
+ case q.command
216
+ when 'PING'
217
+ send_to_server Cmd.pong(q.params[0])
218
+ next
219
+ when 'PRIVMSG'
220
+ if ctcp_message?(q.params[1])
221
+ ctcp_message(q)
222
+ end
223
+ when 'JOIN'
224
+ @state.on_join(nick_of(q), q.params[0])
225
+ when 'PART'
226
+ @state.on_part(nick_of(q), q.params[0])
227
+ when 'NICK'
228
+ @state.on_nick(nick_of(q), q.params[0], q)
229
+ when 'QUIT'
230
+ @state.on_quit(nick_of(q), q.params[0], q)
231
+ when 'TOPIC'
232
+ @state.on_topic(nick_of(q), q.params[0], q.params[1])
233
+ when 'MODE'
234
+ @state.on_mode(nick_of(q), q.params[0], q.params[1..-1])
235
+ when 'KICK'
236
+ @state.on_kick(nick_of(q), q.params[0], q.params[1], q.params[2])
237
+
238
+ when '353' # RPL_NAMREPLY
239
+ @state.on_353(q.params[2], q.params[3])
240
+ when '332' # RPL_TOPIC
241
+ @state.on_332(q.params[1], q.params[2])
242
+
243
+ when '403' # ERR_NOSUCHCHANNEL
244
+ @state.on_403(q.params[1])
245
+
246
+ when '433', '436', '437'
247
+ # ERR_NICKNAMEINUSE, ERR_NICKCOLLISION, ERR_UNAVAILRESOURCE
248
+ # change try nick
249
+ case q.params[1]
250
+ when /\A[\#&!+]/
251
+ # retry join after 1 minute
252
+ Thread.start(q.params[1]) do |ch|
253
+ sleep 60
254
+ join_to_channel ch
255
+ end
256
+ else
257
+ nick = @state.nick_succ(q.params[1])
258
+ send_to_server Cmd.nick(nick)
259
+ @logger.slog("Retry nick setting: #{nick}")
260
+ end
261
+
262
+ when '005' # RPL_ISUPPORT or RPL_BOUNCE
263
+ if /supported/i =~ q.params[-1]
264
+ q.params[1..-2].each do |param|
265
+ if /\A(-)?([A-Z0-9]+)(?:=(.*))?\z/ =~ param
266
+ negate, key, value = $~.captures
267
+ if negate
268
+ @isupport.delete(key)
269
+ else
270
+ @isupport[key] = value || true
271
+ end
272
+ end
273
+ end
274
+ end
275
+ @logger.dlog "isupport: #{@isupport.inspect}"
276
+
277
+ else
278
+ #
279
+ end
280
+
281
+
282
+ send_to_clients q
283
+ @logger.logging q
284
+ send_to_bot q
285
+ end
286
+ end
287
+
288
+ def join_to_channel ch
289
+ if @config.channel_info[ch] && @config.channel_info[ch][:key]
290
+ send_to_server Cmd.join(ch, @config.channel_info[ch][:key])
291
+ else
292
+ send_to_server Cmd.join(ch)
293
+ end
294
+ end
295
+
296
+ def enter_away
297
+ return if @exitting || !@connected
298
+
299
+ send_to_server Cmd.away(@config.away_message) if @config.away_message
300
+
301
+ # change nick
302
+ if @state.nick != @config.away_nick && @config.away_nick
303
+ @state.original_nick = @state.nick
304
+ send_to_server Cmd.nick(@config.away_nick)
305
+ end
306
+
307
+ # part channel
308
+ @config.login_channels.each{|ch|
309
+ if @config.channel_info[ch] && @state.channels.include?(ch)
310
+ if @config.channel_info[ch][:part_message]
311
+ send_to_server Cmd.part(ch, @config.channel_info[ch][:part_message])
312
+ else
313
+ send_to_server Cmd.part(ch)
314
+ end
315
+ end
316
+ }
317
+ end
318
+
319
+ def leave_away
320
+ return if @exitting || !@connected
321
+
322
+ send_to_server Cmd.away()
323
+
324
+ if @config.away_nick && @state.original_nick
325
+ sleep 2 # wait for server response
326
+ send_to_server Cmd.nick(@state.original_nick)
327
+ @state.original_nick = nil
328
+ sleep 1 # wait for server response
329
+ end
330
+
331
+ @config.login_channels.each{|ch|
332
+ send_to_server Cmd.join(ch)
333
+ }
334
+ end
335
+
336
+ def start_clients_thread
337
+ return unless @config.client_server_port
338
+ @clients_thread = Thread.new{
339
+ begin
340
+ @cserver = TCPServer.new(@config.client_server_host,
341
+ @config.client_server_port)
342
+ @logger.slog "Open Client Server Port: #{@cserver.addr.join(' ')}"
343
+
344
+ while true
345
+ # wait for client connections
346
+ Thread.start(@cserver.accept){|cc|
347
+ client = nil
348
+ begin
349
+ if !@config.acl_object || @config.acl_object.allow_socket?(cc)
350
+ client = NDK_Client.new(@config, cc, self)
351
+ @clients << client
352
+ client.start
353
+ else
354
+ @logger.slog "ACL denied: #{cc.peeraddr.join(' ')}"
355
+ end
356
+ rescue Exception => e
357
+ ndk_error e
358
+ ensure
359
+ @clients.delete client
360
+ invoke_event :enter_away, client_count
361
+ cc.close unless cc.closed?
362
+ end
363
+ }
364
+ end
365
+ rescue Exception => e
366
+ ndk_error e
367
+ ensure
368
+ @clients.each{|cl|
369
+ cl.kill
370
+ }
371
+ if @cserver
372
+ @logger.slog "Close Client Server Port: #{@cserver.addr.join(' ')}"
373
+ @cserver.close unless @cserver.closed?
374
+ end
375
+ @server_thread.kill if @server_thread.alive?
376
+ end
377
+ }
378
+ end
379
+
380
+ def start
381
+ start_server_thread
382
+ start_clients_thread
383
+ timer_thread = Thread.new{
384
+ begin
385
+ @pong_recieved = true
386
+ @pong_fail_count = 0
387
+ while true
388
+ slp = Time.now.to_i % TimerIntervalSec
389
+ slp = TimerIntervalSec if slp < (TimerIntervalSec / 2)
390
+ sleep slp
391
+ send_to_bot :timer, Time.now
392
+
393
+ if @connected
394
+ if @pong_recieved
395
+ @pong_fail_count = 0
396
+ else
397
+ # fail
398
+ @pong_fail_count += 1
399
+ @logger.slog "PONG MISS: #{@pong_fail_count}"
400
+
401
+ if @pong_fail_count > MAX_PONG_FAIL
402
+ @pong_fail_count = 0
403
+ invoke_event :reconnect_to_server
404
+ end
405
+ end
406
+
407
+ @pong_recieved = false
408
+ @server << Cmd.ping(@server.server)
409
+ else
410
+ @pong_recieved = true
411
+ @pong_fail_count = 0
412
+ end
413
+
414
+ end
415
+
416
+ rescue Exception => e
417
+ ndk_error e
418
+ end
419
+ }
420
+
421
+ begin
422
+ @server_thread.join
423
+ rescue Interrupt
424
+ @exitting = true
425
+ ensure
426
+ @server_thread.kill if @server_thread && @server_thread.alive?
427
+ @clients_thread.kill if @clients_thread && @clients_thread.alive?
428
+ timer_thread.kill if timer_thread && timer_thread.alive?
429
+
430
+ @server.close if @server
431
+ end
432
+ end
433
+
434
+ def send_to_server msg
435
+ str = msg.to_s
436
+ if /[\r\n]/ =~ str.chomp
437
+ @logger.dlog "![>S] #{str}"
438
+ raise NDK_InvalidMessage, "Message must not include [\\r\\n]: #{str.inspect}"
439
+ else
440
+ @logger.dlog "[>S] #{str}"
441
+ @server << msg
442
+ end
443
+ end
444
+
445
+ def recv_from_server
446
+ while q = @rq.pop
447
+
448
+ # Event
449
+ if q.kind_of? Array
450
+ exec_event q
451
+ next
452
+ end
453
+
454
+ # Server -> Nadoka message
455
+ if !@config.primitive_filters.nil? && !@config.primitive_filters[q.command].nil? && !@config.primitive_filters[q.command].empty?
456
+ next unless filter_message(@config.primitive_filters[q.command], q)
457
+ end
458
+
459
+ case q.command
460
+ when 'PING'
461
+ @server << Cmd.pong(q.params[0])
462
+ when 'PONG'
463
+ @pong_recieved = true
464
+ when 'NOTICE'
465
+ @logger.dlog "[<S] #{q}"
466
+ if msg = filter_message(@config.notice_filter, q)
467
+ return q
468
+ end
469
+ when 'PRIVMSG'
470
+ @logger.dlog "[<S] #{q}"
471
+ if msg = filter_message(@config.privmsg_filter, q)
472
+ return q
473
+ end
474
+ else
475
+ @logger.dlog "[<S] #{q}"
476
+ return q
477
+ end
478
+ end
479
+ end
480
+
481
+ def filter_message filter, msg
482
+ return msg if filter.nil? || filter.empty?
483
+
484
+ begin
485
+ if filter.respond_to? :each
486
+ filter.each{|fil|
487
+ fil.call msg.dup
488
+ }
489
+ else
490
+ filter.call msg.dup
491
+ end
492
+ rescue NDK_FilterMessage_SendCancel
493
+ @logger.dlog "[NDK] Message Canceled"
494
+ return false
495
+ rescue NDK_FilterMessage_Replace => e
496
+ @logger.dlog "[NDK] Message Replaced: #{e}"
497
+ return e.msg
498
+ rescue NDK_FilterMessage_OnlyBot
499
+ @logger.dlog "[NDK] Message only bot"
500
+ send_to_bot msg
501
+ return false
502
+ rescue NDK_FilterMessage_OnlyLog
503
+ @logger.dlog "[NDK] Message only log"
504
+ @logger.logging msg
505
+ return false
506
+ rescue NDK_FilterMessage_BotAndLog
507
+ @logger.dlog "[NDK] Message log and bot"
508
+ send_to_bot msg
509
+ @logger.logging msg
510
+ return false
511
+ end
512
+ msg
513
+ end
514
+
515
+ def invoke_event ev, *arg
516
+ arg.unshift ev
517
+ @rq && (@rq << arg)
518
+ end
519
+
520
+ def exec_event q
521
+ # special event
522
+ case q[0]
523
+ when :reload_config
524
+ # q[1] must be client object
525
+ begin
526
+ reload_config
527
+ @logger.slog "configuration is reloaded"
528
+ rescue Exception => e
529
+ @logger.slog "error is occure while reloading configuration"
530
+ ndk_error e
531
+ end
532
+
533
+ when :quit_program
534
+ @exitting = true
535
+ Thread.main.raise NDK_QuitProgram
536
+
537
+ when :restart_program
538
+ @exitting = true
539
+ Thread.main.raise NDK_RestartProgram
540
+
541
+ when :reconnect_to_server
542
+ @connected = false
543
+ @server_thread.raise NDK_ReconnectToServer
544
+
545
+ when :invoke_bot
546
+ # q[1], q[2] are message and argument
547
+ send_to_bot q[1], *q[2..-1]
548
+
549
+ when :enter_away
550
+ if q[1] == 0
551
+ enter_away
552
+ end
553
+
554
+ when :leave_away
555
+ if q[1] == 1
556
+ leave_away
557
+ end
558
+ end
559
+ end
560
+
561
+ def set_signal_trap
562
+ list = Signal.list
563
+ Signal.trap(:INT){
564
+ # invoke_event :quit_program
565
+ Thread.main.raise NDK_QuitProgram
566
+ } if list['INT']
567
+ Signal.trap(:TERM){
568
+ # invoke_event :quit_program
569
+ Thread.main.raise NDK_QuitProgram
570
+ } if list.any?{|e| e == 'TERM'}
571
+
572
+ Signal.trap(:HUP){
573
+ # reload config
574
+ invoke_event :reload_config
575
+ } if list['HUP']
576
+ trap(:USR1){
577
+ # SIGUSR1
578
+ invoke_event :invoke_bot, :sigusr1
579
+ } if list['USR1']
580
+ trap(:USR2){
581
+ # SIGUSR2
582
+ invoke_event :invoke_bot, :sigusr2
583
+ } if list['USR2']
584
+ end
585
+
586
+ def about_me? msg
587
+ qnick = Regexp.quote(@state.nick || '')
588
+ if msg.prefix =~ /^#{qnick}!/
589
+ true
590
+ else
591
+ false
592
+ end
593
+ end
594
+
595
+ def own_nick_change? msg
596
+ if msg.command == 'NICK' && msg.params[0] == @state.nick
597
+ nick_of(msg)
598
+ else
599
+ false
600
+ end
601
+ end
602
+
603
+ def part_from_all_channels
604
+ @state.channels.each{|ch, cs|
605
+ cmd = Cmd.part(ch)
606
+ cmd.prefix = @state.nick #m
607
+ send_to_clients cmd
608
+ }
609
+ @state.clear_channels_member
610
+ end
611
+
612
+ # server -> clients
613
+ def send_to_clients msg
614
+ if msg.command == 'PRIVMSG' && !(msg = filter_message(@config.privmsg_filter_light, msg))
615
+ return
616
+ end
617
+
618
+ if(old_nick = own_nick_change?(msg))
619
+ @clients.each{|cl|
620
+ cl.add_prefix2(msg, old_nick)
621
+ cl << msg
622
+ }
623
+ elsif about_me? msg
624
+ @clients.each{|cl|
625
+ cl.add_prefix(msg)
626
+ cl << msg
627
+ }
628
+ else
629
+ @clients.each{|cl|
630
+ cl << msg
631
+ }
632
+ end
633
+ end
634
+
635
+ def ping_to_clients
636
+ @clients.each{|cl|
637
+ cl << Cmd.ping(cl.remote_host)
638
+ }
639
+ end
640
+
641
+ # clientA -> other clients
642
+ # bot -> clients
643
+ def send_to_clients_otherwise msg, elt
644
+ @clients.each{|cl|
645
+ if cl != elt
646
+ cl.add_prefix(msg) unless msg.prefix
647
+ cl << msg
648
+ end
649
+ }
650
+ invoke_event :invoke_bot, msg if elt
651
+ @logger.logging msg
652
+ end
653
+
654
+ def ctcp_message? arg
655
+ arg[0] == ?\x1
656
+ end
657
+
658
+ def ctcp_message msg
659
+ if /\001(.+)\001/ =~ msg.params[1]
660
+ ctcp_cmd = $1
661
+ case ctcp_cmd
662
+ when 'VERSION'
663
+ send_to_server Cmd.notice(nick_of(msg), "\001VERSION #{Nadoka.version}\001")
664
+ when 'TIME'
665
+ send_to_server Cmd.notice(nick_of(msg), "\001TIME #{Time.now}\001")
666
+ else
667
+
668
+ end
669
+ end
670
+ end
671
+
672
+ def nick_of msg
673
+ if /^([^!]+)\!?/ =~ msg.prefix.to_s
674
+ $1
675
+ else
676
+ @state.nick
677
+ end
678
+ end
679
+
680
+ class PrefixObject
681
+ def initialize prefix
682
+ parse_prefix prefix
683
+ @prefix = prefix
684
+ end
685
+ attr_reader :nick, :user, :host, :prefix
686
+
687
+ def parse_prefix prefix
688
+ if /^(.+?)\!(.+?)@(.+)/ =~ prefix.to_s
689
+ # command
690
+ @nick, @user, @host = $1, $2, $3
691
+ else
692
+ # server reply
693
+ @nick, @user, @host = nil, nil, prefix
694
+ end
695
+ end
696
+
697
+ def to_s
698
+ @prefix
699
+ end
700
+ end
701
+
702
+ def make_prefix_object msg
703
+ prefix = msg.prefix
704
+ if prefix
705
+ PrefixObject.new(prefix)
706
+ else
707
+ if /^d+$/ =~ msg.command
708
+ PrefixObject.new(@config.nadoka_server_name)
709
+ else
710
+ PrefixObject.new("#{@state.nick}!#{@config.user}@#{@config.nadoka_server_name}")
711
+ end
712
+ end
713
+ end
714
+
715
+ # dispatch to bots
716
+ def send_to_bot msg, *arg
717
+
718
+ selector = 'on_' +
719
+ if msg.respond_to? :command
720
+ if /^\d+$/ =~ msg.command
721
+ # reply
722
+ prefix = make_prefix_object msg
723
+ RICE::Reply::Replies_num_to_name[msg.command]
724
+ else
725
+ # command
726
+ prefix = make_prefix_object msg
727
+ msg.command.downcase
728
+ end
729
+ else
730
+ prefix = nil
731
+ msg.to_s
732
+ end
733
+
734
+ @config.bots.each{|bot|
735
+ begin
736
+ if bot.respond_to? selector
737
+ unless prefix
738
+ bot.__send__(selector, *arg)
739
+ else
740
+ bot.__send__(selector, prefix, *msg.params)
741
+ end
742
+ end
743
+
744
+ if prefix && bot.respond_to?(:on_every_message)
745
+ bot.__send__(:on_every_message, prefix, msg.command, *msg.params)
746
+ end
747
+
748
+ rescue NDK_BotBreak
749
+ break
750
+
751
+ rescue NDK_BotSendCancel
752
+ return false
753
+
754
+ rescue Exception
755
+ ndk_error $!
756
+ end
757
+ }
758
+ true
759
+ end
760
+
761
+ def ndk_status
762
+ [ '== Nadoka Running Status ==',
763
+ '- nadoka version: ' + Nadoka.version,
764
+ '- connecting to ' + "#{@server.server}:#{@server.port}",
765
+ '- clients status:',
766
+ @clients.map{|e| '-- ' + e.state},
767
+ '- Bots status:',
768
+ @config.bots.map{|bot| '-- ' + bot.bot_state},
769
+ '== End of Status =='
770
+ ].flatten
771
+ end
772
+
773
+ def ndk_error err
774
+ @logger.slog "Exception #{err.class} - #{err}"
775
+ @logger.slog "-- backtrace --"
776
+ err.backtrace.each{|line|
777
+ @logger.slog "| " + line
778
+ }
779
+ end
780
+
781
+ end
782
+ end
783
+
784
+