backpack_tf 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a80c2c34c6cd7c6ec419533195231f742faeb5f7
4
- data.tar.gz: d434f3c196f776d1836072733f3422a9a4cfcf1e
3
+ metadata.gz: 5b3f89b16c6ef3907403d7a9caab7457e740bd02
4
+ data.tar.gz: e41e8dbf4ba4bc3db93a40197cac519e22d3b106
5
5
  SHA512:
6
- metadata.gz: 5b9cf050101bec789981bdd69a88f14f828e98ec0e9e2a1c132d7c924c87ee265a8505d664ff9b777f7ecf1c5e199095c2cf922ed25b5b4db24b5e4b5d0d0ea6
7
- data.tar.gz: 2cdf8ad385ebeaf324de8add2ce3456b35719879dd55aaa27a016b2f5ed7d443e3f88287a94719e106cb5221332d1123b141c1f95c4a8d5697144b5e378a19a1
6
+ metadata.gz: 29b1583190c592fcba721473e9800d1b9ed2d7b80a2d841917469513ef9db6e73a66ae3ac6c1e044718024f6e04f2192b1440d6da26234075c3a4deff332566e
7
+ data.tar.gz: 83f26e2dafd51d449cf205d6244a0575afe9e882a017dec8b8e86b88fb7ab30257c2d40d2ed6d983995c00fc63683ba7409eec4b0ea20f4a10527c966822cb39
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.sw*
2
+ *dump.rdb
3
+
4
+ *.gem
5
+ Gemfile.lock
6
+
7
+ spec/fixtures/*yml
8
+ spec/other-library-examples
9
+
10
+ scratch/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'httparty'
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'webmock'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.3.8)
5
+ crack (0.4.2)
6
+ safe_yaml (~> 1.0.0)
7
+ diff-lcs (1.2.5)
8
+ httparty (0.13.3)
9
+ json (~> 1.8)
10
+ multi_xml (>= 0.5.2)
11
+ json (1.8.2)
12
+ multi_xml (0.5.5)
13
+ redis (3.2.1)
14
+ rspec (3.2.0)
15
+ rspec-core (~> 3.2.0)
16
+ rspec-expectations (~> 3.2.0)
17
+ rspec-mocks (~> 3.2.0)
18
+ rspec-core (3.2.3)
19
+ rspec-support (~> 3.2.0)
20
+ rspec-expectations (3.2.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.2.0)
23
+ rspec-mocks (3.2.1)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.2.0)
26
+ rspec-support (3.2.2)
27
+ safe_yaml (1.0.4)
28
+ vcr (2.9.3)
29
+ webmock (1.21.0)
30
+ addressable (>= 2.3.6)
31
+ crack (>= 0.3.2)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ httparty
38
+ redis
39
+ rspec
40
+ vcr
41
+ webmock
data/TODO.md ADDED
@@ -0,0 +1,15 @@
1
+ ##TODO list
2
+
3
+ #####testing:
4
+ * a mock backpack.tf service
5
+ * stubs & archived cache data
6
+
7
+ #####documentation:
8
+ * better documentation on all classes, modules, methods, attributes, etc
9
+ * support for YARD or RDOC
10
+
11
+ #####caching:
12
+ * options to cache data and force a refresh
13
+
14
+ #####formats:
15
+ * the API outputs 3 formats: JSON (default), JSONP and VDF. Currently, the gem supports JSON input only. Support for JSONP & VDF is needed.
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'backpack_tf'
3
+ s.version = '0.2.1'
4
+ s.date = '2015-05-01'
5
+ s.summary = 'a wrapper for the backpack.tf API'
6
+ s.author = 'Rafael Espinoza'
7
+ s.email = 'rafael@rafaelespinoza.com'
8
+ s.homepage = 'https://github.com/NerdDiffer/backpack_tf'
9
+ s.license = 'MIT'
10
+ s.description = s.summary
11
+
12
+ s.require_paths = ['lib']
13
+ s.files = `git ls-files`.split("\n")
14
+
15
+ s.add_runtime_dependency 'httparty', '~>0.13', '>=0.13.3'
16
+
17
+ s.add_development_dependency 'rspec', '~>3.2', '>=3.2.0'
18
+ s.add_development_dependency 'webmock', '~>1.21', '>=1.21.0'
19
+ end
@@ -0,0 +1,102 @@
1
+ module BackpackTF
2
+
3
+ class Client
4
+
5
+ include HTTParty
6
+
7
+ ###########################
8
+ # Class Methods
9
+ ###########################
10
+
11
+ # store your API key as an environment variable
12
+ # `export <env_var>='<your api key>'`
13
+ @@env_var = 'BPTF_API_KEY'
14
+ def self.env_var; @@env_var; end
15
+
16
+ def self.api_key key = nil
17
+ default = key || ENV[@@env_var]
18
+
19
+ if default.nil?
20
+ msg = "Assign your API key to an environment variable.\n"
21
+ msg << "ex: `export #{@@env_var}=value`"
22
+ raise KeyError, msg
23
+ elsif default.class == String && (default.length != 24 || !!default[/\H/])
24
+ msg = "The key should be a hexadecimal number, 24-digits long"
25
+ raise ArgumentError, msg
26
+ else
27
+ default
28
+ end
29
+ end
30
+
31
+ base_uri 'http://backpack.tf/api'
32
+ default_timeout 5
33
+ default_params(:key => api_key)
34
+
35
+ def self.build_url_via action, query_options = {}
36
+ case action
37
+ when :get_prices
38
+ version = 4
39
+ interface_url = "/#{Prices.interface}/v#{version}/?"
40
+ when :get_currencies
41
+ version = 1
42
+ interface_url = "/#{Currencies.interface}/v#{version}/?"
43
+ when :get_special_items
44
+ version = 1
45
+ interface_url = "/IGetSpecialItems/v#{version}/?"
46
+ when :get_users
47
+ version = 3
48
+ interface_url = "/IGetUsers/v#{version}/?"
49
+ when :get_user_listings
50
+ version = 1
51
+ interface_url = "/IGetUserListings/v#{version}/?"
52
+ else
53
+ raise ArgumentError, 'pass in valid action as a Symbol object'
54
+ end
55
+
56
+ base_uri + interface_url + extract_query_string(query_options)
57
+ end
58
+
59
+ def self.extract_query_string options = {}
60
+ options.each_pair.map do |key, val|
61
+ unless val.class == Array
62
+ "#{key}=#{val}"
63
+ else
64
+ "#{key}=#{val.join(',')}"
65
+ end
66
+ end.join('&')
67
+ end
68
+
69
+ ###########################
70
+ # Instance Methods
71
+ ###########################
72
+ attr_reader :db
73
+
74
+ def initialize
75
+ @db = nil
76
+ end
77
+
78
+ def get_data action, query_options = {}
79
+ handle_timeouts do
80
+ url = self.class.build_url_via(action, query_options)
81
+ self.class.get(url)#['response']
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ # HTTParty raises an errors after time limit defined by ::default_timeout
88
+ # * if it cannot connect to server, then it raises Net::OpenTimeout
89
+ # * if it cannot read response from server, then it raises Net::ReadTimeout
90
+ # if one of those happen, then an empty hash is returned
91
+ def handle_timeouts
92
+ begin
93
+ yield
94
+ rescue Net::OpenTimeout, Net::ReadTimeout
95
+ {}
96
+ end
97
+ end
98
+
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,62 @@
1
+ module BackpackTF
2
+
3
+ # ruby representations of a JSON response to
4
+ # `IGetCurrencies`['response']
5
+ class Currencies
6
+
7
+ ###########################
8
+ # Class Methods
9
+ ###########################
10
+
11
+ include BackpackTF::Response
12
+
13
+ @interface = :IGetCurrencies
14
+
15
+ def self.currencies
16
+ @@currencies = hash_keys_to_sym(@response[:currencies])
17
+ end
18
+
19
+ ###########################
20
+ # Instance Methods
21
+ ###########################
22
+
23
+ # @return [Fixnum] the quality index of the currency
24
+ attr_reader :quality
25
+ # @return [Fixnum] the internal priceindex of the currency
26
+ attr_reader :priceindex
27
+ # @return [String] the single form of noun that is used in the suffix
28
+ attr_reader :single
29
+ # @return [String] the plural form of noun that is used in the suffix
30
+ attr_reader :plural
31
+ # @return [Fixnum] the number of decimal places the price should be rounded to
32
+ attr_reader :round
33
+ # @return [Symbol] either :Craftable or :Non-Craftable to signify currency's craftability
34
+ attr_reader :craftable
35
+ # @return [Symbol] either :Tradable or :Non-Tradable to signify currency's tradability
36
+ attr_reader :tradable
37
+ # @return [Fixnum] the definition index of the currency
38
+ attr_reader :defindex
39
+ # TODO: what does the :blanket attribute mean?
40
+ # it is set to 0 by default. However, it is set to 1 for :hat.
41
+ # :hat also has an extra property & value :blanket_name => 'Random Craft Hat'
42
+ # @return [Fixnum]
43
+ attr_reader :blanket
44
+
45
+ def initialize name, attr
46
+ attr = check_attr_keys(attr)
47
+
48
+ @name = name.to_s
49
+ @quality = attr[:quality]
50
+ @priceindex = attr[:priceindex]
51
+ @single = attr[:single]
52
+ @plural = attr[:plural]
53
+ @round = attr[:round]
54
+ @craftable = attr[:craftable].to_sym
55
+ @tradable = attr[:tradable].to_sym
56
+ @defindex = attr[:defindex]
57
+ @blanket = attr[:blanket]
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,19 @@
1
+ module BackpackTF
2
+
3
+ class Item
4
+
5
+ # @return [String] the name of item
6
+ attr_reader :item_name
7
+ # @return [Fixnum] the index on which you can link this item to Team Fortress 2's Item Schema
8
+ attr_reader :defindex
9
+ # @return [Hash<Fixnum, ItemPrice>] a hash object
10
+ attr_reader :prices
11
+
12
+ def initialize item_name, attr
13
+ @item_name = item_name
14
+ @defindex = attr['defindex'][0]
15
+ @prices = nil
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ module BackpackTF
2
+
3
+ class ItemPrice
4
+
5
+ attr_reader :quality, :tradability, :craftability, :priceindex,
6
+ :currency, :value, :value_high, :value_raw, :value_high_raw,
7
+ :last_update, :difference
8
+
9
+ def initialize
10
+ end
11
+
12
+ # mapping official API quality integers to quality names
13
+ # https://wiki.teamfortress.com/wiki/WebAPI/GetSchema#Result_Data
14
+ @@qualities = [
15
+ 'Normal',
16
+ 'Genuine',
17
+ nil,
18
+ 'Vintage',
19
+ nil,
20
+ 'Unusual',
21
+ 'Unique',
22
+ 'Community',
23
+ 'Valve',
24
+ 'Self-Made',
25
+ nil,
26
+ 'Strange',
27
+ nil,
28
+ 'Haunted',
29
+ "Collector's"
30
+ ]
31
+
32
+ @@tradabilities = [:Tradable, :Untradable]
33
+ @@craftabilities = [:Craftable, :Uncraftable]
34
+
35
+ # returns JSON data for the item
36
+ # does not return data for items with a special particle effect
37
+ def self.get_item_price quality, item_name
38
+ item = find_item_by_name(item_name)
39
+ ind = @@qualities.find_index(quality)
40
+
41
+ prefix = item['prices'][ind.to_s]['Tradable']
42
+ if prefix.nil?
43
+ raise(ArgumentError, "The item, #{quality} #{item_name}, is not Tradable")
44
+ end
45
+ prefix = prefix['Craftable']
46
+ if prefix.nil?
47
+ raise(ArgumentError, "The item, #{quality} #{item_name}, is not Craftable")
48
+ end
49
+
50
+ # oddly, there are cases (such as the "Lugermorph"), where the
51
+ # type of the object at this point in the JSON data (saved to the `prefix` variable)
52
+ # is a Hash object rather than an Array object.
53
+ # That makes the PriceIndex key a String, "0", rather than a Fixnum, 0.
54
+ if prefix[0].nil?
55
+ prefix[0.to_s]
56
+ else
57
+ prefix[0]
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,70 @@
1
+ module BackpackTF
2
+
3
+ # ruby representations of a JSON response to
4
+ # `IGetPrices`['response']
5
+ class Prices
6
+
7
+ include BackpackTF::Response
8
+
9
+ @interface = :IGetPrices
10
+
11
+ def self.items
12
+ @@items = @response[:items]
13
+ end
14
+
15
+ def self.defindex_to_item_name defindex
16
+ items = get_items_hash
17
+ keys = items.keys#.shuffle
18
+
19
+ i = 0
20
+ while i < keys.length
21
+ current_defindex = items[keys[i]]['defindex']
22
+ if(current_defindex[0] == defindex)
23
+ return keys[i]
24
+ end
25
+ i += 1
26
+ end
27
+ raise KeyError, "item with a defindex of #{defindex} was not found"
28
+ end
29
+
30
+ def self.get_name_of_random_item
31
+ items = get_items_hash
32
+ items.keys.sample
33
+ end
34
+
35
+ # returns JSON representation of pricing for the item
36
+ def self.find_item_by_name item_name
37
+ items = get_items_hash
38
+ if items[item_name].nil?
39
+ raise KeyError, "item with the name #{item_name} was not found"
40
+ else
41
+ items[item_name]
42
+ end
43
+ end
44
+
45
+ # @param [String] item_name, the item name (according to item_name of item's schema)
46
+ # @param [Symbol] type, checking to see if item is of this type
47
+ # @return [Boolean] `true` if the item is the type
48
+ def self.is_item_of_type? item_name, type = :weapon
49
+ item = find_item_by_name(item_name)
50
+ defindex = item['defindex'][0]
51
+ tf2_item = Trade.tf2_item_schema.items[defindex]
52
+
53
+ case type
54
+ when :cosmetic
55
+ tf2_item[:item_class] == 'tf_wearable'
56
+ else
57
+ tf2_item[:item_slot] == 'primary' ||
58
+ tf2_item[:item_slot] == 'secondary' ||
59
+ tf2_item[:item_slot] == 'melee'
60
+ end
61
+ end
62
+
63
+ def initialize
64
+ msg = "This class is meant to receive the JSON response from the #{self.class.interface} interface. It holds a Hash array of prices of items, but not is meant to be instantiated. See the Item class if you are interested in an item. However, information on items should be stored in the @items property of #{self.class}"
65
+ raise RuntimeError, msg
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,59 @@
1
+ module BackpackTF
2
+ module Response
3
+
4
+ def self.included(other)
5
+ puts "#{self} included in (#{other})"
6
+ other.extend(ClassMethods)
7
+ super
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ @response = nil
13
+ def interface; @interface; end
14
+
15
+ def response force_update = false
16
+ # if force is true, set value of @response to results of ::fetch
17
+ # otherwise, use a `nil guard` to return @response
18
+ # if the value is already set, then it returns value of @response
19
+ # if the value is not set, then it runs ::fetch
20
+ force_update ?
21
+ @response = fetch :
22
+ @response ||= fetch
23
+ end
24
+
25
+ def fetch client_stuff
26
+ @response = hash_keys_to_sym(client_stuff)
27
+ end
28
+
29
+ # checks the data type of the keys of a Hash object
30
+ # if the key is a String, then changes it to a Symbol
31
+ # otherwise, leaves it as is
32
+ def hash_keys_to_sym hash
33
+ hash.each_pair.inject({}) do |new_hash, (key, val)|
34
+ unless key.class == String
35
+ new_hash[key] = val
36
+ else
37
+ new_hash[key.to_sym] = val
38
+ end
39
+ new_hash
40
+ end
41
+ end
42
+ end
43
+
44
+ ############################
45
+ # PRIVATE INSTANCE METHODS
46
+ ############################
47
+ private
48
+ def check_attr_keys attr
49
+ raise TypeError, 'pass in a Hash object' unless attr.class == Hash
50
+
51
+ unless attr.keys.all? { |k| k.class == String }
52
+ raise TypeError, 'all keys must be String object'
53
+ end
54
+
55
+ self.class.hash_keys_to_sym(attr)
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ require 'httparty'
2
+ require 'byebug'
3
+
4
+ # namespace for classes & modules inside of the wrapper for the BackpackTF API
5
+ module BackpackTF
6
+
7
+ end
8
+
9
+ # IMPORTANT! require the Response module before any other class or module
10
+ require 'backpack_tf/response'
11
+ require 'backpack_tf/client'
12
+ require 'backpack_tf/currencies'
13
+ require 'backpack_tf/item'
14
+ require 'backpack_tf/item_price'
15
+ require 'backpack_tf/prices'
data/readme.md ADDED
@@ -0,0 +1,56 @@
1
+ #backpack_tf
2
+ Backpack.tf is a website for the in-game economies of Team Fortress 2 and Dota 2. This gem is a wrapper for the backpack.tf [API](http://backpack.tf/api). The goal is to capture the results and turn them into Ruby objects for use in your application.
3
+
4
+ It is in the very early stages of development. See the [TODO](TODO.md) list if you are interested in contributing.
5
+
6
+ ###Installation
7
+ Install it as a gem:
8
+ `gem install backpack_tf`
9
+
10
+ Or add it to your project's Gemfile:
11
+ `gem 'backpack_tf'`
12
+
13
+ ###Usage
14
+ * [Register an API key](http://backpack.tf/api). You'll need to log in with your Steam account if you haven't already done so.
15
+ * Assign your key to an environment variable through your terminal:
16
+ `export BPTF_API_KEY='SECRET_KEY'`
17
+ * Load the gem into your project:
18
+ `require 'backpack_tf'`
19
+
20
+ ##Interfaces
21
+
22
+ ####IGetPrices
23
+ * Get pricing data for all priced items
24
+ * [official doc](http://backpack.tf/api/prices)
25
+
26
+ API responses from this interfaces are captured in the `Prices` class. The `Prices` class is not meant to be instantiated. One of the class attributes is `@@items`, a Hash object. Information on any particular item, ie: 'Kritzkrieg', is captured in an instance of the `Item` class. Furthermore, there may be several prices for the same item, ie: a Kritzkrieg with the Unique quality has a different price than a Kritzkrieg with a Vintage quality. Each price is an instance of the `ItemPrice` class, and is stored in the `@prices` hash of that item.
27
+
28
+ ######a visual
29
+ * `Prices` class
30
+ * `@@items` hash of `Price` class.
31
+ * `Item` object (ie: 'Beast From Below')
32
+ * `Item` object (ie: 'Taunt: Rock, Paper Scissors')
33
+ * `Item` object (ie: 'Kritzkrieg')
34
+ * `@prices` hash of an `Item` object
35
+ * `ItemPrices` object (ie: price for Unique Kritzkrieg)
36
+ * `ItemPrices` object (ie: price for Collector's Kritzkrieg)
37
+ * `ItemPrices` object (ie: price for Vintage Kritzkrieg)
38
+
39
+ ####IGetCurrencies
40
+ * Get internal currency data for a given game
41
+ * [official doc](http://backpack.tf/api/currencies)
42
+
43
+ API responses from this interface are captured in the `Currencies` class. Similar the `Prices` class, it has a set of class methods, and attributes to describe the API response. Unlike, the `Prices` class, this one can be instantiated. There are currently 4 currencies available through the API. Each one is an instance of `Currencies` and is held in the `@@currencies` hash of the `Currencies` class.
44
+
45
+ ####IGetSpecialItems *(not yet implemented)*
46
+ * Get internal backpack.tf item placeholders for a given game.
47
+ * [official doc](http://backpack.tf/api/special)
48
+
49
+ ####IGetUsers *(not yet implemented)*
50
+ * Get profile info for a list of 64-bit Steam IDs.
51
+ * Does not require an API key
52
+ * [official doc](http://backpack.tf/api/users)
53
+
54
+ ####IGetUserListings *(not yet implemented)*
55
+ * Get classified listings for a given user
56
+ * [official doc](http://backpack.tf/api/classifieds)
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ module BackpackTF
4
+ describe 'Client' do
5
+ context 'The Client class' do
6
+
7
+ it 'has these default options' do
8
+ ans = { base_uri: 'http://backpack.tf/api', timeout: 5 }
9
+ ans[:default_params] = { key: ENV[Client.env_var] }
10
+ expect(Client.default_options).to eq ans
11
+ end
12
+
13
+ describe '::api_key' do
14
+ it 'Raises ArgumentError, if key is not a hexadecimal string' do
15
+ fake_key = 'abcdefghijklmnopqrstuvwx'
16
+ expect{Client.api_key(fake_key)}.to raise_error ArgumentError
17
+ end
18
+ it 'Raises ArgumentError, if key is not 24 digits long' do
19
+ fake_key = 'abcdef0987654321'
20
+ expect{Client.api_key(fake_key)}.to raise_error ArgumentError
21
+ end
22
+ it 'lets an otherwise theoretically-valid key, pass through' do
23
+ key = generate_fake_api_key
24
+ expect(Client.api_key(key)).to eq key
25
+ end
26
+ end
27
+
28
+ describe '::extract_query_string' do
29
+ it 'produces a query parameter string' do
30
+ opts = {:key => Client.api_key, :appid => 440, :format => 'json', :compress => 1, :raw => 2}
31
+ ans = "key=#{Client.api_key}&appid=440&format=json&compress=1&raw=2"
32
+ expect(Client.extract_query_string(opts)).to eq ans
33
+ end
34
+ end
35
+
36
+ describe '::build_url_via' do
37
+ it 'returns correct destination url when asking for pricing data' do
38
+ opts = {:key => Client.api_key, :compress => 1}
39
+ expect(Client.build_url_via(:get_prices, opts)).to eq "http://backpack.tf/api/IGetPrices/v4/?key=#{Client.api_key}&compress=1"
40
+ end
41
+ it 'raises an error when asking for any unexpected interface' do
42
+ expect{Client.build_url_via(:foo)}.to raise_error ArgumentError
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'Instances of Client' do
48
+ let(:bp) { Client.new }
49
+
50
+ describe '#get_data' do
51
+
52
+ it 'returns results from archived json file' do
53
+ stub_http_response_with('currencies.json')
54
+ opts = { :key => Client.api_key, :compress => 1, :appid => 440 }
55
+ currencies = bp.get_data(:get_currencies, opts)
56
+
57
+ expect(currencies['response']).to have_key('success')
58
+ expect(currencies['response']).to have_key('currencies')
59
+ expect(currencies['response']).to have_key('name')
60
+ expect(currencies['response']).to have_key('url')
61
+ expect(currencies['response']).to have_key('current_time')
62
+ end
63
+
64
+ it 'client requests are returned as ruby Hash objects' do
65
+ stub_http_response_with('prices.json')
66
+ opts = {:app_id => 440, :compress => 1}
67
+ expect(bp.get_data(:get_prices, opts)).to be_instance_of Hash
68
+ end
69
+
70
+ end
71
+
72
+
73
+ end
74
+ end
75
+ end