osu-api-extended
Version:
Advanced osu! api wrapper cover all V2 and V1 endpoints, and provide useful tools
283 lines (282 loc) • 13.3 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.download = exports.request = void 0;
// PACKAGES
const querystring_1 = __importDefault(require("querystring"));
const https_1 = __importDefault(require("https"));
const fs_1 = __importDefault(require("fs"));
// CREDENTIALS
const auth = __importStar(require("./auth"));
;
// VALUES
let total_retries = 0;
const sanitize_query = (obj) => {
const object = Object.assign({}, obj);
const array = Object.keys(object);
for (let i = 0; i < array.length; i++) {
const key = array[i];
const value = object[key];
if (value != null)
continue;
delete object[key];
}
;
return querystring_1.default.encode(object);
};
const request = (url, { method, headers, data, params = {}, addons = {} }) => new Promise((resolve, reject) => {
// check required args
if (url == null) {
return resolve({ error: 'URL not specified', });
}
;
if (method == null) {
return resolve({ error: 'Method not specified', });
}
;
// V1 add credentials
if (url.includes('https://osu.ppy.sh/api/') && !url.includes('https://osu.ppy.sh/api/v2')) {
params.k = addons.authKey || auth.cache.v1;
if (params.k == null) {
return resolve({ error: 'v1 api key not specified' });
}
;
}
;
// V2 add credentials
if (url.includes('https://osu.ppy.sh/api/v2')) {
if (!headers)
headers = {};
headers.Authorization = `Bearer ${addons.authKey || auth.cache.v2}`;
if (!headers.Accept)
headers.Accept = `application/json`;
if (!headers['Content-Type'])
headers['Content-Type'] = `application/json`;
headers['x-api-version'] = addons.apiVersion == '' ? null : addons.apiVersion || '20240130';
}
;
if (addons.legacy_only != null)
params.legacy_only = addons.legacy_only == true ? 1 : 0;
const generate_query = sanitize_query(params);
const build_url = url + (generate_query ? `?${generate_query}` : '');
// console.log({ url: build_url, method, headers, data, generate_query, params }); // debug
const req = https_1.default.request(build_url, { method, headers }, (response) => {
const { location } = response.headers;
if (response.headers['x-ratelimit-limit'])
auth.cache['ratelimit-limit'] = parseInt(response.headers['x-ratelimit-limit'].toString() || '60');
if (response.headers['x-ratelimit-remaining'])
auth.cache['ratelimit-remaining'] = parseInt(response.headers['x-ratelimit-remaining'].toString() || '60');
if (location) {
(0, exports.request)(location, { method, headers, data, params, addons })
.then(resolve)
.catch(reject);
return;
}
;
// console.log(response.statusCode, response.statusMessage, response.headers.accept, {
// 'ratelimit-limit': auth.cache['ratelimit-limit'],
// 'ratelimit-remaining': auth.cache['ratelimit-remaining'],
// }, {
// url: build_url, method, headers, data, generate_query, params,
// }); // debug
const chunks = [];
// handle response events
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () => __awaiter(void 0, void 0, void 0, function* () {
const chunks_data = Buffer.concat(chunks).toString();
if (/^application\/json/.test(response.headers['content-type'])) {
try {
const parse = JSON.parse(chunks_data);
if (parse.authentication === 'basic' && addons.ignoreSessionRefresh != true) {
if (total_retries > 3) {
return resolve({ error: 'Unnable to refresh session attempted 3 times, double check your credentials (or report to package author)' });
}
;
total_retries++;
const refresh = yield auth.refresh_token();
if (refresh == null) {
return resolve({ error: 'Cannot refresh session, double check your credentials (or report to package author)' });
}
;
const retry_request = yield (0, exports.request)(url, { method, headers, data, params });
return resolve(retry_request);
}
;
if ('error' in parse && Object.keys(parse).length == 1) {
if (parse.error === null) {
return resolve({ error: `osu returned empty error, double check your parameters (request)` });
}
;
return resolve({ error: parse.error });
}
;
total_retries = 0;
if (response.statusCode != 200)
return resolve({ error: `${response.statusCode}: ${response.statusMessage}` });
return resolve(parse);
}
catch (error) {
return resolve({ error: error.name });
}
;
}
;
if (response.statusCode != 200)
return resolve({ error: `${response.statusCode}: ${response.statusMessage}` });
return resolve(chunks_data);
}));
});
// send error
req.on('error', (error) => {
resolve({ error: error.name });
});
// timeout
req.setTimeout(addons.timeout_ms || auth.settings.timeout, () => {
req.destroy();
resolve({ error: `Request to ${build_url} time out after ${addons.timeout_ms || auth.settings.timeout}ms` });
});
// write body to request, if specified
if (data)
req.write(data);
req.end();
});
exports.request = request;
/**
* Executes an HTTP request
* @param {string} url The url
* @param {string} dest The file destination
* @returns {Promise<any>} The response
*/
const download = (url, dest, { _callback, headers = {}, data, params, callback, addons = {} }) => {
return new Promise((resolve, reject) => {
const start_time = performance.now();
if (url.includes('https://osu.ppy.sh/api/v2'))
headers['Authorization'] = `Bearer ${(params === null || params === void 0 ? void 0 : params.v2) || auth.cache.v2}`;
if (!headers['accept'])
headers['accept'] = `application/octet-stream`;
const generate_query = sanitize_query(params);
const build_url = url + (generate_query ? `?${generate_query}` : '');
// console.log({ url: build_url, headers, data }); // debug
const req = https_1.default.request(build_url, { method: 'GET', headers }, response => {
const { location } = response.headers;
// console.log(url, response.headers['content-type'], response.headers); // debug too
if (location) {
(0, exports.download)(location, dest, { _callback, headers, data, params, callback, addons })
.then(resolve)
.catch(error => ({ error: error.message }));
return;
}
;
if (response.statusCode === 404) {
return resolve({ error: 'file unavailable' });
}
;
if (response.headers['content-type'] == 'application/json' || (response.headers['content-type'] == null && +(response.headers['content-length'] || 0) < 150)) {
const chunks = [];
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () => __awaiter(void 0, void 0, void 0, function* () {
const data = Buffer.concat(chunks).toString();
try {
const parse = JSON.parse(data);
if (parse.authentication === 'basic' && addons.ignoreSessionRefresh != true) {
if (total_retries > 3) {
return resolve({ error: 'Unnable to refresh session attempted 3 times, double check your credentials (or report to package author)' });
}
;
total_retries++;
const refresh = yield auth.refresh_token();
if (refresh == null) {
return resolve({ error: 'Cannot refresh session, double check your credentials (or report to package author)' });
}
;
const retry_request = yield (0, exports.download)(url, dest, { _callback, headers, data, params, callback, addons });
return resolve(retry_request);
}
;
if ('error' in parse && parse.error == null) {
return resolve({ error: 'osu returned empty error (download)' });
}
;
return resolve(parse);
}
catch (error) {
return resolve({ error: `Unable to download file: ${data} (${url}): ${error === null || error === void 0 ? void 0 : error.message}` });
}
;
}));
return;
}
;
const file = fs_1.default.createWriteStream(dest, { encoding: 'utf8' });
file.on('error', error => {
fs_1.default.unlinkSync(dest);
resolve({ error: error.message });
});
file.on('finish', () => {
file.close();
const finish_time = performance.now();
resolve({ status: 'finished', destination: dest, elapsed_time: finish_time - start_time });
});
if (_callback == true && callback !== undefined) {
const totalLength = parseInt(response.headers['content-length']);
let progress = 0;
let progressBar = 0;
response.on('data', (chunk) => {
progress += chunk.length;
progressBar = 100 * (progress / totalLength);
callback(progressBar);
});
}
response.pipe(file);
});
// send error
req.on('error', (error) => {
resolve({ error: error.message });
});
// timeout
req.setTimeout(addons.timeout_ms || auth.settings.timeout, () => {
req.destroy();
resolve({ error: `Request to ${build_url} time out after ${addons.timeout_ms || auth.settings.timeout}ms` });
});
if (data) {
req.write(data);
}
;
req.end();
});
};
exports.download = download;
;