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.
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