ircbot 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +71 -0
  4. data/README +72 -3
  5. data/bin/ircbot +3 -0
  6. data/config/samples/postgres.yml +19 -0
  7. data/config/{sama-zu.yml → samples/sama-zu.yml} +1 -1
  8. data/config/{yml.erb → samples/yml.erb} +0 -0
  9. data/ircbot.gemspec +13 -0
  10. data/lib/ircbot.rb +3 -1
  11. data/lib/ircbot/client.rb +6 -0
  12. data/lib/ircbot/client/config.rb +9 -0
  13. data/lib/ircbot/client/plugins.rb +14 -1
  14. data/lib/ircbot/core_ext/message.rb +4 -1
  15. data/lib/ircbot/plugin.rb +17 -0
  16. data/lib/ircbot/plugins.rb +68 -13
  17. data/lib/ircbot/utils/html_parser.rb +26 -0
  18. data/lib/ircbot/utils/watcher.rb +36 -0
  19. data/lib/ircbot/version.rb +1 -1
  20. data/old/plugins/summary.cpi +267 -0
  21. data/plugins/plugins.rb +1 -1
  22. data/plugins/reminder.rb +79 -175
  23. data/plugins/summary/ch2.rb +272 -0
  24. data/plugins/summary/engines.rb +30 -0
  25. data/plugins/summary/engines/base.rb +105 -0
  26. data/plugins/summary/engines/ch2.rb +14 -0
  27. data/plugins/summary/engines/https.rb +6 -0
  28. data/plugins/summary/engines/none.rb +10 -0
  29. data/plugins/summary/engines/twitter.rb +16 -0
  30. data/plugins/summary/spec/ch2_spec.rb +64 -0
  31. data/plugins/summary/spec/spec_helper.rb +19 -0
  32. data/plugins/summary/spec/summarizers_none_spec.rb +15 -0
  33. data/plugins/summary/spec/summarizers_spec.rb +23 -0
  34. data/plugins/summary/summary.rb +58 -0
  35. data/plugins/watchdog/db.rb +80 -0
  36. data/plugins/watchdog/exceptions.rb +4 -0
  37. data/plugins/watchdog/updater.rb +21 -0
  38. data/plugins/watchdog/watchdog.rb +82 -0
  39. data/spec/plugin_spec.rb +11 -0
  40. data/spec/plugins_spec.rb +35 -1
  41. data/spec/utils/html_parser_spec.rb +30 -0
  42. data/spec/utils/spec_helper.rb +1 -0
  43. 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,4 @@
1
+ module Watchdog
2
+ ######################################################################
3
+ ### Exceptions
4
+ 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
+
@@ -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
 
@@ -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