mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-02 20:08:17 +03:00
[WIP] Added boilerplate for profile view
This commit is contained in:
@@ -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",
|
||||
|
||||
+2
-1
@@ -8,5 +8,6 @@ export enum LoggerLayers {
|
||||
server = "server",
|
||||
serverAuth = "serverAuth",
|
||||
botConfigSetup = "botConfigSetup",
|
||||
tachiFetch = "tachiFetch"
|
||||
tachiFetch = "tachiFetch",
|
||||
profile = "profile"
|
||||
}
|
||||
|
||||
+21
-4
@@ -1,10 +1,12 @@
|
||||
import { Client, Intents } from "discord.js";
|
||||
import { UserGameStats } from "tachi-common";
|
||||
import { buildProfileIntractable } from "./profile/buildProfileEmbed";
|
||||
import { ProcessEnv } from "./setup";
|
||||
import { LoggerLayers } from "./config";
|
||||
import { registerSlashCommands, slashCommands, SlashCommand } from "./slashCommands/register";
|
||||
import { TachiServerV1Get } from "./utils/fetch-tachi";
|
||||
import { createLayeredLogger } from "./utils/logger";
|
||||
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.client);
|
||||
|
||||
const client = new Client({
|
||||
@@ -12,9 +14,22 @@ const client = new Client({
|
||||
});
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
try {
|
||||
if (interaction.isSelectMenu()) {
|
||||
/** @TODO Move this to its own handler!
|
||||
* We only have one intractable but for now this works */
|
||||
const userId = interaction.customId.split(":")[1];
|
||||
const userData = (await TachiServerV1Get<UserGameStats[]>(`/users/${userId}/game-stats`))?.body;
|
||||
if (userData) {
|
||||
await interaction.update(buildProfileIntractable(userData, userId, interaction.values[0]));
|
||||
} else {
|
||||
throw new Error("Failed to re-fetch user data! This is a bad sign!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
const command = slashCommands.find((command: SlashCommand) => {
|
||||
return command.info.name === interaction.commandName;
|
||||
});
|
||||
@@ -38,7 +53,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,65 @@
|
||||
import { InteractionReplyOptions, MessageActionRow, MessageEmbed, MessagePayload, MessageSelectMenu } from "discord.js";
|
||||
import { FormatGame, Game, Playtypes, UserGameStats } from "tachi-common";
|
||||
import { IDStrings, UGSRatingsLookup } from "tachi-common/js/types";
|
||||
import { find } from "lodash";
|
||||
|
||||
export const buildProfileEmbed = (data: UserGameStats[], game?: string): MessageEmbed => {
|
||||
const embed = new MessageEmbed().setColor("#cc527a");
|
||||
|
||||
/** @TODO FETCH USERS ACTUAL DETAILS */
|
||||
embed.addField("Foo", "Foo");
|
||||
embed.addField("Bar", "Bar");
|
||||
embed.addField("Baz", "Baz");
|
||||
embed.setThumbnail("https://cdn.mos.cms.futurecdn.net/mrArzwHcNuQbRwbEmuiwdJ.jpg");
|
||||
|
||||
if (game) {
|
||||
const specificData: UserGameStats | undefined = find(data, (item) => game === `${item.game}:${item.playtype}`);
|
||||
if (!specificData) {
|
||||
throw new Error(`Could not get ratings for requested game: ${game}`);
|
||||
} else {
|
||||
const ratings: string[] = [];
|
||||
Object.keys(specificData.ratings).map((rating) => {
|
||||
/** @TODO MAKE THIS ITS OWN FUNCTION THIS IS FILTHY */
|
||||
ratings.push(
|
||||
`${rating}: ${
|
||||
Math.round(
|
||||
(specificData.ratings[rating as UGSRatingsLookup[IDStrings]] || 0 + Number.EPSILON) * 100
|
||||
) / 100
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
/** @TODO DONT SPLIT THE STRING HERE HOLY SHIT */
|
||||
embed.addField(
|
||||
`Stats for: ${FormatGame(game.split(":")[0] as Game, game.split(":")[1] as Playtypes[never])}`,
|
||||
`${ratings.join("\n")}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
embed.addField("Select a game to see stats", "\u200B");
|
||||
}
|
||||
|
||||
return embed;
|
||||
};
|
||||
|
||||
export const buildProfileIntractable = (
|
||||
data: UserGameStats[],
|
||||
userId: string,
|
||||
game?: string
|
||||
): InteractionReplyOptions | MessagePayload => {
|
||||
const dropdown = new MessageActionRow().addComponents(
|
||||
new MessageSelectMenu()
|
||||
.setCustomId(`SelectGameForProfile:${userId}`)
|
||||
.setPlaceholder("Browse By Game")
|
||||
.addOptions(
|
||||
data.map((game) => {
|
||||
return {
|
||||
label: `${FormatGame(game.game, game.playtype)}`,
|
||||
value: `${game.game}:${game.playtype}`
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return { embeds: [buildProfileEmbed(data, game)], components: [dropdown] };
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import { LoggerLayers } from "../config";
|
||||
import { BotConfig } from "../setup";
|
||||
import { TachiServerV1Get } from "../utils/fetch-tachi";
|
||||
import { createLayeredLogger } from "../utils/logger";
|
||||
import { UserGameStats } from "tachi-common";
|
||||
import { buildProfileIntractable } from "./buildProfileEmbed";
|
||||
|
||||
const logger = createLayeredLogger(LoggerLayers.profile);
|
||||
|
||||
export const getProfileByName = async (interaction: CommandInteraction): Promise<void> => {
|
||||
try {
|
||||
const userId = interaction.options.getString("user", false) || "TODO FALLBACK TO SELF";
|
||||
logger.info(`Fetching user ${userId}`);
|
||||
|
||||
const userData = (await TachiServerV1Get<UserGameStats[]>(`/users/${userId}/game-stats`))?.body;
|
||||
logger.verbose(userData);
|
||||
|
||||
if (userData) {
|
||||
await interaction.reply(buildProfileIntractable(userData, userId));
|
||||
} 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,6 +3,7 @@ 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 "../help/help";
|
||||
@@ -23,6 +24,14 @@ 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")
|
||||
.addStringOption((option) => option.setName("user").setDescription("The users id").setRequired(false))
|
||||
.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);
|
||||
|
||||
+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