fix scoresaber api url
This commit is contained in:
parent
e6f510cec3
commit
47a23f0484
@ -1,33 +1,42 @@
|
||||
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);
|
||||
|
||||
const {fetchJson, fetchHtml, ...queueToReturn} = queue;
|
||||
const { fetchJson, fetchHtml, ...queueToReturn } = queue;
|
||||
|
||||
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]);
|
||||
return item ? {...item, value} : {label: matches[1], value};
|
||||
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 => {
|
||||
let ret = {lastUpdated: new Date()};
|
||||
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(/&/g, '&')
|
||||
.replace(/<span class="__cf_email__" data-cfemail="[^"]+">\[email protected]<\/span>/g, '')
|
||||
.replace(/&/g, "&")
|
||||
.replace(
|
||||
/<span class="__cf_email__" data-cfemail="[^"]+">\[email 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};
|
||||
}
|
||||
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.countries.push({country, rank: null});
|
||||
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(''', "'") : 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("'", "'")
|
||||
: 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};
|
||||
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};
|
||||
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,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user