playapi 0.0.1 → 0.0.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.
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: