ircbot 0.1.2 → 0.1.4
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.
- 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
|