playapi 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/playapi.rb CHANGED
@@ -1,3 +1,26 @@
1
- class Playapi
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'hashie'
4
+ require 'playapi/configurable'
5
+ require 'playapi/client'
6
+ require 'playapi/entity'
7
+ require 'playapi/interaction'
8
+ require 'playapi/feature'
9
+ require 'playapi/campaign'
10
+ require 'playapi/visual'
11
+ require 'playapi/utils'
2
12
 
3
- end
13
+
14
+ module Playapi
15
+ class << self
16
+ include Playapi::Configurable
17
+
18
+ def client
19
+ @client = Playapi::Client.new(options) unless defined?(@client) && @client.hash == options.hash
20
+ @client
21
+ end
22
+
23
+ end
24
+ end
25
+
26
+ Playapi.reset! # sets default values
@@ -0,0 +1,60 @@
1
+ require 'playapi/utils'
2
+
3
+ module Playapi
4
+ class Campaign
5
+ extend Playapi::Utils
6
+
7
+ # stub this out for now, it will probably do crazy stuff when we integrate campaign controls.
8
+
9
+ class << self
10
+
11
+ # Get the primary campaign for which you are currently authorized, one will be able to
12
+ # pass the name of the campaign, if your credentials are good for multiple campaigns...
13
+ # but not yet.
14
+ def get(campaign_name = "index")
15
+ url = "api/v2/campaigns/"
16
+ #url += "/#{campaign_name}" unless campaign_name == nil
17
+ get_object(:get, "campaign", url)
18
+ end
19
+
20
+ # Get interactions for a campaign, optional paramaters include
21
+ # Optional params hash (opts)
22
+ # :sort=STRING (Valid options are: leaders, newest, oldest)
23
+ # :approved=BOOL (Show only approved interactions)
24
+ # :pending=BOOL (Show only pending (unapproved) interactions)
25
+ # :limit=INTEGER (Limit to a number of entries, defaults to 10)
26
+ # :page=INTEGER (Determines the offset for limit, defaults to 1)
27
+ # :type=STRING (Valid options are Instapic, Tweet, Checkin)
28
+ def get_interactions(campaign_name, opts = {})
29
+ #* This could just called Playapi::Interaction.list....
30
+ url = "api/v2/campaigns/#{campaign_name}/interactions"
31
+ get_object(:get, "interactions", url, opts).interactions
32
+ end
33
+
34
+ # Get interactions with the most points (highest score) for a campaign
35
+ # Optional params hash (opts)
36
+ # :limit=INTEGER (Limit to a number of entries, defaults to 10)
37
+ # :page=INTEGER (Determines the offset for limit, defaults to 1)
38
+ # :type=STRING (Valid options are Instapic, Tweet, Checkin)
39
+ def leaders(campaign_name, opts = {})
40
+ #* This could just call Playapi::Interactions.list with most points params...
41
+ url = "api/v2/campaigns/#{campaign_name}/leaders"
42
+ get_object(:get, "results", url, opts)
43
+ end
44
+
45
+ def create(opts)
46
+ #raise "WE DON'T MAKE CAMPAIGNS YET"
47
+ #url = "api/v2/campaigns"
48
+ #get_object(:get, "interaction", url, {:entity => opts})
49
+ end
50
+
51
+ def update(name, opts)
52
+ #raise "WE DON'T UPDATE CAMPAIGNS YET"
53
+ #url = "api/v2/campaigns/#{id}"
54
+ #get_object(:put, "campaign", url, {:entity => opts})
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ require 'faraday'
2
+
3
+ module Playapi
4
+ class Client
5
+ include Playapi::Configurable # i don't think this is needed
6
+
7
+ def initialize(options={})
8
+ Playapi::Configurable.keys.each do |key|
9
+ instance_variable_set(:"@#{key}", options[key] || Playapi.instance_variable_get(:"@#{key}"))
10
+ end
11
+ end
12
+
13
+ def connection
14
+ params = {}
15
+ params[:client_id] = @client_id
16
+ params[:client_secret] = @client_secret
17
+ params[:token] = @oauth_token
18
+
19
+
20
+ @connection ||= Faraday::Connection.new(:url => @endpoint,
21
+ :ssl => @ssl,
22
+ :params => params,
23
+ :headers => default_headers
24
+ ) do |builder|
25
+ @connection_middleware.each do |mw|
26
+ builder.use *mw
27
+ end
28
+ builder.adapter Faraday.default_adapter
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def default_headers
35
+ headers = {
36
+ :accept => "application/json",
37
+ :user_agent => "PlayAPI Gem 0.0.1"
38
+ }
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,94 @@
1
+ module Playapi
2
+ module Configurable
3
+ extend Forwardable
4
+ attr_writer :client_id, :client_secret, :oauth_token
5
+ attr_accessor :endpoint, :connection_options, :identity_map, :connection_middleware
6
+ def_delegator :options, :hash
7
+
8
+
9
+ DEFAULT_CONNECTION_MIDDLEWARE = [
10
+ Faraday::Request::Multipart,
11
+ Faraday::Request::UrlEncoded,
12
+ FaradayMiddleware::Mashify,
13
+ FaradayMiddleware::ParseJson
14
+ ]
15
+
16
+ class << self
17
+
18
+ def keys
19
+ @keys ||= [
20
+ :client_id,
21
+ :client_secret,
22
+ :oauth_token,
23
+ :endpoint,
24
+ :connection_options,
25
+ :identity_map,
26
+ :connection_middleware,
27
+ ]
28
+ end
29
+
30
+ end
31
+
32
+ # Convenience method to allow configuration options to be set in a block
33
+ #
34
+ # @raise [Twitter::Error::ConfigurationError] Error is raised when supplied
35
+ # twitter credentials are not a String or Symbol.
36
+ def configure
37
+ yield self
38
+ validate_credential_type!
39
+ self
40
+ end
41
+
42
+ # @return [Boolean]
43
+ def credentials?
44
+ credentials.values.all?
45
+ end
46
+
47
+ def reset!
48
+ defaultz = {client_id: "foo", client_secret: "foo", oauth_token: "foo", endpoint: "http://198.199.69.11"}
49
+ # we can probably chuck conn_ops
50
+ conn_ops = {
51
+ :connection_options => {:headers => {:accept => "application/json", :user_agent => "PlayAPI Gem 0.0.1"},
52
+ :ssl => {:verify => false}}
53
+ }
54
+ #defaultz.merge!(conn_ops)
55
+
56
+ defaultz.merge!(:connection_middleware => DEFAULT_CONNECTION_MIDDLEWARE)
57
+
58
+ Playapi::Configurable.keys.each do |key|
59
+ instance_variable_set(:"@#{key}", defaultz[key])
60
+ end
61
+ self
62
+ end
63
+ alias setup reset!
64
+
65
+ private
66
+
67
+ # @return [Hash]
68
+ def credentials
69
+ {
70
+ :client_id => @client_id,
71
+ :client_secret => @client_secret,
72
+ :token => @oauth_token,
73
+ }
74
+ end
75
+
76
+ # @return [Hash]
77
+ def options
78
+ Hash[Playapi::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
79
+ end
80
+
81
+ # Ensures that all credentials set during configuration are of a
82
+ # valid type. Valid types are String and Symbol.
83
+ def validate_credential_type!
84
+ credentials.each do |credential, value|
85
+ next if value.nil?
86
+
87
+ unless value.is_a?(String) || value.is_a?(Symbol)
88
+ raise "Invalid #{credential} specified: #{value} must be a string or symbol."
89
+ end
90
+ end
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,27 @@
1
+ require 'playapi/utils'
2
+
3
+ module Playapi
4
+ class Entity
5
+ extend Playapi::Utils
6
+
7
+ class << self
8
+
9
+ def get(id)
10
+ url = "api/v2/entities/#{id}"
11
+ get_object(:get, "entity", url)
12
+ end
13
+
14
+ def create(opts)
15
+ url = "api/v2/entities"
16
+ get_object(:get, "entity", url, {:entity => opts})
17
+ end
18
+
19
+ def update(id, opts)
20
+ url = "api/v2/entities/#{id}"
21
+ get_object(:put, "entity", url, {:entity => opts})
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ require 'playapi/utils'
2
+ require 'playapi/validation/feature'
3
+ require 'playapi/validation/instagrabber'
4
+ require 'playapi/validation/twitterscraper'
5
+ require 'playapi/validation/picking'
6
+
7
+
8
+ module Playapi
9
+ class Feature
10
+ extend Playapi::Utils
11
+
12
+ def initialize
13
+
14
+ end
15
+
16
+ class << self
17
+ # Get a list of features for auth campaign
18
+ # TODO: add filters and options
19
+ def list
20
+ url = "api/v2/features"
21
+ get_object(:get, "features", url)
22
+ end
23
+
24
+ # Fetch a feature by id
25
+ def get(id)
26
+ url = "api/v2/features/#{id}"
27
+ get_object(:get, "feature", url)
28
+ end
29
+
30
+ # Create a new feature for authed campaign
31
+ #
32
+ # Type is a string that corresponds to a Playapi Feature class
33
+ #
34
+ # Valid options are:
35
+ # type=STRING (Valid Options: Picking, Voting, Instagrabber, TwitterScraper, Foursquare)
36
+ #
37
+ # Required fields for ALL types (opts)
38
+ # :name=STRING
39
+ # :description=STRING
40
+ #
41
+ # Required fields for Instagrabber, TwitterScraper, Foursquare
42
+ # :default_points=FLOAT (Default is 0)
43
+ #
44
+ # Required fields for Instagrabber, TwitterScraper
45
+ # :hashtags=Array (Array of hashtags to watch)
46
+
47
+ def create(type, opts)
48
+ url = "api/v2/features"
49
+ #validator = "Playapi::Validation::#{type.split('_').map {|w| w.capitalize}.join}".split("::").inject(Module) {|acc, val| acc.const_get(val)}
50
+ #validator.validate(opts)
51
+ get_object(:post, "feature", url, {:feature => opts, :type => type})
52
+ end
53
+
54
+ # Update a feature with the given id
55
+ # See create for field details.
56
+
57
+ def update(id, opts)
58
+ url = "api/v2/features/#{id}"
59
+ get_object(:put, "feature", url, {:feature => opts})
60
+ end
61
+
62
+ # destroy a feature
63
+ def destroy(id)
64
+ url = "api/v2/features/#{id}"
65
+ get_object(:delete, "feature", url)
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,94 @@
1
+ require 'playapi/utils'
2
+ require 'playapi/validation/instapic'
3
+ require 'playapi/validation/tweet'
4
+ require 'playapi/validation/custom'
5
+
6
+
7
+
8
+ module Playapi
9
+ class Interaction
10
+ extend Playapi::Utils
11
+
12
+ class << self
13
+
14
+ # Get interactions for a campaign, optional paramaters include
15
+ # Optional params hash (opts)
16
+ # :sort=STRING (Valid options are: leaders, newest, oldest)
17
+ # :approved=BOOL (Show only approved interactions)
18
+ # :pending=BOOL (Show only pending (unapproved) interactions)
19
+ # :limit=INTEGER (Limit to a number of entries, defaults to 10)
20
+ # :page=INTEGER (Determines the offset for limit, defaults to 1)
21
+ # :type=STRING (Valid options are Instapic, Tweet, Checkin)
22
+ def list
23
+ url = "api/v2/interactions"
24
+ get_object(:get, "interactions", url)
25
+ end
26
+
27
+ # Get an interaction with the given id
28
+ def get(id)
29
+ url = "api/v2/interactions/#{id}"
30
+ get_object(:get, "interaction", url)
31
+ end
32
+
33
+ # Create a custom interaction for your campaign
34
+ # Optional fields (opts)
35
+ # :entity_id=STRING (Playapi Entity ID - ONLY USE THIS IF YOU HAVE A PLAYAPI PROVIDED ENTITY_ID)
36
+ # :points=Float (Points for this interaction)
37
+ # WILL BE DEPRECATED IN VERSION 0.0.2, please use classed_interaction! NOT A JOKE
38
+ def create(opts)
39
+ url = "api/v2/interactions"
40
+ get_object(:post, "interaction", url, {:interaction => opts})
41
+ end
42
+
43
+ # Update a specific interaction for your campaign
44
+ def update(id, opts)
45
+ url = "api/v2/interactions/#{id}"
46
+ get_object(:put, "interaction", url, {:interaction => opts})
47
+ end
48
+
49
+ def destroy(id)
50
+ url = "api/v2/interactions/#{id}"
51
+ get_object(:delete, "interaction", url)
52
+ end
53
+
54
+ # pass in a hash of options to find things, currently the only one that is applicable is content_id
55
+ # content_id=String
56
+ def find_by_facet(opts)
57
+ url = "api/v2/interactions/facet"
58
+ get_object(:get, :interactions, url, opts)
59
+ end
60
+
61
+ # Create a classed interaction for authed campaign
62
+ #
63
+ # Type is a string corresponds to a Playapi Interaction class
64
+ #
65
+ # Valid options are:
66
+ # :type=STRING (Valid Options: Custom, Instapic, Tweet)
67
+ # Instapic(:content_id, :)
68
+ #
69
+ #
70
+ # ID VALUES FOR THIS CLASS ARE PROVIDED BY PLAYAPI AND MUST MATCH !!!
71
+ # IF THE ID VALUES DON'T MATCH, YOUR GONNA HAVE A BAD TIME !!!
72
+ #
73
+ # Required fields for ALL types (opts)
74
+ # :feature_id=STRING (Playapi Feature ID )
75
+ # :points=FLOAT (Points for this interaction)
76
+ # :content_id=STRING (3rd party (twitter status_id, etc) id from which content was sourced)
77
+ #
78
+ # Required fields for Instapics
79
+ # :asset_url=STRING (URL of the instagram image)
80
+ #
81
+ # Required fields for Tweet
82
+ # :text=STRING (Text from a Tweet)
83
+
84
+ def classed_interaction(type="Custom", opts = {})
85
+ url = "api/v2/interactions"
86
+ validator = "Playapi::Validation::#{type.split('_').map {|w| w.capitalize}.join}".split("::").inject(Module) {|acc, val| acc.const_get(val)}
87
+ validator.validate(opts)
88
+ get_object(:post, "interaction", url, {:interaction => opts, :type => type})
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,31 @@
1
+ module Playapi
2
+ module Utils
3
+ class << self
4
+ def hi
5
+ return true
6
+ end
7
+ end
8
+
9
+ private
10
+
11
+ def get_object(method, key, path, options={})
12
+ res = Playapi.client.connection.send(method.to_sym, path, options)
13
+ if res.status == 200
14
+ res.body[key]
15
+ else
16
+ errors = res.body["errors"]
17
+ raise "Error returned: #{res.status} #{errors}"
18
+ end
19
+ end
20
+
21
+ def get_objects(method, path, options={})
22
+ res = Playapi.client.connection.send(method.to_sym, path, options)
23
+ if res.status == 200
24
+ res.body
25
+ else
26
+ raise "Error returned: #{res.status}"
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Playapi
2
+ module Validation
3
+ module Custom
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :name
10
+ ]
11
+ end
12
+
13
+ def validate(opts)
14
+ pkeys.each do |p|
15
+ unless opts.include?(p)
16
+ raise "#{p} must not be nil"
17
+ end
18
+ end
19
+ opts.each do |interaction_field, value|
20
+ #next if value.nil?
21
+
22
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
23
+ raise("Invalid #{interaction_field} specified: #{value} must be a string or a number") unless value == points
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ module Playapi
2
+ module Validation
3
+ module Feature
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :name,
10
+ :description ]
11
+ end
12
+
13
+ def validate(opts)
14
+ pkeys.each do |p|
15
+ unless opts.include?(p)
16
+ raise "#{p} must not be nil"
17
+ end
18
+ end
19
+ opts.each do |field, value|
20
+ raise "Type should not be specified in options!" if field.to_s == "type"
21
+ #next if value.nil?
22
+
23
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
24
+ raise("Invalid #{value} specified: #{field} must be a string or a number") unless value == points
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ module Playapi
2
+ module Validation
3
+ module Instagrabber
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :name,
10
+ :description,
11
+ :type,
12
+ :client_id,
13
+ :client_secret
14
+ ]
15
+ end
16
+
17
+ def validate(opts)
18
+ pkeys.each do |p|
19
+ unless opts.include?(p)
20
+ raise "#{p} must not be nil"
21
+ end
22
+ end
23
+ opts.each do |field, value|
24
+ raise "Type should not be specified in options!" if field.to_s == "type"
25
+ #next if value.nil?
26
+
27
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
28
+ raise("Invalid #{value} specified: #{field} must be a string or a number") unless value == points
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ module Playapi
2
+ module Validation
3
+ module Instapic
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :feature_id,
10
+ :asset_url,
11
+ :content_id,
12
+ :name
13
+ ]
14
+ end
15
+
16
+ def validate(opts)
17
+ pkeys.each do |p|
18
+ unless opts.include?(p)
19
+ raise "#{p} must not be nil"
20
+ end
21
+ end
22
+ opts.each do |interaction_field, value|
23
+ #next if value.nil?
24
+
25
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
26
+ raise("Invalid #{interaction_field} specified: #{value} must be a string or a number")
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module Playapi
2
+ module Validation
3
+ module Picking
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :name,
10
+ :description,
11
+ :feature_targets
12
+ ]
13
+ end
14
+
15
+ def validate(opts)
16
+ pkeys.each do |p|
17
+ unless opts.include?(p)
18
+ raise "#{p} must not be nil"
19
+ end
20
+ end
21
+ opts.each do |field, value|
22
+ raise "Type should not be specified in options!" if field.to_s == "type"
23
+ #next if value.nil?
24
+
25
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
26
+ raise("Invalid #{value} specified: #{field} must be a string or a number") unless value == points
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ module Playapi
2
+ module Validation
3
+ module Tweet
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :feature_id,
10
+ :content_id,
11
+ :name
12
+ ]
13
+ end
14
+
15
+ def validate(opts)
16
+ pkeys.each do |p|
17
+ unless opts.include?(p)
18
+ raise "#{p} must not be nil"
19
+ end
20
+ end
21
+ opts.each do |interaction_field, value|
22
+ #next if value.nil?
23
+
24
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
25
+ raise("Invalid #{interaction_field} specified: #{value} must be a string or a number")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ module Playapi
2
+ module Validation
3
+ module Twitterscraper
4
+
5
+ class << self
6
+
7
+ def pkeys
8
+ @pkeys ||= [
9
+ :name,
10
+ :description,
11
+ :client_id,
12
+ :client_secret,
13
+ :oauth_token,
14
+ :oauth_token_secret
15
+ ]
16
+ end
17
+
18
+ def validate(opts)
19
+ pkeys.each do |p|
20
+ unless opts.include?(p)
21
+ raise "#{p} must not be nil"
22
+ end
23
+ end
24
+ opts.each do |field, value|
25
+ raise "Type should not be specified in options!" if field.to_s == "type"
26
+ #next if value.nil?
27
+
28
+ unless value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float)
29
+ raise("Invalid #{value} specified: #{field} must be a string or a number") unless value == points
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'playapi/utils'
2
+
3
+ module Playapi
4
+ class Visual
5
+ extend Playapi::Utils
6
+
7
+ class << self
8
+
9
+ def get_all
10
+ url "api/v2/visuals"
11
+ get_object(:get, "visuals", url)
12
+ end
13
+
14
+ def get(id)
15
+ url = "api/v2/visuals/#{id}"
16
+ get_object(:get, "visual", url)
17
+ end
18
+
19
+ def create(opts)
20
+ url = "api/v2/visuals"
21
+ get_object(:post, "visual", url, {:visual => opts})
22
+ end
23
+
24
+ def update(id, opts)
25
+ url = "api/v2/visuals/#{id}"
26
+ get_object(:put, "visual", url, {:visual => opts})
27
+ end
28
+
29
+ def destroy(id)
30
+ url = "api/v2/visuals/#{id}"
31
+ get_object(:delete, "visual", url)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: playapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-11 00:00:00.000000000 Z
12
+ date: 2013-05-06 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Interact with PlayAPI
15
15
  email: france@playapi.com
@@ -18,6 +18,21 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - lib/playapi.rb
21
+ - lib/playapi/client.rb
22
+ - lib/playapi/entity.rb
23
+ - lib/playapi/interaction.rb
24
+ - lib/playapi/visual.rb
25
+ - lib/playapi/feature.rb
26
+ - lib/playapi/campaign.rb
27
+ - lib/playapi/configurable.rb
28
+ - lib/playapi/utils.rb
29
+ - lib/playapi/validation/instapic.rb
30
+ - lib/playapi/validation/tweet.rb
31
+ - lib/playapi/validation/instagrabber.rb
32
+ - lib/playapi/validation/twitterscraper.rb
33
+ - lib/playapi/validation/picking.rb
34
+ - lib/playapi/validation/feature.rb
35
+ - lib/playapi/validation/custom.rb
21
36
  homepage: http://rubygems.org/gems/playapi
22
37
  licenses: []
23
38
  post_install_message: