poe-watch-api 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8bcf552167fc4cc9a0b3945ebd661e7830f3d39da65ba739a34b9dcc62f953d3
4
+ data.tar.gz: 0fb40c82867df71538b03e187bc09fa4b62f43007edf39d022b601c25279f434
5
+ SHA512:
6
+ metadata.gz: 4c6624a5ffc9c2ea4a5ab717121e4fdc20d48bcb40d4ba5eb42996f70c314b3b331a71ac1df9b9f88d34c5a67e0dceda0d543cc36c0148003b0f2f3bc2a6e03c
7
+ data.tar.gz: fae8e08f03d03d1f0e2859e9d1963144df65fe5108b1ba3e0c6715328717974669dece5baa8882862d04be74a132abc6e6e1c62064d62a97eae411b1e0587cc7
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
@@ -0,0 +1,310 @@
1
+ # Poe Watch Ruby API
2
+
3
+ A Ruby wrapper around the [poe.watch API](https://poe.watch/api). It implements the same DSL you are used to when using `ActiveRecord` but doesn't require Rails at all.
4
+
5
+ Inspired by [klayveR's poe-watch-api JS wrapper](https://github.com/klayveR/poe-watch-api).
6
+
7
+ ## Table of content
8
+
9
+ - [Requirements](#requirements)
10
+ - [Installation](#installation)
11
+ - [Dependencies](#dependencies)
12
+ - [Usage](#usage)
13
+ * [Items](#items)
14
+ + [Get all item data](#get-all-item-data)
15
+ + [Get multiple specific items](#get-multiple-specific-items)
16
+ + [Find one specific item](#find-one-specific-item)
17
+ + [Item properties](#item-properties)
18
+ * [Item price data](#item-price-data)
19
+ + [Price data properties](#price-data-properties)
20
+ * [Leagues](#leagues)
21
+ + [Get all league data](#get-all-league-data)
22
+ + [Get multiple specific leagues](#get-multiple-specific-leagues)
23
+ + [Find one specific league](#find-one-specific-league)
24
+ + [League properties](#league-properties)
25
+ * [Categories](#categories)
26
+ + [Get all categories data](#get-all-categories-data)
27
+ + [Get multiple specific categories](#get-multiple-specific-categories)
28
+ + [Find one specific category](#find-one-specific-category)
29
+ + [Category properties](#category-properties)
30
+ + [API Cache](#api-cache)
31
+ - [Shortcomings](#shortcomings)
32
+ - [Contribution](#contribution)
33
+ - [License](#license)
34
+
35
+
36
+ ## Requirements
37
+
38
+ - Redis: 4.0+
39
+ - Ruby 2.3+
40
+
41
+ ## Installation
42
+
43
+ ```
44
+ gem install poe-watch-api
45
+ ```
46
+
47
+ or add it to your `Gemfile`
48
+
49
+ ```
50
+ gem 'poe-watch-api'
51
+ ```
52
+
53
+ ## Dependencies
54
+
55
+ This library has a dependency over `redis`. For it to work you need to have redis installed on your machine (and your server, in production, if using Heroku, you can use an addon like `rediscloud`).
56
+ We use `redis` because we need to cache the data from Poe Watch to avoid refetching all the items data to often (there are almost 30k items in PoE which is quite a lot).
57
+
58
+ There are two ways of making this gem work with redis:
59
+
60
+ - Set a global `$redis` variable.
61
+ Example:
62
+ ```ruby
63
+ $redis = Redis.new
64
+
65
+ # In production for instance if you were to use rediscloud on heroku :
66
+ url = ENV["REDISCLOUD_URL"] # or whatever is the URL of your redis
67
+
68
+ if url
69
+ $redis = Redis.new(:url => url)
70
+ end
71
+ ```
72
+
73
+ - Provide your own instance of redis through `PoeWatch::Api.redis = Redis.new(YOUR_PARAMS)`
74
+ Example:
75
+ ```ruby
76
+ PoeWatch::Api.redis = Redis.new
77
+
78
+ # In production for instance, if you were to use rediscloud on heroku:
79
+ url = ENV["REDISCLOUD_URL"] # or whatever is the URL of your redis
80
+
81
+ if url
82
+ PoeWatch::Api.redis = Redis.new(:url => url)
83
+ end
84
+ ```
85
+
86
+ The total footprint of the data from PoeWatch API in redis is a bit less than **2 megabytes**. You can check the exact memory footprint in your redis by calling `PoeWatch::Api.memory_footprint`, the data will be displayed in kilobytes.
87
+ The default time to live (TTL) of the cache is 45 minutes.
88
+
89
+ ## Usage
90
+
91
+ Here is a simple usage example:
92
+ ```
93
+ # See the dependencies section
94
+ PoeWatch::Api.redis = Redis.new
95
+
96
+ # Non mandatory
97
+ PoeWatch::Api.refresh!(3600) # cache for 1 hour
98
+
99
+ PoeWatch::Item.find({ name: "Circle of Nostalgia }).price_for_league('Metamorph')
100
+ ```
101
+
102
+ ### Items
103
+
104
+ #### Get all item data
105
+
106
+ ```ruby
107
+ PoeWatch::Item.all # => array of PoeWatch::Item objects
108
+ PoeWatch::Item.count # => total number of items
109
+ ```
110
+
111
+ #### Get multiple specific items
112
+ ```ruby
113
+ items = PoeWatch::Item.where(name: /circle/i) # Returns all items with a name containing "circle"
114
+
115
+ items.first.id # => 223
116
+ items.first.name # => "Hubris Circlet"
117
+ ```
118
+
119
+ #### Find one specific item
120
+ ```ruby
121
+ item = PoeWatch::Item.find(name: "Circle of Nostalgia") # Returns the item "Circle of Nostalgia"
122
+ item = PoeWatch::Item.find(name: /circle/i) # Returns the first item with a name containing "circle"
123
+
124
+ items.id # => 223
125
+ items.name # => "Hubris Circlet"
126
+ ```
127
+
128
+ #### Item properties
129
+
130
+ All properties have an accessor you can use, e.g: `item.id`, `item.name`, `item.hardcore`.
131
+ All boolean properties have an additionnal question mark accessor, e.g: `item.hardcore?`.
132
+
133
+ Items only have relevant properties (e.g: map tiers only on maps, stack_size only on currencies, etc...)
134
+
135
+ | Name | Type | Description |
136
+ | ----------- | --------- | ------------ |
137
+ | id | `Integer` | poe.watch ID |
138
+ | name | `String` | name |
139
+ | type | `String` | base type |
140
+ | category | `String` | item category |
141
+ | group | `String` | item category group |
142
+ | frame | `Integer` | frame type (item rarity) |
143
+ | map_series | `Integer` | 5 for Synthesis, 1 for Awakening, etc |
144
+ | map_tier | `Integer` | map tier |
145
+ | gem_level | `Integer` | gem level |
146
+ | gem_quality | `Integer` | gem quality |
147
+ | gem_is_corrupted | `Boolean` | is the gem corrupted |
148
+ | link_count | `Integer` | links count |
149
+ | base_item_level | `Integer` | item level |
150
+ | variation | `String` | variations of the same item (e.g: Vessel of Vinktar) |
151
+ | enchant_min | `Integer` | enchantment's minimum has numeric value
152
+ | enchant_max | `Integer` | enchantment's maximum has numeric value
153
+ | relic | `Integer` | whether the item is a relic or not. `true` always sets `properties.frame` to `9` |
154
+ | icon | `String` | icon URL |
155
+ | influences | `Array` | shaper / elder / conqueror influences |
156
+ | stack_size | `Integer` | default stack size of item type |
157
+
158
+
159
+ ### Item price data
160
+
161
+ ```ruby
162
+ item = PoeWatch::Item.find(name: "Circle of Nostalgia")
163
+
164
+ # Returns price data for all leagues
165
+ item.prices
166
+
167
+ # Returns price data for all leagues matching the name
168
+ item.price_for_leagues("Metamorph") # => all metamorph leagues (HC / SC) price data
169
+
170
+ # Returns price data for a specific league
171
+ item.price_for_leagues("Metamorph") # => Price data for "Metamorph"
172
+ item.price_for_leagues(/metamorph/i) # => Price data for the first metamorph league found
173
+ ```
174
+
175
+ #### Price data properties
176
+
177
+ | Name | Type | Description |
178
+ | ----------- | --------- | ------------ |
179
+ | id | `Integer` | league ID |
180
+ | name | `String` | league name |
181
+ | display | `String` | league display name |
182
+ | current | `Integer` | nb of items currently on sale |
183
+ | daily | `Integer` | nb of items found per 24h |
184
+ | accepted | `Integer` | nb of items accepted for price calculation |
185
+ | exalted | `Float` | mean price in exalted |
186
+ | max | `Float` | max accepted average price |
187
+ | min | `Float` | min accepted average price |
188
+ | mean | `Float` | mean average price |
189
+ | median | `Float` | median average price |
190
+ | mode | `Float` | mode average price |
191
+ | active | `Boolean` | is league still active |
192
+ | start | `String` | league end date |
193
+ | end | `String` | league end date |
194
+
195
+ ### Leagues
196
+
197
+ #### Get all league data
198
+
199
+ ```ruby
200
+ PoeWatch::League.all # => array of PoeWatch::League objects
201
+ PoeWatch::League.count # => total number of leagues
202
+ ```
203
+
204
+ #### Get multiple specific leagues
205
+ ```ruby
206
+ leagues = PoeWatch::League.where(hardcore: false) # Returns all non hardcore leagues
207
+ leagues = PoeWatch::League.where(name: /metamorph/i) # You can use regular expressions or strings for your queries
208
+
209
+ leagues.first.name # => "Hardcore Metamorph"
210
+ leagues.first.hardcore? # => true
211
+ leagues.first.start? # => "2019-12-13T20:00:00Z"
212
+ ```
213
+
214
+ #### Find one specific league
215
+ ```ruby
216
+ league = PoeWatch::League.find(name: "Metamorph") # Return the non hardcore "Metamorph" league
217
+ league = PoeWatch::League.find(name: /metamorph/i) # Will return the first Metamorph league found
218
+
219
+ league.name # => "Metamorph"
220
+ league.hardcore? # => false
221
+ league.start? # => "2019-12-13T20:00:00Z"
222
+ ```
223
+
224
+ #### League properties
225
+
226
+ All properties have an accessor you can use, e.g: `league.id`, `league.name`, `league.hardcore`.
227
+ All boolean properties have an additionnal question mark accessor, e.g: `league.hardcore?`.
228
+
229
+ | Name | Type | Description |
230
+ | -------- | --- | ---------------- |
231
+ |id | `Integer` | poe.watch ID |
232
+ |name | `String` | name |
233
+ |display | `String` | display name |
234
+ |hardcore | `Boolean` | is hardcore |
235
+ |active | `Boolean` | is active |
236
+ |start | `String` | start date |
237
+ |end | `String` | end date |
238
+ |challenge | `Boolean` | is a challenge league |
239
+ |event | `Boolean` | is an event league |
240
+ |upcoming | `Boolean` | is upcoming |
241
+
242
+ ### Categories
243
+
244
+ #### Get all categories data
245
+
246
+ ```ruby
247
+ PoeWatch::Categories.all # => array of PoeWatch::Category objects
248
+ PoeWatch::Categories.count # => total number of categories
249
+ ```
250
+
251
+ #### Get multiple specific categories
252
+ ```ruby
253
+ categories = PoeWatch::Category.where(name: /accessory/i) # You can use regular expressions or strings for your queries
254
+
255
+ categories.first.display # => "Accessories"
256
+ ```
257
+
258
+ #### Find one specific category
259
+ ```ruby
260
+ category = PoeWatch::Category.find(name: /accessory/i) # You can use regular expressions or strings for your queries
261
+
262
+ category.display # => "Accessories"
263
+ category.groups # => [#<OpenStruct id=1, name="amulet", display="Amulets">, ...]
264
+ category.groups.first.name # => "amulet"
265
+ ```
266
+
267
+ #### Category properties
268
+
269
+ All properties have an accessor you can use, e.g: `category.name`, `category.groups`.
270
+
271
+ | Name | Type | Description |
272
+ | -------- | --- | ---------------- |
273
+ |id | `Integer` | poe.watch ID |
274
+ |name | `String` | name |
275
+ |display | `String` | display name |
276
+ |groups | `Array<OpenStruct>` | an array of item types that belongs to this category. Stored as OpenStructs so you can access them with the `.` syntax. A group has 3 attributes: `id`, `name` and `display` |
277
+
278
+ ### API Cache
279
+
280
+ ```ruby
281
+ # Fetch all the data from poe.watch API and cache it for the next 45 minutes.
282
+ # You don't actually need to call this. It is automatically called when using `::find`, `::where`, `::all` or `::count`, and won't do anything if the cache already contains data.
283
+ # Although beware that if you don't call it first, the first call to find/where/all/count every 45 minutes will take longer (around 2-4 seconds, there are quite a lot of items to fetch).
284
+ PoeWatch::Api.refresh!
285
+ # You can also specify a cache TTL different than 45 minutes
286
+ PoeWatch::Api.refresh!(3600) # 1 hour cache
287
+
288
+ # If you need to force clear the cache, you can use
289
+ PoeWatch::Api.clear!
290
+
291
+ # You can check if the cache has expired with
292
+ PoeWatch::Api.has_expired?
293
+ # Or do the opposite with
294
+ PoeWatch::Api.ready?
295
+ ```
296
+
297
+ ## Shortcomings
298
+
299
+ - Doing a find or where on an item will always take some time (1-2 seconds) because it has to traverse all ~30k objects from PoE to find your matches. This could probably be optimised but I haven't had the time to get around to do it.
300
+ - No rate limiter set for the moment so when fetching a bunch of item prices you could hit the rate limiter of `poe.watch`. I'll add one at some point in the future if is needed or requested.
301
+
302
+ ## Contribution
303
+
304
+ The code is well documented, using [tomdoc](http://tomdoc.org/), is you wish to contribute, raise an issue or fork this project and create a pull request :).
305
+
306
+ Most of the codebase is in `lib/api`, `lib/base` and `lib/item`.
307
+
308
+ ## License
309
+
310
+ Licensed under WTFPL.
@@ -0,0 +1,175 @@
1
+ # TODO: Check item fields
2
+ # @base_is_elder=false,
3
+ # @base_is_shaper=true,
4
+ # @base_item_level=86,
5
+ # @enchant_max=25,
6
+ # @enchant_min=25,
7
+ # corrupted field
8
+
9
+ require 'redis'
10
+ require 'uri'
11
+ require 'net/http'
12
+
13
+ # Public: Wrapper around the Poe Watch API.
14
+ # Uses redis to store items, leagues and categories data
15
+ #
16
+ # Examples
17
+ #
18
+ # PoeWatch::Api.refresh! # => fetches the latest data from the API
19
+ #
20
+ # PoeWatch::Item.all # => All items
21
+ # PoeWatch::Item.find({ name: 'Mirror of Kalandra' }).getPriceDataByLeague('Metamorph')
22
+ #
23
+ # PoeWatch::League.find({ name: 'Metamorph' })
24
+ #
25
+ module PoeWatch
26
+ class Api
27
+ class Error < StandardError; end
28
+
29
+ BULK_APIS = {
30
+ item_data: "https://api.poe.watch/itemdata",
31
+ categories: "https://api.poe.watch/categories",
32
+ leagues: "https://api.poe.watch/leagues",
33
+ }
34
+
35
+ ITEM_API = "https://api.poe.watch/item"
36
+
37
+ DEFAULT_EXPIRY = 45 * 60 # 45 minutes
38
+
39
+ @updating = false
40
+ @redis = $redis
41
+
42
+ class << self
43
+ # Returns the redis instance, defaults to the global $redis
44
+ def redis
45
+ raise PoeWatch::Api::Error.new("You need to configure redis by either setting the global $redis variable or calling PoeWatchApi.redis = Redis.new(...)") unless (@redis || $redis)
46
+ @redis || $redis
47
+ end
48
+
49
+ # Sets the redis instance
50
+ def redis=(redis)
51
+ @redis = redis
52
+ end
53
+
54
+ # Public: Count the number of items
55
+ #
56
+ # expiry - The time to live (TTL) for the redis cache, defaults to 45 minutes.
57
+ #
58
+ # Examples
59
+ #
60
+ # PoeWatch::Api.refresh!
61
+ # PoeWatch::Api.refresh!(3600) # => 1 hour cache
62
+ #
63
+ # Returns an Integer.
64
+ def refresh!(expiry = DEFAULT_EXPIRY)
65
+ # If we are not already updating and the cache is empty
66
+ if !@updating && !ready?
67
+ #puts "Updating..."
68
+ @updating = true
69
+
70
+ # Fetch each API
71
+ BULK_APIS.each do |name, api_url|
72
+ raw_response = request(api_url)
73
+ redis.set(redis_key(name), raw_response)
74
+ redis.expire(redis_key(name), expiry)
75
+ end
76
+ @updating = false
77
+ true
78
+ else
79
+ return raise PoeWatch::Api::Error.new("An update is already in progress") if @updating
80
+ # puts "Already up to date"
81
+ end
82
+ end
83
+
84
+ # Internal: Does a get request
85
+ #
86
+ # api - api url
87
+ # params - a hash of params
88
+ # parse - if the data should be parsed as JSON or returned as String. Defaults to false.
89
+ #
90
+ # Examples
91
+ #
92
+ # PoeWatch::Api.request(BULK_APIS[:leagues])
93
+ #
94
+ # Returns a String or a Hash.
95
+ def request(api, params = {}, parse = false)
96
+ uri = URI.parse(api)
97
+
98
+ if (params.any?)
99
+ uri.query = URI.encode_www_form(params)
100
+ end
101
+
102
+ request = Net::HTTP::Get.new(uri.request_uri)
103
+ request["Content-Type"] = "application/json"
104
+
105
+ http = Net::HTTP.new(uri.hostname, uri.port)
106
+ http.use_ssl = true
107
+
108
+ response = http.request(request)
109
+
110
+ raise PoeWatch::Api::Error.new("Couldn't connect to poe.watch") unless response.is_a?(Net::HTTPSuccess)
111
+
112
+ parse ? JSON.parse(response.body) : response.body
113
+ end
114
+
115
+ # Returns all items
116
+ def items
117
+ @items ||= get_data(:item_data)
118
+ @items || []
119
+ end
120
+
121
+ # Returns all categories
122
+ def categories
123
+ @categories ||= get_data(:categories)
124
+ @categories || []
125
+ end
126
+
127
+ # Returns all leagues
128
+ def leagues
129
+ @leagues ||= get_data(:leagues)
130
+ @leagues || []
131
+ end
132
+
133
+ # Get data from redis and parse it to JSON
134
+ def get_data(type)
135
+ data = redis.get(redis_key(type))
136
+ data ? JSON.parse(data) : nil
137
+ end
138
+
139
+ # Clear the redis cache
140
+ def clear!
141
+ BULK_APIS.keys
142
+ .each { |key| redis.del(redis_key(key)) }
143
+ end
144
+
145
+ # Is the cache filled?
146
+ def ready?
147
+ !has_expired?
148
+ end
149
+
150
+ # Check if any of the redis stored data is missing
151
+ def has_expired?
152
+ BULK_APIS.keys
153
+ .map { |key| redis.exists(redis_key(key)) }
154
+ .reject { |k| !k }
155
+ .empty?
156
+ end
157
+
158
+ # Returns the redis cache key, e.g: poe_watch_itemdata, etc...
159
+ def redis_key(type)
160
+ "poe_watch_#{type}"
161
+ end
162
+
163
+ # Returns the redis memory footprint for each api data (leagues, itemdata and categories) in kilobytes.
164
+ def memory_footprint
165
+ BULK_APIS.keys.map do |key|
166
+ size = nil
167
+ if redis.exists(redis_key(key))
168
+ size = redis.debug("object", redis_key(key)).match(/serializedlength:(\d+)/)[1].to_i / 1024.0
169
+ end
170
+ [key, size]
171
+ end.to_h.compact
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,172 @@
1
+ require_relative './utils'
2
+
3
+ # Public: Base class for all Poe Watch items.
4
+ # This is pretty much active record for PoeWatch.
5
+ # It automatically fetches the necessary data and defines getters, setters as well as question mark methods for boolean values.
6
+ # It also defines `::find(params)`, `::where(params)` and `::all` class methods.
7
+ #
8
+ # Examples
9
+ #
10
+ # class PoeWatch::Item < PoeWatch::Base
11
+ # end
12
+ #
13
+ module PoeWatch
14
+ class Base
15
+ INFLECTOR = {
16
+ category: 'categories',
17
+ item: 'items',
18
+ league: 'leagues'
19
+ }
20
+
21
+ class << self
22
+ # Defines an instance variable that'll be memoized and used to store Poe Watch data
23
+ def __data
24
+ if not defined? @__data
25
+ @__data = []
26
+ end
27
+ @__data
28
+ end
29
+
30
+ # Setter for instance variable
31
+ def __data=(value)
32
+ @__data = value
33
+ end
34
+
35
+ # Public: Gets the object type
36
+ #
37
+ # Examples
38
+ #
39
+ # PoeWatch::League.type # => :league
40
+ #
41
+ # Returns a Symbol.
42
+ def type
43
+ self.name.split('::').last.downcase.to_sym
44
+ end
45
+
46
+ # Public: Gets all data as instance of the class
47
+ #
48
+ # Examples
49
+ #
50
+ # PoeWatch::League.all # => array of PoeWatch::League items
51
+ #
52
+ # Returns an Array of class instances.
53
+ def all
54
+ PoeWatch::Api.refresh!
55
+
56
+ return __data if __data.any?
57
+
58
+ # If data isn't already loaded
59
+ # puts "Loading #{type} data..."
60
+ json_data = PoeWatch::Api.send(INFLECTOR[type])
61
+ if json_data.any?
62
+ __data = json_data.map { |raw_data| self.new(raw_data) }
63
+ else
64
+ []
65
+ end
66
+ end
67
+
68
+ # Public: Count the number of items
69
+ #
70
+ # Examples
71
+ #
72
+ # PoeWatch::League.count # => 30
73
+ #
74
+ # Returns an Integer.
75
+ def count
76
+ PoeWatch::Api.refresh!
77
+
78
+ return __data if __data.any?
79
+
80
+ # If data isn't already loaded
81
+ # puts "Loading #{type} data..."
82
+ json_data = PoeWatch::Api.send(INFLECTOR[type])
83
+ if json_data.any?
84
+ __data = json_data.length
85
+ else
86
+ 0
87
+ end
88
+ end
89
+
90
+ # Public: Gets items filtered by a query
91
+ # You can pass regular expressions for string parameters. It will do a match.
92
+ #
93
+ # params - A hash of parameters. Eg: { hardcore: true }
94
+ #
95
+ # Examples
96
+ #
97
+ # PoeWatch::League.where({ hardcore: true }) # => array of PoeWatch::League items
98
+ # PoeWatch::League.where({ name: /metamorph/i, hardcore: true }) # => array of PoeWatch::League items
99
+ #
100
+ # Returns an Array of class instances.
101
+ def where(params)
102
+ PoeWatch::Api.refresh!
103
+ self.all.select do |element|
104
+ params.map do |k, v|
105
+ if v.is_a? Regexp
106
+ !!element.send(k).match(v)
107
+ else
108
+ element.send(k) == v
109
+ end
110
+ end.all?(TrueClass)
111
+ end
112
+ end
113
+
114
+ # Public: Gets a specific item
115
+ # You can pass regular expressions for string parameters. It will do a match.
116
+ #
117
+ # params - A hash of parameters. Eg: { name: /Metamorph/, hardcore: true }
118
+ #
119
+ # Examples
120
+ #
121
+ # PoeWatch::League.find({ name: "Metamorph", hardcore: true }) # =>PoeWatch::League item
122
+ #
123
+ # Returns an instance of the class.
124
+ def find(params)
125
+ PoeWatch::Api.refresh!
126
+ self.all.find do |element|
127
+ params.map do |k, v|
128
+ if v.is_a? Regexp
129
+ !!element.send(k).match(v)
130
+ else
131
+ element.send(k) == v
132
+ end
133
+ end.all?(TrueClass)
134
+ end
135
+ end
136
+ end
137
+
138
+ # Public: Initialize a Poe Watch item.
139
+ # It will automatically create instance variables, setters, getters for instance variables,
140
+ # and question mark methods for variables with boolean values.
141
+ # It is to note that any hash or array of hashes will be transformed into an OpenStruct.
142
+ #
143
+ # raw_data - An array of hash
144
+ #
145
+ # Returns an instance of the class.
146
+ def initialize(raw_data)
147
+ raw_data.each do |k, v|
148
+ key = snakify(k)
149
+ if v.is_a?(Hash)
150
+ value = OpenStruct.new(v)
151
+ elsif v.is_a?(Array) && v.first.is_a?(Hash)
152
+ value = v.map { |arr_val| OpenStruct.new(arr_val) }
153
+ else
154
+ value = v
155
+ end
156
+
157
+ instance_variable_set("@#{key}", value)
158
+
159
+ define_singleton_method key do
160
+ instance_variable_get("@#{key}")
161
+ end
162
+
163
+ # If it is a boolean we also create a `key?` method`
164
+ if [TrueClass, FalseClass].include?(value.class)
165
+ define_singleton_method "#{key}?" do
166
+ instance_variable_get("@#{key}")
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,6 @@
1
+ require_relative './base'
2
+
3
+ module PoeWatch
4
+ class Category < PoeWatch::Base
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ require_relative './base'
2
+ require_relative './price_data'
3
+
4
+ module PoeWatch
5
+ class Item < PoeWatch::Base
6
+ def prices
7
+ @prices ||= PoeWatch::Api.request(PoeWatch::Api::ITEM_API, { id: self.id }, true)["leagues"].map { |league_data| PoeWatch::PriceData.new(league_data) }
8
+ end
9
+
10
+ def price_for_leagues(name_or_regexp)
11
+ regexp = name_or_regexp.is_a?(String) ? Regexp.new(name_or_regexp, 'i') : name_or_regexp
12
+ prices.select { |data| data.name.match(regexp) }
13
+ end
14
+
15
+ def price_for_league(name_or_regexp)
16
+ prices.find { |data| name_or_regexp.is_a?(String) ? data.name.downcase == name_or_regexp.downcase : data.name.match(name_or_regexp) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ require_relative './base'
2
+
3
+ module PoeWatch
4
+ class League < PoeWatch::Base
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ require_relative './api'
2
+ require_relative './item'
3
+ require_relative './league'
4
+ require_relative './category'
5
+
@@ -0,0 +1,4 @@
1
+ require_relative './base'
2
+
3
+ class PoeWatch::PriceData < PoeWatch::Base
4
+ end
@@ -0,0 +1,7 @@
1
+ def snakify(string)
2
+ string.gsub(/::/, '/').
3
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
4
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
5
+ tr("-", "_").
6
+ downcase
7
+ end
@@ -0,0 +1,3 @@
1
+ module PoeWatch
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: poe-watch-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - GabrielDehan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.1.0
27
+ description: Ruby Poe Watch API wrapper.
28
+ email: dehan.gabriel@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - lib/api.rb
36
+ - lib/base.rb
37
+ - lib/category.rb
38
+ - lib/item.rb
39
+ - lib/league.rb
40
+ - lib/poe_watch.rb
41
+ - lib/price_data.rb
42
+ - lib/utils.rb
43
+ - lib/version.rb
44
+ homepage: https://github.com/gabriel-dehan/poe-watch-api
45
+ licenses: []
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.7.6
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Ruby Poe Watch API wrapper.
67
+ test_files: []