mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-11 00:38:28 +03:00
Merge pull request #31 from TeamNewGuys/user-can-view-profile
Added Profile view
This commit is contained in:
+4
-1
@@ -18,7 +18,10 @@
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab"
|
||||
"tab",
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
|
||||
@@ -3,3 +3,4 @@ node_modules
|
||||
.vscode
|
||||
yarn-error.log
|
||||
.env
|
||||
conf.json5
|
||||
|
||||
+3
-1
@@ -12,6 +12,7 @@
|
||||
"@types/express-serve-static-core": "^4.17.24",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/json5": "^2.2.0",
|
||||
"@types/lodash": "^4.14.172",
|
||||
"@types/node": "^16.7.10",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.1",
|
||||
"@typescript-eslint/parser": "^4.29.1",
|
||||
@@ -24,12 +25,13 @@
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.5.0",
|
||||
"@discordjs/rest": "^0.1.0-canary.0",
|
||||
"axios": "^0.21.4",
|
||||
"deezer-api-ts": "^1.2.1",
|
||||
"discord.js": "^13.1.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"json5": "^2.2.0",
|
||||
"node-fetch": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"prudence": "^0.9.6",
|
||||
"songlink-api": "^1.0.0",
|
||||
"tachi-common": "^0.2.1",
|
||||
|
||||
+4
-2
@@ -1,5 +1,4 @@
|
||||
/** @TODO Potentially re-work this to only run on PROD env, handy for DEV env! **/
|
||||
/** @deprecated replace with database call & cache */
|
||||
export const allowedChannels = ["876321394005254214"];
|
||||
|
||||
export enum LoggerLayers {
|
||||
@@ -8,5 +7,8 @@ export enum LoggerLayers {
|
||||
server = "server",
|
||||
serverAuth = "serverAuth",
|
||||
botConfigSetup = "botConfigSetup",
|
||||
tachiFetch = "tachiFetch"
|
||||
tachiFetch = "tachiFetch",
|
||||
profile = "profile",
|
||||
selectInteractionHandler = "selectInteractionHandler",
|
||||
buildProfileEmbed = "buildProfileEmbed"
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,10 +1,11 @@
|
||||
import { Client, Intents } from "discord.js";
|
||||
import { handleIsCommand } from "./interactionHandlers/command/handleIsCommand";
|
||||
import { handleIsSelectMenu } from "./interactionHandlers/selectMenu/handleIsSelectMenu";
|
||||
import { ProcessEnv } from "./setup";
|
||||
import { LoggerLayers } from "./config";
|
||||
import { registerSlashCommands, slashCommands, SlashCommand } from "./slashCommands/register";
|
||||
import { registerSlashCommands } from "./slashCommands/register";
|
||||
import { createLayeredLogger } from "./utils/logger";
|
||||
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.client);
|
||||
|
||||
const client = new Client({
|
||||
@@ -12,16 +13,13 @@ const client = new Client({
|
||||
});
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
try {
|
||||
const command = slashCommands.find((command: SlashCommand) => {
|
||||
return command.info.name === interaction.commandName;
|
||||
});
|
||||
if (interaction.isSelectMenu()) {
|
||||
return await handleIsSelectMenu(interaction);
|
||||
}
|
||||
|
||||
if (command && command.exec) {
|
||||
logger.info(`Running ${command.info.name} interaction`);
|
||||
command.exec(interaction);
|
||||
if (interaction.isCommand()) {
|
||||
return await handleIsCommand(interaction);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Failed to run interaction");
|
||||
@@ -38,7 +36,9 @@ client.on("interactionCreate", async (interaction) => {
|
||||
await client.login(ProcessEnv.DISCORD_TOKEN);
|
||||
logger.info(`Logged in successfully to ${client.guilds.cache.size} guilds`);
|
||||
logger.info(
|
||||
`Invite URL: https://discord.com/api/oauth2/authorize?client_id=${client.application!.id}&permissions=8&scope=applications.commands%20bot`
|
||||
`Invite URL: https://discord.com/api/oauth2/authorize?client_id=${
|
||||
client.application!.id
|
||||
}&permissions=8&scope=applications.commands%20bot`
|
||||
);
|
||||
|
||||
await registerSlashCommands(client);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Interaction } from "discord.js";
|
||||
import { LoggerLayers } from "../../config";
|
||||
import { SlashCommand, slashCommands } from "../../slashCommands/register";
|
||||
import { createLayeredLogger } from "../../utils/logger";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.selectInteractionHandler);
|
||||
|
||||
export const handleIsCommand = async (interaction: Interaction): Promise<void> => {
|
||||
try {
|
||||
/** Rechecking required to enforce types */
|
||||
if (interaction.isCommand()) {
|
||||
const command = slashCommands.find((command: SlashCommand) => {
|
||||
return command.info.name === interaction.commandName;
|
||||
});
|
||||
|
||||
if (command && command.exec) {
|
||||
logger.info(`Running ${command.info.name} interaction`);
|
||||
command.exec(interaction);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Failed to handle isCommand interaction");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Interaction } from "discord.js";
|
||||
import { LoggerLayers } from "../../config";
|
||||
import { buildProfileIntractable } from "../../profile/buildProfileEmbed";
|
||||
import { createLayeredLogger } from "../../utils/logger";
|
||||
import { stringToSimpleGameType } from "../../utils/utils";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.selectInteractionHandler);
|
||||
|
||||
export const handleIsSelectMenu = async (interaction: Interaction): Promise<void> => {
|
||||
try {
|
||||
/** Rechecking required to enforce types */
|
||||
if (interaction.isSelectMenu()) {
|
||||
/** @TODO Split this out when we add more dropdowns */
|
||||
const userId = interaction.customId.split(":")[1];
|
||||
await interaction.update(
|
||||
await buildProfileIntractable(userId, stringToSimpleGameType(interaction.values[0]))
|
||||
);
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
logger.error("Failed to handle isSelectMenu interaction");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import { InteractionReplyOptions, MessageActionRow, MessageEmbed, MessagePayload, MessageSelectMenu } from "discord.js";
|
||||
import { Game, UserGameStats, IDStrings, PublicUserDocument, UGSRatingsLookup } from "tachi-common";
|
||||
import { find } from "lodash";
|
||||
import { LoggerLayers } from "../config";
|
||||
import { TachiServerV1Get } from "../utils/fetch-tachi";
|
||||
import { createLayeredLogger } from "../utils/logger";
|
||||
import {
|
||||
formatGameScoreRating,
|
||||
formatGameWrapper,
|
||||
getPfpUrl,
|
||||
prettyRatingString,
|
||||
SimpleGameType,
|
||||
simpleGameTypeToString
|
||||
} from "../utils/utils";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.buildProfileEmbed);
|
||||
|
||||
const pullRatings = <I extends IDStrings = IDStrings>(
|
||||
gpt: SimpleGameType<Game>,
|
||||
ratings: Partial<Record<UGSRatingsLookup[I], number>>
|
||||
): string[] => {
|
||||
const allRatings: string[] = [];
|
||||
const RatingKeys = <UGSRatingsLookup[I][]>Object.keys(ratings);
|
||||
|
||||
RatingKeys.forEach((rating) => {
|
||||
const ratingValue: string = formatGameScoreRating(gpt, rating, ratings[rating] || 0);
|
||||
allRatings.push(`${prettyRatingString(rating)}: ${ratingValue}`);
|
||||
});
|
||||
|
||||
return allRatings;
|
||||
};
|
||||
|
||||
export const fetchUserDetails = async (userId: number): Promise<PublicUserDocument> => {
|
||||
try {
|
||||
logger.info(`Fetching public user document for ${userId}`);
|
||||
const data = (await TachiServerV1Get<PublicUserDocument>(`/users/${userId}`)).body;
|
||||
if (data) {
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(`Could not find public user document for ${userId}`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Unable to fetch user details");
|
||||
throw new Error("Unable to fetch user details");
|
||||
}
|
||||
};
|
||||
|
||||
export const buildProfileEmbed = async (data: UserGameStats[], game?: SimpleGameType<Game>): Promise<MessageEmbed> => {
|
||||
try {
|
||||
logger.info(`Building profile embed${game ? `for ${formatGameWrapper(game)}` : ""}`);
|
||||
const embed = new MessageEmbed().setColor("#cc527a");
|
||||
|
||||
const userId = data[0].userID;
|
||||
const userDetails = await fetchUserDetails(userId);
|
||||
const pfp = userDetails.customPfp
|
||||
? getPfpUrl(userId)
|
||||
: "https://cdn.mos.cms.futurecdn.net/mrArzwHcNuQbRwbEmuiwdJ.jpg";
|
||||
embed.setTitle(`${userDetails.username}'s Profile`);
|
||||
embed.setThumbnail(pfp);
|
||||
embed.setAuthor(`@${userDetails.username}`, pfp);
|
||||
|
||||
logger.info(`Embed is for ${userDetails.username}`);
|
||||
|
||||
if (game) {
|
||||
const specificData: UserGameStats | undefined = find(
|
||||
data,
|
||||
(item) => simpleGameTypeToString(game) === `${item.game}:${item.playtype}`
|
||||
);
|
||||
if (specificData) {
|
||||
embed.addField(
|
||||
`Stats for: ${formatGameWrapper(game)}`,
|
||||
`${pullRatings(game, specificData.ratings).join("\n")}`
|
||||
);
|
||||
} else {
|
||||
embed.addField("Select a game to see stats", `No stats for ${formatGameWrapper(game)}`);
|
||||
}
|
||||
} else {
|
||||
embed.addField("Select a game to see stats", "\u200B");
|
||||
}
|
||||
|
||||
return embed;
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
throw new Error("Unable to build profile embed");
|
||||
}
|
||||
};
|
||||
|
||||
export const buildProfileIntractable = async (
|
||||
userId: string,
|
||||
game?: SimpleGameType<Game>
|
||||
): Promise<InteractionReplyOptions | MessagePayload> => {
|
||||
try {
|
||||
const data = (await TachiServerV1Get<UserGameStats[]>(`/users/${userId}/game-stats`))?.body;
|
||||
logger.verbose(data);
|
||||
|
||||
/** @TODO Verify we got valid data here! */
|
||||
if (data) {
|
||||
const dropdown = new MessageActionRow().addComponents(
|
||||
new MessageSelectMenu()
|
||||
.setCustomId(`SelectGameForProfile:${userId}`)
|
||||
.setPlaceholder("Browse By Game")
|
||||
.addOptions(
|
||||
data.map((_game) => {
|
||||
return {
|
||||
label: `${formatGameWrapper(_game)}`,
|
||||
value: `${_game.game}:${_game.playtype}`
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return { embeds: [await buildProfileEmbed(data, game)], components: [dropdown] };
|
||||
}
|
||||
|
||||
throw new Error(`No data found for user ${userId}`);
|
||||
} catch (e) {
|
||||
logger.error("Unable to build profile interactable");
|
||||
throw new Error("Unable to build profile interactable");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import { IDStrings } from "tachi-common";
|
||||
import { LoggerLayers } from "../config";
|
||||
import { BotConfig } from "../setup";
|
||||
import { createLayeredLogger } from "../utils/logger";
|
||||
import { stringToSimpleGameType } from "../utils/utils";
|
||||
import { buildProfileIntractable } from "./buildProfileEmbed";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.profile);
|
||||
|
||||
export const getProfileByName = async (interaction: CommandInteraction): Promise<void> => {
|
||||
try {
|
||||
/** @TODO this can be an @User, userId or userName!!! Handle this at some point */
|
||||
const userId = interaction.options.getString("user", false) || "TODO FALLBACK TO SELF";
|
||||
logger.info(`Fetching user ${userId}`);
|
||||
|
||||
const optionalGameOption = <IDStrings | undefined>interaction.options.getString("game", false);
|
||||
const game = optionalGameOption ? stringToSimpleGameType(optionalGameOption) : undefined;
|
||||
|
||||
if (userId) {
|
||||
await interaction.reply(await buildProfileIntractable(userId, game));
|
||||
} else {
|
||||
await interaction.reply("No user data found for user");
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
await interaction.reply(BotConfig.GENERIC_ERROR_MESSAGE);
|
||||
}
|
||||
};
|
||||
+13
-7
@@ -18,6 +18,7 @@ export interface BotConfig {
|
||||
SERVER_PORT: number;
|
||||
TACHI_SERVER_LOCATION: string;
|
||||
OUR_URL: string;
|
||||
GENERIC_ERROR_MESSAGE: string;
|
||||
}
|
||||
|
||||
function ParseBotConfig(fileLoc = "conf.json5"): BotConfig {
|
||||
@@ -36,6 +37,7 @@ function ParseBotConfig(fileLoc = "conf.json5"): BotConfig {
|
||||
SERVER_PORT: Prudence.isPositiveNonZeroInteger,
|
||||
TACHI_SERVER_LOCATION: "string",
|
||||
OUR_URL: "string",
|
||||
GENERIC_ERROR_MESSAGE: "string"
|
||||
});
|
||||
|
||||
if (err) {
|
||||
@@ -55,12 +57,17 @@ export interface ProcessEnvironment {
|
||||
}
|
||||
|
||||
function ParseEnvVars() {
|
||||
const err = Prudence(process.env, {
|
||||
ENV: "string",
|
||||
DISCORD_TOKEN: "string",
|
||||
BOT_CLIENT_SECRET: "string",
|
||||
BOT_CLIENT_ID: "string"
|
||||
}, {}, { allowExcessKeys: true });
|
||||
const err = Prudence(
|
||||
process.env,
|
||||
{
|
||||
ENV: "string",
|
||||
DISCORD_TOKEN: "string",
|
||||
BOT_CLIENT_SECRET: "string",
|
||||
BOT_CLIENT_ID: "string"
|
||||
},
|
||||
{},
|
||||
{ allowExcessKeys: true }
|
||||
);
|
||||
|
||||
if (err) {
|
||||
logger.error(FormatPrError(err, "Invalid environment. Cannot safely boot."));
|
||||
@@ -73,5 +80,4 @@ function ParseEnvVars() {
|
||||
|
||||
export const BotConfig: BotConfig = ParseBotConfig(process.env.CONF_JSON5_LOCATION);
|
||||
|
||||
|
||||
export const ProcessEnv = ParseEnvVars();
|
||||
|
||||
@@ -3,10 +3,12 @@ import { REST } from "@discordjs/rest";
|
||||
import { APIApplicationCommandOption } from "discord-api-types";
|
||||
import { Routes } from "discord-api-types/v9";
|
||||
import { Client, CommandInteraction } from "discord.js";
|
||||
import { getProfileByName } from "../profile/fetch";
|
||||
import { ProcessEnv } from "../setup";
|
||||
import { LoggerLayers } from "../config";
|
||||
import { help } from "../commands/help/help";
|
||||
import { createLayeredLogger } from "../utils/logger";
|
||||
import { gamesToChoicesObject } from "../utils/utils";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.slashCommands);
|
||||
|
||||
@@ -23,6 +25,18 @@ export const slashCommands: SlashCommand[] = [
|
||||
{
|
||||
info: new SlashCommandBuilder().setName("help").setDescription("Shows information about this bot").toJSON(),
|
||||
exec: async (interaction: CommandInteraction) => await help(interaction)
|
||||
},
|
||||
{
|
||||
info: new SlashCommandBuilder()
|
||||
.setName("profile")
|
||||
.setDescription("Displays a Kamaitachi Profile")
|
||||
/** @TODO Make this optional once we have a fallback */
|
||||
.addStringOption((option) => option.setName("user").setDescription("The users id").setRequired(true))
|
||||
.addStringOption((option) =>
|
||||
option.setName("game").setDescription("The Game").setRequired(false).addChoices(gamesToChoicesObject())
|
||||
)
|
||||
.toJSON(),
|
||||
exec: async (interaction: CommandInteraction) => await getProfileByName(interaction)
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import axios from "axios";
|
||||
import { LoggerLayers } from "../config";
|
||||
import { BotConfig } from "../setup";
|
||||
import { integer, SuccessfulAPIResponse, UnsuccessfulAPIResponse } from "tachi-common";
|
||||
@@ -5,14 +6,16 @@ import { createLayeredLogger } from "./logger";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.tachiFetch);
|
||||
|
||||
export type APIResponse<T> = (SuccessfulAPIResponse<T> | UnsuccessfulAPIResponse) & { statusCode: integer };
|
||||
export type APIResponse<T> = (SuccessfulAPIResponse<T> | (UnsuccessfulAPIResponse & { body: null })) & {
|
||||
statusCode: integer;
|
||||
};
|
||||
|
||||
export enum RequestTypes {
|
||||
GET = "GET",
|
||||
POST = "POST",
|
||||
PATCH = "PATCH",
|
||||
PUT = "PUT",
|
||||
DELETE = "DELETE",
|
||||
DELETE = "DELETE"
|
||||
// HEAD, OPTIONS not used by tachi-server anywhere.
|
||||
}
|
||||
|
||||
@@ -24,22 +27,26 @@ export enum RequestTypes {
|
||||
* @param body - Optionally, provide some content for the request body.
|
||||
* @param T - A generic that asserts the type of the response contents. Defaults to unknown.
|
||||
*/
|
||||
export async function TachiServerV1Request<T>(method: Exclude<RequestTypes, RequestTypes.GET>, url: string, body: unknown = {}): Promise<APIResponse<T>> {
|
||||
export async function TachiServerV1Request<T>(
|
||||
method: Exclude<RequestTypes, RequestTypes.GET>,
|
||||
url: string,
|
||||
body: unknown = {}
|
||||
): Promise<APIResponse<T>> {
|
||||
const realUrl = PrependTachiUrl(url, "1");
|
||||
const loggerUrl = `${method} ${realUrl}`;
|
||||
|
||||
logger.debug(`Making a request to ${loggerUrl}.`);
|
||||
|
||||
try {
|
||||
const res = await fetch(realUrl, {
|
||||
const res = await axios(realUrl, {
|
||||
method,
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
data: JSON.stringify(body)
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
const json = (await res.data) as APIResponse<T>;
|
||||
const contents = { ...json, statusCode: res.status };
|
||||
|
||||
LogRequestResult(loggerUrl, contents);
|
||||
@@ -60,14 +67,17 @@ export async function TachiServerV1Request<T>(method: Exclude<RequestTypes, Requ
|
||||
* @param params - Any URL params for this request.
|
||||
* @param T - A generic that asserts the type of the response contents. Defaults to unknown.
|
||||
*/
|
||||
export async function TachiServerV1Get<T = unknown>(url: string, params: Record<string, string> = {}): Promise<APIResponse<T>> {
|
||||
export async function TachiServerV1Get<T = unknown>(
|
||||
url: string,
|
||||
params: Record<string, string> = {}
|
||||
): Promise<APIResponse<T>> {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(params);
|
||||
|
||||
const realUrl = `${PrependTachiUrl(url, "1")}?${urlParams.toString()}`;
|
||||
|
||||
const res = await fetch(realUrl);
|
||||
const json = await res.json();
|
||||
const res = await axios(realUrl, { method: RequestTypes.GET });
|
||||
const json = (await res.data) as APIResponse<T>;
|
||||
const contents = { ...json, statusCode: res.status };
|
||||
|
||||
LogRequestResult(`GET ${realUrl}`, contents);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { FormatGame, Game, Playtypes, IDStrings, UGSRatingsLookup, GetGamePTConfig } from "tachi-common";
|
||||
import { ProcessEnv } from "../setup";
|
||||
import { PrependTachiUrl } from "./fetch-tachi";
|
||||
|
||||
export interface SimpleGameType<T extends Game> {
|
||||
game: T;
|
||||
playtype: Playtypes[T];
|
||||
}
|
||||
|
||||
export const simpleGameTypeToString = <T extends Game>(gpt: SimpleGameType<T>): IDStrings =>
|
||||
<IDStrings>`${gpt.game}:${gpt.playtype}`;
|
||||
|
||||
export const stringToSimpleGameType = (gptString: string): SimpleGameType<Game> => {
|
||||
return { game: <Game>gptString.split(":")[0], playtype: <Playtypes[never]>gptString.split(":")[1] };
|
||||
};
|
||||
|
||||
export const formatGameWrapper = (gpt: SimpleGameType<Game>): string => FormatGame(gpt.game, gpt.playtype);
|
||||
|
||||
export const capitalise = (s: string): string => (s && s[0].toUpperCase() + s.slice(1)) || "";
|
||||
|
||||
export const prettyRatingString = <I extends IDStrings = IDStrings>(rating: UGSRatingsLookup[I]): string => {
|
||||
return capitalise(rating);
|
||||
};
|
||||
|
||||
export const getPfpUrl = (userId: number): string => {
|
||||
if (ProcessEnv.ENV !== "prod") {
|
||||
return `https://kamaitachi.xyz/static/images/users/${userId}-pfp.png`;
|
||||
}
|
||||
return PrependTachiUrl(`/users/${userId}/pfp`);
|
||||
};
|
||||
|
||||
export const gameIdentifierStrings = [
|
||||
"iidx:SP",
|
||||
"iidx:DP",
|
||||
"sdvx:Single",
|
||||
"usc:Single",
|
||||
"ddr:SP",
|
||||
"ddr:DP",
|
||||
"maimai:Single",
|
||||
"museca:Single",
|
||||
"bms:7K",
|
||||
"bms:14K",
|
||||
"chunithm:Single",
|
||||
"gitadora:Gita",
|
||||
"gitadora:Dora"
|
||||
];
|
||||
export const gamesToChoicesObject = (): [name: string, value: string][] => {
|
||||
return gameIdentifierStrings.map((identifier) => {
|
||||
return [formatGameWrapper(stringToSimpleGameType(identifier)), identifier];
|
||||
});
|
||||
};
|
||||
|
||||
export const formatGameScoreRating = <I extends IDStrings = IDStrings>(
|
||||
gpt: SimpleGameType<Game>,
|
||||
ratingName: UGSRatingsLookup[I],
|
||||
value: number
|
||||
): string => {
|
||||
const gptConfig = GetGamePTConfig(gpt.game, gpt.playtype);
|
||||
const val = gptConfig.profileRatingAlgFormatters[ratingName];
|
||||
if (val) {
|
||||
return val(value);
|
||||
}
|
||||
return value.toFixed(2);
|
||||
};
|
||||
+18
-26
@@ -862,6 +862,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@^4.14.172":
|
||||
version "4.14.172"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
||||
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
|
||||
|
||||
"@types/mime@^1":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||
@@ -1282,6 +1287,13 @@ axios@^0.19.0:
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
|
||||
axios@^0.21.4:
|
||||
version "0.21.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
babel-jest@^27.1.0:
|
||||
version "27.1.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.1.0.tgz#e96ca04554fd32274439869e2b6d24de9d91bc4e"
|
||||
@@ -1846,11 +1858,6 @@ dashdash@^1.12.0:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
data-uri-to-buffer@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
|
||||
integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==
|
||||
|
||||
data-urls@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
|
||||
@@ -2397,13 +2404,6 @@ fecha@^4.2.0:
|
||||
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz"
|
||||
integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==
|
||||
|
||||
fetch-blob@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.2.tgz#6bc438675f3851ecea51758ac91f6a1cd1bacabd"
|
||||
integrity sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==
|
||||
dependencies:
|
||||
web-streams-polyfill "^3.0.3"
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
|
||||
@@ -2478,6 +2478,11 @@ follow-redirects@1.5.10:
|
||||
dependencies:
|
||||
debug "=3.1.0"
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.14.3"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
|
||||
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
|
||||
|
||||
foreground-child@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
|
||||
@@ -3692,7 +3697,7 @@ lodash.truncate@^4.4.2:
|
||||
resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz"
|
||||
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
|
||||
|
||||
lodash@^4.7.0:
|
||||
lodash@^4.17.21, lodash@^4.7.0:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@@ -3953,14 +3958,6 @@ node-fetch@^2.6.1:
|
||||
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0.tgz#79da7146a520036f2c5f644e4a26095f17e411ea"
|
||||
integrity sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==
|
||||
dependencies:
|
||||
data-uri-to-buffer "^3.0.1"
|
||||
fetch-blob "^3.1.2"
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
@@ -5365,11 +5362,6 @@ walker@^1.0.7:
|
||||
dependencies:
|
||||
makeerror "1.0.x"
|
||||
|
||||
web-streams-polyfill@^3.0.3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.1.0.tgz#86f983b4f44745502b0d8563d9ef3afc609d4465"
|
||||
integrity sha512-wO9r1YnYe7kFBLHyyVEhV1H8VRWoNiNnuP+v/HUUmSTaRF8F93Kmd3JMrETx0f11GXxRek6OcL2QtjFIdc5WYw==
|
||||
|
||||
webidl-conversions@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
|
||||
|
||||
Reference in New Issue
Block a user