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 +12 -18
- data/Gemfile +8 -0
- data/Procfile +1 -0
- data/bin/daneel +18 -0
- data/daneel.gemspec +19 -12
- data/lib/daneel.rb +4 -2
- data/lib/daneel/adapter.rb +37 -0
- data/lib/daneel/adapters/campfire.rb +93 -0
- data/lib/daneel/adapters/shell.rb +36 -0
- data/lib/daneel/bot.rb +89 -0
- data/lib/daneel/data.rb +30 -0
- data/lib/daneel/logger.rb +19 -0
- data/lib/daneel/message.rb +28 -0
- data/lib/daneel/options.rb +37 -0
- data/lib/daneel/plugin.rb +21 -0
- data/lib/daneel/room.rb +19 -0
- data/lib/daneel/script.rb +39 -0
- data/lib/daneel/scripts/chatty.rb +106 -0
- data/lib/daneel/scripts/echo.rb +21 -0
- data/lib/daneel/scripts/help.rb +35 -0
- data/lib/daneel/scripts/image_search.rb +50 -0
- data/lib/daneel/scripts/meme.rb +51 -0
- data/lib/daneel/scripts/reload.rb +51 -0
- data/lib/daneel/scripts/vine_search.rb +51 -0
- data/lib/daneel/server.rb +39 -0
- data/lib/daneel/user.rb +26 -0
- data/lib/daneel/version.rb +1 -1
- data/lib/daneel/web.rb +12 -0
- metadata +125 -71
data/.gitignore
CHANGED
@@ -1,18 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
data/Procfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
web: ruby -Ku -rbundler/setup -Ilib -S ./bin/daneel -v -s -a campfire -n "wes" -f "Wesabot"
|
data/bin/daneel
ADDED
@@ -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
|
data/daneel.gemspec
CHANGED
@@ -1,20 +1,27 @@
|
|
1
|
-
#
|
2
|
-
|
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.
|
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.
|
12
|
-
gem.
|
13
|
-
gem.test_files =
|
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 "
|
19
|
-
gem.add_dependency "
|
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
|
data/lib/daneel.rb
CHANGED
@@ -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
|
data/lib/daneel/bot.rb
ADDED
@@ -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
|
data/lib/daneel/data.rb
ADDED
@@ -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
|
data/lib/daneel/room.rb
ADDED
@@ -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
|
data/lib/daneel/user.rb
ADDED
@@ -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
|
data/lib/daneel/version.rb
CHANGED
data/lib/daneel/web.rb
ADDED
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
|
-
|
5
|
-
prerelease:
|
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
|
-
-
|
7
|
+
authors:
|
8
|
+
- André Arko
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
|
-
requirements:
|
42
|
+
requirements:
|
43
43
|
- - ~>
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
84
|
-
|
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
|
-
|
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.
|
154
|
+
rubygems_version: 1.8.24
|
100
155
|
signing_key:
|
101
156
|
specification_version: 3
|
102
|
-
summary: A
|
157
|
+
summary: A 19,230-year-old robot
|
103
158
|
test_files: []
|
104
|
-
|