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.
- 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: []
|