nadoka 0.8.0

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.
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,138 @@
1
+ # -*-ruby-*-
2
+ # nadoka-twit
3
+ #
4
+ # = Usage
5
+ #
6
+ # == Get consumer key
7
+ #
8
+ # 1. access https://twitter.com/apps/new
9
+ # 2. register it
10
+ # 3. memo 'Consumer key' and 'Consumer secret'
11
+ #
12
+ # == Get access token
13
+ #
14
+ # 1. run this script with consumer key and consumer secret like:
15
+ # ruby twitterbot.nb <consumer_key> <consumer_secret>
16
+ # 2. memo access_token and access_token_secret
17
+ #
18
+ # == Setting nadokarc
19
+ #
20
+ # 1. set :consumer_key, :consumer_secret, :access_token,
21
+ # and :acccess_token_secret
22
+ #
23
+ # = Configuration
24
+ #
25
+ # == :ch
26
+ #
27
+ # target channel
28
+ #
29
+ # == :pattern
30
+ #
31
+ # pattern for messages to send twitter
32
+ #
33
+ # == :nkf_encoding
34
+ #
35
+ # the encoding of messages
36
+ #
37
+ # == :consumer_key, :consumer_secret
38
+ #
39
+ # Consumer key and consumer secret
40
+ #
41
+ # == :access_token, :acccess_token_secret
42
+ #
43
+ # Access token and access token secret
44
+ #
45
+ require 'time'
46
+ require 'rubygems'
47
+ require 'rubytter'
48
+ require 'json'
49
+
50
+ if __FILE__ == $0
51
+ key = ARGV.shift
52
+ secret = ARGV.shift
53
+ unless key && secret
54
+ puts "Usage: #$0 <consumer_key> <consumer_secret>"
55
+ end
56
+
57
+ oauth = Rubytter::OAuth.new(key, secret)
58
+ request_token = oauth.get_request_token
59
+ system('open', request_token.authorize_url) || puts("Access here: #{request_token.authorize_url}\nand...")
60
+
61
+ print "Enter PIN: "
62
+ pin = gets.strip
63
+
64
+ access_token = request_token.get_access_token(
65
+ :oauth_token => request_token.token,
66
+ :oauth_verifier => pin
67
+ )
68
+ puts ":access_token => '#{access_token.token}',"
69
+ puts ":access_token_secret => '#{access_token.secret}',"
70
+ exit
71
+ end
72
+
73
+ class TwitterBot < Nadoka::NDK_Bot
74
+ def bot_initialize
75
+ @ch = @bot_config.fetch(:ch, nil)
76
+ @pattern = @bot_config.fetch(:pattern, />tw$/)
77
+ @nkf_encoding = @bot_config.fetch(:nkf_encoding, nil)
78
+
79
+ consumer = OAuth::Consumer.new(
80
+ @bot_config.fetch(:consumer_key, nil),
81
+ @bot_config.fetch(:consumer_secret, nil),
82
+ :site => 'https://api.twitter.com')
83
+ access_token = OAuth::AccessToken.new(consumer,
84
+ @bot_config.fetch(:access_token, nil),
85
+ @bot_config.fetch(:access_token_secret, nil))
86
+ @rt = OAuthRubytter.new(access_token)
87
+ @current_id = -1
88
+ end
89
+
90
+ def on_timer(t)
91
+ @rt.friends_timeline.each do |status|
92
+ id = status.id.to_i
93
+ next unless @current_id < id
94
+ @current_id = id
95
+ time = Time.parse(status.created_at)
96
+ next if time + 5 * 60 < Time.now
97
+ text = status.text.tr("\r\n", ' ')
98
+ text = NKF.nkf('--ic=UTF-8 --oc=' + @nkf_encoding, text) if @nkf_encoding
99
+ send_notice @ch, "#{time.strftime('%H:%M')} #{status.user.screen_name}: #{text}"
100
+ end
101
+ rescue Errno::ETIMEDOUT, Timeout::Error, SocketError
102
+ rescue Exception => err
103
+ puts_error_message(err)
104
+ end
105
+
106
+ def on_client_privmsg(client, ch, message)
107
+ return unless @ch.nil? or @ch.upcase == ch.upcase
108
+ unless @pattern =~ message
109
+ slog 'pattern unmatch, ignored'
110
+ return
111
+ end
112
+ text = message.sub(@pattern, '')
113
+ text = NKF.nkf('--oc=UTF-8 --ic=' + @nkf_encoding, text) if @nkf_encoding
114
+ slog((@rt.update(text) ? 'sent to twitter: ' : 'twitter send faild: ') + message)
115
+ rescue Exception => err
116
+ puts_error_message(err)
117
+ end
118
+
119
+ def slog(msg, nostamp = false)
120
+ current_method = caller.first[/:in \`(.*?)\'/, 1].to_s
121
+ msg.each do |line|
122
+ @logger.slog "#{self.class.to_s}##{current_method} #{line}", nostamp
123
+ end
124
+ end
125
+
126
+ private
127
+ def puts_error_message(err)
128
+ if err.is_a?(Rubytter::APIError)
129
+ @logger.slog "%s: %s (%s) %s" % [err.backtrace[0], err.message, err.class, err.response]
130
+ elsif err.is_a?(JSON::ParserError)
131
+ sleep 180
132
+ return
133
+ else
134
+ @logger.slog "%s: %s (%s)" % [err.backtrace[0], err.message, err.class]
135
+ end
136
+ @logger.slog err.backtrace.select{|l|/\A\/home/=~l}
137
+ end
138
+ end
@@ -0,0 +1,209 @@
1
+ # -*-ruby-*-
2
+ #
3
+ # Copyright (c) 2004-2005 SASADA Koichi <ko1 at atdot.net>
4
+ #
5
+ # This program is free software with ABSOLUTELY NO WARRANTY.
6
+ # You can re-distribute and/or modify this program under
7
+ # the same terms of the Ruby's license.
8
+ #
9
+ #
10
+ # $Id$
11
+ #
12
+
13
+ =begin
14
+
15
+ == Abstract
16
+
17
+ WebA: Web Accessor
18
+ http interface for irc
19
+
20
+ You can access IRC via ((<http://host:port/weba>))
21
+ (by default).
22
+
23
+
24
+ == Configuration
25
+
26
+ BotConfig = [
27
+ {
28
+ :name => :WebA,
29
+ :passwd => 'WebAPassWord', # if passwd is specified, use Basic Access Authentication with id
30
+ :id => 'weba',
31
+ :port => 12345,
32
+ :entry => 'weba',
33
+ # :message_format => ... (see source)
34
+ }
35
+ ]
36
+
37
+ =end
38
+
39
+ require 'webrick'
40
+ require 'lib/tagparts'
41
+
42
+ class WebA < Nadoka::NDK_Bot
43
+ class WebAlet < WEBrick::HTTPServlet::AbstractServlet
44
+ def initialize server, bot, authorizer
45
+ super
46
+ @bot = bot
47
+ @auth = authorizer
48
+ end
49
+
50
+ def do_GET req, res
51
+ @auth.authenticate(req, res) if @auth
52
+ begin
53
+ res.body = @bot.htmlpage(req.query).to_s
54
+ res['content-type'] = 'text/html; charset=Shift_JIS'
55
+ rescue WebARedirect => e
56
+ res.set_redirect(WEBrick::HTTPStatus::Found, "#{req.path}?ch=#{URI.encode(e.ch.tosjis)}")
57
+ res.body = 'moved'
58
+ end
59
+ end
60
+ end
61
+
62
+ class WebARedirect < Exception
63
+ attr_reader :ch
64
+ def initialize ch
65
+ @ch = ch
66
+ end
67
+ end
68
+
69
+ include HTMLParts
70
+
71
+ def htmlpage query
72
+ rch = (query['ch'] || '').tojis
73
+ ch = ccn(rch)
74
+ ch = !ch.empty? && (@state.channels.include?(ch) || ch == 'all') && ch
75
+
76
+ ttl = ch ? " - #{rch.tosjis}" : ''
77
+
78
+ if ch && (msg = query['message']) && !msg.empty?
79
+ msg = msg.tojis + ' (from WebA)'
80
+ send_privmsg(ch, msg)
81
+ raise WebARedirect.new(ch)
82
+ end
83
+
84
+ _html(
85
+ _head(_title("WebA: IRC Web Accessor#{ttl}")),
86
+ _body(
87
+ _h1("WebA#{ttl}"),
88
+ _p(
89
+ _a({:href => "./#{@entry}?ch="+URI.encode((rch || '').tosjis)}, 'reload'),
90
+ _a({:href => "./#{@entry}"}, 'top')
91
+ ),
92
+
93
+ case ch
94
+ when 'command'
95
+ command_ch
96
+ else
97
+ view_ch(rch, ch)
98
+ select_ch(rch, ch)
99
+ end
100
+ ))
101
+ end
102
+
103
+ def select_ch rch, ch
104
+ _p({:class => 'channel-list'},
105
+ (@state.channel_raw_names.sort + ['all']).map{|e|
106
+ e = e.tosjis
107
+ [_a({:href => "./#{@entry}?ch="+ URI.encode(e)}, e), ' ']
108
+ }
109
+ )
110
+ end
111
+
112
+ def view_ch rch, ch
113
+ return unless ch
114
+
115
+ chs = ch.tosjis
116
+
117
+ if ch == 'all'
118
+ msgs = []
119
+ @stores.pools.each{|_, store|
120
+ msgs.concat store.pool
121
+ }
122
+ msgs = msgs.sort_by{|msg| msg[:time]}
123
+ else
124
+ msgs = (@stores.pools[ch] && @stores.pools[ch].pool) || []
125
+ end
126
+
127
+ _div({:class => 'irc-accessor'},
128
+ if(ch != 'all')
129
+ _form({:method => 'get', :action => "./#{@entry}", :class => 'sayform'},
130
+ "msg: ",
131
+ _input({:type => 'text', :name => 'message', :class => 'msgbox'}),
132
+ _input({:type => 'submit', :name => 'say', :value => 'say'}),
133
+ _input({:type => 'hidden', :name => 'ch', :value => ch})
134
+ )
135
+ end,
136
+ _h2("channel #{ch.tosjis}"),
137
+ _div({:class => 'messages'},
138
+ msgs.map{|m|
139
+ if ch == 'all' && m[:ch]
140
+ chn = _a({:href => "./#{@entry}?ch=#{URI.encode(m[:ch])}"}, m[:ch].tosjis)
141
+ msg = @config.log_format_message(@all_message_format, m)
142
+ elsif m[:type] == 'PRIVMSG'
143
+ chn = _a({:href => "./#{@entry}?user=#{URI.encode(m[:user])}"}, "<#{m[:user].tosjis}>")
144
+ msg = @config.log_format_message(@message_format, m)
145
+ else
146
+ msg = @config.log_format_message(@message_format, m)
147
+ chn = ''
148
+ end
149
+
150
+ _div({:class=>'msg'},
151
+ "#{m[:time].strftime('%H:%M')} ", chn, "#{msg}".tosjis)
152
+ }.reverse
153
+ )
154
+ )
155
+ end
156
+
157
+ def bot_initialize
158
+ @stores = @logger.message_stores
159
+ @server = WEBrick::HTTPServer.new({
160
+ :Port => @bot_config.fetch(:port, 12345),
161
+ })
162
+ @entry = @bot_config.fetch(:entry, 'weba')
163
+
164
+ auth = nil
165
+ if passwd = @bot_config.fetch(:passwd, 'WebAPassWord')
166
+ id = @bot_config.fetch(:id, 'weba')
167
+
168
+ userdb = Hash.new
169
+ userdb.extend(WEBrick::HTTPAuth::UserDB)
170
+ userdb.auth_type = WEBrick::HTTPAuth::BasicAuth
171
+ userdb.set_passwd("WebA Authentication", id, passwd)
172
+
173
+ auth = WEBrick::HTTPAuth::BasicAuth.new({
174
+ :Realm => "WebA Authentication",
175
+ :UserDB => userdb,
176
+ :Algorithm => 'MD5-sess',
177
+ :Qop => [ 'auth' ],
178
+ :UseOpaque => true,
179
+ :NonceExpirePeriod => 60,
180
+ :NonceExpireDelta => 5,
181
+ })
182
+ end
183
+
184
+ @server.mount("/#{@entry}", WebAlet, self, auth)
185
+
186
+ @server_thread = Thread.new{
187
+ begin
188
+ @server.start
189
+ rescue Exception => e
190
+ @manager.ndk_error e
191
+ end
192
+ }
193
+ @message_format = @config.default_log[:message_format].merge(
194
+ @bot_config.fetch(:message_format, {
195
+ 'PRIVMSG' => '{msg}',
196
+ 'NOTICE' => '{{user}} {msg}',
197
+ }))
198
+
199
+ @all_message_format = @config.default_log[:message_format].merge(
200
+ @bot_config.fetch(:all_message_format, {}))
201
+ end
202
+
203
+ def bot_destruct
204
+ @server_thread.kill
205
+ @server.shutdown
206
+ sleep 1
207
+ end
208
+ end
209
+
@@ -0,0 +1,113 @@
1
+ #
2
+ # Xi Bot
3
+ #
4
+ # No rights reserved.
5
+ #
6
+ # Synopsis:
7
+ # xi> 2d10 (two dice of ten)
8
+ # [2d10] 13 = 7 + 6
9
+ # xi> 5d
10
+ # [5d6] 14 = 3 + 1 + 3 + 1 + 6
11
+ # xi>100
12
+ # [1d100] 26
13
+ #
14
+
15
+ class XiBot < Nadoka::NDK_Bot
16
+ def bot_initialize
17
+ @available_channel = @bot_config[:ch] || /.*/
18
+ end
19
+
20
+ def dice(count=1, max=6)
21
+ count.times{ count += rand(max) }
22
+ count
23
+ end
24
+
25
+ def on_privmsg prefix, ch, msg
26
+ return unless @available_channel === ch
27
+ return unless /\Axi\s*>\s*/ =~ msg
28
+ case $~.post_match.downcase
29
+ when /character/
30
+ %w/STR DEX CON INT WIS CHA/.each do |name|
31
+ values = (1..3).map{|i|rand(6)+1}
32
+ sum = values.inject(0){|s, i|s += i}
33
+ send_notice(ch, '%s: %2d = %s' % [name, sum, values.join(' + ')])
34
+ end
35
+ when /char/
36
+ values = %w/STR DEX CON INT WIS CHA/.map do |name|
37
+ '%s: %2d' % [name, (1..4).map{|i|rand(6)+1}.sort.last(3).inject(0){|s, i|s += i}]
38
+ end
39
+ send_notice(ch, "#{prefix.nick}: #{values.join(', ')}")
40
+ when /san/
41
+ int = dice(2, 6) + 6
42
+ pow = dice(3, 6)
43
+ san0 = pow * 5
44
+ current = san0
45
+ result = 'int:%d pow:%d san0:%d' % [int, pow, san0]
46
+
47
+ case rand(10)
48
+ when 9
49
+ result <<= ' you saw Great Cthulhu.'
50
+ losts = [dice(1, 10), dice(1, 100)]
51
+ when 7, 8
52
+ result <<= ' you saw a living dead.'
53
+ losts = [1, dice(1, 10)]
54
+ when 4, 5, 6
55
+ result <<= ' you saw a Dimension-Shambler.'
56
+ losts = [0, dice(1, 10)]
57
+ when 2, 3, 4
58
+ result <<= ' you woke up in the grave.'
59
+ losts = [0, dice(1, 6)]
60
+ else
61
+ result <<= ' you find a dead body.'
62
+ losts = [0, dice(1, 3)]
63
+ end
64
+
65
+ check = dice(1, 100)
66
+ result << " check:#{check}"
67
+ lost = losts[check > current ? 1 : 0]
68
+
69
+ insane = false
70
+ if lost > 0
71
+ result << " you lost #{lost} SAN point."
72
+ if lost >= current
73
+ # eternal insanity
74
+ result << ' you went mad. (eternal)'
75
+ insane = true
76
+ elsif lost * 5 > current
77
+ # indefinite insanity
78
+ r = %w/緊張症・痴呆症 記憶喪失 サンチョ・パンザ症、ドンキホーテ症 偏執症
79
+ 恐怖症、フェティッシュ 強迫観念、中毒、けいれん発作 誇大妄想 精神分裂症
80
+ 犯罪性精神異常 多重人格/[rand(10)]
81
+ result << ' you went mad. (indefinite %s)' % NKF.nkf('-jW', r)
82
+ insane = true
83
+ elsif lost >= 5
84
+ idearoll = dice(1, 100)
85
+ result << " idearoll:#{idearoll}"
86
+ if idearoll <= int * 5
87
+ # temporary insanity
88
+ result << ' you went mad. (temporary)'
89
+ insane = true
90
+ end
91
+ end
92
+ end
93
+ result << ' you kept sanity.' unless insane
94
+ #message = '%s: current: %d check: %d result: %s' % [prefix.nick, current, check, result]
95
+ message = "#{prefix.nick}: #{result}"
96
+ send_notice(ch, message)
97
+ when /(?:(\d+)d)?(\d+)?(?:\*([1-9]))?/
98
+ count = $1.to_i
99
+ count = 1 unless (1..100).include? count
100
+ max = $2.to_i
101
+ max = 6 unless (1..1_000_000_000).include? max
102
+ ($3 ? $3.to_i : 1).times do
103
+ values = (1..count).map{|i|rand(max)+1}
104
+ sum = values.inject(0){|s, i|s += i}
105
+ if count == 1
106
+ send_notice(ch, '%s: [%dd%d] %d' % [prefix.nick,count, max, sum])
107
+ else
108
+ send_notice(ch, '%s: [%dd%d] %d = %s' % [prefix.nick,count, max, sum, values.join(' + ')])
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end