fix scoresaber api url

This commit is contained in:
Lee 2023-10-17 23:32:20 +01:00
parent e6f510cec3
commit 47a23f0484

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