1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
#!/usr/bin/env node
/* jshint esnext: true */
"use strict";
const Promise = require("bluebird"),
request = require("request-promise"),
sleep = require("sleep-promise"),
fs = require("fs"),
jsonapi = require("./jsonapi");
const MADGLORY_TOKEN = process.env.MADGLORY_TOKEN,
MADGLORY_URL = process.env.MADGLORY_URL || "https://api.dc01.gamelockerapp.com",
API_TIMEOUT = process.env.API_TIMEOUT || 20, // seconds
ERROR_LOG = process.env.ERROR_LOG || "./errors.json";
if (MADGLORY_TOKEN == undefined) throw "Need an API token";
const api = module.exports;
// send a request to url with options, log data about it
// return [jsonapi parsed data, jsonapi links]
module.exports.request = async (url, options, logger) => {
let response;
while (true) {
try {
const opts = {
uri: url,
headers: {
"X-Title-Id": "semc-vainglory",
"Authorization": MADGLORY_TOKEN
},
qs: options,
json: true,
gzip: true,
time: true,
forever: true,
timeout: API_TIMEOUT*1000,
strictSSL: true,
resolveWithFullResponse: true
};
logger.info("API request", {
uri: opts.uri,
qs: opts.qs
});
response = await request(opts);
return [jsonapi.parse(response.body), response.body.links];
} catch (err) {
response = err.response;
if (err.statusCode == 429) {
logger.warn("rate limited, sleeping");
await sleep(100); // no return, no break => retry
continue;
} else if (err.statusCode != 404) {
logger.error("API error", {
uri: url,
qs: options,
status: err.statusCode,
error: response? response.body : err
});
fs.appendFileSync(ERROR_LOG, JSON.stringify(err) + "\n");
throw err; // rethrow for services to catch and handle
}
logger.warn("not found", {
uri: url,
qs: options,
status: err.statusCode
});
return [undefined, undefined];
} finally {
if (response != undefined) { // else non-requests error
logger.info("API response", {
uri: url,
qs: options,
status: response.statusCode,
connection_start: response.timings.connect,
connection_first: response.timings.response,
connection_end: response.timings.end,
ratelimit_remaining: parseInt(response.headers["x-ratelimit-remaining"])
});
}
}
}
}
// loop over all pages, yield jsonapi parsed data results
// TODO make it an async generator? coroutine?
module.exports.requests = async (endpoint, region, options, logger) => {
let url = api.url(endpoint, region),
result = [];
do {
const [data, links] = await api.request(url, options, logger);
if (data == undefined) break; // 404
if (links == undefined) break; // 404
url = links.next;
result.push(...data);
if (links.next == undefined) break; // last page
} while (true);
return result;
}
module.exports.url = (endpoint, region) =>
MADGLORY_URL + "/shards/" + region + "/" + endpoint;
|