ircbot 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +25 -1
- data/bin/ircbot +3 -0
- data/config/airi.yml +8 -0
- data/config/sama-zu.yml +1 -0
- data/config/wota.yml +4 -3
- data/config/yml.erb +2 -0
- data/lib/ircbot/client.rb +7 -20
- data/lib/ircbot/client/config.rb +10 -7
- data/lib/ircbot/client/config/channels.rb +2 -4
- data/lib/ircbot/client/config/generator.rb +0 -28
- data/lib/ircbot/client/core.rb +4 -0
- data/lib/ircbot/client/eventable.rb +21 -0
- data/lib/ircbot/client/logger.rb +9 -0
- data/lib/ircbot/client/plugins.rb +3 -5
- data/lib/ircbot/client/standalone.rb +9 -0
- data/lib/ircbot/client/timeout.rb +32 -0
- data/lib/ircbot/plugin.rb +5 -0
- data/lib/ircbot/plugins.rb +1 -1
- data/lib/ircbot/version.rb +1 -1
- data/plugins/reminder.rb +276 -0
- data/plugins/tv.rb +262 -0
- data/spec/config_spec.rb +1 -0
- data/spec/plugin_spec.rb +1 -0
- metadata +10 -2
data/README
CHANGED
@@ -17,6 +17,7 @@ Config
|
|
17
17
|
* help: help message for this bot
|
18
18
|
* channels: startup channel names
|
19
19
|
* plugins: plugin names
|
20
|
+
* timeout: bot will exit when no new pings come for this sec
|
20
21
|
|
21
22
|
|
22
23
|
Usage
|
@@ -57,7 +58,30 @@ Example
|
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
60
|
-
|
61
|
+
|
62
|
+
Advanced
|
63
|
+
========
|
64
|
+
|
65
|
+
Messages are passed into plugins automatically.
|
66
|
+
If you don't want this plugin chain, throw :done will help you.
|
67
|
+
|
68
|
+
config:
|
69
|
+
plugins: a b
|
70
|
+
|
71
|
+
class APlugin < Ircbot::Plugin
|
72
|
+
def reply(text)
|
73
|
+
throw :done, "This is A"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class BPlugin < Ircbot::Plugin
|
78
|
+
def reply(text)
|
79
|
+
"This is B"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
BPlugin#reply will be never invoked.
|
84
|
+
|
61
85
|
|
62
86
|
Required
|
63
87
|
========
|
data/bin/ircbot
CHANGED
data/config/airi.yml
ADDED
data/config/sama-zu.yml
CHANGED
data/config/wota.yml
CHANGED
data/config/yml.erb
CHANGED
data/lib/ircbot/client.rb
CHANGED
@@ -3,30 +3,17 @@
|
|
3
3
|
require "ircbot/plugin"
|
4
4
|
require "ircbot/plugins"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# escape from nil black hole
|
10
|
-
def method_missing(name, *args)
|
11
|
-
case name.to_s
|
12
|
-
when /^on_/
|
13
|
-
# nop for calling super from subclass
|
14
|
-
else
|
15
|
-
raise NameError, "undefined local variable or method `#{name}' for #{self}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class Standalone < Client
|
20
|
-
def initialize(*)
|
21
|
-
super({})
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
6
|
+
######################################################################
|
7
|
+
### Irctob::Client
|
26
8
|
|
9
|
+
require "ircbot/client/core"
|
10
|
+
require "ircbot/client/standalone"
|
11
|
+
require "ircbot/client/eventable"
|
12
|
+
require "ircbot/client/logger"
|
27
13
|
require "ircbot/client/encoding"
|
28
14
|
require "ircbot/client/commands"
|
29
15
|
require "ircbot/client/config"
|
30
16
|
require "ircbot/client/config/channels"
|
31
17
|
require "ircbot/client/config/plugins"
|
18
|
+
require "ircbot/client/timeout"
|
32
19
|
require "ircbot/client/plugins"
|
data/lib/ircbot/client/config.rb
CHANGED
@@ -27,16 +27,20 @@ module Ircbot
|
|
27
27
|
######################################################################
|
28
28
|
### Config
|
29
29
|
def initialize(obj)
|
30
|
-
@obj = obj
|
30
|
+
@obj = Mash.new(obj)
|
31
31
|
end
|
32
32
|
|
33
33
|
def [](key)
|
34
|
-
|
34
|
+
@obj[key]
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
|
-
def method_missing(*args)
|
39
|
-
|
38
|
+
def method_missing(name, *args)
|
39
|
+
if args.empty?
|
40
|
+
self[name]
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
@@ -46,10 +50,9 @@ module Ircbot
|
|
46
50
|
|
47
51
|
def initialize(hash)
|
48
52
|
super(hash[:host], hash[:port], hash)
|
53
|
+
@config = Config.new(hash)
|
49
54
|
end
|
50
55
|
|
51
|
-
|
52
|
-
@config ||= Config.new(opts)
|
53
|
-
end
|
56
|
+
attr_reader :config
|
54
57
|
end
|
55
58
|
end
|
@@ -20,13 +20,11 @@ module Ircbot
|
|
20
20
|
######################################################################
|
21
21
|
### Event
|
22
22
|
|
23
|
-
|
24
|
-
super
|
25
|
-
|
23
|
+
event(:rpl_welcome) {
|
26
24
|
config.channels.each do |channel|
|
27
25
|
post JOIN, channel
|
28
26
|
end
|
29
|
-
|
27
|
+
}
|
30
28
|
|
31
29
|
######################################################################
|
32
30
|
### Command
|
@@ -32,34 +32,6 @@ module Ircbot
|
|
32
32
|
"unknown"
|
33
33
|
end
|
34
34
|
end
|
35
|
-
def channels
|
36
|
-
case (val = super)
|
37
|
-
when Array
|
38
|
-
val
|
39
|
-
else
|
40
|
-
val.to_s.split
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
######################################################################
|
46
|
-
### Event
|
47
|
-
|
48
|
-
def on_rpl_welcome(m)
|
49
|
-
super
|
50
|
-
|
51
|
-
config.channels.each do |channel|
|
52
|
-
post JOIN, channel
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
######################################################################
|
57
|
-
### Command
|
58
|
-
|
59
|
-
def broadcast(text)
|
60
|
-
config.channels.each do |channel|
|
61
|
-
privmsg channel, text
|
62
|
-
end
|
63
35
|
end
|
64
36
|
end
|
65
37
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ircbot
|
2
|
+
class Client
|
3
|
+
include Extlib::Hook
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def event(name, &block)
|
7
|
+
name = "on_#{name}".intern
|
8
|
+
unless instance_methods.include?(name.to_s)
|
9
|
+
define_method(name){|m|}
|
10
|
+
end
|
11
|
+
before name, &block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# escape from nil black hole
|
16
|
+
private
|
17
|
+
def method_missing(name, *args)
|
18
|
+
raise NameError, "undefined local variable or method `#{name}' for #{self}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -15,18 +15,16 @@ module Ircbot
|
|
15
15
|
######################################################################
|
16
16
|
### Events
|
17
17
|
|
18
|
-
|
19
|
-
super
|
20
|
-
|
18
|
+
event(:privmsg) {|m|
|
21
19
|
text = decode(m.params[1].to_s)
|
22
20
|
args = [text, m.prefix.nick, m]
|
23
21
|
|
24
22
|
plugins_call_replies(args, m)
|
25
|
-
|
23
|
+
}
|
26
24
|
|
27
25
|
private
|
28
26
|
def plugins_call_replies(args, m)
|
29
|
-
text = catch(:
|
27
|
+
text = catch(:done) do
|
30
28
|
plugins.active.each do |plugin|
|
31
29
|
plugins_call_action(:reply, plugin, args, m, :reply=>true)
|
32
30
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Ircbot
|
4
|
+
class Client
|
5
|
+
######################################################################
|
6
|
+
### Event
|
7
|
+
|
8
|
+
event(:ping) {
|
9
|
+
if (sec = config.timeout.to_i) > 0
|
10
|
+
@kill_myself_at.kill if @kill_myself_at.is_a?(Thread)
|
11
|
+
@kill_myself_at = Thread.new { sleep sec; timeouted }
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
class Timeouted < RuntimeError; end
|
16
|
+
|
17
|
+
def start
|
18
|
+
super
|
19
|
+
rescue Exception => e
|
20
|
+
sec = [config.wait_before_restart.to_i, 10].max
|
21
|
+
sleep sec
|
22
|
+
$stderr.puts "Restarting after timeouted"
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def timeouted
|
28
|
+
$stderr.puts "No new pings for #{config.timeout}sec."
|
29
|
+
raise Timeouted
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/ircbot/plugin.rb
CHANGED
@@ -29,6 +29,7 @@ module Ircbot
|
|
29
29
|
### Accessors
|
30
30
|
|
31
31
|
delegate :plugin!, :client, :bot, :config, :to=>"@plugins"
|
32
|
+
delegate :debug, :to=>"@plugins"
|
32
33
|
|
33
34
|
def plugin_name
|
34
35
|
@plugin_name ||= Extlib::Inflection.foreign_key(self.class.name).sub(/(_plugin)?_id$/,'')
|
@@ -59,5 +60,9 @@ module Ircbot
|
|
59
60
|
def direct?
|
60
61
|
message.channel == config.nick
|
61
62
|
end
|
63
|
+
|
64
|
+
def nick
|
65
|
+
message.prefix.nick
|
66
|
+
end
|
62
67
|
end
|
63
68
|
end
|
data/lib/ircbot/plugins.rb
CHANGED
data/lib/ircbot/version.rb
CHANGED
data/plugins/reminder.rb
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
#!/usr/bin/env ruby -Ku
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
######################################################################
|
5
|
+
# [Install]
|
6
|
+
#
|
7
|
+
# gem install chawan night-time dm-core dm-migrations dm-timestamps do_sqlite3 data_objects
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'ircbot'
|
12
|
+
require 'chawan'
|
13
|
+
require 'night-time'
|
14
|
+
|
15
|
+
require 'dm-core'
|
16
|
+
require 'dm-migrations'
|
17
|
+
require 'dm-timestamps'
|
18
|
+
|
19
|
+
module Reminder
|
20
|
+
def self.connect(path = nil)
|
21
|
+
@connecteds ||= {}
|
22
|
+
@connecteds[path] ||=
|
23
|
+
(
|
24
|
+
path = Pathname(path || Pathname(Dir.getwd) + "db" + "reminder.db").expand_path
|
25
|
+
path.parent.mkpath
|
26
|
+
DataMapper.setup(:default, "sqlite3://#{path}")
|
27
|
+
Reminder::Event.auto_upgrade!
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
######################################################################
|
32
|
+
### Exceptions
|
33
|
+
|
34
|
+
class EventNotFound < RuntimeError; end
|
35
|
+
class EventNotSaved < RuntimeError
|
36
|
+
attr_accessor :event
|
37
|
+
def initialize(event)
|
38
|
+
@event = event
|
39
|
+
end
|
40
|
+
end
|
41
|
+
class EventHasDone < EventNotSaved; end
|
42
|
+
class StartNotFound < EventNotSaved; end
|
43
|
+
|
44
|
+
######################################################################
|
45
|
+
### Event
|
46
|
+
|
47
|
+
class Event
|
48
|
+
include DataMapper::Resource
|
49
|
+
|
50
|
+
property :id , Serial
|
51
|
+
property :st , DateTime # 開始日時
|
52
|
+
property :en , DateTime # 終了日時
|
53
|
+
property :title , String # 件名
|
54
|
+
property :desc , String # 詳細
|
55
|
+
property :where , String # 場所
|
56
|
+
property :allday , Boolean , :default=>false # 終日フラグ
|
57
|
+
property :alerted , Boolean , :default=>false # お知らせ済
|
58
|
+
property :alert_at , DateTime # お知らせ日時
|
59
|
+
|
60
|
+
######################################################################
|
61
|
+
### Class methods
|
62
|
+
|
63
|
+
class << self
|
64
|
+
def default_storage_name
|
65
|
+
"event"
|
66
|
+
end
|
67
|
+
|
68
|
+
def reminders
|
69
|
+
all(:alerted=>false, :alert_at.lt=>Time.now, :order=>[:alert_at])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
######################################################################
|
74
|
+
### Instance methods
|
75
|
+
|
76
|
+
def done!
|
77
|
+
self.alerted = true
|
78
|
+
save
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
desc.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module TimeParser
|
87
|
+
def parse(text)
|
88
|
+
event = Event.new
|
89
|
+
event.desc = text
|
90
|
+
event.title = text.sub(%r{^[\s\d:-]+}, '')
|
91
|
+
event.allday = false
|
92
|
+
|
93
|
+
t = Date._parse(text)
|
94
|
+
# => {:zone=>"-14:55", :year=>2010, :hour=>13, :min=>30, :mday=>4, :offset=>-53700, :mon=>1}
|
95
|
+
|
96
|
+
if t[:year] && t[:mon] && t[:mday] && t[:hour]
|
97
|
+
event.st = Time.mktime(t[:year], t[:mon], t[:mday], t[:hour], t[:min], t[:sec])
|
98
|
+
if t[:zone].to_s =~ /^-?(\d+):(\d+)(:(\d+))?$/
|
99
|
+
event.en = Time.mktime(t[:year], t[:mon], t[:mday], $1, $2, $4)
|
100
|
+
end
|
101
|
+
else
|
102
|
+
event.allday = true
|
103
|
+
event.st = Time.mktime(t[:year], t[:mon], t[:mday]) rescue nil
|
104
|
+
end
|
105
|
+
|
106
|
+
return event
|
107
|
+
end
|
108
|
+
|
109
|
+
def register(text)
|
110
|
+
event = parse(text)
|
111
|
+
event.st or raise StartNotFound, event
|
112
|
+
if event.st.to_time > Time.now
|
113
|
+
event.alert_at = Time.at(event.st.to_time.to_i - 30*60)
|
114
|
+
event.save or raise EventNotSaved, event
|
115
|
+
return event
|
116
|
+
else
|
117
|
+
raise EventHasDone, event
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
extend TimeParser
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
class ReminderPlugin < Ircbot::Plugin
|
127
|
+
class EventWatcher
|
128
|
+
attr_accessor :interval
|
129
|
+
attr_accessor :callback
|
130
|
+
|
131
|
+
def initialize(options = {})
|
132
|
+
@interval = options[:interval] || 60
|
133
|
+
@callback = options[:callback] || proc{|e| puts e}
|
134
|
+
end
|
135
|
+
|
136
|
+
def start
|
137
|
+
loop do
|
138
|
+
if callback
|
139
|
+
events = Reminder::Event.reminders
|
140
|
+
#debug "#{self.class} found #{events.size} events"
|
141
|
+
events.each do |event|
|
142
|
+
callback.call(event)
|
143
|
+
event.done!
|
144
|
+
end
|
145
|
+
end
|
146
|
+
sleep interval
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def reply(text)
|
152
|
+
start_reminder
|
153
|
+
|
154
|
+
case text
|
155
|
+
when %r{^\d{4}.?\d{1,2}.?\d{1,2}}
|
156
|
+
event = Reminder.register(text)
|
157
|
+
text = "Remind you again at %s" % event.alert_at.strftime("%Y-%m-%d %H:%M")
|
158
|
+
throw :done, text
|
159
|
+
end
|
160
|
+
return nil
|
161
|
+
|
162
|
+
rescue Reminder::EventNotFound
|
163
|
+
return nil
|
164
|
+
|
165
|
+
rescue Reminder::StartNotFound => e
|
166
|
+
return "Reminder cannot detect start: #{e.event.st}"
|
167
|
+
|
168
|
+
rescue Reminder::EventHasDone => e
|
169
|
+
puts "Reminder ignores past event: #{e.event.st}"
|
170
|
+
return nil
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
def start_reminder(&callback)
|
175
|
+
bot = self.bot
|
176
|
+
callback ||= proc{|event| bot.broadcast event.to_s}
|
177
|
+
@event_watcher_thread ||=
|
178
|
+
(connect
|
179
|
+
reminder = EventWatcher.new(:interval=>60, :callback=>callback)
|
180
|
+
Thread.new { reminder.start })
|
181
|
+
end
|
182
|
+
|
183
|
+
def reminder_db_path
|
184
|
+
Ircbot.root + "db" + "reminder-#{config.nick}.db"
|
185
|
+
end
|
186
|
+
|
187
|
+
def connect
|
188
|
+
@connect ||= Reminder.connect(reminder_db_path)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
######################################################################
|
194
|
+
### Setup database
|
195
|
+
|
196
|
+
if $0 == __FILE__
|
197
|
+
|
198
|
+
def spec(src, buffer, &block)
|
199
|
+
buffer = "require '#{Pathname(src).expand_path}'\n" + buffer
|
200
|
+
tmp = Tempfile.new("dynamic-spec")
|
201
|
+
tmp.print(buffer)
|
202
|
+
tmp.close
|
203
|
+
block.call(tmp)
|
204
|
+
ensure
|
205
|
+
tmp.close(true)
|
206
|
+
end
|
207
|
+
|
208
|
+
spec($0, DATA.read{}) do |tmp|
|
209
|
+
system("spec -cfs #{tmp.path}")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
__END__
|
214
|
+
|
215
|
+
require 'spec'
|
216
|
+
require 'ostruct'
|
217
|
+
|
218
|
+
module Spec
|
219
|
+
module Example
|
220
|
+
module Subject
|
221
|
+
module ExampleGroupMethods
|
222
|
+
def parse(text, &block)
|
223
|
+
describe "(#{text})" do
|
224
|
+
subject {
|
225
|
+
event = Reminder.parse(text)
|
226
|
+
hash = {
|
227
|
+
:st => (event.st.strftime("%Y-%m-%d %H:%M:%S") rescue nil),
|
228
|
+
:en => (event.en.strftime("%Y-%m-%d %H:%M:%S") rescue nil),
|
229
|
+
:title => event.title.to_s,
|
230
|
+
:desc => event.title.to_s,
|
231
|
+
:allday => event.allday,
|
232
|
+
}
|
233
|
+
OpenStruct.new(hash)
|
234
|
+
}
|
235
|
+
instance_eval(&block)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe "Reminder#parse" do
|
244
|
+
|
245
|
+
parse '' do
|
246
|
+
its(:st) { should == nil }
|
247
|
+
end
|
248
|
+
|
249
|
+
parse '2010-01-04 CX' do
|
250
|
+
its(:st) { should == "2010-01-04 00:00:00" }
|
251
|
+
its(:en) { should == nil }
|
252
|
+
its(:title) { should == "CX" }
|
253
|
+
its(:allday) { should == true }
|
254
|
+
end
|
255
|
+
|
256
|
+
parse '2010-01-04 13:30 CX' do
|
257
|
+
its(:st) { should == "2010-01-04 13:30:00" }
|
258
|
+
its(:en) { should == nil }
|
259
|
+
its(:title) { should == "CX" }
|
260
|
+
its(:allday) { should == false }
|
261
|
+
end
|
262
|
+
|
263
|
+
parse '2010-01-04 13:30-14:55 CX' do
|
264
|
+
its(:st) { should == "2010-01-04 13:30:00" }
|
265
|
+
its(:en) { should == "2010-01-04 14:55:00" }
|
266
|
+
its(:title) { should == "CX" }
|
267
|
+
its(:allday) { should == false }
|
268
|
+
end
|
269
|
+
|
270
|
+
parse '2010-01-18 27:15-27:45 TX' do
|
271
|
+
its(:st) { should == "2010-01-19 03:15:00" }
|
272
|
+
its(:en) { should == "2010-01-19 03:45:00" }
|
273
|
+
its(:title) { should == "TX" }
|
274
|
+
its(:allday) { should == false }
|
275
|
+
end
|
276
|
+
end
|
data/plugins/tv.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# tv.rb
|
4
|
+
# Author:
|
5
|
+
# Created: Sun Jan 10 18:44:35 2010
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'ircbot'
|
9
|
+
require '/git/plugins/ircbot-plugins/lib/tvguide'
|
10
|
+
require '/git/plugins/ircbot-plugins/lib/tv-cast'
|
11
|
+
require 'chawan'
|
12
|
+
|
13
|
+
class TVGuide::Program
|
14
|
+
def id
|
15
|
+
"#{st}:#{sn}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TvPlugin < Ircbot::Plugin
|
20
|
+
######################################################################
|
21
|
+
### Extensions to Plugin
|
22
|
+
|
23
|
+
class WeightedRandomizer
|
24
|
+
def initialize(hash)
|
25
|
+
@hash = construct(hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
def construct(obj)
|
29
|
+
case obj
|
30
|
+
when Hash
|
31
|
+
return obj
|
32
|
+
when Array
|
33
|
+
hash = {}
|
34
|
+
obj.reverse.each_with_index do |val,i|
|
35
|
+
hash[val] = i+1
|
36
|
+
end
|
37
|
+
return hash
|
38
|
+
else
|
39
|
+
raise NotImplementedError, "expected Hash or Array, but got #{hash.class}: #{hash.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute
|
44
|
+
maps = []
|
45
|
+
stop = 0
|
46
|
+
@hash.each_pair do |val, key|
|
47
|
+
start = stop
|
48
|
+
stop += key
|
49
|
+
maps << [start...stop, val]
|
50
|
+
end
|
51
|
+
hit = rand(stop)
|
52
|
+
maps.each do |(range, val)|
|
53
|
+
return val if range === hit
|
54
|
+
end
|
55
|
+
return @hash.keys.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module ReplyApiFu
|
60
|
+
class Reply < RuntimeError
|
61
|
+
def initialize(text)
|
62
|
+
@text = text
|
63
|
+
end
|
64
|
+
|
65
|
+
def text
|
66
|
+
case @text
|
67
|
+
when Hash
|
68
|
+
WeightedRandomizer.new(@text).execute
|
69
|
+
else
|
70
|
+
@text.to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Nop < RuntimeError; end
|
76
|
+
|
77
|
+
def nop
|
78
|
+
raise Nop
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
include ReplyApiFu
|
83
|
+
|
84
|
+
######################################################################
|
85
|
+
### TvPlugin
|
86
|
+
|
87
|
+
STATION_ABBRS = {
|
88
|
+
'NHK' => 'NHK総合',
|
89
|
+
'NHKG' => 'NHK総合',
|
90
|
+
'NHKE' => 'NHK教育',
|
91
|
+
'ETV' => 'NHK教育',
|
92
|
+
'日テレ' => '日本テレビ',
|
93
|
+
'NTV' => '日本テレビ',
|
94
|
+
'TBS' => 'TBSテレビ',
|
95
|
+
'フジ' => 'フジテレビ',
|
96
|
+
'CX' => 'フジテレビ',
|
97
|
+
'テレ朝' => 'テレビ朝日',
|
98
|
+
'EX' => 'テレビ朝日',
|
99
|
+
'テレ東' => 'テレビ東京',
|
100
|
+
'TX' => 'テレビ東京',
|
101
|
+
'MX' => 'TOKYO MX',
|
102
|
+
'BS' => 'NHK衛星第一',
|
103
|
+
'BS1' => 'NHK衛星第一',
|
104
|
+
'BS2' => 'NHK衛星第二',
|
105
|
+
}
|
106
|
+
|
107
|
+
class Conversation
|
108
|
+
attr_reader :nick
|
109
|
+
attr_reader :messages
|
110
|
+
|
111
|
+
Message = Struct.new(:time, :text)
|
112
|
+
|
113
|
+
delegate :[], :first, :last, :clear, :to=>"@messages"
|
114
|
+
|
115
|
+
def initialize(nick)
|
116
|
+
@nick = nick
|
117
|
+
@messages = []
|
118
|
+
end
|
119
|
+
|
120
|
+
def <<(text)
|
121
|
+
messages << Message.new(Time.now, text.to_s)
|
122
|
+
end
|
123
|
+
|
124
|
+
class One < Conversation
|
125
|
+
def <<(text)
|
126
|
+
clear
|
127
|
+
super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
EagarProgram = Struct.new(:nick, :time, :program)
|
133
|
+
class EagarProgram
|
134
|
+
def push(program)
|
135
|
+
self.time = Time.now
|
136
|
+
self.program = program
|
137
|
+
end
|
138
|
+
|
139
|
+
def shift
|
140
|
+
self.time = nil
|
141
|
+
return program
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def reply(text)
|
146
|
+
respond_to_eagar_program
|
147
|
+
|
148
|
+
names = extract_station_names(text)
|
149
|
+
case text
|
150
|
+
when /キャスト/
|
151
|
+
do_cast(names.first)
|
152
|
+
when /(だれ|誰).*(\?|?)/
|
153
|
+
do_who(names.first)
|
154
|
+
else
|
155
|
+
do_summary(names)
|
156
|
+
end
|
157
|
+
|
158
|
+
nop
|
159
|
+
rescue Nop
|
160
|
+
return nil
|
161
|
+
rescue Reply => reply
|
162
|
+
return reply.text
|
163
|
+
end
|
164
|
+
|
165
|
+
def do_summary(names)
|
166
|
+
reports = names.map{|name| summary(current[name])}.compact
|
167
|
+
raise Reply, reports.join("\n")
|
168
|
+
end
|
169
|
+
|
170
|
+
def do_who(name)
|
171
|
+
prog = current[name]
|
172
|
+
if prog.casts.size > 0
|
173
|
+
raise Reply, "#{name}: #{prog.casts.join(',')}"
|
174
|
+
else
|
175
|
+
eagar_program_for(nick).push prog
|
176
|
+
raise Reply, {
|
177
|
+
"知るかよ!" => 80,
|
178
|
+
"#{nick}必死w" => 10,
|
179
|
+
"EPGには記述されていません" => 1,
|
180
|
+
}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def do_cast(name)
|
185
|
+
p = current[name]
|
186
|
+
query = "%s %s" % [p.title, name]
|
187
|
+
casts = TvCast.lookup!(query)
|
188
|
+
if casts.size > 0
|
189
|
+
raise Reply, "#{name}: #{casts.join(',')}"
|
190
|
+
else
|
191
|
+
raise Reply, "見つかりませんでした"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def extract_station_names(text)
|
198
|
+
text = text.sub(/(誰|キャスト)/, ',\1')
|
199
|
+
nouns = Chawan.parse(text.gsub(/\s+/,',')).compact.noun.map(&:to_s)
|
200
|
+
names = nouns.map{|name| STATION_ABBRS[name] || name} & station_names
|
201
|
+
nop if names.empty?
|
202
|
+
return names
|
203
|
+
end
|
204
|
+
|
205
|
+
def eagar_program_for(nick)
|
206
|
+
@eagar_programs ||= {}
|
207
|
+
@eagar_programs[nick] ||= EagarProgram.new(nick)
|
208
|
+
end
|
209
|
+
|
210
|
+
def respond_to_eagar_program
|
211
|
+
eager = eagar_program_for(nick)
|
212
|
+
if eager.time and Time.now.to_i < eager.time.to_i + 10 and prog = eager.shift
|
213
|
+
query = "%s %s" % [prog.title, prog.station_name]
|
214
|
+
casts = TvCast.lookup!(query)
|
215
|
+
if casts.size > 0
|
216
|
+
random = WeightedRandomizer.new(casts)
|
217
|
+
name = random.execute
|
218
|
+
raise Reply, {
|
219
|
+
"#{name}と思われ" => 50,
|
220
|
+
"#{name}?" => 50,
|
221
|
+
"#{name}の予感!" => 50,
|
222
|
+
"#{casts[0,2].join('か')}のどっちか" => 30,
|
223
|
+
"ググった限り#{name}ぽい>#{prog.title}" => 20,
|
224
|
+
"#{name}に大決定や!(確度#{(100/casts.size).to_i}%)" => 10,
|
225
|
+
casts.inspect => 2,
|
226
|
+
}
|
227
|
+
end
|
228
|
+
raise Reply, text
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def current
|
233
|
+
@current ||= TVGuide.current
|
234
|
+
end
|
235
|
+
|
236
|
+
def station_names
|
237
|
+
@station_names ||= current.station_names
|
238
|
+
end
|
239
|
+
|
240
|
+
def reporteds
|
241
|
+
@reporteds ||= {}
|
242
|
+
end
|
243
|
+
|
244
|
+
def summary(p)
|
245
|
+
nop if reporteds[p.id]
|
246
|
+
reporteds[p.id] = true
|
247
|
+
time1 = p.st.strftime("%H:%M") rescue ''
|
248
|
+
time2 = p.en.strftime("%H:%M") rescue ''
|
249
|
+
"#{time1}-#{time2} #{p.title} (#{p.sn})"
|
250
|
+
rescue
|
251
|
+
nop
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
if $0 == __FILE__
|
257
|
+
$KCODE = 'u'
|
258
|
+
require 'pp'
|
259
|
+
|
260
|
+
# pp TVGuide::TXML301PG.new.map
|
261
|
+
pp TvPlugin.new.reply('NHK総合')
|
262
|
+
end
|
data/spec/config_spec.rb
CHANGED
@@ -33,6 +33,7 @@ describe Ircbot::Client do
|
|
33
33
|
its(:real) { should == "sama~zu" }
|
34
34
|
its(:host) { should == "localhost" }
|
35
35
|
its(:port) { should == "6667" }
|
36
|
+
its(:unknown_key) { should == nil } # should not raise error
|
36
37
|
|
37
38
|
describe "#channels" do
|
38
39
|
it "should return the array when an array is given" do
|
data/spec/plugin_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ircbot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- maiha
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-27 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -52,8 +52,13 @@ files:
|
|
52
52
|
- lib/ircbot/client/config/plugins.rb
|
53
53
|
- lib/ircbot/client/config/generator.rb
|
54
54
|
- lib/ircbot/client/encoding.rb
|
55
|
+
- lib/ircbot/client/core.rb
|
55
56
|
- lib/ircbot/client/commands.rb
|
57
|
+
- lib/ircbot/client/standalone.rb
|
56
58
|
- lib/ircbot/client/plugins.rb
|
59
|
+
- lib/ircbot/client/logger.rb
|
60
|
+
- lib/ircbot/client/eventable.rb
|
61
|
+
- lib/ircbot/client/timeout.rb
|
57
62
|
- lib/ircbot/core_ext/extending.rb
|
58
63
|
- lib/ircbot/core_ext/message.rb
|
59
64
|
- lib/ircbot/core_ext/delegation.rb
|
@@ -69,11 +74,14 @@ files:
|
|
69
74
|
- spec/spec_helper.rb
|
70
75
|
- spec/framework_spec.rb
|
71
76
|
- spec/plugins_spec.rb
|
77
|
+
- plugins/tv.rb
|
78
|
+
- plugins/reminder.rb
|
72
79
|
- plugins/echo.rb
|
73
80
|
- plugins/plugins.rb
|
74
81
|
- plugins/which.rb
|
75
82
|
- plugins/irc.rb
|
76
83
|
- config/wota.yml
|
84
|
+
- config/airi.yml
|
77
85
|
- config/sama-zu.yml
|
78
86
|
- config/yml.erb
|
79
87
|
has_rdoc: true
|