daneel 0.0.1 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,18 +1,12 @@
1
- *.gem
2
- *.rbc
3
- .bundle/
4
- .config/
5
- .yardoc/
6
- _yardoc/
7
- Gemfile.lock
8
- InstalledFiles
9
- coverage/
10
- doc/
11
- lib/bundler/man/
12
- pkg/
13
- rdoc/
14
- spec/reports/
15
- test/tmp/
16
- test/version_tmp/
17
- tmp/
18
- vendor/bundle/
1
+ /*.gem
2
+ /.bundle/
3
+ /.env
4
+ /.yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /exec/
8
+ /Gemfile.lock
9
+ /pkg/
10
+ /rdoc/
11
+ /tmp/
12
+ /vendor/bundle/
data/Gemfile CHANGED
@@ -2,3 +2,11 @@ source 'http://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in daneel.gemspec
4
4
  gemspec
5
+
6
+ # Override gem dependencies that aren't released yet
7
+ gem 'sinatra', :github => 'sinatra'
8
+ gem 'sparks', :github => 'indirect/sparks', :branch => 'master'
9
+
10
+ group :development do
11
+ gem 'pry-debugger'
12
+ end
@@ -0,0 +1 @@
1
+ web: ruby -Ku -rbundler/setup -Ilib -S ./bin/daneel -v -s -a campfire -n "wes" -f "Wesabot"
@@ -0,0 +1,18 @@
1
+ require 'daneel'
2
+
3
+ trap(:INT) do
4
+ puts "Quitting...\n"
5
+ exit 0
6
+ end
7
+
8
+ options = Daneel::Options.parse(ARGV)
9
+ options[:logger] = Daneel::Logger.new(STDOUT, options[:verbose])
10
+
11
+ if options.has_key?(:server)
12
+ require 'daneel/server'
13
+ sopts = options.delete(:server)
14
+ sopts[:logger] = options[:logger]
15
+ Daneel::Server.new(sopts).run
16
+ end
17
+
18
+ Daneel::Bot.new(options).run
@@ -1,20 +1,27 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/daneel/version', __FILE__)
1
+ # encoding: UTF-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'daneel/version'
3
5
 
4
6
  Gem::Specification.new do |gem|
5
- gem.authors = ["Andre Arko"]
7
+ gem.name = "daneel"
8
+ gem.version = Daneel::VERSION
9
+ gem.summary = %q{A 19,230-year-old robot}
10
+ gem.description = %q{Daneel is a chatbot inspired by the late, lamented Wesabot. And also Hubot.}
11
+ gem.authors = ["André Arko"]
6
12
  gem.email = ["andre@arko.net"]
7
- gem.description = %q{A 19,230-year-old Campfire bot}
8
- gem.summary = %q{A library and rack app to bot up your Campfire rooms}
9
13
  gem.homepage = "http://github.com/indirect/daneel"
14
+ gem.license = "MIT"
10
15
 
11
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
- gem.files = `git ls-files`.split("\n")
13
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
- gem.name = "daneel"
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
19
  gem.require_paths = ["lib"]
16
- gem.version = Daneel::VERSION
17
20
 
18
- gem.add_dependency "firering", "~> 1.2.0"
19
- gem.add_dependency "activerecord", "~> 3.1.0"
21
+ gem.add_dependency "sparks", "~> 0.4"
22
+ gem.add_dependency "sinatra", "~> 1.4.0"
23
+ gem.add_dependency "puma", "~> 1.6.3"
24
+
25
+ gem.add_development_dependency "rake", "~> 10.0"
26
+ gem.add_development_dependency "bundler", "~> 1.2"
20
27
  end
@@ -1,5 +1,7 @@
1
- require "daneel/version"
1
+ require 'daneel/bot'
2
+ require 'daneel/logger'
3
+ require 'daneel/options'
4
+ require 'daneel/version'
2
5
 
3
6
  module Daneel
4
- # Your code goes here...
5
7
  end
@@ -0,0 +1,37 @@
1
+ require 'daneel/message'
2
+ require 'daneel/plugin'
3
+ require 'daneel/room'
4
+ require 'daneel/user'
5
+
6
+ module Daneel
7
+ class Adapter < Plugin
8
+
9
+ def run
10
+ # listen to rooms and dispatch messages to robot.receive
11
+ end
12
+
13
+ def say(room_id, message)
14
+ # get the message into the room!
15
+ end
16
+
17
+ def announce(message)
18
+ # say the message into every room the bot is in
19
+ end
20
+
21
+ def me
22
+ @me ||= User.new(0, "R. Daneel Olivaw").tap do |me|
23
+ me.short_name = "Daneel"
24
+ me.initials = "DO"
25
+ end
26
+ end
27
+
28
+ class << self
29
+ def named(name)
30
+ require File.join('daneel/adapters', name.downcase)
31
+ adapter = Daneel::Adapters.const_get(name.capitalize)
32
+ adapter || raise("Couldn't find Daneel::Adapters::#{a.capitalize}")
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,93 @@
1
+ require 'sparks'
2
+ require 'daneel/adapter'
3
+
4
+ module Daneel
5
+ class Adapters
6
+ class Campfire < Daneel::Adapter
7
+ requires_env %w(CAMPFIRE_SUBDOMAIN CAMPFIRE_API_TOKEN CAMPFIRE_ROOM_IDS)
8
+
9
+ def initialize(robot)
10
+ super
11
+ domain = ENV['CAMPFIRE_SUBDOMAIN']
12
+ token = ENV['CAMPFIRE_API_TOKEN']
13
+ @fire = Sparks.new(domain, token, :logger => logger)
14
+
15
+ ENV['CAMPFIRE_ROOM_IDS'].split(",").map(&:to_i).map do |id|
16
+ # Get info about the room state
17
+ room = Room.new(id, self, @fire.room(id))
18
+ robot.data.rooms[id] = room
19
+ # Save the user info for all the users in the room
20
+ room.data["users"].each do |data|
21
+ user = User.new(data["id"], data["name"], data)
22
+ robot.data.users[user.id] = user
23
+ end
24
+ end
25
+ end
26
+
27
+ def run
28
+ @threads ||= []
29
+ robot.data.rooms.each do |id, room|
30
+ t = Thread.new { watch_room(room) } until t
31
+ t.abort_on_exception = true
32
+ @threads << t
33
+ end
34
+ @threads.each{|t| t.join }
35
+ end
36
+
37
+ def say(id, *texts)
38
+ texts.each do |text|
39
+ text =~ /\n/ ? @fire.paste(id, text) : @fire.speak(id, text)
40
+ end
41
+ end
42
+
43
+ def announce(*texts)
44
+ robot.data.rooms.each do |id, room|
45
+ say id, *texts
46
+ end
47
+ end
48
+
49
+ def leave
50
+ # stop the listening threads
51
+ @threads.each{|t| t.kill }
52
+ # leave each room
53
+ robot.data.rooms.each{|r| @fire.room(r.id).leave }
54
+ end
55
+
56
+ def me
57
+ @me ||= begin
58
+ data = @fire.me
59
+ me = User.new(data["id"], data["name"], data)
60
+ robot.data.users[me.id] = me
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def find_user(id)
67
+ robot.data.users[id] ||= begin
68
+ data = @fire.user(id)
69
+ User.new(data["id"], data["name"], data)
70
+ end
71
+ end
72
+
73
+ def watch_room(room)
74
+ @fire.watch(room.id) do |data|
75
+ next if data["type"] == "TimestampMessage"
76
+
77
+ # TODO pass through self-messages, once they are filtered by
78
+ # the accept? method on scripts
79
+ next if data["user_id"] == me.id
80
+
81
+ text = data["body"]
82
+ time = Time.parse(data["created_at"]) rescue Time.now
83
+ type = data["type"].gsub(/Message$/, '').downcase
84
+ mesg = Message.new(text, time, type)
85
+ room = robot.data.rooms[data["room_id"]]
86
+ user = find_user(data["user_id"])
87
+ robot.receive room, mesg, user
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,36 @@
1
+ require 'daneel/adapter'
2
+ require 'readline'
3
+
4
+ module Daneel
5
+ class Adapters
6
+ class Shell < Daneel::Adapter
7
+
8
+ def initialize(robot)
9
+ super
10
+ @room = Room.new("shell", self)
11
+ @user = User.new(1, ENV['USER'])
12
+ end
13
+
14
+ def run
15
+ # End the line we were on when we exit
16
+ trap(:EXIT){ print "\n" }
17
+
18
+ while text = Readline.readline("> ", true)
19
+ next if text.empty?
20
+ message = Message.new(text, Time.now, "text")
21
+ robot.receive @room, message, @user
22
+ end
23
+ end
24
+
25
+ def say(id, *strings)
26
+ puts *strings
27
+ end
28
+
29
+ def announce(*strings)
30
+ puts
31
+ say @room.id, *strings
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,89 @@
1
+ require 'daneel/adapter'
2
+ require 'daneel/script'
3
+ require 'daneel/data'
4
+
5
+ module Daneel
6
+ class Bot
7
+ attr_reader :adapter, :data, :full_name, :logger, :name, :scripts
8
+ attr_accessor :debug_mode
9
+
10
+ def initialize(options = {})
11
+ @logger = options[:logger] || Daneel::Logger.new
12
+ @name = options[:name] || "daneel"
13
+ @full_name = options[:full_name] || options[:name] || "R. Daneel Olivaw"
14
+ @debug_mode = options[:verbose] && options[:adapter] && options[:adapter] != "shell"
15
+
16
+ @data = Data.new
17
+ logger.debug "Data source #{data.class}"
18
+
19
+ Script.files.each{|file| try_require file }
20
+ # TODO add script priorities to replicate this
21
+ list = Script.list
22
+ list.push list.delete(Scripts::ImageSearch)
23
+ list.push list.delete(Scripts::Chatty)
24
+ @scripts = list.map{|s| s.new(self) }
25
+ logger.debug "Booted with scripts: #{@scripts.map(&:class).inspect}"
26
+
27
+ @adapter = Adapter.named(options[:adapter] || "shell").new(self)
28
+ logger.debug "Using the #{adapter.class} adapter"
29
+ end
30
+
31
+ def receive(room, message, user)
32
+ logger.debug "[room #{room.id}] #{user.name}: #{message.text}"
33
+ message.command = command_from(message.text)
34
+
35
+ scripts.each do |script|
36
+ next unless script.accepts?(room, message, user)
37
+ script.receive(room, message, user)
38
+ break if message.done
39
+ end
40
+ message
41
+ rescue => e
42
+ msg = %|#{e.class}: #{e.message}\n #{e.backtrace.join("\n ")}|
43
+ logger.error msg
44
+ adapter.announce "crap, something went wrong. :(", msg if @debug_mode
45
+ end
46
+
47
+ def run
48
+ # TODO add i18n so that people can customize their bot's attitude
49
+ # http://titusd.co.uk/2010/03/04/i18n-internationalization-without-rails/
50
+ # TODO add Confabulator processing so the bot can be chatty without being static
51
+
52
+ # Heroku cycles every process at least once per day by sending it a TERM
53
+ trap(:TERM) do
54
+ @adapter.announce "asked to stop, brb"
55
+ exit
56
+ end
57
+
58
+ @adapter.announce "hey guys"
59
+ @adapter.run
60
+ rescue Interrupt
61
+ adapter.leave
62
+ end
63
+
64
+ def user
65
+ @adapter.me
66
+ end
67
+
68
+ def inspect
69
+ %|#<#{self.class}:#{object_id} @name="#{name}" @adapter=#{adapter.class}>|
70
+ end
71
+
72
+ private
73
+
74
+ def command_from(text)
75
+ return if text.nil? || text.empty?
76
+ m = text.match(/^@#{name}\s+(.*)/i)
77
+ m ||= text.match(/^#{name}(?:[,:]\s*|\s+)(.*)/i)
78
+ m ||= text.match(/^\s*(.*?)(?:,\s*)?\b#{name}[.!?\s]*$/i)
79
+ m && m[1]
80
+ end
81
+
82
+ def try_require(name)
83
+ require name
84
+ rescue Script::DepError => e
85
+ logger.warn "Couldn't load #{File.basename(name)}: #{e.message}"
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,30 @@
1
+ module Daneel
2
+ # Non-persistent storage, extend if you want more.
3
+ class Data
4
+
5
+ def initialize
6
+ @store = {}
7
+ end
8
+
9
+ def save
10
+ # it's a hash, what do you want?
11
+ end
12
+
13
+ def [](key)
14
+ @store[key]
15
+ end
16
+
17
+ def []=(key, value)
18
+ @store[key] = value
19
+ end
20
+
21
+ def users
22
+ @store["users"] ||= {}
23
+ end
24
+
25
+ def rooms
26
+ @store["rooms"] ||= {}
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'logger'
2
+
3
+ module Daneel
4
+ class Logger < ::Logger
5
+
6
+ def initialize(io = $stdout, verbose = false)
7
+ super
8
+ self.level = Logger::INFO unless verbose
9
+ self.formatter = proc do |severity, datetime, progname, msg|
10
+ "#{severity} #{msg}\n"
11
+ end
12
+ end
13
+
14
+ def inspect
15
+ %|#<#{self.class}:#{object_id} @level=#{level}>|
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ require 'shellwords'
2
+
3
+ module Daneel
4
+ class Message
5
+ attr_reader :command, :done, :room, :text, :time, :type
6
+ attr_accessor :args
7
+
8
+ def initialize(text, time = Time.now, type = :text)
9
+ @text, @time, @type = text, time, type
10
+ end
11
+
12
+ def command=(text)
13
+ @command = text
14
+ @args = text ? Shellwords.split(text) : nil
15
+ rescue ArgumentError # shellwords didn't like this
16
+ @args = nil
17
+ end
18
+
19
+ def done!
20
+ @done = true
21
+ end
22
+
23
+ def inspect
24
+ "#<#{self.class} #{text.inspect} #{time.inspect} #{type.inspect}>"
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ require 'optparse'
2
+
3
+ module Daneel
4
+ module Options
5
+
6
+ def self.parse(args)
7
+ options = {}
8
+
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: daneel [options]"
11
+
12
+ opts.on("-v", "--verbose", "Print debugging information") do |v|
13
+ options[:verbose] = v
14
+ end
15
+
16
+ opts.on("-s", "--server [PORT]", "Run the HTTP server on PORT (default 3333)") do |port|
17
+ options[:server] = {:port => port}
18
+ end
19
+
20
+ opts.on("-a", "--adapter ADAPTER", "Which interaction adapter to use") do |name|
21
+ options[:adapter] = name
22
+ end
23
+
24
+ opts.on("-n", "--name NAME", "The name your bot should respond to") do |name|
25
+ options[:name] = name
26
+ end
27
+
28
+ opts.on("-f", "--full-name FULLNAME", "The name your bot will use to refer to itself") do |name|
29
+ options[:full_name] = name
30
+ end
31
+ end.parse(args)
32
+
33
+ return options
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module Daneel
2
+ class Plugin
3
+ attr_reader :robot
4
+
5
+ def initialize(robot)
6
+ @robot = robot
7
+ end
8
+
9
+ def logger
10
+ robot.logger
11
+ end
12
+
13
+ class DepError < LoadError; end
14
+
15
+ def self.requires_env(*keys)
16
+ missing = keys.flatten.select{|k| ENV[k].nil? || ENV[k].empty? }
17
+ raise DepError, "#{missing.join(',')} must be set" if missing.any?
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module Daneel
2
+ class Room
3
+ attr_reader :id
4
+ attr_accessor :data
5
+
6
+ def initialize(id, adapter, data = nil)
7
+ @id, @adapter, @data = id, adapter, data
8
+ end
9
+
10
+ def say(*strings)
11
+ @adapter.say @id, *strings
12
+ end
13
+
14
+ def inspect
15
+ %|#<#{self.class} @id=#{@id.inspect} @adapter=#{@adapter.class}>|
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ require 'daneel/plugin'
2
+
3
+ module Daneel
4
+ class Script < Plugin
5
+
6
+ def accepts?(room, message, user)
7
+ true unless user.id == robot.user.id
8
+ end
9
+
10
+ def receive(room, message, user)
11
+ # do stuff here!
12
+ end
13
+
14
+ def help
15
+ # return a hash of commands and descriptions for help listings
16
+ {}
17
+ end
18
+
19
+ # Track scripts that have loaded so we can use them
20
+ class << self
21
+
22
+ def list
23
+ @list ||= []
24
+ end
25
+
26
+ def inherited(subclass)
27
+ list << subclass
28
+ end
29
+
30
+ def files
31
+ Dir[File.expand_path("../scripts/*.rb", __FILE__)]
32
+ end
33
+
34
+ # TODO accept method for script classes
35
+ # should handle options like :text, :enter, :leave, :topic, :all
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,106 @@
1
+ require 'daneel/script'
2
+
3
+ module Daneel
4
+ module Scripts
5
+ class Chatty < Daneel::Script
6
+ # TODO make this script the last-priority script
7
+ # priority 20
8
+
9
+ def receive(room, message, user)
10
+ # Said to the room in general
11
+ case message.text
12
+ when /^(night|good ?night)(,?\s(all|every(body|one)))$/i
13
+ room.say "goodnight, #{user}"
14
+ when /^(morning|good ?morning)(,?\s(all|every(body|one)))$/i
15
+ room.say "good morning, #{user}"
16
+ end
17
+
18
+ # Said directly to the bot
19
+ case message.command
20
+ when nil
21
+ # don't reply to things not addressed to the bot
22
+ when /^\s*$/
23
+ # question questioners, exclaim at exclaimers, dot dotters
24
+ message.text.match(/([?!.])$/)
25
+ room.say "#{user}#{$1}"
26
+ when /^(hey|hi|hello|sup|howdy)/i
27
+ room.say "#{$1} #{user}"
28
+ when /how are (you|things)|how\'s it (going|hanging)/i
29
+ room.say [
30
+ "Oh, you know, the usual.",
31
+ "can't complain",
32
+ "alright, how about you?",
33
+ "people say things, I say things back"
34
+ ].sample
35
+ when /(^later|(?:good\s*)?bye)/i
36
+ room.say("#{$1} #{user}")
37
+ when /you rock|awesome|cool|nice/i
38
+ room.say [
39
+ "Thanks, #{user}, you're pretty cool yourself.",
40
+ "I try.",
41
+ "Aw, shucks. Thanks, #{user}."
42
+ ].sample
43
+ when /(^|you|still)\s*there/i, /\byt\b/i
44
+ room.say %w{Yup y}.sample
45
+ when /wake up|you awake/i
46
+ room.say("yo")
47
+ when /thanks|thank you/i
48
+ room.say ["No problem.", "np", "any time", "that's what I'm here for", "You're welcome."].sample
49
+ when /^(good\s?night|(?:g')?night)$/i
50
+ room.say [
51
+ "see you later, #{user}",
52
+ "later, #{user}",
53
+ "night",
54
+ "goodnight",
55
+ "bye",
56
+ "have a good night"
57
+ ].sample
58
+ when /^(see you(?: later)?)$/i
59
+ room.say [
60
+ "see you later, #{user}",
61
+ "later, #{user}",
62
+ "bye",
63
+ "later",
64
+ "see ya",
65
+ ].sample
66
+ when /^(?:(?:get|grab|fetch|bring) (.*?)|i need|time for)(?: (?:a|some))? coffee$/i
67
+ person = $1
68
+ if person =~ /i|me|us/
69
+ person, do_they = "you", "do you"
70
+ else
71
+ do_they = "does #{person}"
72
+ end
73
+
74
+ room.say [
75
+ "would #{person} like cream or sugar?",
76
+ "how #{do_they} take it?",
77
+ "coming right up",
78
+ "It is by caffeine alone I set my mind in motion",
79
+ "It is by the beans of Java that thoughts acquire speed",
80
+ "The hands acquire shakes, the shakes become a warning",
81
+ "It is by caffeine alone I set my mind in motion"
82
+ ].sample
83
+ else
84
+ room.say [
85
+ "I have no idea what you're talking about, #{user}.",
86
+ "eh?",
87
+ "oh, interesting",
88
+ "say more, #{user}",
89
+ "#{user}, you do realize that you're talking to a bot with a very limited vocabulary, don't you?",
90
+ "Whatever, #{user}.",
91
+ # TODO implement Bot#other_person
92
+ # "#{bot.other_person(user)}, tell #{user} to leave me alone.",
93
+ "Not now, #{user}.",
94
+ "brb crying",
95
+ "what do you think, #{user}?",
96
+ "That's really something.",
97
+ "but what can I do? I'm just a lowly bot",
98
+ "I'll get some electrons on that right away",
99
+ "How do you feel when someone says '#{message.command}' to you, #{user}?"
100
+ ].sample
101
+ end
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ require 'daneel/script'
2
+
3
+ module Daneel
4
+ module Scripts
5
+ class Echo < Daneel::Script
6
+
7
+ def receive(room, message, user)
8
+ case message.command
9
+ when /^(?:echo|say)\s(.+)/
10
+ room.say $1
11
+ message.done!
12
+ end
13
+ end
14
+
15
+ def help
16
+ {"echo TEXT" => "are you copying me? stop copying me!"}
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ require 'daneel/script'
2
+
3
+ module Daneel
4
+ module Scripts
5
+ class Help < Daneel::Script
6
+
7
+ def receive(room, message, user)
8
+ case message.command
9
+ when /help$/
10
+ col = helps.keys.map(&:length).max + 2
11
+ room.say helps.map{|k,v| "%-#{col}s %s" % [k,v] }.sort.join("\n")
12
+ message.done!
13
+ end
14
+ end
15
+
16
+ def help
17
+ {"help" => "show this help summary"}
18
+ end
19
+
20
+ private
21
+
22
+ def helps
23
+ @helps ||= begin
24
+ helps = {}
25
+ robot.scripts.each do |script|
26
+ helps.merge!(script.help)
27
+ end
28
+ logger.debug "Found helps: #{helps.inspect}"
29
+ helps
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ require 'cgi'
2
+ require 'net/http/persistent'
3
+
4
+ module Daneel
5
+ # You need an Azure Marketplace Bing Search API key. You can sign up for one here:
6
+ # https://datamarket.azure.com/dataset/bing/search
7
+ # A free subscription allows 5,000 searches per month.
8
+ module Scripts
9
+ class ImageSearch < Daneel::Script
10
+ requires_env "AZURE_API_KEY"
11
+
12
+ def initialize(robot)
13
+ super
14
+ @token = ENV['AZURE_API_KEY']
15
+ @http = Net::HTTP::Persistent.new('daneel')
16
+ end
17
+
18
+ def receive(room, message, user)
19
+ case message.command
20
+ when /image me (.*?)/, /^(?:find) (?:me )?(?:a |another )?(?:picture of )?(.*)$/
21
+ room.say find_image_url_for($1)
22
+ message.done!
23
+ end
24
+ end
25
+
26
+ def help
27
+ {"find a THING" => "scours the internets for a picture of THING to show you"}
28
+ end
29
+
30
+ def find_image_url_for(search)
31
+ logger.debug "Searching for images of #{search}"
32
+ query = CGI.escape("'#{search.gsub(/'/, "\\\\'")}'")
33
+ uri = URI("https://api.datamarket.azure.com/Bing/Search/v1/Composite")
34
+ uri.query = "Sources=%27image%27&Adult=%27Moderate%27&$format=JSON&Query=#{query}"
35
+ request = Net::HTTP::Get.new(uri.request_uri)
36
+ request.basic_auth 'x', @token
37
+ response = @http.request uri, request
38
+ logger.debug "GET #{uri}"
39
+ results = JSON.parse(response.body)["d"]["results"].first["Image"]
40
+ logger.debug "got back #{results.size} images"
41
+ # Random image from the first 50 results
42
+ results.sample["MediaUrl"]
43
+ rescue => e
44
+ logger.error "#{e.class}: #{e.message}"
45
+ room.say "Sorry, something went wrong when I looked for '#{query}'"
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ require 'daneel/script'
2
+
3
+ module Daneel
4
+ module Scripts
5
+ class Meme < Daneel::Script
6
+
7
+ def receive(room, message, user)
8
+ case message.text
9
+ when /^[\:\*\(e]?facepalm[\:\*\)]?|EFACEPALM|:fp|m\($/i
10
+ room.say [
11
+ # picard facepalm
12
+ "https://img.skitch.com/20110224-875h7w1654tgdhgrxm9bikhkwq.jpg",
13
+ # polar bear facepalm
14
+ "https://img.skitch.com/20110224-bsd2ds251eit8d3t1y2mkjtfx8.jpg"
15
+ ].sample
16
+ when /^[\:\*\(e]?double ?facepalm[\:\*\)]?|:fpfp|m\( m\($/i
17
+ # picard + riker facepalm
18
+ room.say "https://img.skitch.com/20110224-ncacgpudhfr2s4te6nswenxaqt.jpg"
19
+ when /^i see your problem/i
20
+ # pony mechanic
21
+ room.say "https://img.skitch.com/20110224-8fmfwdmg6kkrcpijhamhqu7tm6.jpg"
22
+ when /^works on my machine|womm$/i
23
+ # works on my machine
24
+ room.say "https://img.skitch.com/20110224-jrcf6e4gc936a2mxc3mueah2in.png"
25
+ when /^stacktrace or gtfo|stacktrace or it didn't happen|stacktrace\!$/i
26
+ # stacktrace or gtfo
27
+ room.say "https://img.skitch.com/20110224-pqtmiici9wp9nygqi4nw8gs6hg.png"
28
+ when /^this is sparta\!*$/i
29
+ # this is sparta
30
+ room.say "https://img.skitch.com/20110225-k9xpadr2hk37pe5ed4crcqria1.png"
31
+ when /^i have no idea what i'm doing$/i
32
+ # I have no idea what I'm doing
33
+ room.say "https://img.skitch.com/20110304-1tcmatkhapiq6t51wqqq9igat5.jpg"
34
+ when /^party[?!.]*$/i
35
+ # party party party party party cat
36
+ room.say "https://img.skitch.com/20110309-qtd33sy8k5yrdaeqa9e119bwd1.jpg"
37
+ when /^bomb|system error$/i
38
+ # sorry, a system error has occurred
39
+ room.say "https://img.skitch.com/20110312-8g31a37spacdjgatr82g3g98j1.jpg"
40
+ when /^stop hitting yourself$/i
41
+ # and the angel said to him, Stop hitting yourself!
42
+ room.say "https://img.skitch.com/20110316-q7h49p69pjhhyy8756rha2a1jf.jpg"
43
+ when /^[:*(]php[:*)]$/
44
+ # PHP: training wheels without the bike
45
+ room.say "http://tnx.nl/php.jpg"
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ require 'daneel/script'
2
+ require 'pathname'
3
+
4
+ module Daneel
5
+ module Scripts
6
+ class Reload < Daneel::Script
7
+
8
+ def receive(room, message, user)
9
+ case message.command
10
+ when /^update$/
11
+ return unless in_git?
12
+ system("cd #{root} && git pull origin master && bundle install")
13
+ room.say "updated, brb"
14
+ restart
15
+ when /^reload$/
16
+ room.say "k, brb"
17
+ restart
18
+ end
19
+ end
20
+
21
+ def help
22
+ reload = {"reload" => "restarts and reloads #{robot.name}'s code"}
23
+ update = {"update" => "updates #{robot.name}'s code from git and restarts"}
24
+ in_git? ? reload.merge(update) : reload
25
+ end
26
+
27
+ private
28
+
29
+ def restart
30
+ bin = root.join("bin/daneel").realpath.to_s
31
+ cmd = [Gem.ruby, "-S", bin, *ARGV]
32
+
33
+ logger.debug "Reloading: #{cmd.join(' ')}"
34
+ exec *cmd
35
+ end
36
+
37
+ def root
38
+ @root ||= begin
39
+ root = Pathname.new(__FILE__).join("../../../..").expand_path
40
+ logger.debug "Found root directory #{root}"
41
+ root
42
+ end
43
+ end
44
+
45
+ def in_git?
46
+ @in_git ||= root.join(".git").directory?
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ require 'cgi'
2
+ require 'json'
3
+ require 'net/http/persistent'
4
+
5
+ module Daneel
6
+ module Scripts
7
+ class VineSearch < Daneel::Script
8
+
9
+ def initialize(robot)
10
+ super
11
+ @http = Net::HTTP::Persistent.new('daneel')
12
+ end
13
+
14
+ def receive(room, message, user)
15
+ case message.command
16
+ when /vine me (.+)$/, /^(?:find) (?:me )?(?:a |another )?(?:vine of )(.*)$/
17
+ url = find_vine_url_for($1)
18
+ if url
19
+ room.say url
20
+ else
21
+ room.say "Sorry, Twitter didn't have any Vines about that."
22
+ end
23
+ message.done!
24
+ end
25
+ end
26
+
27
+ def help
28
+ {"find a vine of THING" => "shows you a vine that was tweeted mentioning THING"}
29
+ end
30
+
31
+ def find_vine_url_for(search)
32
+ logger.debug "Searching for a vine of #{search}"
33
+ uri = URI("http://search.twitter.com/search.json")
34
+ query = CGI.escape("#{search} source:vine_for_ios")
35
+ uri.query = "rpp=5&include_entities=true&q=#{query}"
36
+ request = Net::HTTP::Get.new(uri.request_uri)
37
+ response = @http.request uri, request
38
+ logger.debug "GET #{uri}"
39
+ results = JSON.parse(response.body)["results"]
40
+ logger.debug "got back #{results.size} vines"
41
+ return nil if results.empty?
42
+ # Pull out the Vine display url of a random result
43
+ results.sample["entities"]["urls"].first["expanded_url"]
44
+ rescue => e
45
+ logger.error "#{e.class}: #{e.message}"
46
+ room.say "Sorry, something went wrong when I looked for '#{query}'"
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http/persistent'
2
+ require 'daneel'
3
+
4
+ module Daneel
5
+ class Server
6
+ attr_reader :logger
7
+
8
+ def initialize(options = {})
9
+ require 'daneel/web'
10
+ @logger = options[:logger] || Daneel::Logger.new
11
+ @options = {:app => Daneel::Web, :server => "puma"}.merge(options)
12
+ @options[:port] = ENV["PORT"] if ENV["PORT"]
13
+ # Rack expects the port key to be capitalized. Sad day.
14
+ @options[:Port] = @options.delete(:port) if @options[:port]
15
+ logger.debug "Server with options: #{@options}"
16
+ end
17
+
18
+ def run
19
+ Thread.new { run_server }
20
+ sleep 0.1 # boot server before allowing possible interaction
21
+ Thread.new { run_self_ping }
22
+ end
23
+
24
+ def run_server
25
+ Rack::Server.start(@options)
26
+ end
27
+
28
+ def run_self_ping
29
+ return unless ENV['HEROKU_URL']
30
+ uri = URI(ENV['HEROKU_URL'])
31
+ http = Net::HTTP::Persistent.new 'daneel'
32
+ loop do
33
+ http.request uri
34
+ sleep (60 * 20) # 20m
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ module Daneel
3
+ # Represents a generic user. Doesn't have a room, because multiple rooms can
4
+ # contain the same user. The user's unique identifier is supplied by the
5
+ # adapter, and only needs to be unique within the context of that adapter.
6
+ class User
7
+ attr_reader :id, :name
8
+ attr_accessor :data, :initials, :short_name
9
+
10
+ def initialize(id, name, data = nil)
11
+ @id, @name, @data = id, name, data
12
+
13
+ # First, try to get initials from the upper-case letters
14
+ @initials = name.gsub(/\P{Upper}/,'')
15
+ # If that fails, just go with the first letter of each word
16
+ @initials = name.gsub(/(?<!^|\s)./,'') if @initials.empty?
17
+ # Short name is just the bit up to the first space
18
+ @short_name = name.match(/^(\S+)/)[0]
19
+ end
20
+
21
+ def to_s
22
+ [short_name, short_name.downcase, initials].sample
23
+ end
24
+
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Daneel
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1"
3
3
  end
@@ -0,0 +1,12 @@
1
+ require 'sinatra/base'
2
+ module Daneel
3
+ class Web < Sinatra::Base
4
+ set :logging, true
5
+
6
+ get '/' do
7
+ content_type "text/plain"
8
+ "I hope to one day have a positronic brain, like my namesake R. Daneel Olivaw."
9
+ end
10
+
11
+ end
12
+ end
metadata CHANGED
@@ -1,104 +1,158 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: daneel
3
- version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 0
9
- - 1
10
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
13
- - Andre Arko
7
+ authors:
8
+ - André Arko
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-11-09 00:00:00 -10:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: firering
12
+ date: 2013-01-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sparks
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.4'
22
+ type: :runtime
23
23
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.4'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sinatra
32
+ requirement: !ruby/object:Gem::Requirement
25
33
  none: false
26
- requirements:
34
+ requirements:
27
35
  - - ~>
28
- - !ruby/object:Gem::Version
29
- hash: 31
30
- segments:
31
- - 1
32
- - 2
33
- - 0
34
- version: 1.2.0
36
+ - !ruby/object:Gem::Version
37
+ version: 1.4.0
35
38
  type: :runtime
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: activerecord
39
39
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
41
  none: false
42
- requirements:
42
+ requirements:
43
43
  - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 3
46
- segments:
47
- - 3
48
- - 1
49
- - 0
50
- version: 3.1.0
44
+ - !ruby/object:Gem::Version
45
+ version: 1.4.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: puma
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.6.3
51
54
  type: :runtime
52
- version_requirements: *id002
53
- description: A 19,230-year-old Campfire bot
54
- email:
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.6.3
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '10.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '10.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '1.2'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '1.2'
94
+ description: Daneel is a chatbot inspired by the late, lamented Wesabot. And also
95
+ Hubot.
96
+ email:
55
97
  - andre@arko.net
56
- executables: []
57
-
98
+ executables:
99
+ - daneel
58
100
  extensions: []
59
-
60
101
  extra_rdoc_files: []
61
-
62
- files:
102
+ files:
63
103
  - .gitignore
64
104
  - Gemfile
105
+ - Gemfile.lock
106
+ - Procfile
65
107
  - Rakefile
108
+ - bin/daneel
66
109
  - daneel.gemspec
67
110
  - lib/daneel.rb
111
+ - lib/daneel/adapter.rb
112
+ - lib/daneel/adapters/campfire.rb
113
+ - lib/daneel/adapters/shell.rb
114
+ - lib/daneel/bot.rb
115
+ - lib/daneel/data.rb
116
+ - lib/daneel/logger.rb
117
+ - lib/daneel/message.rb
118
+ - lib/daneel/options.rb
119
+ - lib/daneel/plugin.rb
120
+ - lib/daneel/room.rb
121
+ - lib/daneel/script.rb
122
+ - lib/daneel/scripts/chatty.rb
123
+ - lib/daneel/scripts/echo.rb
124
+ - lib/daneel/scripts/help.rb
125
+ - lib/daneel/scripts/image_search.rb
126
+ - lib/daneel/scripts/meme.rb
127
+ - lib/daneel/scripts/reload.rb
128
+ - lib/daneel/scripts/vine_search.rb
129
+ - lib/daneel/server.rb
130
+ - lib/daneel/user.rb
68
131
  - lib/daneel/version.rb
69
- has_rdoc: true
132
+ - lib/daneel/web.rb
70
133
  homepage: http://github.com/indirect/daneel
71
- licenses: []
72
-
134
+ licenses:
135
+ - MIT
73
136
  post_install_message:
74
137
  rdoc_options: []
75
-
76
- require_paths:
138
+ require_paths:
77
139
  - lib
78
- required_ruby_version: !ruby/object:Gem::Requirement
140
+ required_ruby_version: !ruby/object:Gem::Requirement
79
141
  none: false
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- hash: 3
84
- segments:
85
- - 0
86
- version: "0"
87
- required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
147
  none: false
89
- requirements:
90
- - - ">="
91
- - !ruby/object:Gem::Version
92
- hash: 3
93
- segments:
94
- - 0
95
- version: "0"
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
96
152
  requirements: []
97
-
98
153
  rubyforge_project:
99
- rubygems_version: 1.3.7
154
+ rubygems_version: 1.8.24
100
155
  signing_key:
101
156
  specification_version: 3
102
- summary: A library and rack app to bot up your Campfire rooms
157
+ summary: A 19,230-year-old robot
103
158
  test_files: []
104
-