slack-ruby-bot-bhe 0.5.5.3
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.
- checksums.yaml +17 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +58 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +81 -0
- data/CONTRIBUTING.md +139 -0
- data/DEPLOYMENT.md +33 -0
- data/Gemfile +3 -0
- data/LICENSE.md +22 -0
- data/README.md +263 -0
- data/RELEASING.md +67 -0
- data/Rakefile +19 -0
- data/TUTORIAL.md +205 -0
- data/UPGRADING.md +64 -0
- data/examples/minimal/Gemfile +3 -0
- data/examples/minimal/Procfile +1 -0
- data/examples/minimal/pongbot.rb +9 -0
- data/examples/weather/Gemfile +3 -0
- data/examples/weather/Procfile +1 -0
- data/examples/weather/weatherbot.rb +9 -0
- data/lib/config/application.rb +14 -0
- data/lib/config/boot.rb +8 -0
- data/lib/config/environment.rb +3 -0
- data/lib/initializers/giphy.rb +5 -0
- data/lib/slack-ruby-bot.rb +25 -0
- data/lib/slack-ruby-bot/about.rb +7 -0
- data/lib/slack-ruby-bot/app.rb +46 -0
- data/lib/slack-ruby-bot/bot.rb +7 -0
- data/lib/slack-ruby-bot/client.rb +44 -0
- data/lib/slack-ruby-bot/commands.rb +5 -0
- data/lib/slack-ruby-bot/commands/about.rb +12 -0
- data/lib/slack-ruby-bot/commands/base.rb +121 -0
- data/lib/slack-ruby-bot/commands/help.rb +9 -0
- data/lib/slack-ruby-bot/commands/hi.rb +9 -0
- data/lib/slack-ruby-bot/commands/unknown.rb +11 -0
- data/lib/slack-ruby-bot/config.rb +40 -0
- data/lib/slack-ruby-bot/hooks.rb +3 -0
- data/lib/slack-ruby-bot/hooks/base.rb +10 -0
- data/lib/slack-ruby-bot/hooks/hello.rb +11 -0
- data/lib/slack-ruby-bot/hooks/message.rb +50 -0
- data/lib/slack-ruby-bot/rspec.rb +12 -0
- data/lib/slack-ruby-bot/rspec/support/fixtures/slack/auth_test.yml +16 -0
- data/lib/slack-ruby-bot/rspec/support/fixtures/slack/migration_in_progress.yml +30 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/it_behaves_like_a_slack_bot.rb +27 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb +30 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb +19 -0
- data/lib/slack-ruby-bot/rspec/support/slack_api_key.rb +5 -0
- data/lib/slack-ruby-bot/rspec/support/slack_ruby_bot_configure.rb +9 -0
- data/lib/slack-ruby-bot/rspec/support/vcr.rb +8 -0
- data/lib/slack-ruby-bot/server.rb +111 -0
- data/lib/slack-ruby-bot/version.rb +3 -0
- data/lib/slack_ruby_bot.rb +1 -0
- data/screenshots/aliases.gif +0 -0
- data/screenshots/demo.gif +0 -0
- data/screenshots/dms.gif +0 -0
- data/screenshots/register-bot.png +0 -0
- data/screenshots/weather.gif +0 -0
- data/slack-ruby-bot.gemspec +28 -0
- data/slack.png +0 -0
- data/spec/slack-ruby-bot/app_spec.rb +15 -0
- data/spec/slack-ruby-bot/commands/about_spec.rb +19 -0
- data/spec/slack-ruby-bot/commands/aliases_spec.rb +22 -0
- data/spec/slack-ruby-bot/commands/bot_spec.rb +19 -0
- data/spec/slack-ruby-bot/commands/commands_precedence_spec.rb +22 -0
- data/spec/slack-ruby-bot/commands/commands_regexp_escape_spec.rb +17 -0
- data/spec/slack-ruby-bot/commands/commands_spaces_spec.rb +20 -0
- data/spec/slack-ruby-bot/commands/commands_spec.rb +21 -0
- data/spec/slack-ruby-bot/commands/commands_with_block_spec.rb +23 -0
- data/spec/slack-ruby-bot/commands/direct_messages_spec.rb +38 -0
- data/spec/slack-ruby-bot/commands/empty_text_spec.rb +22 -0
- data/spec/slack-ruby-bot/commands/help_spec.rb +10 -0
- data/spec/slack-ruby-bot/commands/hi_spec.rb +19 -0
- data/spec/slack-ruby-bot/commands/match_spec.rb +17 -0
- data/spec/slack-ruby-bot/commands/message_loop_spec.rb +34 -0
- data/spec/slack-ruby-bot/commands/nil_message_spec.rb +22 -0
- data/spec/slack-ruby-bot/commands/not_implemented_spec.rb +15 -0
- data/spec/slack-ruby-bot/commands/operators_spec.rb +20 -0
- data/spec/slack-ruby-bot/commands/operators_with_block_spec.rb +23 -0
- data/spec/slack-ruby-bot/commands/send_gif_spec.rb +32 -0
- data/spec/slack-ruby-bot/commands/send_message_spec.rb +19 -0
- data/spec/slack-ruby-bot/commands/send_message_with_gif_spec.rb +73 -0
- data/spec/slack-ruby-bot/commands/unknown_spec.rb +18 -0
- data/spec/slack-ruby-bot/config_spec.rb +55 -0
- data/spec/slack-ruby-bot/rspec/respond_with_error_spec.rb +19 -0
- data/spec/slack-ruby-bot/server_spec.rb +73 -0
- data/spec/slack-ruby-bot/version_spec.rb +7 -0
- data/spec/spec_helper.rb +1 -0
- metadata +313 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
class App < Server
|
|
3
|
+
def initialize(options = {})
|
|
4
|
+
SlackRubyBot.configure do |config|
|
|
5
|
+
config.token = ENV['SLACK_API_TOKEN'] || fail("Missing ENV['SLACK_API_TOKEN'].")
|
|
6
|
+
config.aliases = ENV['SLACK_RUBY_BOT_ALIASES'].split(' ') if ENV['SLACK_RUBY_BOT_ALIASES']
|
|
7
|
+
end
|
|
8
|
+
Slack.configure do |config|
|
|
9
|
+
config.token = SlackRubyBot.config.token
|
|
10
|
+
end
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def config
|
|
15
|
+
SlackRubyBot.config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.instance
|
|
19
|
+
@instance ||= new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def auth!
|
|
25
|
+
super
|
|
26
|
+
SlackRubyBot.configure do |config|
|
|
27
|
+
config.url = client.auth['url']
|
|
28
|
+
config.team = client.auth['team']
|
|
29
|
+
config.user = client.auth['user']
|
|
30
|
+
config.team_id = client.auth['team_id']
|
|
31
|
+
config.user_id = client.auth['user_id']
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reset!
|
|
36
|
+
super
|
|
37
|
+
SlackRubyBot.configure do |config|
|
|
38
|
+
config.url = nil
|
|
39
|
+
config.team = nil
|
|
40
|
+
config.user = nil
|
|
41
|
+
config.team_id = nil
|
|
42
|
+
config.user_id = nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
class Client < Slack::RealTime::Client
|
|
3
|
+
attr_accessor :auth
|
|
4
|
+
attr_accessor :aliases
|
|
5
|
+
attr_accessor :send_gifs
|
|
6
|
+
|
|
7
|
+
def initialize(attrs = {})
|
|
8
|
+
super(attrs)
|
|
9
|
+
@aliases = attrs[:aliases]
|
|
10
|
+
@send_gifs = attrs.key?(:send_gifs) ? !!attrs[:send_gifs] : true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def names
|
|
14
|
+
[
|
|
15
|
+
SlackRubyBot::Config.user,
|
|
16
|
+
auth ? auth['user'] : nil,
|
|
17
|
+
aliases,
|
|
18
|
+
SlackRubyBot::Config.aliases,
|
|
19
|
+
auth ? "<@#{auth['user_id'].downcase}>" : nil,
|
|
20
|
+
SlackRubyBot::Config.user_id ? "<@#{SlackRubyBot::Config.user_id.downcase}>" : nil,
|
|
21
|
+
auth ? "<@#{auth['user_id'].downcase}>:" : nil,
|
|
22
|
+
SlackRubyBot::Config.user_id ? "<@#{SlackRubyBot::Config.user_id.downcase}>:" : nil,
|
|
23
|
+
auth ? "#{auth['user']}:" : nil,
|
|
24
|
+
SlackRubyBot::Config.user ? "#{SlackRubyBot::Config.user}:" : nil
|
|
25
|
+
].compact.flatten
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def name?(name)
|
|
29
|
+
name && names.include?(name.downcase)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def send_gifs?
|
|
33
|
+
send_gifs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def name
|
|
37
|
+
SlackRubyBot.config.user || (auth && auth['user'])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def url
|
|
41
|
+
SlackRubyBot.config.url || (auth && auth['url'])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
module Commands
|
|
3
|
+
class Default < Base
|
|
4
|
+
command 'about'
|
|
5
|
+
match(/^(?<bot>[[:alnum:][:punct:]@<>]*)$/u)
|
|
6
|
+
|
|
7
|
+
def self.call(client, data, _match)
|
|
8
|
+
send_message_with_gif client, data.channel, SlackRubyBot::ABOUT, 'selfie'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
module Commands
|
|
3
|
+
class Base
|
|
4
|
+
class_attribute :routes
|
|
5
|
+
|
|
6
|
+
def self.send_message(client, channel, text, options = {})
|
|
7
|
+
if text && text.length > 0
|
|
8
|
+
send_client_message(client, { channel: channel, text: text }.merge(options))
|
|
9
|
+
else
|
|
10
|
+
send_message_with_gif client, channel, 'Nothing to see here.', 'nothing', options
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.send_message_with_gif(client, channel, text, keywords, options = {})
|
|
15
|
+
get_gif_and_send({
|
|
16
|
+
client: client,
|
|
17
|
+
channel: channel,
|
|
18
|
+
text: text,
|
|
19
|
+
keywords: keywords
|
|
20
|
+
}.merge(options))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.send_gif(client, channel, keywords, options = {})
|
|
24
|
+
get_gif_and_send({
|
|
25
|
+
client: client,
|
|
26
|
+
channel: channel,
|
|
27
|
+
keywords: keywords
|
|
28
|
+
}.merge(options))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.logger
|
|
32
|
+
@logger ||= begin
|
|
33
|
+
$stdout.sync = true
|
|
34
|
+
Logger.new(STDOUT)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.default_command_name
|
|
39
|
+
name && name.split(':').last.downcase
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.operator(*values, &block)
|
|
43
|
+
values.each do |value|
|
|
44
|
+
match Regexp.new("^(?<operator>\\#{value})(?<expression>.*)$", Regexp::IGNORECASE), &block
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.command(*values, &block)
|
|
49
|
+
values.each do |value|
|
|
50
|
+
escaped = Regexp.escape(value)
|
|
51
|
+
match Regexp.new("^(?<bot>[[:alnum:][:punct:]@<>]*)[\\s]+(?<command>#{escaped})$", Regexp::IGNORECASE), &block
|
|
52
|
+
match Regexp.new("^(?<bot>[[:alnum:][:punct:]@<>]*)[\\s]+(?<command>#{escaped})[\\s]+(?<expression>.*)$", Regexp::IGNORECASE), &block
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.invoke(client, data)
|
|
57
|
+
self.finalize_routes!
|
|
58
|
+
expression, text = parse(client, data)
|
|
59
|
+
called = false
|
|
60
|
+
routes.each_pair do |route, method|
|
|
61
|
+
match = route.match(expression)
|
|
62
|
+
match ||= route.match(text) if text
|
|
63
|
+
next unless match
|
|
64
|
+
next if match.names.include?('bot') && !client.name?(match['bot'])
|
|
65
|
+
called = true
|
|
66
|
+
if method
|
|
67
|
+
method.call(client, data, match)
|
|
68
|
+
elsif self.respond_to?(:call)
|
|
69
|
+
send(:call, client, data, match)
|
|
70
|
+
else
|
|
71
|
+
fail NotImplementedError, data.text
|
|
72
|
+
end
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
called
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.match(match, &block)
|
|
79
|
+
self.routes ||= {}
|
|
80
|
+
self.routes[match] = block
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def self.parse(client, data)
|
|
86
|
+
text = data.text
|
|
87
|
+
return text unless data.channel && data.channel[0] == 'D' && data.user && data.user != SlackRubyBot.config.user_id
|
|
88
|
+
client.names.each do |name|
|
|
89
|
+
text.downcase.tap do |td|
|
|
90
|
+
return text if td == name || td.starts_with?("#{name} ")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
["#{client.name} #{text}", text]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.finalize_routes!
|
|
97
|
+
return if self.routes && self.routes.any?
|
|
98
|
+
command default_command_name
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.get_gif_and_send(options = {})
|
|
102
|
+
options = options.dup
|
|
103
|
+
keywords = options.delete(:keywords)
|
|
104
|
+
client = options.delete(:client)
|
|
105
|
+
gif = begin
|
|
106
|
+
Giphy.random(keywords)
|
|
107
|
+
rescue StandardError => e
|
|
108
|
+
logger.warn "Giphy.random: #{e.message}"
|
|
109
|
+
nil
|
|
110
|
+
end if SlackRubyBot::Config.send_gifs? && client.send_gifs?
|
|
111
|
+
text = options.delete(:text)
|
|
112
|
+
text = [text, gif && gif.image_url.to_s].compact.join("\n")
|
|
113
|
+
send_client_message(client, { text: text }.merge(options))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.send_client_message(client, data)
|
|
117
|
+
client.message(data)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
module Commands
|
|
3
|
+
class Unknown < Base
|
|
4
|
+
match(/^(?<bot>\S*)[\s]*(?<expression>.*)$/)
|
|
5
|
+
|
|
6
|
+
def self.call(client, data, _match)
|
|
7
|
+
send_message_with_gif client, data.channel, "Sorry <@#{data.user}>, I don't understand that command!", 'idiot'
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
module Config
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
attr_accessor :token
|
|
6
|
+
attr_accessor :url
|
|
7
|
+
attr_accessor :aliases
|
|
8
|
+
attr_accessor :user
|
|
9
|
+
attr_accessor :user_id
|
|
10
|
+
attr_accessor :team
|
|
11
|
+
attr_accessor :team_id
|
|
12
|
+
attr_accessor :allow_message_loops
|
|
13
|
+
attr_accessor :send_gifs
|
|
14
|
+
|
|
15
|
+
def allow_message_loops?
|
|
16
|
+
allow_message_loops
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def send_gifs?
|
|
20
|
+
v = boolean_from_env('SLACK_RUBY_BOT_SEND_GIFS')
|
|
21
|
+
v.nil? ? (send_gifs.nil? || send_gifs) : v
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def boolean_from_env(key)
|
|
27
|
+
value = ENV[key]
|
|
28
|
+
case value
|
|
29
|
+
when nil
|
|
30
|
+
nil
|
|
31
|
+
when 0, 'false', 'no'
|
|
32
|
+
false
|
|
33
|
+
when 1, 'true', 'yes'
|
|
34
|
+
true
|
|
35
|
+
else
|
|
36
|
+
fail ArgumentError, "Invalid value for #{key}: #{value}."
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module SlackRubyBot
|
|
2
|
+
module Hooks
|
|
3
|
+
module Message
|
|
4
|
+
extend Base
|
|
5
|
+
|
|
6
|
+
def message(client, data)
|
|
7
|
+
data = Hashie::Mash.new(data)
|
|
8
|
+
return if !SlackRubyBot::Config.allow_message_loops? && (client.self && client.self['id'] == data.user)
|
|
9
|
+
data.text.strip! if data.text
|
|
10
|
+
result = child_command_classes.detect { |d| d.invoke(client, data) }
|
|
11
|
+
result ||= built_in_command_classes.detect { |d| d.invoke(client, data) }
|
|
12
|
+
result ||= SlackRubyBot::Commands::Unknown.tap { |d| d.invoke(client, data) }
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# All commands.
|
|
20
|
+
#
|
|
21
|
+
# @return [Array] Descendants of SlackRubyBot::Commands::Base.
|
|
22
|
+
#
|
|
23
|
+
def command_classes
|
|
24
|
+
SlackRubyBot::Commands::Base.descendants
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# All non-built-in, ie. custom commands.
|
|
29
|
+
#
|
|
30
|
+
# @return [Array] Non-built-in descendants of SlackRubyBot::Commands::Base.
|
|
31
|
+
#
|
|
32
|
+
def child_command_classes
|
|
33
|
+
command_classes.reject do |k|
|
|
34
|
+
k.name && k.name.starts_with?('SlackRubyBot::Commands::')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# All built-in commands.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array] Built-in descendants of SlackRubyBot::Commands::Base.
|
|
42
|
+
#
|
|
43
|
+
def built_in_command_classes
|
|
44
|
+
command_classes.select do |k|
|
|
45
|
+
k.name && k.name.starts_with?('SlackRubyBot::Commands::') && k != SlackRubyBot::Commands::Unknown
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'rspec'
|
|
5
|
+
require 'rack/test'
|
|
6
|
+
|
|
7
|
+
require 'config/environment'
|
|
8
|
+
require 'slack-ruby-bot'
|
|
9
|
+
|
|
10
|
+
Dir[File.join(File.dirname(__FILE__), 'rspec/support', '**/*.rb')].each do |file|
|
|
11
|
+
require file
|
|
12
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
http_interactions:
|
|
3
|
+
- request:
|
|
4
|
+
method: post
|
|
5
|
+
uri: https://slack.com/api/auth.test
|
|
6
|
+
body:
|
|
7
|
+
string: token=token
|
|
8
|
+
response:
|
|
9
|
+
status:
|
|
10
|
+
code: 200
|
|
11
|
+
message: OK
|
|
12
|
+
body:
|
|
13
|
+
encoding: UTF-8
|
|
14
|
+
string: '{"ok":true,"url":"https:\/\/rubybot.slack.com\/","team":"team_name","user":"user_name","team_id":"TDEADBEEF","user_id":"UBAADFOOD"}'
|
|
15
|
+
http_version:
|
|
16
|
+
recorded_at: Tue, 28 Apr 2015 12:55:22 GMT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
http_interactions:
|
|
3
|
+
- request:
|
|
4
|
+
method: post
|
|
5
|
+
uri: https://slack.com/api/rtm.start
|
|
6
|
+
body:
|
|
7
|
+
string: token=token
|
|
8
|
+
response:
|
|
9
|
+
status:
|
|
10
|
+
code: 200
|
|
11
|
+
message: OK
|
|
12
|
+
body:
|
|
13
|
+
encoding: UTF-8
|
|
14
|
+
string: '{"ok":false,"error":"migration_in_progress"}'
|
|
15
|
+
http_version:
|
|
16
|
+
recorded_at: Tue, 28 Apr 2015 12:55:22 GMT
|
|
17
|
+
- request:
|
|
18
|
+
method: post
|
|
19
|
+
uri: https://slack.com/api/rtm.start
|
|
20
|
+
body:
|
|
21
|
+
string: token=token
|
|
22
|
+
response:
|
|
23
|
+
status:
|
|
24
|
+
code: 200
|
|
25
|
+
message: OK
|
|
26
|
+
body:
|
|
27
|
+
encoding: UTF-8
|
|
28
|
+
string: '{"ok":false,"error":"unknown"}'
|
|
29
|
+
http_version:
|
|
30
|
+
recorded_at: Tue, 28 Apr 2015 12:55:22 GMT
|