123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import * as fs from 'fs/promises';
- import {constants} from 'fs';
- import fetch from 'node-fetch';
- import {Beatmap, Calculator} from 'rosu-pp';
- import {osu_fetch} from './api.js';
- import db from './database.js';
- // Promise queue to make sure we're only scanning one map at a time (eliminating race conditions)
- const queue = [];
- async function run_queue_loop() {
- while (queue.length > 0) {
- try {
- const res = await _get_map_info(queue[0].map_id, queue[0].api_res);
- queue[0].resolve(res);
- } catch (err) {
- queue[0].reject(err);
- }
- queue.shift();
- }
- }
- function get_map_info(map_id, api_res) {
- return new Promise((resolve, reject) => {
- queue.push({map_id, api_res, resolve, reject});
- // First item in the queue: init queue loop
- if (queue.length == 1) run_queue_loop();
- });
- }
- // Get metadata and pp from map ID (downloads it if not already downloaded)
- async function _get_map_info(map_id, api_res) {
- const map = db.prepare(`SELECT * FROM map WHERE map_id = ?`).get(map_id);
- if (map) {
- return map;
- }
- // 1. Download the map
- // Looking for .osu files? peppy provides monthly dumps here: https://data.ppy.sh/
- const file = `maps/${parseInt(map_id, 10)}.osu`;
- try {
- await fs.access(file, constants.F_OK);
- } catch (err) {
- console.log(`Beatmap id ${map_id} not found, downloading it.`);
- const new_file = await fetch(`https://osu.ppy.sh/osu/${map_id}`);
- const text = await new_file.text();
- if (text == '') {
- // While in most cases an empty page means the map ID doesn't exist, in
- // some rare cases osu! servers actually don't have the .osu file for a
- // valid map ID. But we can't do much about it.
- throw new Error('Invalid map ID');
- }
- await fs.writeFile(file, text);
- }
- // 2. Process it with rosu-pp
- const rosu_map = new Beatmap({path: file});
- const calc = new Calculator();
- const attrs = calc.mapAttributes(rosu_map);
- // 3. Get additionnal map info from osu!api
- // (we can't get the following just from the .osu file: set_id, length, ranked, dmca)
- if (!api_res) {
- console.info(`[API] Fetching map data for map ID ${map_id}`);
- api_res = await osu_fetch(`https://osu.ppy.sh/api/v2/beatmaps/lookup?id=${map_id}`);
- }
- // 4. Save map metadata
- db.prepare(`
- INSERT INTO map (
- map_id, name, mode, ar, cs, hp, od, bpm, set_id, length, ranked, dmca
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
- map_id, api_res.beatmapset.title, api_res.mode_int,
- attrs.ar, attrs.cs, attrs.hp, attrs.od, attrs.bpm,
- api_res.beatmapset.id, api_res.total_length, api_res.beatmapset.ranked,
- api_res.beatmapset.availability.download_disabled ? 1 : 0,
- );
- // 5. Process all mod combinations
- console.info('Computing pp for map', map_id);
- const compute_and_insert = (mods) => {
- const calc = new Calculator({mods});
- const perf = calc.performance(rosu_map);
- db.prepare(
- `INSERT INTO pp (map_id, mods, stars, pp) VALUES (?, ?, ?, ?)`,
- ).run(
- map_id, mods, perf.difficulty.stars, perf.pp,
- );
- };
- const nm = 1 << 0;
- const ez = 1 << 1;
- const hd = 1 << 3;
- const hr = 1 << 4;
- const dt = 1 << 6;
- const ht = 1 << 8;
- const fl = 1 << 10;
- compute_and_insert(nm);
- compute_and_insert(hd);
- compute_and_insert(hr);
- compute_and_insert(dt);
- compute_and_insert(fl);
- compute_and_insert(ez);
- compute_and_insert(ht);
- compute_and_insert(hd | hr);
- compute_and_insert(hd | dt);
- compute_and_insert(hd | fl);
- compute_and_insert(hr | dt);
- compute_and_insert(hr | fl);
- compute_and_insert(dt | fl);
- compute_and_insert(hd | ez);
- compute_and_insert(hd | ht);
- compute_and_insert(ez | ht);
- compute_and_insert(ez | fl);
- compute_and_insert(ht | fl);
- compute_and_insert(ez | dt);
- compute_and_insert(hr | ht);
- compute_and_insert(hd | hr | dt);
- compute_and_insert(hd | hr | fl);
- compute_and_insert(hd | dt | fl);
- compute_and_insert(hr | dt | fl);
- compute_and_insert(hd | ez | ht);
- compute_and_insert(hd | ez | fl);
- compute_and_insert(hd | ht | fl);
- compute_and_insert(ez | ht | fl);
- compute_and_insert(hd | ez | dt);
- compute_and_insert(ez | dt | fl);
- compute_and_insert(hd | hr | ht);
- compute_and_insert(hr | ht | fl);
- compute_and_insert(hd | hr | dt | fl);
- compute_and_insert(hd | ez | ht | fl);
- compute_and_insert(hd | ez | dt | fl);
- compute_and_insert(hd | hr | ht | fl);
- return await _get_map_info(map_id, api_res);
- }
- export {get_map_info};
|