dog-bot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem_spec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activesupport (3.2.7)
5
+ i18n (~> 0.6)
6
+ multi_json (~> 1.0)
7
+ blather (0.8.0)
8
+ activesupport (>= 2.3.11)
9
+ eventmachine (>= 0.12.6)
10
+ girl_friday
11
+ niceogiri (~> 1.0)
12
+ nokogiri (~> 1.5.5)
13
+ connection_pool (0.1.0)
14
+ eventmachine (0.12.10)
15
+ girl_friday (0.9.7)
16
+ connection_pool (~> 0.1.0)
17
+ google_image_api (0.0.1)
18
+ i18n (0.6.0)
19
+ multi_json (1.3.6)
20
+ niceogiri (1.0.2)
21
+ nokogiri (~> 1.4)
22
+ nokogiri (1.5.5)
23
+ rake (0.9.2.2)
24
+ rufus-scheduler (2.0.17)
25
+ tzinfo (>= 0.3.23)
26
+ tzinfo (0.3.33)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ blather (~> 0.8.0)
33
+ google_image_api
34
+ rake
35
+ rufus-scheduler
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ Dog [![Build Status](https://secure.travis-ci.org/benmills/dog.png)](http://travis-ci.org/benmills/dog)
2
+ ===
3
+
4
+ A man's best chat bot
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "spec/**/*_spec.rb"
5
+ end
6
+
7
+ task :default => :test
data/bin/dog ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ require "dog"
3
+
4
+ unless ARGV.count == 3
5
+ puts "Usage: dog [USER] [PASSWORD] [CONFIG PATH]"
6
+ exit 1
7
+ end
8
+
9
+ user, password, config_path = ARGV
10
+
11
+ xmpp_client = Blather::Client.setup user, password
12
+ xmpp_connection = Dog::Connection.new xmpp_client
13
+
14
+ @bot = Dog::Bot.new xmpp_connection, config_path
15
+
16
+ xmpp_client.register_handler(:ready) { @bot.config }
17
+ xmpp_client.register_handler(:message, :chat?, :body) { |m| @bot.process_chat_message m }
18
+ xmpp_client.register_handler(:message, :groupchat?, :body) { |m| @bot.process_group_chat_message m }
19
+
20
+ EM.run { xmpp_client.run }
@@ -0,0 +1,62 @@
1
+ task "say hi" do |t|
2
+ t.every "10min"
3
+ t.action { "Hello world!" }
4
+ end
5
+
6
+ command "get body of tweet" do |c|
7
+ c.matches /twitter\.com/
8
+ c.action do |message|
9
+ id = message.split("/").last
10
+ uri = URI.parse "https://api.twitter.com/1/statuses/show.json?id=#{id}"
11
+ tweet = JSON.parse uri.open.read
12
+
13
+ "#{tweet["text"]} - @#{tweet["user"]["screen_name"]}"
14
+ end
15
+ end
16
+
17
+ command "dog" do |c|
18
+ c.matches "dog"
19
+ c.action { "bark!" }
20
+
21
+ c.subcommand "xkcd" do |sc|
22
+ sc.matches "xkcd"
23
+ sc.action do
24
+ uri = URI.parse "http://xkcd.com/info.0.json"
25
+ comic = JSON.parse uri.open.read
26
+ "#{comic["img"]}\n#{comic["alt"]}"
27
+ end
28
+ end
29
+
30
+ c.subcommand "fetch image" do |sc|
31
+ sc.matches "fetch", "image"
32
+ sc.action do |message|
33
+ query = message.split(" ")[2..-1].join(" ")
34
+ GoogleImageApi.find(query).images.first["url"]
35
+ end
36
+ end
37
+
38
+ c.subcommand "ruby gems" do |sc|
39
+ sc.matches "gem"
40
+ sc.action do |message|
41
+ begin
42
+ gem_name = message.split(" ").last
43
+ uri = URI.parse "http://rubygems.org/api/v1/gems/#{gem_name}.json"
44
+ gem = JSON.parse uri.open.read
45
+
46
+ "'#{gem["name"]}' version: #{gem["version"]} downloads: #{gem["downloads"]}"
47
+ rescue
48
+ "gem not found"
49
+ end
50
+ end
51
+ end
52
+
53
+ c.subcommand "join channel" do |sc|
54
+ sc.matches "join"
55
+ sc.action { :join }
56
+ end
57
+
58
+ c.subcommand "reload config" do |sc|
59
+ sc.matches "reload"
60
+ sc.action { :reload }
61
+ end
62
+ end
data/lib/dog.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "rubygems"
2
+ require "blather/client/client"
3
+ require "rufus/scheduler"
4
+ require "uri"
5
+ require "open-uri"
6
+ require "json"
7
+ require "google_image_api"
8
+
9
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
10
+
11
+ require "dog/bot"
12
+ require "dog/configure"
13
+ require "dog/connection"
14
+ require "dog/command"
data/lib/dog/bot.rb ADDED
@@ -0,0 +1,106 @@
1
+ require 'rubygems'
2
+ require 'rufus/scheduler'
3
+
4
+ module Dog
5
+ class Bot
6
+ def initialize connection, config_path
7
+ @config_path = config_path
8
+ @connection = connection
9
+ @commands = []
10
+ @rooms = []
11
+ @data = {}
12
+ @data.default = {}
13
+ end
14
+
15
+ def process_chat_message message
16
+ response = process message.body
17
+ @connection.say message.from, response unless response.nil?
18
+ end
19
+
20
+ def process_group_chat_message message
21
+ return if _from_self(message) || message.delayed?
22
+
23
+ response = process message.body
24
+ @connection.say_to_chat message.from.node, response unless response.nil?
25
+ end
26
+
27
+ def process message
28
+ response = respond_to message
29
+
30
+ if response.is_a? Symbol
31
+ respond_to_action message, response
32
+ else
33
+ response
34
+ end
35
+ end
36
+
37
+ def respond_to text
38
+ @commands.each do |command|
39
+ response = command.respond_to text
40
+ return response unless response.nil?
41
+ end
42
+
43
+ nil
44
+ end
45
+
46
+ def respond_to_action message, kind
47
+ case kind
48
+ when :join then
49
+ room_name = message.split.last
50
+ join room_name
51
+ "joined #{room_name}"
52
+ when :reload then
53
+ config
54
+ "config reloaded"
55
+ else "invalid action"
56
+ end
57
+ end
58
+
59
+ def join room_name
60
+ @connection.join room_name
61
+ @rooms << room_name
62
+ end
63
+
64
+ def config
65
+ config_string = File.read @config_path
66
+ config = Configure.parse config_string
67
+
68
+ @commands = config.commands
69
+ @scheduled_tasks = config.scheduled_tasks
70
+
71
+ schedule_tasks
72
+ end
73
+
74
+ def schedule_tasks
75
+ @scheduler.stop unless @scheduler.nil?
76
+
77
+ @scheduler = Rufus::Scheduler.start_new
78
+
79
+ @scheduled_tasks.each do |task|
80
+ @scheduler.every task.frequency do
81
+ response = task.run self
82
+ next if response.nil?
83
+
84
+ @rooms.each do |room|
85
+ @connection.say_to_chat room, response
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def save_data key, val
92
+ @data[key] = val
93
+ end
94
+
95
+ def get_data key
96
+ @data[key]
97
+ end
98
+
99
+ def _from_self message
100
+ @rooms.each do |room|
101
+ return true if "#{room}@conference.#{@connection.jid.domain}/#{@connection.jid.node}" == message.from
102
+ end
103
+ false
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,50 @@
1
+ module Dog
2
+ class Command
3
+ def initialize title
4
+ @title = title
5
+ @matchers = []
6
+ @subcommands = []
7
+ @action = -> { }
8
+ end
9
+
10
+ def action &action
11
+ @action = action
12
+ end
13
+
14
+ def matches *matchers
15
+ @matchers += matchers
16
+ end
17
+
18
+ def matches? input_string
19
+ @matchers.any? do |matcher|
20
+ if matcher.is_a? String
21
+ input_string.match /(\s|^)#{matcher}(\s|$)/
22
+ else
23
+ input_string.match matcher
24
+ end
25
+ end
26
+ end
27
+
28
+ def subcommand title
29
+ subcommand = Command.new title
30
+ yield subcommand
31
+ @subcommands << subcommand
32
+ end
33
+
34
+ def subcommand_response input_string
35
+ @subcommands.each do |subcommand|
36
+ response = subcommand.respond_to input_string
37
+ return response unless response.nil?
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ def respond_to input_string
44
+ if matches? input_string
45
+ sc_res = subcommand_response input_string
46
+ sc_res || @action.call(input_string)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "command"
2
+ require_relative "scheduled_task"
3
+
4
+ module Dog
5
+ class Configure
6
+ def self.parse config_string
7
+ config = Configure.new
8
+ config.instance_eval config_string
9
+ config
10
+ end
11
+
12
+ attr_reader :commands, :scheduled_tasks
13
+
14
+ def initialize
15
+ @commands = []
16
+ @scheduled_tasks = []
17
+ end
18
+
19
+ def command title
20
+ command = Command.new title
21
+ yield command
22
+ @commands << command
23
+ end
24
+
25
+ def task title
26
+ task = ScheduledTask.new title
27
+ yield task
28
+ @scheduled_tasks << task
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Dog
2
+ class Connection
3
+ def initialize client
4
+ @client = client
5
+ end
6
+
7
+ def jid
8
+ @client.jid
9
+ end
10
+
11
+ def join room_name
12
+ room = "#{room_name}@conference.#{jid.domain}"
13
+ service = jid.node
14
+
15
+ join_stanza = Blather::Stanza::Presence::MUC.new
16
+ join_stanza.to = "#{room}/#{service}"
17
+
18
+ @client.write join_stanza
19
+ end
20
+
21
+ def say to, text
22
+ @client.write Blather::Stanza::Message.new(to, text, :chat)
23
+ end
24
+
25
+ def say_to_chat room_name, text
26
+ to = Blather::JID.new room_name, "conference.#{jid.domain}"
27
+ @client.write Blather::Stanza::Message.new(to, text, :groupchat)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ module Dog
2
+ class ScheduledTask
3
+ attr_reader :frequency, :title
4
+
5
+ def initialize title
6
+ @title = title
7
+ end
8
+
9
+ def every frequency
10
+ @frequency = frequency
11
+ end
12
+
13
+ def action &block
14
+ @action = block
15
+ end
16
+
17
+ def run context
18
+ @action.call context
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,100 @@
1
+ require "ostruct"
2
+ require "minitest/spec"
3
+ require "minitest/autorun"
4
+
5
+ require_relative "../../lib/dog/bot"
6
+ require_relative "../../lib/dog/command"
7
+
8
+ class FakeConnection
9
+ attr_reader :output, :chat_output, :rooms
10
+
11
+ def initialize
12
+ @output = []
13
+ @chat_output = []
14
+ @rooms = []
15
+ end
16
+
17
+ def say from, response
18
+ @output << [from, response]
19
+ end
20
+
21
+ def say_to_chat from, response
22
+ @chat_output << [from, response]
23
+ end
24
+
25
+ def join room_name
26
+ @rooms << room_name
27
+ end
28
+
29
+ def jid
30
+ OpenStruct.new(
31
+ :jid => OpenStruct.new(:domain => "example.com"),
32
+ :node => "dog"
33
+ )
34
+ end
35
+ end
36
+
37
+ class Dog::Bot
38
+ def config
39
+ command = Dog::Command.new "greet"
40
+ command.matches "hi"
41
+ command.action { "hello" }
42
+ @commands = [command]
43
+ end
44
+ end
45
+
46
+ describe Dog::Bot do
47
+ let(:connection) { FakeConnection.new }
48
+ subject { Dog::Bot.new connection, "config" }
49
+
50
+ before(:each) { subject.config }
51
+
52
+ describe ".process_chat_message" do
53
+ it "processes a message that matches" do
54
+ message = OpenStruct.new :from => "bob", :body => "hi dog"
55
+ subject.process_chat_message message
56
+ connection.output.last.must_equal ["bob", "hello"]
57
+ end
58
+
59
+ it "doesn't process a non-matching message" do
60
+ message = OpenStruct.new :from => "bob", :body => "bad string"
61
+ subject.process_chat_message message
62
+ connection.output.must_be_empty
63
+ end
64
+ end
65
+
66
+ describe ".process_group_chat_message" do
67
+ it "processes a message that matches" do
68
+ message = OpenStruct.new(
69
+ :from => OpenStruct.new(:node => "bob", :domain => "chat"),
70
+ :body => "hi dog",
71
+ :delayed? => false
72
+ )
73
+ subject.process_group_chat_message message
74
+ connection.chat_output.last.must_equal ["bob", "hello"]
75
+ end
76
+
77
+ it "doesn't process a non-matching message" do
78
+ message = OpenStruct.new(
79
+ :from => OpenStruct.new(:node => "bob", :domain => "chat"),
80
+ :body => "bad string",
81
+ :delayed? => false
82
+ )
83
+ subject.process_group_chat_message message
84
+ connection.chat_output.must_be_empty
85
+ end
86
+ end
87
+
88
+ describe ".respond_to_action" do
89
+ it "joins a room" do
90
+ output = subject.respond_to_action "dog join chatroom", :join
91
+ output.must_equal "joined chatroom"
92
+ connection.rooms.last.must_equal "chatroom"
93
+ end
94
+
95
+ it "reloads" do
96
+ output = subject.respond_to_action "dog reload", :reload
97
+ output.must_equal "config reloaded"
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,72 @@
1
+ require "minitest/spec"
2
+ require "minitest/autorun"
3
+ require_relative "../../lib/dog/command.rb"
4
+
5
+ describe Dog::Command do
6
+ subject do
7
+ command = Dog::Command.new "Greet"
8
+ command.matches "hello", /example\.com/
9
+ command.action { "hi" }
10
+ command.subcommand "foo" do |subcommand|
11
+ subcommand.matches "foo"
12
+ subcommand.action { "bar" }
13
+ end
14
+
15
+ command
16
+ end
17
+
18
+ describe ".action" do
19
+ it "sets the action of the command" do
20
+ subject.action { "new action" }
21
+ subject.respond_to("hello").must_equal "new action"
22
+ end
23
+
24
+ it "passes the body of the incoming text to the action" do
25
+ subject.action { |body| body }
26
+ subject.respond_to("hello foobarbaz1").must_equal "hello foobarbaz1"
27
+ end
28
+ end
29
+
30
+ describe ".matches" do
31
+ it "adds a matcher to the command" do
32
+ subject.matches "newmatcher"
33
+ subject.respond_to("newmatcher").must_equal "hi"
34
+ end
35
+
36
+ it "can be called multiple times" do
37
+ subject.matches "firstnewmatcher"
38
+ subject.matches "secondnewmatcher"
39
+
40
+ subject.respond_to("firstnewmatcher").must_equal "hi"
41
+ subject.respond_to("secondnewmatcher").must_equal "hi"
42
+ end
43
+ end
44
+
45
+ describe ".respond_to" do
46
+ it "calls it's block if the input string matches a matcher" do
47
+ subject.respond_to("hello").must_equal "hi"
48
+ end
49
+
50
+ it "calls it's block if a matcher matches a multi-word input string" do
51
+ subject.respond_to("hello dog!").must_equal "hi"
52
+ end
53
+
54
+ it "returns nil if no matcher maches" do
55
+ subject.respond_to("bad input").must_be_nil
56
+ end
57
+
58
+ it "matches only words" do
59
+ subject.respond_to("hellonomatch").must_be_nil
60
+ end
61
+
62
+ it "can use a regex matcher" do
63
+ subject.respond_to("http:/www.example.com").must_equal "hi"
64
+ end
65
+ end
66
+
67
+ describe ".subcommand" do
68
+ it "matches subcommands" do
69
+ subject.respond_to("hello foo").must_equal "bar"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ require "minitest/spec"
2
+ require "minitest/autorun"
3
+ require_relative "../../lib/dog/configure.rb"
4
+
5
+ describe Dog::Configure do
6
+ let :command_config_string do
7
+ <<CONFIG
8
+ command "greet" do |c|
9
+ c.matches "hello", "hi", "whats up"
10
+ c.action { "hello!" }
11
+
12
+ c.subcommand "cool greet" do |subcommand|
13
+ subcommand.matches "yo"
14
+ subcommand.action { "yo yo yo, hello!" }
15
+ end
16
+ end
17
+ CONFIG
18
+ end
19
+
20
+ let :task_config_string do
21
+ <<CONFIG
22
+ task "say hi" do |t|
23
+ t.every "1m"
24
+ t.action { "hello!" }
25
+ end
26
+ CONFIG
27
+ end
28
+
29
+ describe "#parse" do
30
+ it "parses a string into an array of commands" do
31
+ config = Dog::Configure.parse command_config_string
32
+ command = config.commands.first
33
+
34
+ command.respond_to("hi").must_equal "hello!"
35
+ command.respond_to("hello").must_equal "hello!"
36
+ command.respond_to("whats up").must_equal "hello!"
37
+ end
38
+
39
+ it "parses subcommands" do
40
+ config = Dog::Configure.parse command_config_string
41
+ command = config.commands.first
42
+
43
+ command.respond_to("hi yo").must_equal "yo yo yo, hello!"
44
+ end
45
+
46
+ it "parses a string into an array of scheduled tasks" do
47
+ config = Dog::Configure.parse task_config_string
48
+ task = config.scheduled_tasks.first
49
+
50
+ task.run({}).must_equal "hello!"
51
+ task.frequency.must_equal "1m"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ require "minitest/spec"
2
+ require "minitest/autorun"
3
+ require_relative "../../lib/dog/scheduled_task.rb"
4
+
5
+ describe Dog::ScheduledTask do
6
+ subject { Dog::ScheduledTask.new "my task" }
7
+
8
+ describe ".every" do
9
+ it "sets the frequency of the task" do
10
+ subject.every "1m"
11
+ subject.frequency.must_equal "1m"
12
+ end
13
+ end
14
+
15
+ describe ".action" do
16
+ it "sets the action of the task" do
17
+ subject.action { "my action" }
18
+ subject.run({}).must_equal "my action"
19
+ end
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dog-bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ben Mills
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: blather
16
+ requirement: &17501260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.8.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *17501260
25
+ - !ruby/object:Gem::Dependency
26
+ name: google_image_api
27
+ requirement: &17500320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 0.0.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *17500320
36
+ - !ruby/object:Gem::Dependency
37
+ name: rufus-scheduler
38
+ requirement: &17499020 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.0.17
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *17499020
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &17498200 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.2.2
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *17498200
58
+ description: Chatbot
59
+ email:
60
+ - ben@bmdev.org
61
+ executables:
62
+ - dog
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - .travis.yml
68
+ - Gemfile
69
+ - Gemfile.lock
70
+ - README.md
71
+ - Rakefile
72
+ - dog_config_example.rb
73
+ - lib/dog.rb
74
+ - lib/dog/bot.rb
75
+ - lib/dog/command.rb
76
+ - lib/dog/configure.rb
77
+ - lib/dog/connection.rb
78
+ - lib/dog/scheduled_task.rb
79
+ - spec/dog/bot_spec.rb
80
+ - spec/dog/command_spec.rb
81
+ - spec/dog/configure_spec.rb
82
+ - spec/dog/scheduled_task_spec.rb
83
+ - bin/dog
84
+ homepage: https://github.com/benmills/dog
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.11
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Extensible XMPP chatbot
108
+ test_files:
109
+ - spec/dog/bot_spec.rb
110
+ - spec/dog/command_spec.rb
111
+ - spec/dog/configure_spec.rb
112
+ - spec/dog/scheduled_task_spec.rb