poe-watch-api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +310 -0
- data/lib/api.rb +175 -0
- data/lib/base.rb +172 -0
- data/lib/category.rb +6 -0
- data/lib/item.rb +19 -0
- data/lib/league.rb +6 -0
- data/lib/poe_watch.rb +5 -0
- data/lib/price_data.rb +4 -0
- data/lib/utils.rb +7 -0
- data/lib/version.rb +3 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/lib/api.rb
ADDED
@@ -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
|
data/lib/base.rb
ADDED
@@ -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
|
data/lib/category.rb
ADDED
data/lib/item.rb
ADDED
@@ -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
|
data/lib/league.rb
ADDED
data/lib/poe_watch.rb
ADDED
data/lib/price_data.rb
ADDED
data/lib/utils.rb
ADDED
data/lib/version.rb
ADDED
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: []
|