dog-bot 0.0.1

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