aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2021-06-29 17:52:00 -0500
committerGitHub <noreply@github.com>2021-06-29 17:52:00 -0500
commitc0c534dafb54ebf9f95a5054f576ad99de29f232 (patch)
tree8a8a81f5bd6fbb372899f769be402f0bec326149
parent6c7d2de36be559f62560f00fc2297c7deec3a051 (diff)
downloadskyblock-api-c0c534dafb54ebf9f95a5054f576ad99de29f232.tar.gz
skyblock-api-c0c534dafb54ebf9f95a5054f576ad99de29f232.tar.bz2
skyblock-api-c0c534dafb54ebf9f95a5054f576ad99de29f232.zip
enable strictNullChecks and fix all related issues (#65)
-rw-r--r--build/cleaners/player.js2
-rw-r--r--build/cleaners/skyblock/member.js10
-rw-r--r--build/cleaners/skyblock/minions.js2
-rw-r--r--build/cleaners/skyblock/profile.js2
-rw-r--r--build/cleaners/skyblock/stats.js1
-rw-r--r--build/constants.js2
-rw-r--r--build/database.js29
-rw-r--r--build/discord.js4
-rw-r--r--build/hypixel.js41
-rw-r--r--build/hypixelApi.js7
-rw-r--r--build/hypixelCached.js46
-rw-r--r--build/index.js9
-rw-r--r--src/cleaners/player.ts4
-rw-r--r--src/cleaners/skyblock/collections.ts2
-rw-r--r--src/cleaners/skyblock/inventory.ts4
-rw-r--r--src/cleaners/skyblock/member.ts13
-rw-r--r--src/cleaners/skyblock/minions.ts2
-rw-r--r--src/cleaners/skyblock/profile.ts8
-rw-r--r--src/cleaners/skyblock/profiles.ts11
-rw-r--r--src/cleaners/skyblock/stats.ts11
-rw-r--r--src/constants.ts4
-rw-r--r--src/database.ts62
-rw-r--r--src/discord.ts6
-rw-r--r--src/hypixel.ts55
-rw-r--r--src/hypixelApi.ts12
-rw-r--r--src/hypixelCached.ts77
-rw-r--r--src/index.ts9
-rw-r--r--src/mojang.ts7
-rw-r--r--src/util.ts2
-rw-r--r--tsconfig.json3
30 files changed, 278 insertions, 169 deletions
diff --git a/build/cleaners/player.js b/build/cleaners/player.js
index dadb129..42c76b8 100644
--- a/build/cleaners/player.js
+++ b/build/cleaners/player.js
@@ -9,7 +9,7 @@ async function cleanPlayerResponse(data) {
var _a, _b;
// Cleans up a 'player' api response
if (!data)
- return; // bruh
+ return null; // bruh
return {
uuid: util_1.undashUuid(data.uuid),
username: data.displayname,
diff --git a/build/cleaners/skyblock/member.js b/build/cleaners/skyblock/member.js
index 8e72be8..cc66488 100644
--- a/build/cleaners/skyblock/member.js
+++ b/build/cleaners/skyblock/member.js
@@ -31,8 +31,10 @@ const zones_1 = require("./zones");
const skills_1 = require("./skills");
const cached = __importStar(require("../../hypixelCached"));
const constants = __importStar(require("../../constants"));
-async function cleanSkyBlockProfileMemberResponseBasic(member, included = null) {
+async function cleanSkyBlockProfileMemberResponseBasic(member) {
const player = await cached.fetchPlayer(member.uuid);
+ if (!player)
+ return null;
return {
uuid: member.uuid,
username: player.username,
@@ -43,13 +45,13 @@ async function cleanSkyBlockProfileMemberResponseBasic(member, included = null)
}
exports.cleanSkyBlockProfileMemberResponseBasic = cleanSkyBlockProfileMemberResponseBasic;
/** Cleans up a member (from skyblock/profile) */
-async function cleanSkyBlockProfileMemberResponse(member, included = null) {
+async function cleanSkyBlockProfileMemberResponse(member, included = undefined) {
var _a;
// profiles.members[]
- const inventoriesIncluded = included === null || included.includes('inventories');
+ const inventoriesIncluded = included === undefined || included.includes('inventories');
const player = await cached.fetchPlayer(member.uuid);
if (!player)
- return;
+ return null;
const fairySouls = fairysouls_1.cleanFairySouls(member);
const { max_fairy_souls: maxFairySouls } = await constants.fetchConstantValues();
if (fairySouls.total > (maxFairySouls !== null && maxFairySouls !== void 0 ? maxFairySouls : 0))
diff --git a/build/cleaners/skyblock/minions.js b/build/cleaners/skyblock/minions.js
index fae3c96..d2bfc41 100644
--- a/build/cleaners/skyblock/minions.js
+++ b/build/cleaners/skyblock/minions.js
@@ -89,7 +89,7 @@ function combineMinionArrays(minions) {
// This should never happen, but in case the length of `minion.levels` is longer than
// `matchingMinionReference.levels`, then it should be extended to be equal length
while (matchingMinionReference.levels.length < minion.levels.length)
- matchingMinionReference.levels.push(null);
+ matchingMinionReference.levels.push(false);
for (let i = 0; i < minion.levels.length; i++) {
if (minion.levels[i])
matchingMinionReference.levels[i] = true;
diff --git a/build/cleaners/skyblock/profile.js b/build/cleaners/skyblock/profile.js
index 45ef57b..da988cc 100644
--- a/build/cleaners/skyblock/profile.js
+++ b/build/cleaners/skyblock/profile.js
@@ -34,7 +34,7 @@ async function cleanSkyblockProfileResponseLighter(data) {
// we pass an empty array to make it not check stats
promises.push(member_1.cleanSkyBlockProfileMemberResponseBasic(memberRaw));
}
- const cleanedMembers = await Promise.all(promises);
+ const cleanedMembers = (await Promise.all(promises)).filter(m => m);
return {
uuid: data.profile_id,
name: data.cute_name,
diff --git a/build/cleaners/skyblock/stats.js b/build/cleaners/skyblock/stats.js
index 482771c..4ca671b 100644
--- a/build/cleaners/skyblock/stats.js
+++ b/build/cleaners/skyblock/stats.js
@@ -74,6 +74,7 @@ function getStatUnit(name) {
return unitName;
}
}
+ return null;
}
exports.getStatUnit = getStatUnit;
function cleanProfileStats(data) {
diff --git a/build/constants.js b/build/constants.js
index 22327c5..414a6c5 100644
--- a/build/constants.js
+++ b/build/constants.js
@@ -57,7 +57,7 @@ async function fetchGithubApi(method, route, headers, json) {
console.debug('fetching github api', method, route);
const data = await node_fetch_1.default(githubApiBase + route, {
agent: () => httpsAgent,
- body: json ? JSON.stringify(json) : null,
+ body: json ? JSON.stringify(json) : undefined,
method,
headers: Object.assign({
'Authorization': `token ${process.env.github_token}`
diff --git a/build/database.js b/build/database.js
index eb5a63f..c34222d 100644
--- a/build/database.js
+++ b/build/database.js
@@ -137,9 +137,11 @@ async function fetchAllLeaderboardsCategorized() {
const categorizedLeaderboards = {};
for (const leaderboard of [...memberLeaderboardAttributes, ...profileLeaderboardAttributes]) {
const { category } = stats_1.categorizeStat(leaderboard);
- if (!categorizedLeaderboards[category])
- categorizedLeaderboards[category] = [];
- categorizedLeaderboards[category].push(leaderboard);
+ if (category) {
+ if (!categorizedLeaderboards[category])
+ categorizedLeaderboards[category] = [];
+ categorizedLeaderboards[category].push(leaderboard);
+ }
}
// move misc to end by removing and readding it
const misc = categorizedLeaderboards.misc;
@@ -277,8 +279,9 @@ async function fetchMemberLeaderboard(name) {
var _a;
const leaderboardRaw = await fetchMemberLeaderboardRaw(name);
const fetchLeaderboardPlayer = async (i) => {
+ const player = await cached.fetchBasicPlayer(i.uuid);
return {
- player: await cached.fetchBasicPlayer(i.uuid),
+ player,
profileUuid: i.profile,
value: i.value
};
@@ -301,8 +304,11 @@ async function fetchProfileLeaderboard(name) {
const leaderboardRaw = await fetchProfileLeaderboardRaw(name);
const fetchLeaderboardProfile = async (i) => {
const players = [];
- for (const playerUuid of i.players)
- players.push(await cached.fetchBasicPlayer(playerUuid));
+ for (const playerUuid of i.players) {
+ const player = await cached.fetchBasicPlayer(playerUuid);
+ if (player)
+ players.push(player);
+ }
return {
players: players,
profileUuid: i.uuid,
@@ -340,7 +346,11 @@ exports.fetchLeaderboard = fetchLeaderboard;
async function fetchMemberLeaderboardSpots(player, profile) {
var _a;
const fullProfile = await cached.fetchProfile(player, profile);
+ if (!fullProfile)
+ return null;
const fullMember = fullProfile.members.find(m => m.username.toLowerCase() === player.toLowerCase() || m.uuid === player);
+ if (!fullMember)
+ return null;
// update the leaderboard positions for the member
await updateDatabaseMember(fullMember, fullProfile);
const applicableAttributes = await getApplicableMemberLeaderboardAttributes(fullMember);
@@ -404,8 +414,8 @@ async function getApplicableProfileLeaderboardAttributes(profile) {
}
let leaderboardsCount = Object.keys(applicableAttributes).length;
const leaderboardsCountRequirement = await getLeaderboardRequirement('leaderboards_count', 'member');
- if ((leaderboardsCountRequirement === null)
- || (leaderboardsCount > leaderboardsCountRequirement)) {
+ if (leaderboardsCountRequirement === null
+ || leaderboardsCount > leaderboardsCountRequirement) {
applicableAttributes['leaderboards_count'] = leaderboardsCount;
}
return applicableAttributes;
@@ -423,7 +433,8 @@ async function updateDatabaseMember(member, profile) {
recentlyUpdated.set(profile.uuid + member.uuid, true);
if (_1.debug)
console.debug('adding member to leaderboards', member.username);
- await constants.addStats(Object.keys(member.rawHypixelStats));
+ if (member.rawHypixelStats)
+ await constants.addStats(Object.keys(member.rawHypixelStats));
await constants.addCollections(member.collections.map(coll => coll.name));
await constants.addSkills(member.skills.map(skill => skill.name));
await constants.addZones(member.visited_zones.map(zone => zone.name));
diff --git a/build/discord.js b/build/discord.js
index a92521a..ba9f3eb 100644
--- a/build/discord.js
+++ b/build/discord.js
@@ -13,6 +13,10 @@ const httpsAgent = new https_1.Agent({
async function exchangeCode(redirectUri, code) {
const API_ENDPOINT = 'https://discord.com/api/v6';
const CLIENT_SECRET = process.env.discord_client_secret;
+ if (!CLIENT_SECRET) {
+ console.error('discord_client_secret isn\'t in env, couldn\'t login with discord');
+ return null;
+ }
const data = {
'client_id': DISCORD_CLIENT_ID,
'client_secret': CLIENT_SECRET,
diff --git a/build/hypixel.js b/build/hypixel.js
index 8750170..414139e 100644
--- a/build/hypixel.js
+++ b/build/hypixel.js
@@ -62,22 +62,25 @@ async function cleanResponse({ path, data }, options) {
* used inclusions: player, profiles
*/
async function fetchUser({ user, uuid, username }, included = ['player'], customization) {
+ var _a, _b;
if (!uuid) {
// If the uuid isn't provided, get it
- uuid = await cached.uuidFromUser(user || username);
+ if (!username && !user)
+ return null;
+ uuid = await cached.uuidFromUser((user !== null && user !== void 0 ? user : username));
}
- const websiteAccountPromise = customization ? database_1.fetchAccount(uuid) : null;
if (!uuid) {
// the user doesn't exist.
if (_1.debug)
console.debug('error:', user, 'doesnt exist');
return null;
}
+ const websiteAccountPromise = customization ? database_1.fetchAccount(uuid) : null;
const includePlayers = included.includes('player');
const includeProfiles = included.includes('profiles');
let profilesData;
let basicProfilesData;
- let playerData;
+ let playerData = null;
if (includePlayers) {
playerData = await cached.fetchPlayer(uuid);
// if not including profiles, include lightweight profiles just in case
@@ -86,28 +89,26 @@ async function fetchUser({ user, uuid, username }, included = ['player'], custom
if (playerData)
delete playerData.profiles;
}
- if (includeProfiles) {
+ if (includeProfiles)
profilesData = await cached.fetchSkyblockProfiles(uuid);
- }
- let activeProfile = null;
+ let activeProfile;
let lastOnline = 0;
if (includeProfiles) {
for (const profile of profilesData) {
- const member = profile.members.find(member => member.uuid === uuid);
- if (member.last_save > lastOnline) {
+ const member = (_a = profile.members) === null || _a === void 0 ? void 0 : _a.find(member => member.uuid === uuid);
+ if (member && member.last_save > lastOnline) {
lastOnline = member.last_save;
activeProfile = profile;
}
}
}
- let websiteAccount = undefined;
- if (websiteAccountPromise) {
+ let websiteAccount = null;
+ if (websiteAccountPromise)
websiteAccount = await websiteAccountPromise;
- }
return {
- player: playerData !== null && playerData !== void 0 ? playerData : null,
+ player: playerData,
profiles: profilesData !== null && profilesData !== void 0 ? profilesData : basicProfilesData,
- activeProfile: includeProfiles ? activeProfile === null || activeProfile === void 0 ? void 0 : activeProfile.uuid : undefined,
+ activeProfile: includeProfiles ? (_b = activeProfile) === null || _b === void 0 ? void 0 : _b.uuid : undefined,
online: includeProfiles ? lastOnline > (Date.now() - exports.saveInterval) : undefined,
customization: websiteAccount === null || websiteAccount === void 0 ? void 0 : websiteAccount.customization
};
@@ -123,7 +124,7 @@ exports.fetchUser = fetchUser;
async function fetchMemberProfile(user, profile, customization) {
const playerUuid = await cached.uuidFromUser(user);
if (!playerUuid)
- return;
+ return null;
// we don't await the promise immediately so it can load while we do other stuff
const websiteAccountPromise = customization ? database_1.fetchAccount(playerUuid) : null;
const profileUuid = await cached.fetchProfileUuid(user, profile);
@@ -133,8 +134,12 @@ async function fetchMemberProfile(user, profile, customization) {
if (!playerUuid)
return null;
const player = await cached.fetchPlayer(playerUuid);
+ if (!player)
+ return null; // this should never happen, but if it does just return null
const cleanProfile = await cached.fetchProfile(playerUuid, profileUuid);
const member = cleanProfile.members.find(m => m.uuid === playerUuid);
+ if (!member)
+ return null; // this should never happen, but if it does just return null
// remove unnecessary member data
const simpleMembers = cleanProfile.members.map(m => {
return {
@@ -146,7 +151,7 @@ async function fetchMemberProfile(user, profile, customization) {
};
});
cleanProfile.members = simpleMembers;
- let websiteAccount = undefined;
+ let websiteAccount = null;
if (websiteAccountPromise)
websiteAccount = await websiteAccountPromise;
return {
@@ -172,7 +177,7 @@ async function fetchMemberProfileUncached(playerUuid, profileUuid) {
const profile = await sendCleanApiRequest({
path: 'skyblock/profile',
args: { profile: profileUuid }
- }, null, { mainMemberUuid: playerUuid });
+ }, undefined, { mainMemberUuid: playerUuid });
// queue updating the leaderboard positions for the member, eventually
for (const member of profile.members)
database_1.queueUpdateDatabaseMember(member, profile);
@@ -189,7 +194,7 @@ async function fetchBasicProfileFromUuidUncached(profileUuid) {
const profile = await sendCleanApiRequest({
path: 'skyblock/profile',
args: { profile: profileUuid }
- }, null, { basic: true });
+ }, undefined, { basic: true });
return profile;
}
exports.fetchBasicProfileFromUuidUncached = fetchBasicProfileFromUuidUncached;
@@ -199,7 +204,7 @@ async function fetchMemberProfilesUncached(playerUuid) {
args: {
uuid: playerUuid
}
- }, null, {
+ }, undefined, {
// only the inventories for the main player are generated, this is for optimization purposes
mainMemberUuid: playerUuid
});
diff --git a/build/hypixelApi.js b/build/hypixelApi.js
index df6d426..a35dd64 100644
--- a/build/hypixelApi.js
+++ b/build/hypixelApi.js
@@ -60,6 +60,7 @@ exports.getKeyUsage = getKeyUsage;
/** Send an HTTP request to the Hypixel API */
async function sendApiRequest({ path, key, args }) {
// Send a raw http request to api.hypixel.net, and return the parsed json
+ var _a, _b, _c;
if (key)
// If there's an api key, add it to the arguments
args.key = key;
@@ -84,9 +85,9 @@ async function sendApiRequest({ path, key, args }) {
if (fetchResponse.headers.get('ratelimit-limit'))
// remember how many uses it has
apiKeyUsage[key] = {
- remaining: parseInt(fetchResponse.headers.get('ratelimit-remaining')),
- limit: parseInt(fetchResponse.headers.get('ratelimit-limit')),
- reset: Date.now() + parseInt(fetchResponse.headers.get('ratelimit-reset')) * 1000
+ remaining: parseInt((_a = fetchResponse.headers.get('ratelimit-remaining')) !== null && _a !== void 0 ? _a : '0'),
+ limit: parseInt((_b = fetchResponse.headers.get('ratelimit-limit')) !== null && _b !== void 0 ? _b : '0'),
+ reset: Date.now() + parseInt((_c = fetchResponse.headers.get('ratelimit-reset')) !== null && _c !== void 0 ? _c : '0') * 1000
};
if (fetchJsonParsed.throttle) {
if (apiKeyUsage[key])
diff --git a/build/hypixelCached.js b/build/hypixelCached.js
index d732f2d..d54aa68 100644
--- a/build/hypixelCached.js
+++ b/build/hypixelCached.js
@@ -95,7 +95,7 @@ async function uuidFromUser(user) {
const username = exports.usernameCache.get(util_1.undashUuid(user));
// sometimes the username will be null, return that
if (username === null)
- return null;
+ return undefined;
// if it has .then, then that means its a waitForCacheSet promise. This is done to prevent requests made while it is already requesting
if (username.then) {
const { key: uuid, value: _username } = await username;
@@ -134,14 +134,17 @@ exports.uuidFromUser = uuidFromUser;
* @param user A user can be either a uuid or a username
*/
async function usernameFromUser(user) {
+ var _a;
if (exports.usernameCache.has(util_1.undashUuid(user))) {
if (_1.debug)
console.debug('Cache hit! usernameFromUser', user);
- return exports.usernameCache.get(util_1.undashUuid(user));
+ return (_a = exports.usernameCache.get(util_1.undashUuid(user))) !== null && _a !== void 0 ? _a : null;
}
if (_1.debug)
console.debug('Cache miss: usernameFromUser', user);
let { uuid, username } = await mojang.profileFromUser(user);
+ if (!uuid)
+ return null;
uuid = util_1.undashUuid(uuid);
exports.usernameCache.set(uuid, username);
return username;
@@ -150,6 +153,8 @@ exports.usernameFromUser = usernameFromUser;
let fetchingPlayers = new Set();
async function fetchPlayer(user) {
const playerUuid = await uuidFromUser(user);
+ if (!playerUuid)
+ return null;
if (exports.playerCache.has(playerUuid))
return exports.playerCache.get(playerUuid);
// if it's already in the process of fetching, check every 100ms until it's not fetching the player anymore and fetch it again, since it'll be cached now
@@ -166,7 +171,7 @@ async function fetchPlayer(user) {
});
fetchingPlayers.delete(playerUuid);
if (!cleanPlayer)
- return;
+ return null;
// clone in case it gets modified somehow later
exports.playerCache.set(playerUuid, cleanPlayer);
exports.usernameCache.set(playerUuid, cleanPlayer.username);
@@ -179,16 +184,21 @@ exports.fetchPlayer = fetchPlayer;
/** Fetch a player without their profiles. This is heavily cached. */
async function fetchBasicPlayer(user) {
const playerUuid = await uuidFromUser(user);
+ if (!playerUuid)
+ return null;
if (exports.basicPlayerCache.has(playerUuid))
return exports.basicPlayerCache.get(playerUuid);
const player = await fetchPlayer(playerUuid);
- if (!player)
+ if (!player) {
console.debug('no player? this should never happen', user, playerUuid);
+ return null;
+ }
delete player.profiles;
return player;
}
exports.fetchBasicPlayer = fetchBasicPlayer;
async function fetchSkyblockProfiles(playerUuid) {
+ var _a;
if (exports.profilesCache.has(playerUuid)) {
if (_1.debug)
console.debug('Cache hit! fetchSkyblockProfiles', playerUuid);
@@ -203,7 +213,7 @@ async function fetchSkyblockProfiles(playerUuid) {
const basicProfile = {
name: profile.name,
uuid: profile.uuid,
- members: profile.members.map(m => {
+ members: (_a = profile.members) === null || _a === void 0 ? void 0 : _a.map(m => {
return {
uuid: m.uuid,
username: m.username,
@@ -224,7 +234,7 @@ exports.fetchSkyblockProfiles = fetchSkyblockProfiles;
async function fetchBasicProfiles(user) {
const playerUuid = await uuidFromUser(user);
if (!playerUuid)
- return; // invalid player, just return
+ return null; // invalid player, just return
if (exports.basicProfilesCache.has(playerUuid)) {
if (_1.debug)
console.debug('Cache hit! fetchBasicProfiles', playerUuid);
@@ -239,6 +249,8 @@ async function fetchBasicProfiles(user) {
}
const profiles = player.profiles;
exports.basicProfilesCache.set(playerUuid, profiles);
+ if (!profiles)
+ return null;
// cache the profile names and uuids to profileNameCache because we can
for (const profile of profiles)
exports.profileNameCache.set(`${playerUuid}.${profile.uuid}`, profile.name);
@@ -250,6 +262,7 @@ async function fetchBasicProfiles(user) {
* @param profile A profile name or profile uuid
*/
async function fetchProfileUuid(user, profile) {
+ var _a;
// if a profile wasn't provided, return
if (!profile) {
if (_1.debug)
@@ -260,10 +273,10 @@ async function fetchProfileUuid(user, profile) {
console.debug('Cache miss: fetchProfileUuid', user, profile);
const profiles = await fetchBasicProfiles(user);
if (!profiles)
- return; // user probably doesnt exist
+ return null; // user probably doesnt exist
const profileUuid = util_1.undashUuid(profile);
for (const p of profiles) {
- if (p.name.toLowerCase() === profileUuid.toLowerCase())
+ if (((_a = p.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === profileUuid.toLowerCase())
return util_1.undashUuid(p.uuid);
else if (util_1.undashUuid(p.uuid) === util_1.undashUuid(profileUuid))
return util_1.undashUuid(p.uuid);
@@ -278,6 +291,8 @@ exports.fetchProfileUuid = fetchProfileUuid;
*/
async function fetchProfile(user, profile) {
const playerUuid = await uuidFromUser(user);
+ if (!playerUuid)
+ return null;
const profileUuid = await fetchProfileUuid(playerUuid, profile);
if (!profileUuid)
return null;
@@ -290,6 +305,8 @@ async function fetchProfile(user, profile) {
if (_1.debug)
console.debug('Cache miss: fetchProfile', user, profile);
const profileName = await fetchProfileName(user, profile);
+ if (!profileName)
+ return null; // uhh this should never happen but if it does just return null
const cleanProfile = await hypixel.fetchMemberProfileUncached(playerUuid, profileUuid);
// we know the name from fetchProfileName, so set it here
cleanProfile.name = profileName;
@@ -307,6 +324,8 @@ async function fetchBasicProfileFromUuid(profileUuid) {
if (_1.debug)
console.debug('Cache hit! fetchBasicProfileFromUuid', profileUuid);
const profile = exports.profileCache.get(profileUuid);
+ if (!profile)
+ return undefined;
return {
uuid: profile.uuid,
members: profile.members.map(m => ({
@@ -329,24 +348,29 @@ exports.fetchBasicProfileFromUuid = fetchBasicProfileFromUuid;
* @param profile A profile uuid or name
*/
async function fetchProfileName(user, profile) {
+ var _a, _b;
// we're fetching the profile and player uuid again in case we were given a name, but it's cached so it's not much of a problem
const profileUuid = await fetchProfileUuid(user, profile);
if (!profileUuid)
return null;
const playerUuid = await uuidFromUser(user);
+ if (!playerUuid)
+ return null;
if (exports.profileNameCache.has(`${playerUuid}.${profileUuid}`)) {
// Return the profile name if it's cached
if (_1.debug)
console.debug('Cache hit! fetchProfileName', profileUuid);
- return exports.profileNameCache.get(`${playerUuid}.${profileUuid}`);
+ return (_a = exports.profileNameCache.get(`${playerUuid}.${profileUuid}`)) !== null && _a !== void 0 ? _a : null;
}
if (_1.debug)
console.debug('Cache miss: fetchProfileName', user, profile);
const basicProfiles = await fetchBasicProfiles(playerUuid);
- let profileName;
+ if (!basicProfiles)
+ return null;
+ let profileName = null;
for (const basicProfile of basicProfiles)
if (basicProfile.uuid === playerUuid)
- profileName = basicProfile.name;
+ profileName = (_b = basicProfile.name) !== null && _b !== void 0 ? _b : null;
exports.profileNameCache.set(`${playerUuid}.${profileUuid}`, profileName);
return profileName;
}
diff --git a/build/index.js b/build/index.js
index 736b137..8bb2040 100644
--- a/build/index.js
+++ b/build/index.js
@@ -136,7 +136,12 @@ app.get('/constants', async (req, res) => {
app.post('/accounts/createsession', async (req, res) => {
try {
const { code } = req.body;
- const { access_token: accessToken, refresh_token: refreshToken } = await discord.exchangeCode(`${mainSiteUrl}/loggedin`, code);
+ const codeExchange = await discord.exchangeCode(`${mainSiteUrl}/loggedin`, code);
+ if (!codeExchange) {
+ res.json({ ok: false, error: 'discord_client_secret isn\'t in env' });
+ return;
+ }
+ const { access_token: accessToken, refresh_token: refreshToken } = codeExchange;
if (!accessToken)
// access token is invalid :(
return res.json({ ok: false });
@@ -152,6 +157,8 @@ app.post('/accounts/session', async (req, res) => {
try {
const { uuid } = req.body;
const session = await database_1.fetchSession(uuid);
+ if (!session)
+ return res.json({ ok: false });
const account = await database_1.fetchAccountFromDiscord(session.discord_user.id);
res.json({ session, account });
}
diff --git a/src/cleaners/player.ts b/src/cleaners/player.ts
index 6ac1382..b2dfc77 100644
--- a/src/cleaners/player.ts
+++ b/src/cleaners/player.ts
@@ -17,10 +17,10 @@ export interface CleanPlayer extends CleanBasicPlayer {
// first_join?: number
}
-export async function cleanPlayerResponse(data: HypixelPlayer): Promise<CleanPlayer> {
+export async function cleanPlayerResponse(data: HypixelPlayer): Promise<CleanPlayer | null> {
// Cleans up a 'player' api response
if (!data)
- return // bruh
+ return null // bruh
return {
uuid: undashUuid(data.uuid),
username: data.displayname,
diff --git a/src/cleaners/skyblock/collections.ts b/src/cleaners/skyblock/collections.ts
index 708822b..99fdc27 100644
--- a/src/cleaners/skyblock/collections.ts
+++ b/src/cleaners/skyblock/collections.ts
@@ -84,7 +84,7 @@ export interface Collection {
}
// get a category name (farming) from a collection name (wheat)
-function getCategory(collectionName): CollectionCategory {
+function getCategory(collectionName): CollectionCategory | undefined {
for (const categoryName in COLLECTIONS) {
const categoryItems = COLLECTIONS[categoryName]
if (categoryItems.includes(collectionName))
diff --git a/src/cleaners/skyblock/inventory.ts b/src/cleaners/skyblock/inventory.ts
index 890937a..5ad6617 100644
--- a/src/cleaners/skyblock/inventory.ts
+++ b/src/cleaners/skyblock/inventory.ts
@@ -26,7 +26,7 @@ interface Item {
export type Inventory = Item[]
-function cleanItem(rawItem): Item {
+function cleanItem(rawItem): Item | null {
// if the item doesn't have an id, it isn't an item
if (rawItem.id === undefined) return null
@@ -35,7 +35,7 @@ function cleanItem(rawItem): Item {
const damageValue = rawItem.Damage
const itemTag = rawItem.tag
const extraAttributes = itemTag?.ExtraAttributes ?? {}
- let headId: string
+ let headId: string | undefined
if (vanillaId === 397) {
const headDataBase64 = itemTag?.SkullOwner?.Properties?.textures?.[0]?.Value
diff --git a/src/cleaners/skyblock/member.ts b/src/cleaners/skyblock/member.ts
index 4a2f8cc..cee1864 100644
--- a/src/cleaners/skyblock/member.ts
+++ b/src/cleaners/skyblock/member.ts
@@ -30,7 +30,7 @@ export interface CleanMember extends CleanBasicMember {
rawHypixelStats?: { [ key: string ]: number }
minions: CleanMinion[]
fairy_souls: FairySouls
- inventories: Inventories
+ inventories?: Inventories
objectives: Objective[]
skills: Skill[]
visited_zones: Zone[]
@@ -38,8 +38,9 @@ export interface CleanMember extends CleanBasicMember {
slayers: SlayerData
}
-export async function cleanSkyBlockProfileMemberResponseBasic(member: any, included: Included[] = null): Promise<CleanBasicMember> {
+export async function cleanSkyBlockProfileMemberResponseBasic(member: any): Promise<CleanBasicMember | null> {
const player = await cached.fetchPlayer(member.uuid)
+ if (!player) return null
return {
uuid: member.uuid,
username: player.username,
@@ -50,11 +51,11 @@ export async function cleanSkyBlockProfileMemberResponseBasic(member: any, inclu
}
/** Cleans up a member (from skyblock/profile) */
-export async function cleanSkyBlockProfileMemberResponse(member, included: Included[] = null): Promise<CleanMember> {
+export async function cleanSkyBlockProfileMemberResponse(member, included: Included[] | undefined = undefined): Promise<CleanMember | null> {
// profiles.members[]
- const inventoriesIncluded = included === null || included.includes('inventories')
+ const inventoriesIncluded = included === undefined || included.includes('inventories')
const player = await cached.fetchPlayer(member.uuid)
- if (!player) return
+ if (!player) return null
const fairySouls = cleanFairySouls(member)
const { max_fairy_souls: maxFairySouls } = await constants.fetchConstantValues()
@@ -109,5 +110,5 @@ export interface CleanMemberProfilePlayer extends CleanPlayer {
export interface CleanMemberProfile {
member: CleanMemberProfilePlayer
profile: CleanFullProfileBasicMembers
- customization: AccountCustomization
+ customization?: AccountCustomization
}
diff --git a/src/cleaners/skyblock/minions.ts b/src/cleaners/skyblock/minions.ts
index 21f7b66..d045a7e 100644
--- a/src/cleaners/skyblock/minions.ts
+++ b/src/cleaners/skyblock/minions.ts
@@ -80,7 +80,7 @@ export function combineMinionArrays(minions: CleanMinion[][]): CleanMinion[] {
// This should never happen, but in case the length of `minion.levels` is longer than
// `matchingMinionReference.levels`, then it should be extended to be equal length
while (matchingMinionReference.levels.length < minion.levels.length)
- matchingMinionReference.levels.push(null)
+ matchingMinionReference.levels.push(false)
for (let i = 0; i < minion.levels.length; i++) {
if (minion.levels[i])
diff --git a/src/cleaners/skyblock/profile.ts b/src/cleaners/skyblock/profile.ts
index 0453ff3..5bcea4b 100644
--- a/src/cleaners/skyblock/profile.ts
+++ b/src/cleaners/skyblock/profile.ts
@@ -25,7 +25,7 @@ export interface CleanFullProfileBasicMembers extends CleanProfile {
/** Return a `CleanProfile` instead of a `CleanFullProfile`, useful when we need to get members but don't want to waste much ram */
export async function cleanSkyblockProfileResponseLighter(data): Promise<CleanProfile> {
// We use Promise.all so it can fetch all the usernames at once instead of waiting for the previous promise to complete
- const promises: Promise<CleanBasicMember>[] = []
+ const promises: Promise<CleanBasicMember | null>[] = []
for (const memberUUID in data.members) {
const memberRaw = data.members[memberUUID]
@@ -34,7 +34,7 @@ export async function cleanSkyblockProfileResponseLighter(data): Promise<CleanPr
promises.push(cleanSkyBlockProfileMemberResponseBasic(memberRaw))
}
- const cleanedMembers: CleanBasicMember[] = await Promise.all(promises)
+ const cleanedMembers: CleanBasicMember[] = (await Promise.all(promises)).filter(m => m) as CleanBasicMember[]
return {
uuid: data.profile_id,
@@ -48,7 +48,7 @@ export async function cleanSkyblockProfileResponseLighter(data): Promise<CleanPr
*/
export async function cleanSkyblockProfileResponse(data: any, options?: ApiOptions): Promise<CleanFullProfile|CleanProfile> {
// We use Promise.all so it can fetch all the users at once instead of waiting for the previous promise to complete
- const promises: Promise<CleanMember>[] = []
+ const promises: Promise<CleanMember | null>[] = []
for (const memberUUID in data.members) {
const memberRaw = data.members[memberUUID]
@@ -62,7 +62,7 @@ export async function cleanSkyblockProfileResponse(data: any, options?: ApiOptio
))
}
- const cleanedMembers: CleanMember[] = (await Promise.all(promises)).filter(m => m !== null && m !== undefined)
+ const cleanedMembers: CleanMember[] = (await Promise.all(promises)).filter(m => m !== null && m !== undefined) as CleanMember[]
if (options?.basic) {
return {
diff --git a/src/cleaners/skyblock/profiles.ts b/src/cleaners/skyblock/profiles.ts
index f12b5f6..a44fe48 100644
--- a/src/cleaners/skyblock/profiles.ts
+++ b/src/cleaners/skyblock/profiles.ts
@@ -1,5 +1,10 @@
-import { HypixelPlayerStatsSkyBlockProfiles } from "../../hypixelApi"
-import { CleanBasicProfile, CleanProfile, cleanSkyblockProfileResponse, cleanSkyblockProfileResponseLighter } from "./profile"
+import { HypixelPlayerStatsSkyBlockProfiles } from '../../hypixelApi'
+import {
+ CleanBasicProfile,
+ CleanFullProfile,
+ CleanProfile,
+ cleanSkyblockProfileResponse
+} from './profile'
export function cleanPlayerSkyblockProfiles(rawProfiles: HypixelPlayerStatsSkyBlockProfiles): CleanBasicProfile[] {
let profiles: CleanBasicProfile[] = []
@@ -14,7 +19,7 @@ export function cleanPlayerSkyblockProfiles(rawProfiles: HypixelPlayerStatsSkyBl
/** Convert an array of raw profiles into clean profiles */
export async function cleanSkyblockProfilesResponse(data: any[]): Promise<CleanProfile[]> {
- const promises = []
+ const promises: Promise<CleanProfile | CleanFullProfile>[] = []
for (const profile of data ?? []) {
// let cleanedProfile = await cleanSkyblockProfileResponseLighter(profile)
promises.push(cleanSkyblockProfileResponse(profile))
diff --git a/src/cleaners/skyblock/stats.ts b/src/cleaners/skyblock/stats.ts
index 31a894c..a91ca74 100644
--- a/src/cleaners/skyblock/stats.ts
+++ b/src/cleaners/skyblock/stats.ts
@@ -14,8 +14,8 @@ const statCategories: { [ key: string ]: string[] | null } = { // sorted in orde
}
export interface StatCategory {
- category: string,
- name: string
+ category: string | null
+ name: string | null
}
export function categorizeStat(statNameRaw: string): StatCategory {
@@ -71,11 +71,11 @@ export interface StatItem {
rawName: string
value: number
categorizedName: string
- category: string
- unit: string
+ category: string | null
+ unit: string | null
}
-export function getStatUnit(name: string): string {
+export function getStatUnit(name: string): string | null {
for (const [ unitName, statMatchers ] of Object.entries(statUnits)) {
for (const statMatch of statMatchers) {
let trailingEnd = statMatch[0] === '_'
@@ -88,6 +88,7 @@ export function getStatUnit(name: string): string {
return unitName
}
}
+ return null
}
diff --git a/src/constants.ts b/src/constants.ts
index 9699e57..dcb9acf 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -40,7 +40,7 @@ async function fetchGithubApi(method: string, route: string, headers?: any, json
githubApiBase + route,
{
agent: () => httpsAgent,
- body: json ? JSON.stringify(json) : null,
+ body: json ? JSON.stringify(json) : undefined,
method,
headers: Object.assign({
'Authorization': `token ${process.env.github_token}`
@@ -78,7 +78,7 @@ function fetchFile(path: string): Promise<GithubFile> {
return new Promise(resolve => {
queue.enqueue(async() => {
if (fileCache.has(path))
- return resolve(fileCache.get(path))
+ return resolve(fileCache.get(path)!)
const r = await fetchGithubApi(
'GET',
diff --git a/src/database.ts b/src/database.ts
index 60fbc7e..5d15954 100644
--- a/src/database.ts
+++ b/src/database.ts
@@ -59,7 +59,7 @@ interface profileRawLeaderboardItem {
}
interface MemberLeaderboardItem {
- player: CleanPlayer
+ player: CleanPlayer | null
profileUuid: string
value: number
}
@@ -205,9 +205,11 @@ export async function fetchAllLeaderboardsCategorized(): Promise<{ [ category: s
const categorizedLeaderboards: { [ category: string ]: string[] } = {}
for (const leaderboard of [...memberLeaderboardAttributes, ...profileLeaderboardAttributes]) {
const { category } = categorizeStat(leaderboard)
- if (!categorizedLeaderboards[category])
- categorizedLeaderboards[category] = []
- categorizedLeaderboards[category].push(leaderboard)
+ if (category) {
+ if (!categorizedLeaderboards[category])
+ categorizedLeaderboards[category] = []
+ categorizedLeaderboards[category].push(leaderboard)
+ }
}
// move misc to end by removing and readding it
@@ -366,14 +368,14 @@ async function fetchProfileLeaderboardRaw(name: string): Promise<profileRawLeade
interface MemberLeaderboard {
name: string
- unit?: string
+ unit: string | null
list: MemberLeaderboardItem[]
info?: string
}
interface ProfileLeaderboard {
name: string
- unit?: string
+ unit: string | null
list: ProfileLeaderboardItem[]
info?: string
}
@@ -384,8 +386,9 @@ export async function fetchMemberLeaderboard(name: string): Promise<MemberLeader
const leaderboardRaw = await fetchMemberLeaderboardRaw(name)
const fetchLeaderboardPlayer = async(i: memberRawLeaderboardItem): Promise<MemberLeaderboardItem> => {
+ const player = await cached.fetchBasicPlayer(i.uuid)
return {
- player: await cached.fetchBasicPlayer(i.uuid),
+ player,
profileUuid: i.profile,
value: i.value
}
@@ -408,9 +411,12 @@ export async function fetchProfileLeaderboard(name: string): Promise<ProfileLead
const leaderboardRaw = await fetchProfileLeaderboardRaw(name)
const fetchLeaderboardProfile = async(i: profileRawLeaderboardItem): Promise<ProfileLeaderboardItem> => {
- const players = []
- for (const playerUuid of i.players)
- players.push(await cached.fetchBasicPlayer(playerUuid))
+ const players: CleanPlayer[] = []
+ for (const playerUuid of i.players) {
+ const player = await cached.fetchBasicPlayer(playerUuid)
+ if (player)
+ players.push(player)
+ }
return {
players: players,
profileUuid: i.uuid,
@@ -445,17 +451,26 @@ export async function fetchLeaderboard(name: string): Promise<MemberLeaderboard|
return leaderboard
}
+interface LeaderboardSpot {
+ name: string
+ positionIndex: number
+ value: number
+ unit: string | null
+}
+
/** Get the leaderboard positions a member is on. This may take a while depending on whether stuff is cached */
-export async function fetchMemberLeaderboardSpots(player: string, profile: string) {
+export async function fetchMemberLeaderboardSpots(player: string, profile: string): Promise<LeaderboardSpot[] | null> {
const fullProfile = await cached.fetchProfile(player, profile)
+ if (!fullProfile) return null
const fullMember = fullProfile.members.find(m => m.username.toLowerCase() === player.toLowerCase() || m.uuid === player)
+ if (!fullMember) return null
// update the leaderboard positions for the member
await updateDatabaseMember(fullMember, fullProfile)
const applicableAttributes = await getApplicableMemberLeaderboardAttributes(fullMember)
- const memberLeaderboardSpots = []
+ const memberLeaderboardSpots: LeaderboardSpot[] = []
for (const leaderboardName in applicableAttributes) {
const leaderboard = await fetchMemberLeaderboardRaw(leaderboardName)
@@ -472,7 +487,7 @@ export async function fetchMemberLeaderboardSpots(player: string, profile: strin
return memberLeaderboardSpots
}
-async function getLeaderboardRequirement(name: string, leaderboardType: 'member' | 'profile'): Promise<number> {
+async function getLeaderboardRequirement(name: string, leaderboardType: 'member' | 'profile'): Promise<number | null> {
let leaderboard: memberRawLeaderboardItem[] | profileRawLeaderboardItem[]
if (leaderboardType === 'member')
leaderboard = await fetchMemberLeaderboardRaw(name)
@@ -480,8 +495,8 @@ async function getLeaderboardRequirement(name: string, leaderboardType: 'member'
leaderboard = await fetchProfileLeaderboardRaw(name)
// if there's more than 100 items, return the 100th. if there's less, return null
- if (leaderboard.length >= leaderboardMax)
- return leaderboard[leaderboardMax - 1].value
+ if (leaderboard!.length >= leaderboardMax)
+ return leaderboard![leaderboardMax - 1].value
else
return null
}
@@ -504,7 +519,7 @@ async function getApplicableMemberLeaderboardAttributes(member: CleanMember): Pr
let leaderboardsCount: number = Object.keys(applicableAttributes).length
- const leaderboardsCountRequirement: number = await getLeaderboardRequirement('leaderboards_count', 'member')
+ const leaderboardsCountRequirement: number | null = await getLeaderboardRequirement('leaderboards_count', 'member')
if (
(leaderboardsCountRequirement === null)
@@ -534,11 +549,11 @@ async function getApplicableProfileLeaderboardAttributes(profile: CleanFullProfi
let leaderboardsCount: number = Object.keys(applicableAttributes).length
- const leaderboardsCountRequirement: number = await getLeaderboardRequirement('leaderboards_count', 'member')
+ const leaderboardsCountRequirement: number | null = await getLeaderboardRequirement('leaderboards_count', 'member')
if (
- (leaderboardsCountRequirement === null)
- || (leaderboardsCount > leaderboardsCountRequirement)
+ leaderboardsCountRequirement === null
+ || leaderboardsCount > leaderboardsCountRequirement
) {
applicableAttributes['leaderboards_count'] = leaderboardsCount
}
@@ -557,7 +572,8 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu
if (debug) console.debug('adding member to leaderboards', member.username)
- await constants.addStats(Object.keys(member.rawHypixelStats))
+ if (member.rawHypixelStats)
+ await constants.addStats(Object.keys(member.rawHypixelStats))
await constants.addCollections(member.collections.map(coll => coll.name))
await constants.addSkills(member.skills.map(skill => skill.name))
await constants.addZones(member.visited_zones.map(zone => zone.name))
@@ -738,15 +754,15 @@ export async function createSession(refreshToken: string, userData: discord.Disc
return sessionId
}
-export async function fetchSession(sessionId: string): Promise<SessionSchema> {
+export async function fetchSession(sessionId: string): Promise<SessionSchema | null> {
return await sessionsCollection?.findOne({ _id: sessionId })
}
-export async function fetchAccount(minecraftUuid: string): Promise<AccountSchema> {
+export async function fetchAccount(minecraftUuid: string): Promise<AccountSchema | null> {
return await accountsCollection?.findOne({ minecraftUuid })
}
-export async function fetchAccountFromDiscord(discordId: string): Promise<AccountSchema> {
+export async function fetchAccountFromDiscord(discordId: string): Promise<AccountSchema | null> {
return await accountsCollection?.findOne({ discordId })
}
diff --git a/src/discord.ts b/src/discord.ts
index 48146b5..fac5438 100644
--- a/src/discord.ts
+++ b/src/discord.ts
@@ -26,9 +26,13 @@ export interface DiscordUser {
mfa_enabled: boolean
}
-export async function exchangeCode(redirectUri: string, code: string): Promise<TokenResponse> {
+export async function exchangeCode(redirectUri: string, code: string): Promise<TokenResponse | null> {
const API_ENDPOINT = 'https://discord.com/api/v6'
const CLIENT_SECRET = process.env.discord_client_secret
+ if (!CLIENT_SECRET) {
+ console.error('discord_client_secret isn\'t in env, couldn\'t login with discord')
+ return null
+ }
const data = {
'client_id': DISCORD_CLIENT_ID,
'client_secret': CLIENT_SECRET,
diff --git a/src/hypixel.ts b/src/hypixel.ts
index eb66d48..d6f9661 100644
--- a/src/hypixel.ts
+++ b/src/hypixel.ts
@@ -11,7 +11,7 @@ import { CleanPlayer, cleanPlayerResponse } from './cleaners/player'
import * as cached from './hypixelCached'
import { debug } from '.'
-export type Included = 'profiles' | 'player' | 'stats' | 'inventories'
+export type Included = 'profiles' | 'player' | 'stats' | 'inventories' | undefined
// the interval at which the "last_save" parameter updates in the hypixel api, this is 3 minutes
export const saveInterval = 60 * 3 * 1000
@@ -62,7 +62,7 @@ export interface UserAny {
}
export interface CleanUser {
- player: CleanPlayer
+ player: CleanPlayer | null
profiles?: CleanProfile[]
activeProfile?: string
online?: boolean
@@ -76,24 +76,25 @@ export interface CleanUser {
* @param included lets you choose what is returned, so there's less processing required on the backend
* used inclusions: player, profiles
*/
-export async function fetchUser({ user, uuid, username }: UserAny, included: Included[]=['player'], customization?: boolean): Promise<CleanUser> {
+export async function fetchUser({ user, uuid, username }: UserAny, included: Included[]=['player'], customization?: boolean): Promise<CleanUser | null> {
if (!uuid) {
// If the uuid isn't provided, get it
- uuid = await cached.uuidFromUser(user || username)
+ if (!username && !user) return null
+ uuid = await cached.uuidFromUser((user ?? username)!)
}
- const websiteAccountPromise = customization ? fetchAccount(uuid) : null
if (!uuid) {
// the user doesn't exist.
if (debug) console.debug('error:', user, 'doesnt exist')
return null
}
+ const websiteAccountPromise = customization ? fetchAccount(uuid) : null
const includePlayers = included.includes('player')
const includeProfiles = included.includes('profiles')
- let profilesData: CleanProfile[]
- let basicProfilesData: CleanBasicProfile[]
- let playerData: CleanPlayer
+ let profilesData: CleanProfile[] | undefined
+ let basicProfilesData: CleanBasicProfile[] | undefined
+ let playerData: CleanPlayer | null = null
if (includePlayers) {
playerData = await cached.fetchPlayer(uuid)
@@ -103,31 +104,30 @@ export async function fetchUser({ user, uuid, username }: UserAny, included: Inc
if (playerData)
delete playerData.profiles
}
- if (includeProfiles) {
+ if (includeProfiles)
profilesData = await cached.fetchSkyblockProfiles(uuid)
- }
- let activeProfile: CleanProfile = null
+ let activeProfile: CleanProfile
let lastOnline: number = 0
if (includeProfiles) {
- for (const profile of profilesData) {
- const member = profile.members.find(member => member.uuid === uuid)
- if (member.last_save > lastOnline) {
+ for (const profile of profilesData!) {
+ const member = profile.members?.find(member => member.uuid === uuid)
+ if (member && member.last_save > lastOnline) {
lastOnline = member.last_save
activeProfile = profile
}
}
}
- let websiteAccount: AccountSchema = undefined
+ let websiteAccount: AccountSchema | null = null
- if (websiteAccountPromise) {
+ if (websiteAccountPromise)
websiteAccount = await websiteAccountPromise
- }
+
return {
- player: playerData ?? null,
+ player: playerData,
profiles: profilesData ?? basicProfilesData,
- activeProfile: includeProfiles ? activeProfile?.uuid : undefined,
+ activeProfile: includeProfiles ? activeProfile!?.uuid : undefined,
online: includeProfiles ? lastOnline > (Date.now() - saveInterval): undefined,
customization: websiteAccount?.customization
}
@@ -140,9 +140,9 @@ export async function fetchUser({ user, uuid, username }: UserAny, included: Inc
* @param profile A profile name or profile uuid
* @param customization Whether stuff like the user's custom background will be returned
*/
-export async function fetchMemberProfile(user: string, profile: string, customization: boolean): Promise<CleanMemberProfile> {
+export async function fetchMemberProfile(user: string, profile: string, customization: boolean): Promise<CleanMemberProfile | null> {
const playerUuid = await cached.uuidFromUser(user)
- if (!playerUuid) return
+ if (!playerUuid) return null
// we don't await the promise immediately so it can load while we do other stuff
const websiteAccountPromise = customization ? fetchAccount(playerUuid) : null
const profileUuid = await cached.fetchProfileUuid(user, profile)
@@ -153,9 +153,12 @@ export async function fetchMemberProfile(user: string, profile: string, customiz
const player = await cached.fetchPlayer(playerUuid)
+ if (!player) return null // this should never happen, but if it does just return null
+
const cleanProfile = await cached.fetchProfile(playerUuid, profileUuid) as CleanFullProfileBasicMembers
const member = cleanProfile.members.find(m => m.uuid === playerUuid)
+ if (!member) return null // this should never happen, but if it does just return null
// remove unnecessary member data
const simpleMembers: CleanBasicMember[] = cleanProfile.members.map(m => {
@@ -170,7 +173,7 @@ export async function fetchMemberProfile(user: string, profile: string, customiz
cleanProfile.members = simpleMembers
- let websiteAccount: AccountSchema = undefined
+ let websiteAccount: AccountSchema | null = null
if (websiteAccountPromise)
websiteAccount = await websiteAccountPromise
@@ -178,7 +181,7 @@ export async function fetchMemberProfile(user: string, profile: string, customiz
return {
member: {
// the profile name is in member rather than profile since they sometimes differ for each member
- profileName: cleanProfile.name,
+ profileName: cleanProfile.name!,
// add all the member data
...member,
// add all other data relating to the hypixel player, such as username, rank, etc
@@ -200,7 +203,7 @@ export async function fetchMemberProfile(user: string, profile: string, customiz
path: 'skyblock/profile',
args: { profile: profileUuid }
},
- null,
+ undefined,
{ mainMemberUuid: playerUuid }
)
@@ -223,7 +226,7 @@ export async function fetchMemberProfile(user: string, profile: string, customiz
path: 'skyblock/profile',
args: { profile: profileUuid }
},
- null,
+ undefined,
{ basic: true }
)
@@ -237,7 +240,7 @@ export async function fetchMemberProfilesUncached(playerUuid: string): Promise<C
args: {
uuid: playerUuid
}},
- null,
+ undefined,
{
// only the inventories for the main player are generated, this is for optimization purposes
mainMemberUuid: playerUuid
diff --git a/src/hypixelApi.ts b/src/hypixelApi.ts
index b3a1cae..1b57d8f 100644
--- a/src/hypixelApi.ts
+++ b/src/hypixelApi.ts
@@ -32,10 +32,10 @@ const baseHypixelAPI = 'https://api.hypixel.net'
/** Choose the best current API key */
-export function chooseApiKey(): string {
+export function chooseApiKey(): string | null {
// find the api key with the lowest amount of uses
- let bestKeyUsage: KeyUsage = null
- let bestKey: string = null
+ let bestKeyUsage: KeyUsage | null = null
+ let bestKey: string | null = null
for (let key of shuffle(apiKeys.slice())) {
const keyUsage = apiKeyUsage[key]
@@ -173,9 +173,9 @@ export async function sendApiRequest({ path, key, args }): Promise<HypixelRespon
if (fetchResponse.headers.get('ratelimit-limit'))
// remember how many uses it has
apiKeyUsage[key] = {
- remaining: parseInt(fetchResponse.headers.get('ratelimit-remaining')),
- limit: parseInt(fetchResponse.headers.get('ratelimit-limit')),
- reset: Date.now() + parseInt(fetchResponse.headers.get('ratelimit-reset')) * 1000
+ remaining: parseInt(fetchResponse.headers.get('ratelimit-remaining') ?? '0'),
+ limit: parseInt(fetchResponse.headers.get('ratelimit-limit') ?? '0'),
+ reset: Date.now() + parseInt(fetchResponse.headers.get('ratelimit-reset') ?? '0') * 1000
}
if (fetchJsonParsed.throttle) {
diff --git a/src/hypixelCached.ts b/src/hypixelCached.ts
index 92a7801..4aa96ef 100644
--- a/src/hypixelCached.ts
+++ b/src/hypixelCached.ts
@@ -81,17 +81,17 @@ function waitForCacheSet(cache: NodeCache, key?: string, value?: string): Promis
* Fetch the uuid from a user
* @param user A user can be either a uuid or a username
*/
-export async function uuidFromUser(user: string): Promise<string> {
+export async function uuidFromUser(user: string): Promise<string | undefined> {
// if the user is 32 characters long, it has to be a uuid
if (isUuid(user))
return undashUuid(user)
if (usernameCache.has(undashUuid(user))) {
// check if the uuid is a key
- const username: Promise<KeyValue> | string | null = usernameCache.get<string | Promise<KeyValue>>(undashUuid(user))
+ const username: Promise<KeyValue> | string | undefined = usernameCache.get<string | Promise<KeyValue>>(undashUuid(user))
// sometimes the username will be null, return that
- if (username === null) return null
+ if (username === null) return undefined
// if it has .then, then that means its a waitForCacheSet promise. This is done to prevent requests made while it is already requesting
if ((username as Promise<KeyValue>).then) {
@@ -136,15 +136,16 @@ export async function uuidFromUser(user: string): Promise<string> {
* Fetch the username from a user
* @param user A user can be either a uuid or a username
*/
-export async function usernameFromUser(user: string): Promise<string> {
+export async function usernameFromUser(user: string): Promise<string | null> {
if (usernameCache.has(undashUuid(user))) {
if (debug) console.debug('Cache hit! usernameFromUser', user)
- return usernameCache.get(undashUuid(user))
+ return usernameCache.get(undashUuid(user)) ?? null
}
if (debug) console.debug('Cache miss: usernameFromUser', user)
let { uuid, username } = await mojang.profileFromUser(user)
+ if (!uuid) return null
uuid = undashUuid(uuid)
usernameCache.set(uuid, username)
return username
@@ -152,12 +153,12 @@ export async function usernameFromUser(user: string): Promise<string> {
let fetchingPlayers: Set<string> = new Set()
-export async function fetchPlayer(user: string): Promise<CleanPlayer> {
+export async function fetchPlayer(user: string): Promise<CleanPlayer | null> {
const playerUuid = await uuidFromUser(user)
-
+ if (!playerUuid) return null
if (playerCache.has(playerUuid))
- return playerCache.get(playerUuid)
+ return playerCache.get(playerUuid)!
// if it's already in the process of fetching, check every 100ms until it's not fetching the player anymore and fetch it again, since it'll be cached now
if (fetchingPlayers.has(playerUuid)) {
@@ -176,7 +177,7 @@ export async function fetchPlayer(user: string): Promise<CleanPlayer> {
fetchingPlayers.delete(playerUuid)
- if (!cleanPlayer) return
+ if (!cleanPlayer) return null
// clone in case it gets modified somehow later
playerCache.set(playerUuid, cleanPlayer)
@@ -190,14 +191,19 @@ export async function fetchPlayer(user: string): Promise<CleanPlayer> {
}
/** Fetch a player without their profiles. This is heavily cached. */
-export async function fetchBasicPlayer(user: string): Promise<CleanPlayer> {
+export async function fetchBasicPlayer(user: string): Promise<CleanPlayer | null> {
const playerUuid = await uuidFromUser(user)
+ if (!playerUuid) return null
+
if (basicPlayerCache.has(playerUuid))
- return basicPlayerCache.get(playerUuid)
+ return basicPlayerCache.get(playerUuid)!
const player = await fetchPlayer(playerUuid)
- if (!player) console.debug('no player? this should never happen', user, playerUuid)
+ if (!player) {
+ console.debug('no player? this should never happen', user, playerUuid)
+ return null
+ }
delete player.profiles
return player
@@ -206,7 +212,7 @@ export async function fetchBasicPlayer(user: string): Promise<CleanPlayer> {
export async function fetchSkyblockProfiles(playerUuid: string): Promise<CleanProfile[]> {
if (profilesCache.has(playerUuid)) {
if (debug) console.debug('Cache hit! fetchSkyblockProfiles', playerUuid)
- return profilesCache.get(playerUuid)
+ return profilesCache.get(playerUuid)!
}
if (debug) console.debug('Cache miss: fetchSkyblockProfiles', playerUuid)
@@ -220,7 +226,7 @@ export async function fetchSkyblockProfiles(playerUuid: string): Promise<CleanPr
const basicProfile: CleanProfile = {
name: profile.name,
uuid: profile.uuid,
- members: profile.members.map(m => {
+ members: profile.members?.map(m => {
return {
uuid: m.uuid,
username: m.username,
@@ -240,14 +246,14 @@ export async function fetchSkyblockProfiles(playerUuid: string): Promise<CleanPr
}
/** Fetch an array of `BasicProfile`s */
-async function fetchBasicProfiles(user: string): Promise<CleanBasicProfile[]> {
+async function fetchBasicProfiles(user: string): Promise<CleanBasicProfile[] | null> {
const playerUuid = await uuidFromUser(user)
- if (!playerUuid) return // invalid player, just return
+ if (!playerUuid) return null // invalid player, just return
if (basicProfilesCache.has(playerUuid)) {
if (debug) console.debug('Cache hit! fetchBasicProfiles', playerUuid)
- return basicProfilesCache.get(playerUuid)
+ return basicProfilesCache.get(playerUuid)!
}
if (debug) console.debug('Cache miss: fetchBasicProfiles', user)
@@ -259,6 +265,7 @@ async function fetchBasicProfiles(user: string): Promise<CleanBasicProfile[]> {
}
const profiles = player.profiles
basicProfilesCache.set(playerUuid, profiles)
+ if (!profiles) return null
// cache the profile names and uuids to profileNameCache because we can
for (const profile of profiles)
@@ -272,7 +279,7 @@ async function fetchBasicProfiles(user: string): Promise<CleanBasicProfile[]> {
* @param user A username or uuid
* @param profile A profile name or profile uuid
*/
-export async function fetchProfileUuid(user: string, profile: string): Promise<string> {
+export async function fetchProfileUuid(user: string, profile: string): Promise<string | null> {
// if a profile wasn't provided, return
if (!profile) {
if (debug) console.debug('no profile provided?', user, profile)
@@ -282,12 +289,12 @@ export async function fetchProfileUuid(user: string, profile: string): Promise<s
if (debug) console.debug('Cache miss: fetchProfileUuid', user, profile)
const profiles = await fetchBasicProfiles(user)
- if (!profiles) return // user probably doesnt exist
+ if (!profiles) return null // user probably doesnt exist
const profileUuid = undashUuid(profile)
for (const p of profiles) {
- if (p.name.toLowerCase() === profileUuid.toLowerCase())
+ if (p.name?.toLowerCase() === profileUuid.toLowerCase())
return undashUuid(p.uuid)
else if (undashUuid(p.uuid) === undashUuid(profileUuid))
return undashUuid(p.uuid)
@@ -300,8 +307,9 @@ export async function fetchProfileUuid(user: string, profile: string): Promise<s
* @param user A username or uuid
* @param profile A profile name or profile uuid
*/
-export async function fetchProfile(user: string, profile: string): Promise<CleanFullProfile> {
+export async function fetchProfile(user: string, profile: string): Promise<CleanFullProfile | null> {
const playerUuid = await uuidFromUser(user)
+ if (!playerUuid) return null
const profileUuid = await fetchProfileUuid(playerUuid, profile)
if (!profileUuid) return null
@@ -309,13 +317,15 @@ export async function fetchProfile(user: string, profile: string): Promise<Clean
if (profileCache.has(profileUuid)) {
// we have the profile cached, return it :)
if (debug) console.debug('Cache hit! fetchProfile', profileUuid)
- return profileCache.get(profileUuid)
+ return profileCache.get(profileUuid)!
}
if (debug) console.debug('Cache miss: fetchProfile', user, profile)
const profileName = await fetchProfileName(user, profile)
+ if (!profileName) return null // uhh this should never happen but if it does just return null
+
const cleanProfile: CleanFullProfile = await hypixel.fetchMemberProfileUncached(playerUuid, profileUuid)
// we know the name from fetchProfileName, so set it here
@@ -330,11 +340,12 @@ export async function fetchProfile(user: string, profile: string): Promise<Clean
* Fetch a CleanProfile from the uuid
* @param profileUuid A profile name or profile uuid
*/
-export async function fetchBasicProfileFromUuid(profileUuid: string): Promise<CleanProfile> {
+export async function fetchBasicProfileFromUuid(profileUuid: string): Promise<CleanProfile | undefined> {
if (profileCache.has(profileUuid)) {
// we have the profile cached, return it :)
if (debug) console.debug('Cache hit! fetchBasicProfileFromUuid', profileUuid)
- const profile: CleanFullProfile = profileCache.get(profileUuid)
+ const profile: CleanFullProfile | undefined = profileCache.get(profileUuid)
+ if (!profile) return undefined
return {
uuid: profile.uuid,
members: profile.members.map(m => ({
@@ -357,26 +368,32 @@ export async function fetchBasicProfileFromUuid(profileUuid: string): Promise<Cl
* @param user A player uuid or username
* @param profile A profile uuid or name
*/
-export async function fetchProfileName(user: string, profile: string): Promise<string> {
+export async function fetchProfileName(user: string, profile: string): Promise<string | null> {
// we're fetching the profile and player uuid again in case we were given a name, but it's cached so it's not much of a problem
const profileUuid = await fetchProfileUuid(user, profile)
- if (!profileUuid)
- return null
+ if (!profileUuid) return null
+
const playerUuid = await uuidFromUser(user)
+ if (!playerUuid) return null
+
if (profileNameCache.has(`${playerUuid}.${profileUuid}`)) {
// Return the profile name if it's cached
if (debug) console.debug('Cache hit! fetchProfileName', profileUuid)
- return profileNameCache.get(`${playerUuid}.${profileUuid}`)
+ return profileNameCache.get!(`${playerUuid}.${profileUuid}`) ?? null
}
if (debug) console.debug('Cache miss: fetchProfileName', user, profile)
const basicProfiles = await fetchBasicProfiles(playerUuid)
- let profileName
+
+ if (!basicProfiles) return null
+
+ let profileName: string | null = null
+
for (const basicProfile of basicProfiles)
if (basicProfile.uuid === playerUuid)
- profileName = basicProfile.name
+ profileName = basicProfile.name ?? null
profileNameCache.set(`${playerUuid}.${profileUuid}`, profileName)
return profileName
diff --git a/src/index.ts b/src/index.ts
index 4f4ad00..647038f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -132,7 +132,12 @@ app.get('/constants', async(req, res) => {
app.post('/accounts/createsession', async(req, res) => {
try {
const { code } = req.body
- const { access_token: accessToken, refresh_token: refreshToken } = await discord.exchangeCode(`${mainSiteUrl}/loggedin`, code)
+ const codeExchange = await discord.exchangeCode(`${mainSiteUrl}/loggedin`, code)
+ if (!codeExchange) {
+ res.json({ ok: false, error: 'discord_client_secret isn\'t in env' })
+ return
+ }
+ const { access_token: accessToken, refresh_token: refreshToken } = codeExchange
if (!accessToken)
// access token is invalid :(
return res.json({ ok: false })
@@ -148,6 +153,8 @@ app.post('/accounts/session', async(req, res) => {
try {
const { uuid } = req.body
const session = await fetchSession(uuid)
+ if (!session)
+ return res.json({ ok: false })
const account = await fetchAccountFromDiscord(session.discord_user.id)
res.json({ session, account })
} catch (err) {
diff --git a/src/mojang.ts b/src/mojang.ts
index e22c8d8..84c8c7d 100644
--- a/src/mojang.ts
+++ b/src/mojang.ts
@@ -14,9 +14,8 @@ const httpsAgent = new Agent({
interface MojangApiResponse {
/** These uuids are already undashed */
- uuid: string
-
- username: string
+ uuid: string | null
+ username: string | null
}
/**
@@ -73,7 +72,7 @@ export async function profileFromUsername(username: string): Promise<MojangApiRe
return await profileFromUsername(username)
}
- let data = null
+ let data: any = null
const rawData = await fetchResponse.text()
try {
data = JSON.parse(rawData)
diff --git a/src/util.ts b/src/util.ts
index 67cf4b5..45839cc 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -61,7 +61,7 @@ export const minecraftColorCodes: { [ key: string ]: string } = {
* For example: blue -> 9
* @param colorName The name of the color (blue, red, aqua, etc)
*/
-export function colorCodeFromName(colorName: string): string {
+export function colorCodeFromName(colorName: string): string | undefined {
const hexColor = minecraftColorCodes[colorName.toLowerCase()]
for (const key in minecraftColorCodes) {
const value = minecraftColorCodes[key]
diff --git a/tsconfig.json b/tsconfig.json
index 43f0b28..d54082d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,8 @@
"lib": ["esnext", "dom", "DOM.Iterable"],
"target": "es2019",
"esModuleInterop": true,
- "outDir": "build"
+ "outDir": "build",
+ "strictNullChecks": true
},
"include": ["src"],
"exclude": ["**/node_modules"],