url_2_event 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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +302 -0
  5. data/CONTRIBUTING.md +124 -0
  6. data/Gemfile +2 -0
  7. data/Guardfile +16 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +64 -0
  10. data/Rakefile +2 -0
  11. data/lib/url_2_event.rb +52 -0
  12. data/lib/url_2_event/adapters/eventbrite.rb +68 -0
  13. data/lib/url_2_event/adapters/meetup.rb +72 -0
  14. data/lib/url_2_event/base.rb +43 -0
  15. data/lib/url_2_event/event.rb +4 -0
  16. data/lib/url_2_event/version.rb +3 -0
  17. data/script/console.rb +8 -0
  18. data/spec/cassettes/Url2Event/_parse_event_from_uri/.yml +95 -0
  19. data/spec/cassettes/Url2Event_Eventbrite/get_event/.yml +270 -0
  20. data/spec/cassettes/Url2Event_Eventbrite/get_event/begin_at/.yml +270 -0
  21. data/spec/cassettes/Url2Event_Eventbrite/get_event/description/.yml +270 -0
  22. data/spec/cassettes/Url2Event_Eventbrite/get_event/end_at/.yml +270 -0
  23. data/spec/cassettes/Url2Event_Eventbrite/get_event/link/.yml +270 -0
  24. data/spec/cassettes/Url2Event_Eventbrite/get_event/location/.yml +270 -0
  25. data/spec/cassettes/Url2Event_Eventbrite/get_event/title/.yml +270 -0
  26. data/spec/cassettes/Url2Event_Meetup/get_event/.yml +209 -0
  27. data/spec/cassettes/Url2Event_Meetup/get_event/begin_at/.yml +209 -0
  28. data/spec/cassettes/Url2Event_Meetup/get_event/description/.yml +209 -0
  29. data/spec/cassettes/Url2Event_Meetup/get_event/end_at/.yml +209 -0
  30. data/spec/cassettes/Url2Event_Meetup/get_event/location/.yml +209 -0
  31. data/spec/cassettes/Url2Event_Meetup/get_event/price/.yml +209 -0
  32. data/spec/cassettes/Url2Event_Meetup/get_event/title/.yml +209 -0
  33. data/spec/eventbrite_spec.rb +33 -0
  34. data/spec/meetup_spec.rb +31 -0
  35. data/spec/spec_helper.rb +32 -0
  36. data/spec/url_2_event_spec.rb +33 -0
  37. data/url_2_event.gemspec +35 -0
  38. metadata +295 -0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ # Note: The cmd option is now required due to the increasing number of ways
