fix scoresaber api url
This commit is contained in:
parent
e6f510cec3
commit
47a23f0484
@ -1,23 +1,32 @@
|
|||||||
import {default as createQueue, PRIORITY} from '../http-queue';
|
import ssrConfig from "../../../ssr-config";
|
||||||
import {substituteVars} from '../../../utils/format'
|
import cfDecryptEmail from "../../../utils/cf-email-decrypt";
|
||||||
import {extractDiffAndType} from '../../../utils/scoresaber/format'
|
import { dateFromString } from "../../../utils/date";
|
||||||
import cfDecryptEmail from '../../../utils/cf-email-decrypt'
|
import { substituteVars } from "../../../utils/format";
|
||||||
import {capitalize, getFirstRegexpMatch, opt} from '../../../utils/js'
|
import { capitalize, getFirstRegexpMatch, opt } from "../../../utils/js";
|
||||||
import {dateFromString} from '../../../utils/date'
|
import { LEADERBOARD_SCORES_PER_PAGE } from "../../../utils/scoresaber/consts";
|
||||||
import {LEADERBOARD_SCORES_PER_PAGE} from '../../../utils/scoresaber/consts'
|
import { extractDiffAndType } from "../../../utils/scoresaber/format";
|
||||||
|
import { PRIORITY, default as createQueue } from "../http-queue";
|
||||||
|
|
||||||
export const SS_HOST = 'https://scoresaber.com';
|
export const SS_HOST = "https://scoresaber.com";
|
||||||
const SS_CORS_HOST = '/cors/score-saber';
|
const SS_CORS_HOST = `${ssrConfig.proxy}/${SS_HOST}`;
|
||||||
const RANKEDS_URL = SS_CORS_HOST + '/api.php?function=get-leaderboards&cat=1&limit=5000&ranked=1&page=${page}';
|
const RANKEDS_URL =
|
||||||
const PLAYER_PROFILE_URL = SS_CORS_HOST + '/u/${playerId}?page=1&sort=2'
|
SS_CORS_HOST +
|
||||||
const COUNTRY_RANKING_URL = SS_CORS_HOST + '/global/${page}?country=${country}'
|
"/api.php?function=get-leaderboards&cat=1&limit=5000&ranked=1&page=${page}";
|
||||||
const LEADERBOARD_URL = SS_CORS_HOST + '/leaderboard/${leaderboardId}?page=${page}'
|
const PLAYER_PROFILE_URL = SS_CORS_HOST + "/u/${playerId}?page=1&sort=2";
|
||||||
|
const COUNTRY_RANKING_URL = SS_CORS_HOST + "/global/${page}?country=${country}";
|
||||||
|
const LEADERBOARD_URL =
|
||||||
|
SS_CORS_HOST + "/leaderboard/${leaderboardId}?page=${page}";
|
||||||
|
|
||||||
export const parseSsInt = text => {
|
export const parseSsInt = (text) => {
|
||||||
const value = getFirstRegexpMatch(/(-?[0-9,]+)\s*$/, text)
|
const value = getFirstRegexpMatch(/(-?[0-9,]+)\s*$/, text);
|
||||||
return value ? parseInt(value.replace(/[^\d-]/g, '') , 10) : null;
|
return value ? parseInt(value.replace(/[^\d-]/g, ""), 10) : null;
|
||||||
}
|
};
|
||||||
export const parseSsFloat = text => text ? parseFloat(getFirstRegexpMatch(/([0-9,.]+)\s*$/, text.replace(/[^\d.]/g, ''))) : null;
|
export const parseSsFloat = (text) =>
|
||||||
|
text
|
||||||
|
? parseFloat(
|
||||||
|
getFirstRegexpMatch(/([0-9,.]+)\s*$/, text.replace(/[^\d.]/g, ""))
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
export default (options = {}) => {
|
export default (options = {}) => {
|
||||||
const queue = createQueue(options);
|
const queue = createQueue(options);
|
||||||
@ -27,7 +36,7 @@ export default (options = {}) => {
|
|||||||
const processRankeds = (data) => {
|
const processRankeds = (data) => {
|
||||||
if (!data || !data.songs || !Array.isArray(data.songs)) return null;
|
if (!data || !data.songs || !Array.isArray(data.songs)) return null;
|
||||||
|
|
||||||
return data.songs.map(s => {
|
return data.songs.map((s) => {
|
||||||
const {
|
const {
|
||||||
uid: leaderboardId,
|
uid: leaderboardId,
|
||||||
id: hash,
|
id: hash,
|
||||||
@ -37,128 +46,223 @@ export default (options = {}) => {
|
|||||||
levelAuthorName,
|
levelAuthorName,
|
||||||
stars,
|
stars,
|
||||||
image: imageUrl,
|
image: imageUrl,
|
||||||
diff
|
diff,
|
||||||
} = s;
|
} = s;
|
||||||
|
|
||||||
const diffInfo = extractDiffAndType(diff);
|
const diffInfo = extractDiffAndType(diff);
|
||||||
|
|
||||||
return {leaderboardId, hash, name, subName, authorName, levelAuthorName, imageUrl, stars, diff, diffInfo};
|
return {
|
||||||
})
|
leaderboardId,
|
||||||
}
|
hash,
|
||||||
|
name,
|
||||||
|
subName,
|
||||||
|
authorName,
|
||||||
|
levelAuthorName,
|
||||||
|
imageUrl,
|
||||||
|
stars,
|
||||||
|
diff,
|
||||||
|
diffInfo,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getImgUrl = imgUrl => {
|
const getImgUrl = (imgUrl) => {
|
||||||
try {
|
try {
|
||||||
const aUrl = new URL(imgUrl);
|
const aUrl = new URL(imgUrl);
|
||||||
return SS_HOST + aUrl.pathname;
|
return SS_HOST + aUrl.pathname;
|
||||||
}
|
} catch (err) {
|
||||||
catch(err) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const rankeds = async (page = 1, priority = PRIORITY.BG_NORMAL, options = {}) => fetchJson(substituteVars(RANKEDS_URL, {page}), options, priority)
|
const rankeds = async (
|
||||||
.then(r => {
|
page = 1,
|
||||||
|
priority = PRIORITY.BG_NORMAL,
|
||||||
|
options = {}
|
||||||
|
) =>
|
||||||
|
fetchJson(substituteVars(RANKEDS_URL, { page }), options, priority).then(
|
||||||
|
(r) => {
|
||||||
r.body = processRankeds(r.body);
|
r.body = processRankeds(r.body);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const processPlayerProfile = (playerId, doc) => {
|
const processPlayerProfile = (playerId, doc) => {
|
||||||
cfDecryptEmail(doc);
|
cfDecryptEmail(doc);
|
||||||
|
|
||||||
let avatar = getImgUrl(opt(doc.querySelector('.column.avatar img'), 'src', null));
|
let avatar = getImgUrl(
|
||||||
|
opt(doc.querySelector(".column.avatar img"), "src", null)
|
||||||
|
);
|
||||||
|
|
||||||
let playerName = opt(doc.querySelector('.content .column:not(.avatar) .title a'), 'innerText');
|
let playerName = opt(
|
||||||
|
doc.querySelector(".content .column:not(.avatar) .title a"),
|
||||||
|
"innerText"
|
||||||
|
);
|
||||||
playerName = playerName ? playerName.trim() : null;
|
playerName = playerName ? playerName.trim() : null;
|
||||||
|
|
||||||
let country = getFirstRegexpMatch(/^.*?\/flags\/([^.]+)\..*$/, opt(doc.querySelector('.content .column .title img'), 'src'));
|
let country = getFirstRegexpMatch(
|
||||||
|
/^.*?\/flags\/([^.]+)\..*$/,
|
||||||
|
opt(doc.querySelector(".content .column .title img"), "src")
|
||||||
|
);
|
||||||
country = country ? country.toUpperCase() : null;
|
country = country ? country.toUpperCase() : null;
|
||||||
|
|
||||||
let pageNum = parseSsInt(opt(doc.querySelector('.pagination .pagination-list li a.is-current'), 'innerText', null));
|
let pageNum = parseSsInt(
|
||||||
pageNum = !isNaN(pageNum) ? pageNum : null
|
opt(
|
||||||
|
doc.querySelector(".pagination .pagination-list li a.is-current"),
|
||||||
|
"innerText",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
pageNum = !isNaN(pageNum) ? pageNum : null;
|
||||||
|
|
||||||
let pageQty = parseSsInt(opt(doc.querySelector('.pagination .pagination-list li:last-of-type'), 'innerText', null));
|
let pageQty = parseSsInt(
|
||||||
pageQty = !isNaN(pageQty) ? pageQty : null
|
opt(
|
||||||
|
doc.querySelector(".pagination .pagination-list li:last-of-type"),
|
||||||
|
"innerText",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
pageQty = !isNaN(pageQty) ? pageQty : null;
|
||||||
|
|
||||||
let totalItems = parseSsFloat(getFirstRegexpMatch(/^\s*<strong>(?:[^:]+)\s*:?\s*<\/strong>\s*(.*)$/, opt(doc.querySelector('.columns .column:not(.is-narrow) ul li:nth-of-type(3)'), 'innerHTML')))
|
let totalItems = parseSsFloat(
|
||||||
|
getFirstRegexpMatch(
|
||||||
|
/^\s*<strong>(?:[^:]+)\s*:?\s*<\/strong>\s*(.*)$/,
|
||||||
|
opt(
|
||||||
|
doc.querySelector(
|
||||||
|
".columns .column:not(.is-narrow) ul li:nth-of-type(3)"
|
||||||
|
),
|
||||||
|
"innerHTML"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
totalItems = !isNaN(totalItems) ? totalItems : 0;
|
totalItems = !isNaN(totalItems) ? totalItems : 0;
|
||||||
|
|
||||||
let playerRank = parseSsInt(opt(doc.querySelector('.content .column ul li:first-of-type a:first-of-type'), 'innerText'));
|
let playerRank = parseSsInt(
|
||||||
|
opt(
|
||||||
|
doc.querySelector(
|
||||||
|
".content .column ul li:first-of-type a:first-of-type"
|
||||||
|
),
|
||||||
|
"innerText"
|
||||||
|
)
|
||||||
|
);
|
||||||
playerRank = !isNaN(playerRank) ? playerRank : null;
|
playerRank = !isNaN(playerRank) ? playerRank : null;
|
||||||
|
|
||||||
let countryRank = parseSsInt(opt(doc.querySelector('.content .column ul li:first-of-type a[href^="/global?country="]'), 'innerText'))
|
let countryRank = parseSsInt(
|
||||||
|
opt(
|
||||||
|
doc.querySelector(
|
||||||
|
'.content .column ul li:first-of-type a[href^="/global?country="]'
|
||||||
|
),
|
||||||
|
"innerText"
|
||||||
|
)
|
||||||
|
);
|
||||||
countryRank = !isNaN(countryRank) ? countryRank : null;
|
countryRank = !isNaN(countryRank) ? countryRank : null;
|
||||||
|
|
||||||
const stats = [{key: 'Player ranking', type: 'rank', value: playerRank, countryRank: countryRank}]
|
const stats = [
|
||||||
|
{
|
||||||
|
key: "Player ranking",
|
||||||
|
type: "rank",
|
||||||
|
value: playerRank,
|
||||||
|
countryRank: countryRank,
|
||||||
|
},
|
||||||
|
]
|
||||||
.concat(
|
.concat(
|
||||||
[...doc.querySelectorAll('.content .column ul li')]
|
[...doc.querySelectorAll(".content .column ul li")]
|
||||||
.map(li => {
|
.map((li) => {
|
||||||
const matches = li.innerHTML.match(/^\s*<strong>([^:]+)\s*:?\s*<\/strong>\s*(.*)$/);
|
const matches = li.innerHTML.match(
|
||||||
|
/^\s*<strong>([^:]+)\s*:?\s*<\/strong>\s*(.*)$/
|
||||||
|
);
|
||||||
if (!matches) return null;
|
if (!matches) return null;
|
||||||
|
|
||||||
const mapping = [
|
const mapping = [
|
||||||
{key: 'Performance Points', type: 'number', precision: 2, suffix: 'pp', number: true,},
|
|
||||||
{key: 'Play Count', type: 'number', precision: 0, number: true, colorVar: 'selected',},
|
|
||||||
{key: 'Total Score', type: 'number', precision: 0, number: true, colorVar: 'selected',},
|
|
||||||
{
|
{
|
||||||
key: 'Replays Watched by Others',
|
key: "Performance Points",
|
||||||
type: 'number',
|
type: "number",
|
||||||
precision: 0,
|
precision: 2,
|
||||||
title: 'profile.stats.replays',
|
suffix: "pp",
|
||||||
number: true,
|
number: true,
|
||||||
colorVar: 'dimmed',
|
|
||||||
},
|
},
|
||||||
{key: 'Role', number: false, colorVar: 'dimmed'},
|
{
|
||||||
{key: 'Inactive Account', number: false, colorVar: 'decrease'},
|
key: "Play Count",
|
||||||
{key: 'Banned', number: false, colorVar: 'decrease'},
|
type: "number",
|
||||||
|
precision: 0,
|
||||||
|
number: true,
|
||||||
|
colorVar: "selected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Total Score",
|
||||||
|
type: "number",
|
||||||
|
precision: 0,
|
||||||
|
number: true,
|
||||||
|
colorVar: "selected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Replays Watched by Others",
|
||||||
|
type: "number",
|
||||||
|
precision: 0,
|
||||||
|
title: "profile.stats.replays",
|
||||||
|
number: true,
|
||||||
|
colorVar: "dimmed",
|
||||||
|
},
|
||||||
|
{ key: "Role", number: false, colorVar: "dimmed" },
|
||||||
|
{ key: "Inactive Account", number: false, colorVar: "decrease" },
|
||||||
|
{ key: "Banned", number: false, colorVar: "decrease" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const value = mapping.filter(m => m.number).map(m => m.key).includes(matches[1])
|
const value = mapping
|
||||||
|
.filter((m) => m.number)
|
||||||
|
.map((m) => m.key)
|
||||||
|
.includes(matches[1])
|
||||||
? parseSsFloat(matches[2])
|
? parseSsFloat(matches[2])
|
||||||
: matches[2];
|
: matches[2];
|
||||||
|
|
||||||
const item = mapping.find(m => m.key === matches[1]);
|
const item = mapping.find((m) => m.key === matches[1]);
|
||||||
return item ? { ...item, value } : { label: matches[1], value };
|
return item ? { ...item, value } : { label: matches[1], value };
|
||||||
})
|
})
|
||||||
.filter(s => s)
|
.filter((s) => s)
|
||||||
).reduce((cum, item) => {
|
)
|
||||||
|
.reduce(
|
||||||
|
(cum, item) => {
|
||||||
if (item.key)
|
if (item.key)
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
case 'Player ranking':
|
case "Player ranking":
|
||||||
cum.rank = item.value;
|
cum.rank = item.value;
|
||||||
cum.countryRank = item.countryRank;
|
cum.countryRank = item.countryRank;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Performance Points':
|
case "Performance Points":
|
||||||
cum.pp = item.value;
|
cum.pp = item.value;
|
||||||
break;
|
break;
|
||||||
case 'Play Count':
|
case "Play Count":
|
||||||
cum.playCount = item.value;
|
cum.playCount = item.value;
|
||||||
break;
|
break;
|
||||||
case 'Total Score':
|
case "Total Score":
|
||||||
cum.totalScore = item.value;
|
cum.totalScore = item.value;
|
||||||
break;
|
break;
|
||||||
case 'Replays Watched by Others':
|
case "Replays Watched by Others":
|
||||||
cum.replays = item.value;
|
cum.replays = item.value;
|
||||||
break;
|
break;
|
||||||
case 'Role':
|
case "Role":
|
||||||
cum.role = item.value;
|
cum.role = item.value;
|
||||||
break;
|
break;
|
||||||
case 'Inactive Account':
|
case "Inactive Account":
|
||||||
cum.inactiveAccount = true;
|
cum.inactiveAccount = true;
|
||||||
break;
|
break;
|
||||||
case 'Banned':
|
case "Banned":
|
||||||
cum.bannedAccount = true;
|
cum.bannedAccount = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cum;
|
return cum;
|
||||||
}, {inactiveAccount: false, bannedAccount: false});
|
},
|
||||||
|
{ inactiveAccount: false, bannedAccount: false }
|
||||||
|
);
|
||||||
|
|
||||||
const scores = [...doc.querySelectorAll('table.ranking tbody tr')].map(tr => {
|
const scores = [...doc.querySelectorAll("table.ranking tbody tr")].map(
|
||||||
|
(tr) => {
|
||||||
let ret = { lastUpdated: new Date() };
|
let ret = { lastUpdated: new Date() };
|
||||||
|
|
||||||
const rank = tr.querySelector('th.rank');
|
const rank = tr.querySelector("th.rank");
|
||||||
if (rank) {
|
if (rank) {
|
||||||
const rankMatch = parseSsInt(rank.innerText);
|
const rankMatch = parseSsInt(rank.innerText);
|
||||||
ret.rank = !isNaN(rankMatch) ? rankMatch : null;
|
ret.rank = !isNaN(rankMatch) ? rankMatch : null;
|
||||||
@ -166,76 +270,120 @@ export default (options = {}) => {
|
|||||||
ret.rank = null;
|
ret.rank = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const song = tr.querySelector('th.song a');
|
const song = tr.querySelector("th.song a");
|
||||||
if (song) {
|
if (song) {
|
||||||
const leaderboardId = parseInt(getFirstRegexpMatch(/leaderboard\/(\d+)/, song.href), 10);
|
const leaderboardId = parseInt(
|
||||||
|
getFirstRegexpMatch(/leaderboard\/(\d+)/, song.href),
|
||||||
|
10
|
||||||
|
);
|
||||||
ret.leaderboardId = leaderboardId ? leaderboardId : null;
|
ret.leaderboardId = leaderboardId ? leaderboardId : null;
|
||||||
} else {
|
} else {
|
||||||
ret.leaderboardId = null;
|
ret.leaderboardId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const img = tr.querySelector('th.song img');
|
const img = tr.querySelector("th.song img");
|
||||||
const imgMatch = img ? img.src.match(/([^\/]+)\.(jpg|jpeg|png)$/) : null;
|
const imgMatch = img
|
||||||
|
? img.src.match(/([^\/]+)\.(jpg|jpeg|png)$/)
|
||||||
|
: null;
|
||||||
ret.songHash = imgMatch ? imgMatch[1] : null;
|
ret.songHash = imgMatch ? imgMatch[1] : null;
|
||||||
|
|
||||||
const songPp = tr.querySelector('th.song a .songTop.pp');
|
const songPp = tr.querySelector("th.song a .songTop.pp");
|
||||||
const songMatch = songPp
|
const songMatch = songPp
|
||||||
? songPp.innerHTML
|
? songPp.innerHTML
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, "&")
|
||||||
.replace(/<span class="__cf_email__" data-cfemail="[^"]+">\[email protected]<\/span>/g, '')
|
.replace(
|
||||||
|
/<span class="__cf_email__" data-cfemail="[^"]+">\[email protected]<\/span>/g,
|
||||||
|
""
|
||||||
|
)
|
||||||
.match(/^(.*?)\s*<span[^>]+>(.*?)<\/span>/)
|
.match(/^(.*?)\s*<span[^>]+>(.*?)<\/span>/)
|
||||||
: null;
|
: null;
|
||||||
if (songMatch) {
|
if (songMatch) {
|
||||||
const songAuthorMatch = songMatch[1].match(/^(.*?)\s-\s(.*)$/);
|
const songAuthorMatch = songMatch[1].match(/^(.*?)\s-\s(.*)$/);
|
||||||
if (songAuthorMatch) {
|
if (songAuthorMatch) {
|
||||||
ret.songName = songAuthorMatch[2];
|
ret.songName = songAuthorMatch[2];
|
||||||
ret.songSubName = '';
|
ret.songSubName = "";
|
||||||
ret.songAuthorName = songAuthorMatch[1];
|
ret.songAuthorName = songAuthorMatch[1];
|
||||||
} else {
|
} else {
|
||||||
ret.songName = songMatch[1];
|
ret.songName = songMatch[1];
|
||||||
ret.songSubName = '';
|
ret.songSubName = "";
|
||||||
ret.songAuthorName = '';
|
ret.songAuthorName = "";
|
||||||
}
|
}
|
||||||
ret.difficultyRaw = '_' + songMatch[2].replace('Expert+', 'ExpertPlus') + '_SoloStandard'
|
ret.difficultyRaw =
|
||||||
|
"_" +
|
||||||
|
songMatch[2].replace("Expert+", "ExpertPlus") +
|
||||||
|
"_SoloStandard";
|
||||||
} else {
|
} else {
|
||||||
ret = Object.assign(ret, {songName: null, songSubName: null, songAuthorName: null, difficultyRaw: null});
|
ret = Object.assign(ret, {
|
||||||
|
songName: null,
|
||||||
|
songSubName: null,
|
||||||
|
songAuthorName: null,
|
||||||
|
difficultyRaw: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const songMapper = tr.querySelector('th.song a .songTop.mapper');
|
const songMapper = tr.querySelector("th.song a .songTop.mapper");
|
||||||
ret.levelAuthorName = songMapper ? songMapper.innerText : null;
|
ret.levelAuthorName = songMapper ? songMapper.innerText : null;
|
||||||
|
|
||||||
const songDate = tr.querySelector('th.song span.songBottom.time');
|
const songDate = tr.querySelector("th.song span.songBottom.time");
|
||||||
ret.timeSet = songDate ? dateFromString(songDate.title) : null;
|
ret.timeSet = songDate ? dateFromString(songDate.title) : null;
|
||||||
|
|
||||||
const pp = parseSsFloat(opt(tr.querySelector('th.score .scoreTop.ppValue'), 'innerText'));
|
const pp = parseSsFloat(
|
||||||
|
opt(tr.querySelector("th.score .scoreTop.ppValue"), "innerText")
|
||||||
|
);
|
||||||
ret.pp = !isNaN(pp) ? pp : null;
|
ret.pp = !isNaN(pp) ? pp : null;
|
||||||
|
|
||||||
const ppWeighted = parseSsFloat(getFirstRegexpMatch(/^\(([0-9.]+)pp\)$/, opt(tr.querySelector('th.score .scoreTop.ppWeightedValue'), 'innerText')));
|
const ppWeighted = parseSsFloat(
|
||||||
|
getFirstRegexpMatch(
|
||||||
|
/^\(([0-9.]+)pp\)$/,
|
||||||
|
opt(
|
||||||
|
tr.querySelector("th.score .scoreTop.ppWeightedValue"),
|
||||||
|
"innerText"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
ret.ppWeighted = !isNaN(ppWeighted) ? ppWeighted : null;
|
ret.ppWeighted = !isNaN(ppWeighted) ? ppWeighted : null;
|
||||||
|
|
||||||
const scoreInfo = tr.querySelector('th.score .scoreBottom');
|
const scoreInfo = tr.querySelector("th.score .scoreBottom");
|
||||||
const scoreInfoMatch = scoreInfo ? scoreInfo.innerText.match(/^([^:]+):\s*([0-9,.]+)(?:.*?\((.*?)\))?/) : null;
|
const scoreInfoMatch = scoreInfo
|
||||||
|
? scoreInfo.innerText.match(/^([^:]+):\s*([0-9,.]+)(?:.*?\((.*?)\))?/)
|
||||||
|
: null;
|
||||||
if (scoreInfoMatch) {
|
if (scoreInfoMatch) {
|
||||||
switch (scoreInfoMatch[1]) {
|
switch (scoreInfoMatch[1]) {
|
||||||
case "score":
|
case "score":
|
||||||
ret.acc = null;
|
ret.acc = null;
|
||||||
scoreInfoMatch[3] = scoreInfoMatch[3] ? scoreInfoMatch[3].replace('-','').trim() : null
|
scoreInfoMatch[3] = scoreInfoMatch[3]
|
||||||
ret.mods = scoreInfoMatch[3] && scoreInfoMatch[3].length ? scoreInfoMatch[3].split(',').filter(m => m && m.trim().length) : null;
|
? scoreInfoMatch[3].replace("-", "").trim()
|
||||||
|
: null;
|
||||||
|
ret.mods =
|
||||||
|
scoreInfoMatch[3] && scoreInfoMatch[3].length
|
||||||
|
? scoreInfoMatch[3]
|
||||||
|
.split(",")
|
||||||
|
.filter((m) => m && m.trim().length)
|
||||||
|
: null;
|
||||||
ret.score = parseSsFloat(scoreInfoMatch[2]);
|
ret.score = parseSsFloat(scoreInfoMatch[2]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "accuracy":
|
case "accuracy":
|
||||||
ret.score = null;
|
ret.score = null;
|
||||||
scoreInfoMatch[3] = scoreInfoMatch[3] ? scoreInfoMatch[3].replace('-','').trim() : null
|
scoreInfoMatch[3] = scoreInfoMatch[3]
|
||||||
ret.mods = scoreInfoMatch[3] && scoreInfoMatch[3].length ? scoreInfoMatch[3].split(',').filter(m => m && m.trim().length) : null;
|
? scoreInfoMatch[3].replace("-", "").trim()
|
||||||
|
: null;
|
||||||
|
ret.mods =
|
||||||
|
scoreInfoMatch[3] && scoreInfoMatch[3].length
|
||||||
|
? scoreInfoMatch[3]
|
||||||
|
.split(",")
|
||||||
|
.filter((m) => m && m.trim().length)
|
||||||
|
: null;
|
||||||
ret.acc = parseSsFloat(scoreInfoMatch[2]);
|
ret.acc = parseSsFloat(scoreInfoMatch[2]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
});
|
}
|
||||||
const recentPlay = scores && scores.length && scores[0].timeSet ? scores[0].timeSet : null;
|
);
|
||||||
|
const recentPlay =
|
||||||
|
scores && scores.length && scores[0].timeSet ? scores[0].timeSet : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
player: {
|
player: {
|
||||||
@ -243,19 +391,28 @@ export default (options = {}) => {
|
|||||||
playerId,
|
playerId,
|
||||||
playerName,
|
playerName,
|
||||||
avatar,
|
avatar,
|
||||||
externalProfileUrl: opt(doc.querySelector('.content .column:not(.avatar) .title a'), 'href', null),
|
externalProfileUrl: opt(
|
||||||
history: getFirstRegexpMatch(/data:\s*\[([0-9,]+)\]/, doc.body.innerHTML),
|
doc.querySelector(".content .column:not(.avatar) .title a"),
|
||||||
|
"href",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
history: getFirstRegexpMatch(
|
||||||
|
/data:\s*\[([0-9,]+)\]/,
|
||||||
|
doc.body.innerHTML
|
||||||
|
),
|
||||||
country,
|
country,
|
||||||
badges: [...doc.querySelectorAll('.column.avatar center img')].map(img => ({
|
badges: [...doc.querySelectorAll(".column.avatar center img")].map(
|
||||||
|
(img) => ({
|
||||||
image: getImgUrl(img.src),
|
image: getImgUrl(img.src),
|
||||||
description: img.title
|
description: img.title,
|
||||||
})),
|
})
|
||||||
|
),
|
||||||
rank: stats.rank ? stats.rank : null,
|
rank: stats.rank ? stats.rank : null,
|
||||||
countryRank: stats.countryRank ? stats.countryRank : null,
|
countryRank: stats.countryRank ? stats.countryRank : null,
|
||||||
pp: stats.pp !== undefined ? stats.pp : null,
|
pp: stats.pp !== undefined ? stats.pp : null,
|
||||||
inactive: stats.inactiveAccount ? 1 : 0,
|
inactive: stats.inactiveAccount ? 1 : 0,
|
||||||
banned: stats.bannedAccount ? 1 : 0,
|
banned: stats.bannedAccount ? 1 : 0,
|
||||||
role: '',
|
role: "",
|
||||||
},
|
},
|
||||||
scoreStats: {
|
scoreStats: {
|
||||||
totalScore: stats.totalScore ? stats.totalScore : 0,
|
totalScore: stats.totalScore ? stats.totalScore : 0,
|
||||||
@ -270,41 +427,59 @@ export default (options = {}) => {
|
|||||||
pageNum,
|
pageNum,
|
||||||
pageQty,
|
pageQty,
|
||||||
totalItems,
|
totalItems,
|
||||||
}
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const player = async (playerId, priority = PRIORITY.FG_LOW, options = {}) => fetchHtml(substituteVars(PLAYER_PROFILE_URL, {playerId}), options, priority)
|
const player = async (playerId, priority = PRIORITY.FG_LOW, options = {}) =>
|
||||||
.then(r => {
|
fetchHtml(
|
||||||
|
substituteVars(PLAYER_PROFILE_URL, { playerId }),
|
||||||
|
options,
|
||||||
|
priority
|
||||||
|
).then((r) => {
|
||||||
r.body = processPlayerProfile(playerId, r.body);
|
r.body = processPlayerProfile(playerId, r.body);
|
||||||
|
|
||||||
return r
|
return r;
|
||||||
})
|
});
|
||||||
|
|
||||||
const processCountryRanking = (country, doc) => {
|
const processCountryRanking = (country, doc) => {
|
||||||
cfDecryptEmail(doc);
|
cfDecryptEmail(doc);
|
||||||
|
|
||||||
const data = [...doc.querySelectorAll('.ranking.global .player a')]
|
const data = [...doc.querySelectorAll(".ranking.global .player a")].map(
|
||||||
.map(a => {
|
(a) => {
|
||||||
const tr = a.closest("tr");
|
const tr = a.closest("tr");
|
||||||
const id = getFirstRegexpMatch(/\/(\d+)$/, a.href)
|
const id = getFirstRegexpMatch(/\/(\d+)$/, a.href);
|
||||||
|
|
||||||
const avatar = getImgUrl(opt(tr.querySelector('td.picture img'), 'src', null));
|
const avatar = getImgUrl(
|
||||||
|
opt(tr.querySelector("td.picture img"), "src", null)
|
||||||
|
);
|
||||||
|
|
||||||
let country = getFirstRegexpMatch(/^.*?\/flags\/([^.]+)\..*$/, opt(tr.querySelector('td.player img'), 'src', null));
|
let country = getFirstRegexpMatch(
|
||||||
|
/^.*?\/flags\/([^.]+)\..*$/,
|
||||||
|
opt(tr.querySelector("td.player img"), "src", null)
|
||||||
|
);
|
||||||
country = country ? country.toUpperCase() : null;
|
country = country ? country.toUpperCase() : null;
|
||||||
|
|
||||||
let difference = parseSsInt(opt(tr.querySelector('td.diff'), 'innerText', null));
|
let difference = parseSsInt(
|
||||||
difference = !isNaN(difference) ? difference : null
|
opt(tr.querySelector("td.diff"), "innerText", null)
|
||||||
|
);
|
||||||
|
difference = !isNaN(difference) ? difference : null;
|
||||||
|
|
||||||
let playerName = opt(a.querySelector('.songTop.pp'), 'innerText');
|
let playerName = opt(a.querySelector(".songTop.pp"), "innerText");
|
||||||
playerName = playerName || playerName === '' ? playerName.trim() : null;
|
playerName = playerName || playerName === "" ? playerName.trim() : null;
|
||||||
|
|
||||||
let pp = parseSsFloat(opt(tr.querySelector('td.pp .scoreTop.ppValue'), 'innerText'));
|
let pp = parseSsFloat(
|
||||||
|
opt(tr.querySelector("td.pp .scoreTop.ppValue"), "innerText")
|
||||||
|
);
|
||||||
pp = !isNaN(pp) ? pp : null;
|
pp = !isNaN(pp) ? pp : null;
|
||||||
|
|
||||||
let rank = parseSsInt(getFirstRegexpMatch(/^\s*#(\d+)\s*$/, opt(tr.querySelector('td.rank'), 'innerText', null)));
|
let rank = parseSsInt(
|
||||||
rank = !isNaN(rank) ? rank : null
|
getFirstRegexpMatch(
|
||||||
|
/^\s*#(\d+)\s*$/,
|
||||||
|
opt(tr.querySelector("td.rank"), "innerText", null)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
rank = !isNaN(rank) ? rank : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
avatar,
|
avatar,
|
||||||
@ -315,121 +490,192 @@ export default (options = {}) => {
|
|||||||
playerName,
|
playerName,
|
||||||
pp,
|
pp,
|
||||||
rank,
|
rank,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
|
|
||||||
return { players: data };
|
return { players: data };
|
||||||
}
|
};
|
||||||
|
|
||||||
const countryRanking = async (country, page = 1, priority = PRIORITY.FG_LOW, options = {}) => fetchHtml(substituteVars(COUNTRY_RANKING_URL, {country, page}), options, priority)
|
const countryRanking = async (
|
||||||
.then(r => {
|
country,
|
||||||
r.body = processCountryRanking(country, r.body)
|
page = 1,
|
||||||
|
priority = PRIORITY.FG_LOW,
|
||||||
|
options = {}
|
||||||
|
) =>
|
||||||
|
fetchHtml(
|
||||||
|
substituteVars(COUNTRY_RANKING_URL, { country, page }),
|
||||||
|
options,
|
||||||
|
priority
|
||||||
|
).then((r) => {
|
||||||
|
r.body = processCountryRanking(country, r.body);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
})
|
});
|
||||||
|
|
||||||
const parseSsLeaderboardScores = doc => {
|
const parseSsLeaderboardScores = (doc) => {
|
||||||
cfDecryptEmail(doc);
|
cfDecryptEmail(doc);
|
||||||
|
|
||||||
return [...doc.querySelectorAll('table.ranking tbody tr')].map(tr => {
|
return [...doc.querySelectorAll("table.ranking tbody tr")].map((tr) => {
|
||||||
let ret = {player: {playerInfo: {countries: []}}, score: {lastUpdated: new Date()}};
|
let ret = {
|
||||||
|
player: { playerInfo: { countries: [] } },
|
||||||
|
score: { lastUpdated: new Date() },
|
||||||
|
};
|
||||||
|
|
||||||
const parseValue = selector => {
|
const parseValue = (selector) => {
|
||||||
const val = parseSsFloat(opt(tr.querySelector(selector), 'innerText'));
|
const val = parseSsFloat(opt(tr.querySelector(selector), "innerText"));
|
||||||
|
|
||||||
return !isNaN(val) ? val : null;
|
return !isNaN(val) ? val : null;
|
||||||
}
|
};
|
||||||
|
|
||||||
ret.player.playerInfo.avatar = getImgUrl(opt(tr.querySelector('.picture img'), 'src', null));
|
ret.player.playerInfo.avatar = getImgUrl(
|
||||||
|
opt(tr.querySelector(".picture img"), "src", null)
|
||||||
|
);
|
||||||
|
|
||||||
ret.score.rank = parseSsInt(opt(tr.querySelector('td.rank'), 'innerText'));
|
ret.score.rank = parseSsInt(
|
||||||
|
opt(tr.querySelector("td.rank"), "innerText")
|
||||||
|
);
|
||||||
if (isNaN(ret.score.rank)) ret.score.rank = null;
|
if (isNaN(ret.score.rank)) ret.score.rank = null;
|
||||||
|
|
||||||
const player = tr.querySelector('.player a');
|
const player = tr.querySelector(".player a");
|
||||||
if (player) {
|
if (player) {
|
||||||
let country = getFirstRegexpMatch(/^.*?\/flags\/([^.]+)\..*$/, opt(player.querySelector('img'), 'src', ''));
|
let country = getFirstRegexpMatch(
|
||||||
|
/^.*?\/flags\/([^.]+)\..*$/,
|
||||||
|
opt(player.querySelector("img"), "src", "")
|
||||||
|
);
|
||||||
country = country ? country.toUpperCase() : null;
|
country = country ? country.toUpperCase() : null;
|
||||||
if (country) {
|
if (country) {
|
||||||
ret.player.playerInfo.country = country
|
ret.player.playerInfo.country = country;
|
||||||
ret.player.playerInfo.countries.push({ country, rank: null });
|
ret.player.playerInfo.countries.push({ country, rank: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.player.name = opt(player.querySelector('span.songTop.pp'), 'innerText')
|
ret.player.name = opt(
|
||||||
ret.player.name = ret.player.name ? ret.player.name.trim().replace(''', "'") : null;
|
player.querySelector("span.songTop.pp"),
|
||||||
ret.player.playerId = getFirstRegexpMatch(/\/u\/(\d+)((\?|&|#).*)?$/, opt(player, 'href', ''));
|
"innerText"
|
||||||
ret.player.playerId = ret.player.playerId ? ret.player.playerId.trim() : null;
|
);
|
||||||
|
ret.player.name = ret.player.name
|
||||||
|
? ret.player.name.trim().replace("'", "'")
|
||||||
|
: null;
|
||||||
|
ret.player.playerId = getFirstRegexpMatch(
|
||||||
|
/\/u\/(\d+)((\?|&|#).*)?$/,
|
||||||
|
opt(player, "href", "")
|
||||||
|
);
|
||||||
|
ret.player.playerId = ret.player.playerId
|
||||||
|
? ret.player.playerId.trim()
|
||||||
|
: null;
|
||||||
} else {
|
} else {
|
||||||
ret.player.playerId = null;
|
ret.player.playerId = null;
|
||||||
ret.player.name = null;
|
ret.player.name = null;
|
||||||
ret.player.playerInfo.country = null;
|
ret.player.playerInfo.country = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.score.score = parseValue('td.score');
|
ret.score.score = parseValue("td.score");
|
||||||
|
|
||||||
ret.score.timeSetString = opt(tr.querySelector('td.timeset'), 'innerText', null);
|
ret.score.timeSetString = opt(
|
||||||
if (ret.score.timeSetString) ret.score.timeSetString = ret.score.timeSetString.trim();
|
tr.querySelector("td.timeset"),
|
||||||
|
"innerText",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
if (ret.score.timeSetString)
|
||||||
|
ret.score.timeSetString = ret.score.timeSetString.trim();
|
||||||
|
|
||||||
ret.score.mods = opt(tr.querySelector('td.mods'), 'innerText');
|
ret.score.mods = opt(tr.querySelector("td.mods"), "innerText");
|
||||||
ret.score.mods = ret.score.mods ? ret.score.mods.replace('-','').trim() : null
|
ret.score.mods = ret.score.mods
|
||||||
ret.score.mods = ret.score.mods && ret.score.mods.length ? ret.score.mods.split(',').filter(m => m && m.trim().length) : null;
|
? ret.score.mods.replace("-", "").trim()
|
||||||
|
: null;
|
||||||
|
ret.score.mods =
|
||||||
|
ret.score.mods && ret.score.mods.length
|
||||||
|
? ret.score.mods.split(",").filter((m) => m && m.trim().length)
|
||||||
|
: null;
|
||||||
|
|
||||||
ret.score.pp = parseValue('td.pp .scoreTop.ppValue');
|
ret.score.pp = parseValue("td.pp .scoreTop.ppValue");
|
||||||
|
|
||||||
ret.score.percentage = parseValue('td.percentage');
|
ret.score.percentage = parseValue("td.percentage");
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const processLeaderboard = (leaderboardId, page, doc) => {
|
const processLeaderboard = (leaderboardId, page, doc) => {
|
||||||
cfDecryptEmail(doc);
|
cfDecryptEmail(doc);
|
||||||
|
|
||||||
const diffs = [...doc.querySelectorAll('.tabs ul li a')].map(a => {
|
const diffs = [...doc.querySelectorAll(".tabs ul li a")].map((a) => {
|
||||||
let leaderboardId = parseInt(getFirstRegexpMatch(/leaderboard\/(\d+)$/, a.href), 10);
|
let leaderboardId = parseInt(
|
||||||
|
getFirstRegexpMatch(/leaderboard\/(\d+)$/, a.href),
|
||||||
|
10
|
||||||
|
);
|
||||||
if (isNaN(leaderboardId)) leaderboardId = null;
|
if (isNaN(leaderboardId)) leaderboardId = null;
|
||||||
|
|
||||||
const span = a.querySelector('span');
|
const span = a.querySelector("span");
|
||||||
const color = span ? span.style.color : null;
|
const color = span ? span.style.color : null;
|
||||||
|
|
||||||
return { name: a.innerText, leaderboardId, color };
|
return { name: a.innerText, leaderboardId, color };
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentDiffHuman = opt(doc.querySelector('.tabs li.is-active a span'), 'innerText', null);
|
const currentDiffHuman = opt(
|
||||||
|
doc.querySelector(".tabs li.is-active a span"),
|
||||||
|
"innerText",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
let diff = null;
|
let diff = null;
|
||||||
let diffInfo = null;
|
let diffInfo = null;
|
||||||
if (currentDiffHuman) {
|
if (currentDiffHuman) {
|
||||||
const lowerCaseDiff = currentDiffHuman.toLowerCase().replace('+', 'Plus');
|
const lowerCaseDiff = currentDiffHuman.toLowerCase().replace("+", "Plus");
|
||||||
diff = `_${capitalize(lowerCaseDiff)}_SoloStandard`;
|
diff = `_${capitalize(lowerCaseDiff)}_SoloStandard`;
|
||||||
diffInfo = {type: 'Standard', diff: lowerCaseDiff}
|
diffInfo = { type: "Standard", diff: lowerCaseDiff };
|
||||||
}
|
}
|
||||||
|
|
||||||
const songName = opt(doc.querySelector('.column.is-one-third-desktop .box:first-of-type .title a'), 'innerText', null);
|
const songName = opt(
|
||||||
|
doc.querySelector(
|
||||||
|
".column.is-one-third-desktop .box:first-of-type .title a"
|
||||||
|
),
|
||||||
|
"innerText",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
const imageUrl = getImgUrl(opt(doc.querySelector('.column.is-one-third-desktop .box:first-of-type .columns .column.is-one-quarter img'), 'src', null));
|
const imageUrl = getImgUrl(
|
||||||
|
opt(
|
||||||
|
doc.querySelector(
|
||||||
|
".column.is-one-third-desktop .box:first-of-type .columns .column.is-one-quarter img"
|
||||||
|
),
|
||||||
|
"src",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const songInfo = [
|
const songInfo = [
|
||||||
{id: 'hash', label: 'ID', value: null},
|
{ id: "hash", label: "ID", value: null },
|
||||||
{id: 'scores', label: 'Scores', value: null},
|
{ id: "scores", label: "Scores", value: null },
|
||||||
{id: 'status', label: 'Status', value: null},
|
{ id: "status", label: "Status", value: null },
|
||||||
{id: 'totalScores', label: 'Total Scores', value: null},
|
{ id: "totalScores", label: "Total Scores", value: null },
|
||||||
{id: 'notes', label: 'Note Count', value: null},
|
{ id: "notes", label: "Note Count", value: null },
|
||||||
{id: 'bpm', label: 'BPM', value: null},
|
{ id: "bpm", label: "BPM", value: null },
|
||||||
{id: 'stars', label: 'Star Difficulty', value: null},
|
{ id: "stars", label: "Star Difficulty", value: null },
|
||||||
{id: 'levelAuthorName', label: 'Mapped by', value: null},
|
{ id: "levelAuthorName", label: "Mapped by", value: null },
|
||||||
]
|
]
|
||||||
.map(sid => {
|
.map((sid) => {
|
||||||
let songInfoBox = doc.querySelector('.column.is-one-third-desktop .box:first-of-type')
|
let songInfoBox = doc.querySelector(
|
||||||
|
".column.is-one-third-desktop .box:first-of-type"
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...sid,
|
...sid,
|
||||||
value: songInfoBox ? songInfoBox.innerHTML.match(new RegExp(sid.label + ':\\s*<b>(.*?)</b>', 'i')) : null,
|
value: songInfoBox
|
||||||
}
|
? songInfoBox.innerHTML.match(
|
||||||
|
new RegExp(sid.label + ":\\s*<b>(.*?)</b>", "i")
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.concat([{id: 'name', value: [null, songName]}])
|
.concat([{ id: "name", value: [null, songName] }])
|
||||||
.reduce((cum, sid) => {
|
.reduce(
|
||||||
|
(cum, sid) => {
|
||||||
let value = Array.isArray(sid.value) ? sid.value[1] : null;
|
let value = Array.isArray(sid.value) ? sid.value[1] : null;
|
||||||
|
|
||||||
if (value !== null && ['scores', 'totalScores', 'bpm', 'notes'].includes(sid.id)) {
|
if (
|
||||||
|
value !== null &&
|
||||||
|
["scores", "totalScores", "bpm", "notes"].includes(sid.id)
|
||||||
|
) {
|
||||||
value = parseSsFloat(value);
|
value = parseSsFloat(value);
|
||||||
|
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
@ -438,44 +684,64 @@ export default (options = {}) => {
|
|||||||
|
|
||||||
return cum;
|
return cum;
|
||||||
}
|
}
|
||||||
if (value !== null && sid.id === 'stars') value = parseSsFloat(value);
|
if (value !== null && sid.id === "stars") value = parseSsFloat(value);
|
||||||
if (value && sid.id === 'name') {
|
if (value && sid.id === "name") {
|
||||||
const songAuthorMatch = value.match(/^(.*?)\s-\s(.*)$/);
|
const songAuthorMatch = value.match(/^(.*?)\s-\s(.*)$/);
|
||||||
if (songAuthorMatch) {
|
if (songAuthorMatch) {
|
||||||
value = songAuthorMatch[2];
|
value = songAuthorMatch[2];
|
||||||
cum.authorName = songAuthorMatch[1];
|
cum.authorName = songAuthorMatch[1];
|
||||||
} else {
|
} else {
|
||||||
cum.authorName = '';
|
cum.authorName = "";
|
||||||
}
|
}
|
||||||
cum.subName = '';
|
cum.subName = "";
|
||||||
}
|
}
|
||||||
if (value && sid.id === 'levelAuthorName') {
|
if (value && sid.id === "levelAuthorName") {
|
||||||
const el = doc.createElement('div');
|
const el = doc.createElement("div");
|
||||||
el.innerHTML = value;
|
el.innerHTML = value;
|
||||||
value = el.innerText;
|
value = el.innerText;
|
||||||
}
|
}
|
||||||
if (value && sid.id === 'status') {
|
if (value && sid.id === "status") {
|
||||||
cum.stats[sid.id] = value;
|
cum.stats[sid.id] = value;
|
||||||
return cum;
|
return cum;
|
||||||
}
|
}
|
||||||
if (value !== null) cum[sid.id] = value;
|
if (value !== null) cum[sid.id] = value;
|
||||||
|
|
||||||
return cum;
|
return cum;
|
||||||
}, {imageUrl, stats: {}});
|
},
|
||||||
|
{ imageUrl, stats: {} }
|
||||||
|
);
|
||||||
|
|
||||||
const { stats, ...song } = songInfo;
|
const { stats, ...song } = songInfo;
|
||||||
const leaderboard = { leaderboardId, song, diffInfo, stats };
|
const leaderboard = { leaderboardId, song, diffInfo, stats };
|
||||||
|
|
||||||
let pageQty = parseInt(opt(doc.querySelector('.pagination .pagination-list li:last-of-type'), 'innerText', null), 10)
|
let pageQty = parseInt(
|
||||||
|
opt(
|
||||||
|
doc.querySelector(".pagination .pagination-list li:last-of-type"),
|
||||||
|
"innerText",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
10
|
||||||
|
);
|
||||||
if (isNaN(pageQty)) pageQty = null;
|
if (isNaN(pageQty)) pageQty = null;
|
||||||
|
|
||||||
let scoresQty = opt(stats, 'scores', 0);
|
let scoresQty = opt(stats, "scores", 0);
|
||||||
if (isNaN(scoresQty)) scoresQty = null;
|
if (isNaN(scoresQty)) scoresQty = null;
|
||||||
|
|
||||||
const totalItems = pageQty && scoresQty ? (Math.ceil(scoresQty / LEADERBOARD_SCORES_PER_PAGE) > pageQty ? pageQty * LEADERBOARD_SCORES_PER_PAGE : scoresQty) : null;
|
const totalItems =
|
||||||
|
pageQty && scoresQty
|
||||||
|
? Math.ceil(scoresQty / LEADERBOARD_SCORES_PER_PAGE) > pageQty
|
||||||
|
? pageQty * LEADERBOARD_SCORES_PER_PAGE
|
||||||
|
: scoresQty
|
||||||
|
: null;
|
||||||
|
|
||||||
let diffChartText = getFirstRegexpMatch(/'difficulty',\s*([0-9.,\s]+)\s*\]/, doc.body.innerHTML)
|
let diffChartText = getFirstRegexpMatch(
|
||||||
let diffChart = (diffChartText ? diffChartText : '').split(',').map(i => parseFloat(i)).filter(i => i && !isNaN(i));
|
/'difficulty',\s*([0-9.,\s]+)\s*\]/,
|
||||||
|
doc.body.innerHTML
|
||||||
|
);
|
||||||
|
let diffChart = (diffChartText ? diffChartText : "")
|
||||||
|
.split(",")
|
||||||
|
.map((i) => parseFloat(i))
|
||||||
|
.filter((i) => i && !isNaN(i));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
diffs,
|
diffs,
|
||||||
@ -485,15 +751,24 @@ export default (options = {}) => {
|
|||||||
pageQty,
|
pageQty,
|
||||||
totalItems,
|
totalItems,
|
||||||
scores: parseSsLeaderboardScores(doc),
|
scores: parseSsLeaderboardScores(doc),
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const leaderboard = async (leaderboardId, page = 1, priority = PRIORITY.FG_LOW, options = {}) => fetchHtml(substituteVars(LEADERBOARD_URL, {leaderboardId, page}), options, priority)
|
const leaderboard = async (
|
||||||
.then(r => {
|
leaderboardId,
|
||||||
|
page = 1,
|
||||||
|
priority = PRIORITY.FG_LOW,
|
||||||
|
options = {}
|
||||||
|
) =>
|
||||||
|
fetchHtml(
|
||||||
|
substituteVars(LEADERBOARD_URL, { leaderboardId, page }),
|
||||||
|
options,
|
||||||
|
priority
|
||||||
|
).then((r) => {
|
||||||
r.body = processLeaderboard(leaderboardId, page, r.body);
|
r.body = processLeaderboard(leaderboardId, page, r.body);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rankeds,
|
rankeds,
|
||||||
@ -501,5 +776,5 @@ export default (options = {}) => {
|
|||||||
countryRanking,
|
countryRanking,
|
||||||
leaderboard,
|
leaderboard,
|
||||||
...queueToReturn,
|
...queueToReturn,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
Reference in New Issue
Block a user