ircbot 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +71 -0
- data/README +72 -3
- data/bin/ircbot +3 -0
- data/config/samples/postgres.yml +19 -0
- data/config/{sama-zu.yml → samples/sama-zu.yml} +1 -1
- data/config/{yml.erb → samples/yml.erb} +0 -0
- data/ircbot.gemspec +13 -0
- data/lib/ircbot.rb +3 -1
- data/lib/ircbot/client.rb +6 -0
- data/lib/ircbot/client/config.rb +9 -0
- data/lib/ircbot/client/plugins.rb +14 -1
- data/lib/ircbot/core_ext/message.rb +4 -1
- data/lib/ircbot/plugin.rb +17 -0
- data/lib/ircbot/plugins.rb +68 -13
- data/lib/ircbot/utils/html_parser.rb +26 -0
- data/lib/ircbot/utils/watcher.rb +36 -0
- data/lib/ircbot/version.rb +1 -1
- data/old/plugins/summary.cpi +267 -0
- data/plugins/plugins.rb +1 -1
- data/plugins/reminder.rb +79 -175
- data/plugins/summary/ch2.rb +272 -0
- data/plugins/summary/engines.rb +30 -0
- data/plugins/summary/engines/base.rb +105 -0
- data/plugins/summary/engines/ch2.rb +14 -0
- data/plugins/summary/engines/https.rb +6 -0
- data/plugins/summary/engines/none.rb +10 -0
- data/plugins/summary/engines/twitter.rb +16 -0
- data/plugins/summary/spec/ch2_spec.rb +64 -0
- data/plugins/summary/spec/spec_helper.rb +19 -0
- data/plugins/summary/spec/summarizers_none_spec.rb +15 -0
- data/plugins/summary/spec/summarizers_spec.rb +23 -0
- data/plugins/summary/summary.rb +58 -0
- data/plugins/watchdog/db.rb +80 -0
- data/plugins/watchdog/exceptions.rb +4 -0
- data/plugins/watchdog/updater.rb +21 -0
- data/plugins/watchdog/watchdog.rb +82 -0
- data/spec/plugin_spec.rb +11 -0
- data/spec/plugins_spec.rb +35 -1
- data/spec/utils/html_parser_spec.rb +30 -0
- data/spec/utils/spec_helper.rb +1 -0
- metadata +190 -13
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
require 'ch2'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module RSpec
|
7
|
+
module Core
|
8
|
+
module SharedExampleGroup
|
9
|
+
def ch2(url, &block)
|
10
|
+
describe "(#{url})" do
|
11
|
+
subject { Ch2::Dat.new(url) }
|
12
|
+
instance_eval(&block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "Ch2::Dat" do
|
20
|
+
ch2 'http://news22.2ch.net/test/read.cgi/newsplus/1185716060' do
|
21
|
+
its(:host) { should == "news22.2ch.net" }
|
22
|
+
its(:board) { should == "newsplus" }
|
23
|
+
its(:num) { should == "1185716060" }
|
24
|
+
its(:arg) { should == nil }
|
25
|
+
its(:dat_url) { should == "http://news22.2ch.net/newsplus/dat/1185716060.dat" }
|
26
|
+
its(:valid?) { should == true }
|
27
|
+
end
|
28
|
+
|
29
|
+
ch2 'http://news22.2ch.net/test/read.cgi/newsplus/1185716060/430' do
|
30
|
+
its(:host) { should == "news22.2ch.net" }
|
31
|
+
its(:board) { should == "newsplus" }
|
32
|
+
its(:num) { should == "1185716060" }
|
33
|
+
its(:arg) { should == "430" }
|
34
|
+
its(:dat_url) { should == "http://news22.2ch.net/newsplus/dat/1185716060.dat" }
|
35
|
+
its(:valid?) { should == true }
|
36
|
+
end
|
37
|
+
|
38
|
+
ch2 'http://news22.2ch.net/test/read.cgi/newsplus/1185716060/n' do
|
39
|
+
its(:host) { should == "news22.2ch.net" }
|
40
|
+
its(:board) { should == "newsplus" }
|
41
|
+
its(:num) { should == "1185716060" }
|
42
|
+
its(:arg) { should == "n" }
|
43
|
+
its(:dat_url) { should == "http://news22.2ch.net/newsplus/dat/1185716060.dat" }
|
44
|
+
its(:valid?) { should == true }
|
45
|
+
end
|
46
|
+
|
47
|
+
ch2 'http://news22.2ch.net/test/read.cgi/newsplus/1185716060/5-10' do
|
48
|
+
its(:host) { should == "news22.2ch.net" }
|
49
|
+
its(:board) { should == "newsplus" }
|
50
|
+
its(:num) { should == "1185716060" }
|
51
|
+
its(:arg) { should == "5-10" }
|
52
|
+
its(:dat_url) { should == "http://news22.2ch.net/newsplus/dat/1185716060.dat" }
|
53
|
+
its(:valid?) { should == true }
|
54
|
+
end
|
55
|
+
|
56
|
+
ch2 'http://google.com' do
|
57
|
+
its(:host) { should == "google.com" }
|
58
|
+
its(:board) { should == nil }
|
59
|
+
its(:num) { should == nil }
|
60
|
+
its(:arg) { should == nil }
|
61
|
+
its(:valid?) { should == false }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'pathname'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'engines'
|
6
|
+
|
7
|
+
module RSpec
|
8
|
+
module Core
|
9
|
+
module SharedExampleGroup
|
10
|
+
def summary(url, &block)
|
11
|
+
describe "(#{url})" do
|
12
|
+
subject { Engines.create(url) }
|
13
|
+
instance_eval(&block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
require 'engines'
|
4
|
+
|
5
|
+
describe Engines::None do
|
6
|
+
subject { Engines::None.new('') }
|
7
|
+
|
8
|
+
describe "#execute" do
|
9
|
+
it "should raise Nop" do
|
10
|
+
lambda {
|
11
|
+
subject.execute
|
12
|
+
}.should raise_error(Engines::Nop)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
describe Engines do
|
4
|
+
summary "https://example.com" do
|
5
|
+
its(:class) {should == Engines::Https}
|
6
|
+
end
|
7
|
+
|
8
|
+
summary "https://twitter.com" do
|
9
|
+
its(:class) {should == Engines::Twitter}
|
10
|
+
end
|
11
|
+
|
12
|
+
summary "http://hayabusa3.2ch.net/test/read.cgi/morningcoffee/1333357582/" do
|
13
|
+
its(:class) {should == Engines::Ch2}
|
14
|
+
end
|
15
|
+
|
16
|
+
summary "http://www.asahi.com" do
|
17
|
+
its(:class) {should == Engines::None}
|
18
|
+
end
|
19
|
+
|
20
|
+
summary "" do
|
21
|
+
its(:class) {should == Engines::None}
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ircbot'
|
5
|
+
require 'uri'
|
6
|
+
require 'nkf'
|
7
|
+
require 'engines'
|
8
|
+
|
9
|
+
######################################################################
|
10
|
+
# [INSTALL]
|
11
|
+
# apt-get install curl
|
12
|
+
#
|
13
|
+
# [TEST]
|
14
|
+
# cd plugins/summary
|
15
|
+
# rspec -c spec
|
16
|
+
|
17
|
+
class SummaryPlugin < Ircbot::Plugin
|
18
|
+
Quote = ">> "
|
19
|
+
|
20
|
+
def help
|
21
|
+
"[Summary] summarize web page (responds to only 2ch or https)"
|
22
|
+
end
|
23
|
+
|
24
|
+
def reply(text)
|
25
|
+
scan_urls(text).each do |url|
|
26
|
+
summary = once(url) {
|
27
|
+
Engines.create(url).execute
|
28
|
+
}
|
29
|
+
done(Quote + summary) if summary
|
30
|
+
end
|
31
|
+
return nil
|
32
|
+
|
33
|
+
rescue Engines::NotImplementedError => e
|
34
|
+
$stderr.puts e
|
35
|
+
return nil
|
36
|
+
rescue Engines::Nop
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def scan_urls(text, &block)
|
42
|
+
URI.extract(text).map{|i| i.sub(/^ttp:/, 'http:')}
|
43
|
+
end
|
44
|
+
|
45
|
+
def once(key, &block)
|
46
|
+
@once ||= {}
|
47
|
+
raise Engines::Nop if @once.has_key?(key)
|
48
|
+
return block.call
|
49
|
+
ensure
|
50
|
+
@once[key] = 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
if __FILE__ == $0
|
56
|
+
p summarizer = Engines.create(ARGV.shift)
|
57
|
+
puts summarizer.execute
|
58
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'dm-core'
|
3
|
+
require 'dm-migrations'
|
4
|
+
require 'dm-timestamps'
|
5
|
+
|
6
|
+
require 'nkf'
|
7
|
+
|
8
|
+
module Watchdog
|
9
|
+
REPOSITORY_NAME = :watchdog
|
10
|
+
|
11
|
+
def self.connect(uri)
|
12
|
+
DataMapper.setup(REPOSITORY_NAME, uri)
|
13
|
+
Watchdog::Page.auto_upgrade!
|
14
|
+
end
|
15
|
+
|
16
|
+
######################################################################
|
17
|
+
### Page
|
18
|
+
|
19
|
+
class Page
|
20
|
+
def self.default_repository_name; REPOSITORY_NAME; end
|
21
|
+
def self.default_storage_name ; "page"; end
|
22
|
+
|
23
|
+
include DataMapper::Resource
|
24
|
+
|
25
|
+
property :id , Serial
|
26
|
+
property :name , String, :length=>255 # 件名
|
27
|
+
property :url , String, :length=>255 # 詳細
|
28
|
+
property :digest , String, :length=>255 # DIGEST値
|
29
|
+
property :changed , Boolean , :default=>false # 更新済
|
30
|
+
property :start_at , DateTime #
|
31
|
+
|
32
|
+
######################################################################
|
33
|
+
### Class methods
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def changed
|
37
|
+
all(:changed=>true, :order=>[:id])
|
38
|
+
end
|
39
|
+
|
40
|
+
def current
|
41
|
+
all(:changed=>false, :order=>[:id]).select{|p|
|
42
|
+
! p.start_at or p.start_at.to_time <= Time.now
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
######################################################################
|
48
|
+
### Operations
|
49
|
+
|
50
|
+
include Ircbot::Utils::HtmlParser
|
51
|
+
|
52
|
+
def update!
|
53
|
+
html = Open3.popen3("curl", url) {|i,o,e| o.read{} }
|
54
|
+
utf8 = NKF.nkf("-w", html)
|
55
|
+
hex = Digest::SHA1.hexdigest(utf8)
|
56
|
+
self[:changed] = !! ( self[:changed] || (digest && (digest != hex)) )
|
57
|
+
self[:name] = get_title(utf8)
|
58
|
+
self[:digest] = hex
|
59
|
+
save
|
60
|
+
return self[:changed]
|
61
|
+
end
|
62
|
+
|
63
|
+
######################################################################
|
64
|
+
### Instance methods
|
65
|
+
|
66
|
+
def done!
|
67
|
+
self[:changed] = false
|
68
|
+
save
|
69
|
+
end
|
70
|
+
|
71
|
+
def cooltime!(sec)
|
72
|
+
self[:start_at] = Time.now + sec
|
73
|
+
done!
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"#{name} #{url}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Watchdog
|
5
|
+
class Updater < Ircbot::Utils::Watcher
|
6
|
+
interval 600
|
7
|
+
|
8
|
+
def srcs
|
9
|
+
Page.current
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(page)
|
13
|
+
status = page.to_s
|
14
|
+
page.update!
|
15
|
+
status = "#{page} (#{page.changed}: #{page.digest})"
|
16
|
+
return page.changed
|
17
|
+
ensure
|
18
|
+
$stderr.puts "#{self.class}#process: #{status}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,82 @@
|
|
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 dm-sqlite-adapter -V
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'ircbot'
|
12
|
+
require 'uri'
|
13
|
+
|
14
|
+
require 'watchdog/exceptions'
|
15
|
+
require 'watchdog/db'
|
16
|
+
require 'watchdog/updater'
|
17
|
+
|
18
|
+
class WatchdogPlugin < Ircbot::Plugin
|
19
|
+
INTERVAL = 600 # re-fetch urls after this sec
|
20
|
+
COOLTIME = 3600 # wait this sec when changed
|
21
|
+
|
22
|
+
def help
|
23
|
+
["#{config.nick}.watchdog.list",
|
24
|
+
"#{config.nick}.watchdog.add <URL>",
|
25
|
+
"#{config.nick}.watchdog.del <URL>",
|
26
|
+
].join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup
|
30
|
+
return if @watcher
|
31
|
+
bot = self.bot
|
32
|
+
|
33
|
+
uri = self[:db]
|
34
|
+
unless uri
|
35
|
+
path = Ircbot.root + "db" + "#{config.nick}-watchdog.db"
|
36
|
+
uri = "sqlite3://#{path}"
|
37
|
+
path.parent.mkpath
|
38
|
+
end
|
39
|
+
|
40
|
+
Watchdog.connect(uri)
|
41
|
+
callback = proc{|page| bot.broadcast "Updated: #{page}"; page.cooltime!(COOLTIME) }
|
42
|
+
updater = Watchdog::Updater.new(:interval => INTERVAL, :callback => callback)
|
43
|
+
@watcher = updater.start
|
44
|
+
end
|
45
|
+
|
46
|
+
def add(text)
|
47
|
+
count = 0
|
48
|
+
urls = URI.extract(text).map{|i| i.sub(/^ttp:/, 'http:')}
|
49
|
+
urls.each do |url|
|
50
|
+
next if Watchdog::Page.first(:url=>url)
|
51
|
+
page = Watchdog::Page.create!(:url=>url)
|
52
|
+
page.update!
|
53
|
+
count += 1
|
54
|
+
end
|
55
|
+
return "Added #{count} urls"
|
56
|
+
end
|
57
|
+
|
58
|
+
def del(text)
|
59
|
+
count = 0
|
60
|
+
urls = URI.extract(text).map{|i| i.sub(/^ttp:/, 'http:')}
|
61
|
+
urls.each do |url|
|
62
|
+
page = Watchdog::Page.first(:url=>url)
|
63
|
+
if page
|
64
|
+
page.destroy
|
65
|
+
count += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
return "Deleted #{count} urls"
|
69
|
+
end
|
70
|
+
|
71
|
+
def list
|
72
|
+
pages = Watchdog::Page.all
|
73
|
+
if pages.size == 0
|
74
|
+
return "no watchdogs"
|
75
|
+
else
|
76
|
+
lead = "#{pages.size} watchdog(s)"
|
77
|
+
body = pages.map(&:to_s)[0,5]
|
78
|
+
return ([lead] + body).join("\n")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
data/spec/plugin_spec.rb
CHANGED
@@ -30,6 +30,17 @@ describe Ircbot::Plugin do
|
|
30
30
|
provide :client
|
31
31
|
provide :bot
|
32
32
|
|
33
|
+
provide :attrs=
|
34
|
+
provide :[]
|
35
|
+
describe "#[]" do
|
36
|
+
it "should return attrs" do
|
37
|
+
foo = Foo.new
|
38
|
+
foo[:a].should == nil
|
39
|
+
foo.attrs = {:a=>1}
|
40
|
+
foo[:a].should == 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
33
44
|
######################################################################
|
34
45
|
### private methods
|
35
46
|
|
data/spec/plugins_spec.rb
CHANGED
@@ -10,7 +10,6 @@ describe Ircbot::Plugins do
|
|
10
10
|
provide :client
|
11
11
|
provide :plugins
|
12
12
|
provide :active
|
13
|
-
provide :load_plugins
|
14
13
|
provide :load
|
15
14
|
provide :plugin!
|
16
15
|
provide :plugin
|
@@ -29,4 +28,39 @@ describe Ircbot::Plugins do
|
|
29
28
|
subject.class.ancestors.should include(Enumerable)
|
30
29
|
end
|
31
30
|
|
31
|
+
######################################################################
|
32
|
+
### Initializer
|
33
|
+
|
34
|
+
describe ".new" do
|
35
|
+
let(:client) { nil }
|
36
|
+
let(:args) { [] }
|
37
|
+
|
38
|
+
it "should accept plugin names(Array)" do
|
39
|
+
plugins = Ircbot::Plugins.new(client, ["summary", "reminder"])
|
40
|
+
plugins.active_names.should == ["summary", "reminder"]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should accept plugin attrs(Array(Hash))" do
|
44
|
+
args = [
|
45
|
+
{"name"=>"summary", "db"=>"sqlite:db.sqlite"},
|
46
|
+
{"name"=>"reminder"},
|
47
|
+
]
|
48
|
+
plugins = Ircbot::Plugins.new(client, args)
|
49
|
+
plugins.active_names.should == ["summary", "reminder"]
|
50
|
+
|
51
|
+
plugins["summary"]["db"].should == "sqlite:db.sqlite"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should ignore when plugin name is not set in attrs" do
|
55
|
+
args = [
|
56
|
+
{"db"=>"sqlite:db.sqlite"},
|
57
|
+
{"name"=>"reminder"},
|
58
|
+
]
|
59
|
+
plugins = Ircbot::Plugins.new(client)
|
60
|
+
plugins.should_receive(:invalid_plugin_found)
|
61
|
+
plugins.load(args)
|
62
|
+
|
63
|
+
plugins.active_names.should == ["reminder"]
|
64
|
+
end
|
65
|
+
end
|
32
66
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
4
|
+
|
5
|
+
module TestHtmlParserGetTitle
|
6
|
+
def get_title(html, title)
|
7
|
+
describe "(#{html})" do
|
8
|
+
subject { Object.new.extend Ircbot::Utils::HtmlParser }
|
9
|
+
it "should return #{title}" do
|
10
|
+
subject.get_title(html).should == title
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Ircbot::Utils::HtmlParser do
|
17
|
+
subject { Object.new.extend Ircbot::Utils::HtmlParser }
|
18
|
+
|
19
|
+
######################################################################
|
20
|
+
### accessor methods
|
21
|
+
|
22
|
+
provide :get_title
|
23
|
+
|
24
|
+
describe "#get_title" do
|
25
|
+
extend TestHtmlParserGetTitle
|
26
|
+
get_title "xxx", ""
|
27
|
+
get_title "xxx<title>yyy</title>zzz", "yyy"
|
28
|
+
get_title "xxx<title><a>yyy</a></title>zzz", "yyy"
|
29
|
+
end
|
30
|
+
end
|