ircbot 0.1.5 → 0.2.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.
- 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
|