5
+ # rspec may be run, below are examples of the most common uses.
6
+ # * bundler: 'bundle exec rspec'
7
+ # * bundler binstubs: 'bin/rspec'
8
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
9
+ # installed the spring binstubs per the docs)
10
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
11
+ # * 'just' rspec: 'rspec'
12
+ guard :rspec, cmd: 'bundle exec rspec', failed_mode: :focus do
13
+ watch(%r{^spec/.+_spec\.rb$})
14
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
15
+ watch(%r{^lib/url_2_event/adapters/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
16
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 UP Global
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ [![Coverage Status](https://coveralls.io/repos/StartupWeekend/url_2_event/badge.png?branch=master)](https://coveralls.io/r/StartupWeekend/url_2_event?branch=master)
2
+
3
+ [![Circle CI](https://circleci.com/gh/StartupWeekend/url_2_event.png?style=badge)](https://circleci.com/gh/StartupWeekend/url_2_event)
4
+
5
+ # Url2Event
6
+
7
+ A ruby gem for getting metadata from event URLs.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'url_2_event'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install url_2_event
24
+
25
+ ## Usage
26
+
27
+ ```ruby
28
+ require 'url_2_event'
29
+
30
+ url = "http://www.meetup.com/BKtechmeetup/events/191161262/"
31
+ event = Url2Event.parse_event_from_uri(url)
32
+ ap event
33
+ # =>
34
+ {
35
+ "title" => "BK Tech Meetup - Education",
36
+ "begin_at" => 2014-07-30 12:00:00 -0700,
37
+ "end_at" => 2014-07-30 15:00:00 -0700,
38
+ "location" => "HUGE, Inc., 45 Main St. Suite 220, Brooklyn",
39
+ "description" => "Education! Let's all take our Smarties, meet up to talk about what's going on in Education and hear from two people who are leading major changes in the industry. We're going to be having two incredibly amazing speakers followed by $3 beers and a reserved hang out area at Superfine, just down the street from Huge, where we'll continue mingling. Speakers: Adam Enbar from The Flatiron School Learn to build awesome things with code.We're a school for passionate people who want to love what they do. Stephanie Dua from Homer Homer's mission is to be the world's leading provider of quality early learning experiences for children. We create innovative products that parents trust, children love, and communities unite around. \n\n\n\nHave a company that you would like to demo? Interested in sponsoring our events? Want to join the BK Tech team? Contact us at:  [masked] [masked] [masked]",
40
+ "price" => "5 USD",
41
+ "link" => "http://www.meetup.com/BKtechmeetup/events/191161262/"
42
+ }
43
+
44
+ # unsupported URL
45
+ url = "http://www.meetup.com/about"
46
+ begin
47
+ event = Url2Event.parse_event_from_uri(url)
48
+ rescue Url2Event::UnsupportedSourceError
49
+ puts "#{url} is not a supported URL"
50
+ end
51
+ # => http://www.example.com/page is not a supported URL
52
+
53
+ ```
54
+
55
+ ## Contributing
56
+
57
+ Refer to [CONTRIBUTING.md](https://github.com/StartupWeekend/url_2_event/blob/master/CONTRIBUTING.md)
58
+
59
+ ## Contributors
60
+
61
+ This project was originally built for [Startup Digest](https://www.startupdigest.com/) by [dcromer](https://github.com/dcromer).
62
+
63
+ - [Dan Cromer](https://github.com/dcromer)
64
+ - [Hank Stoever](https://github.com/hstove)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,52 @@
1
+ require 'httparty'
2
+ require 'rails-html-sanitizer'
3
+
4
+ require "url_2_event/version"
5
+ require 'url_2_event/event'
6
+ require 'url_2_event/base'
7
+ Dir["#{File.dirname(__FILE__)}/url_2_event/adapters/*.rb"].each { |f| require(f) }
8
+
9
+ module Url2Event
10
+ class InvalidURLError < ArgumentError; end
11
+ class UnsupportedSourceError < StandardError; end
12
+ class UnparsableSourceError < StandardError; end
13
+
14
+ class << self
15
+ def parse_event_from_uri(uri)
16
+ url = parse_url(uri)
17
+
18
+ klass = get_parser_class(url)
19
+ parser = klass.new(url)
20
+ parser.get_event
21
+ end
22
+
23
+ # Returns the appropriate Url2Event subclass for the given url.
24
+ def get_parser_class(uri)
25
+ parser_class = self::Base.implementations.find { |parser|
26
+ parser.is_parser_for?(uri)
27
+ }
28
+
29
+ if parser_class
30
+ parser_class
31
+ else
32
+ raise self::UnsupportedSourceError
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def parse_url(uri)
39
+ begin
40
+ url = URI.parse(uri)
41
+ rescue
42
+ raise self::InvalidURLError
43
+ end
44
+
45
+ if url.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
46
+ url
47
+ else
48
+ raise self::InvalidURLError
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,68 @@
1
+ module Url2Event
2
+ class Eventbrite < Base
3
+ base_uri 'https://www.eventbriteapi.com'
4
+
5
+ class << self
6
+ def is_parser_for?(uri)
7
+ !uri.hostname.match(%r{(?:www\.)?eventbrite\.(com|co\.uk)}).nil?
8
+ end
9
+ end
10
+
11
+ def options
12
+ @options = {
13
+ headers: {
14
+ "Authorization" => "Bearer #{ENV['EVENTBRITE_TOKEN']}"
15
+ }
16
+ }
17
+ end
18
+
19
+ protected
20
+
21
+ def event_endpoint
22
+ "/v3/events/#{get_id}"
23
+ end
24
+
25
+ def get_id
26
+ matcher = %r{/e/[^\b]+-(\d+)}
27
+ matches = matcher.match(@uri.path)
28
+ if !matches.nil?
29
+ matches.captures[0]
30
+ else
31
+ raise Url2Event::UnparsableSourceError
32
+ end
33
+ end
34
+
35
+ def to_event(response)
36
+ title = response['name']['text'] if response['name']
37
+ link = response['url']
38
+
39
+ if desc = response['description']
40
+ description = sanitize(desc['text'])
41
+ end
42
+
43
+ if venue = response['venue']
44
+ location = venue['name']
45
+ location += ", " + venue['address_1'] if venue['address_1']
46
+ location += " " + venue['address_2'] if venue['address_2']
47
+ location += ", " + venue['city'] if venue['city']
48
+ end
49
+
50
+ if start = response['start']
51
+ begin_at = Time.parse(start['local'] + " +0000")
52
+ end
53
+
54
+ if eend = response['end'] # keywords lol
55
+ end_at = Time.parse(eend['local'] + " +0000")
56
+ end
57
+
58
+ Event.new(
59
+ title: title,
60
+ description: description,
61
+ location: location,
62
+ begin_at: begin_at,
63
+ end_at: end_at,
64
+ link: link
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,72 @@
1
+ module Url2Event
2
+ class Meetup < Base
3
+ base_uri 'api.meetup.com'
4
+
5
+ class << self
6
+ def is_parser_for?(uri)
7
+ !uri.hostname.match(%r{(?:www\.)?meetup\.com}).nil?
8
+ end
9
+ end
10
+
11
+ protected
12
+
13
+ def options
14
+ @options = {
15
+ query: {
16
+ key: ENV['MEETUP_API_KEY'],
17
+ format: 'json'
18
+ }
19
+ }
20
+ end
21
+
22
+ def event_endpoint
23
+ "/2/event/#{get_id}"
24
+ end
25
+
26
+ # Parses the event identifier out of @uri
27
+ def get_id
28
+ matcher = %r{/(\d+)/?$}
29
+ matches = matcher.match(@uri.path)
30
+ if !matches.nil?
31
+ matches.captures[0]
32
+ else
33
+ raise Url2Event::UnparsableSourceError
34
+ end
35
+ end
36
+
37
+ def to_event(response)
38
+ response['duration'] ||= 10800000 # Default of 3 hours
39
+
40
+ if response['venue']
41
+ location = response['venue']['name']
42
+ location += ", " + response['venue']['address_1'] if response['venue']['address_1']
43
+ location += " " + response['venue']['address_2'] if response['venue']['address_2']
44
+ location += " " + response['venue']['address_3'] if response['venue']['address_3']
45
+ location += ", " + response['venue']['city'] if response['venue']['city']
46
+ end
47
+
48
+ description = sanitize(response['description'])
49
+ link = response['event_url']
50
+
51
+ if response['fee']
52
+ price = response['fee']['amount'].to_s if response['fee']['amount']
53
+ price += " " + response['fee']['currency'] if response['fee']['currency']
54
+ end
55
+
56
+ if response['time']
57
+ begin_at = Time.at((response['time'] + response['utc_offset']) / 1000)
58
+ end_at = Time.at((response['time'] + response['duration'] + response['utc_offset']) / 1000)
59
+ end
60
+
61
+ Event.new({
62
+ title: response['name'],
63
+ begin_at: begin_at,
64
+ end_at: end_at,
65
+ location: location,
66
+ description: description,
67
+ price: price,
68
+ link: link
69
+ })
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,43 @@
1
+ module Url2Event
2
+ class Base
3
+ attr_accessor :options
4
+ @implementations = []
5
+
6
+ class << self
7
+ attr_accessor :implementations
8
+
9
+ def inherited(subclass)
10
+ subclass.class_eval do
11
+ include HTTParty
12
+ end
13
+ @implementations << subclass unless @implementations.include?(subclass)
14
+ end
15
+
16
+ def is_parser_for?
17
+ raise NotImplementedError, "You should define #{self.class}.#{__method__}."
18
+ end
19
+ end
20
+
21
+ def initialize(uri)
22
+ @uri = uri
23
+ end
24
+
25
+ def get_event
26
+ response = self.class.get(event_endpoint, options)
27
+ to_event(response)
28
+ end
29
+
30
+ def sanitize(raw)
31
+ Rails::Html::FullSanitizer.new().sanitize(raw)
32
+ end
33
+
34
+ def to_event
35
+ raise NotImplementedError, "You should define #{self.class}##{__method__}."
36
+ end
37
+
38
+ def event_endpoint
39
+ raise NotImplementedError, "You should define #{self.class}##{__method__}."
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ require 'hashie/mash'
2
+
3
+ class Event < Hashie::Mash
4
+ end
@@ -0,0 +1,3 @@
1
+ module Url2Event
2
+ VERSION = "0.0.1"
3
+ end
data/script/console.rb ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'url_2_event'
4
+
5
+ require 'irb'
6
+ require 'logger'
7
+
8
+ IRB.start
@@ -0,0 +1,95 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://api.meetup.com/2/event/191161262?format=json&key=<MEETUP_API_KEY>
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Date:
22
+ - Thu, 20 Nov 2014 19:50:02 GMT
23
+ Content-Type:
24
+ - application/json;charset=utf-8
25
+ Transfer-Encoding:
26
+ - chunked
27
+ Connection:
28
+ - keep-alive
29
+ Set-Cookie:
30
+ - __cfduid=db9ea399f055d41f8970410e3dd7760cd1416513002; expires=Fri, 20-Nov-15
31
+ 19:50:02 GMT; path=/; domain=.meetup.com; HttpOnly
32
+ X-Meetup-Server:
33
+ - api10.int.meetup.com
34
+ X-Meetup-Request-Id:
35
+ - 78b133ad-3835-43a3-9ef0-04b513c06238
36
+ X-Oauth-Scopes:
37
+ - basic
38
+ X-Accepted-Oauth-Scopes:
39
+ - basic
40
+ Etag:
41
+ - W/"fbf2d5f3867d8fcffae5796adcb18f8a"
42
+ Vary:
43
+ - Accept-Encoding,User-Agent
44
+ Server:
45
+ - cloudflare-nginx
46
+ Cf-Ray:
47
+ - 18c72c97b18a0c41-SEA
48
+ body:
49
+ encoding: ASCII-8BIT
50
+ string: !binary |-
51
+ eyJzdGF0dXMiOiJwYXN0IiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIm1heWJl
52
+ X3JzdnBfY291bnQiOjAsInZlbnVlIjp7ImlkIjoxMDE3MjQ2LCJ6aXAiOiIx
53
+ MTIwMSIsImxvbiI6LTczLjk5MDQ4NiwicmVwaW5uZWQiOmZhbHNlLCJuYW1l
54
+ IjoiSFVHRSwgSW5jLiIsInN0YXRlIjoiTlkiLCJhZGRyZXNzXzEiOiI0NSBN
55
+ YWluIFN0LiAiLCJhZGRyZXNzXzIiOiJTdWl0ZSAyMjAiLCJsYXQiOjQwLjcw
56
+ Mjg2OSwiY2l0eSI6IkJyb29rbHluIiwiY291bnRyeSI6InVzIn0sImZlZSI6
57
+ eyJhbW91bnQiOjUsImRlc2NyaXB0aW9uIjoicGVyIHBlcnNvbiIsImxhYmVs
58
+ IjoiUHJpY2UiLCJyZXF1aXJlZCI6IjEiLCJhY2NlcHRzIjoiYW1hem9uIiwi
59
+ Y3VycmVuY3kiOiJVU0QifSwiaWQiOiIxOTExNjEyNjIiLCJ1dGNfb2Zmc2V0
60
+ IjotMTQ0MDAwMDAsInRpbWUiOjE0MDY3NjEyMDAwMDAsIndhaXRsaXN0X2Nv
61
+ dW50IjowLCJ1cGRhdGVkIjoxNDA2ODI2NjMzMDAwLCJ5ZXNfcnN2cF9jb3Vu
62
+ dCI6OTQsImNyZWF0ZWQiOjE0MDM3MDE0NzgwMDAsImV2ZW50X3VybCI6Imh0
63
+ dHA6XC9cL3d3dy5tZWV0dXAuY29tXC9CS3RlY2htZWV0dXBcL2V2ZW50c1wv
64
+ MTkxMTYxMjYyXC8iLCJkZXNjcmlwdGlvbiI6IjxwPkVkdWNhdGlvbiEgTGV0
65
+ J3MgYWxsIHRha2Ugb3VyIFNtYXJ0aWVzLCBtZWV0IHVwIHRvIHRhbGsgYWJv
66
+ dXQgd2hhdCdzIGdvaW5nIG9uIGluIEVkdWNhdGlvbiBhbmQgaGVhciBmcm9t
67
+ IHR3byBwZW9wbGUgd2hvIGFyZSBsZWFkaW5nIG1ham9yIGNoYW5nZXMgaW4g
68
+ dGhlIGluZHVzdHJ5LjxcL3A+IDxwPldlJ3JlIGdvaW5nIHRvIGJlIGhhdmlu
69
+ ZyB0d28gaW5jcmVkaWJseSBhbWF6aW5nIHNwZWFrZXJzIGZvbGxvd2VkIGJ5
70
+ ICQzIGJlZXJzIGFuZCBhIHJlc2VydmVkIGhhbmcgb3V0IGFyZWEgYXQgU3Vw
71
+ ZXJmaW5lLCBqdXN0IGRvd24gdGhlIHN0cmVldCBmcm9tIEh1Z2UsIHdoZXJl
72
+ IHdlJ2xsIGNvbnRpbnVlIG1pbmdsaW5nLjxcL3A+IDxwPlNwZWFrZXJzOjxc
73
+ L3A+IDxwPkFkYW0gRW5iYXIgZnJvbSBUaGUgRmxhdGlyb24gU2Nob29sPFwv
74
+ cD4gPHA+TGVhcm4gdG8gYnVpbGQgYXdlc29tZSB0aGluZ3Mgd2l0aCBjb2Rl
75
+ LldlJ3JlIGEgc2Nob29sIGZvciBwYXNzaW9uYXRlIHBlb3BsZSB3aG8gd2Fu
76
+ dCB0byBsb3ZlIHdoYXQgdGhleSBkby48XC9wPiA8cD5TdGVwaGFuaWUgRHVh
77
+ IGZyb20gSG9tZXI8XC9wPiA8cD5Ib21lcidzIG1pc3Npb24gaXMgdG8gYmUg
78
+ dGhlIHdvcmxkJ3MgbGVhZGluZyBwcm92aWRlciBvZiBxdWFsaXR5IGVhcmx5
79
+ IGxlYXJuaW5nIGV4cGVyaWVuY2VzIGZvciBjaGlsZHJlbi4gV2UgY3JlYXRl
80
+ IGlubm92YXRpdmUgcHJvZHVjdHMgdGhhdCBwYXJlbnRzIHRydXN0LCBjaGls
81
+ ZHJlbiBsb3ZlLCBhbmQgY29tbXVuaXRpZXMgdW5pdGUgYXJvdW5kLjxcL3A+
82
+ IDxwPlxuXG5cblxuSGF2ZSBhIGNvbXBhbnkgdGhhdCB5b3Ugd291bGQgbGlr
83
+ ZSB0byBkZW1vPyBJbnRlcmVzdGVkIGluIHNwb25zb3Jpbmcgb3VyIGV2ZW50
84
+ cz8gV2FudCB0byBqb2luIHRoZSBCSyBUZWNoIHRlYW0/IENvbnRhY3QgdXMg
85
+ YXQ6wqA8XC9wPiA8cD5bbWFza2VkXTxcL3A+IDxwPlttYXNrZWRdPFwvcD4g
86
+ PHA+W21hc2tlZF08XC9wPiIsIm5hbWUiOiJCSyBUZWNoIE1lZXR1cCAtIEVk
87
+ dWNhdGlvbiIsImhlYWRjb3VudCI6OTQsInJhdGluZyI6eyJjb3VudCI6Miwi
88
+ YXZlcmFnZSI6NX0sImdyb3VwIjp7ImlkIjo0MDA2MzUyLCJjcmVhdGVkIjox
89
+ MzM4ODYzMTg1MDAwLCJncm91cF9sYXQiOjQwLjcwOTk5OTA4NDQ3MjY1Niwi
90
+ bmFtZSI6IkJyb29rbHluIFRlY2ggTWVldHVwIiwiZ3JvdXBfbG9uIjotNzMu
91
+ OTQ5OTk2OTQ4MjQyMTksImpvaW5fbW9kZSI6Im9wZW4iLCJ1cmxuYW1lIjoi
92
+ Qkt0ZWNobWVldHVwIiwid2hvIjoiQ3JlYXRvcnMifX0K
93
+ http_version:
94
+ recorded_at: Thu, 20 Nov 2014 19:50:02 GMT
95
+ recorded_with: VCR 2.9.3