Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do some player operations by Discord id #217

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/kol.js/src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export class Client extends (EventEmitter as new () => TypedEmitter<Events>) {

if (options.searchParams) {
const searchParams = options.searchParams as URLSearchParams;
if (searchParams.get("pwd") !== "false") searchParams.set("pwd", this.#pwd);
if (searchParams.get("pwd") !== "false")
searchParams.set("pwd", this.#pwd);
}

return next(options);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "vitest";

import { validPlayerIdentifier } from "./whois.js";
import { validPlayerIdentifier } from "./_player.js";

describe("Player validation", () => {
test("real queries pass validation", () => {
Expand Down
54 changes: 54 additions & 0 deletions packages/oaf/src/commands/_player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Prisma } from "@prisma/client";
import { Player } from "kol.js";

import { prisma } from "../clients/database.js";
import { kolClient } from "../clients/kol.js";

export function validPlayerIdentifier(identifier: string) {
// If a player id: a number!
// If a username: 3 to 30 alphanumeric characters, starting with alpha, may contain underscores or spaces
return /^([a-zA-Z][a-zA-Z0-9_ ]{2,29})|[0-9]+$/.test(identifier);
gausie marked this conversation as resolved.
Show resolved Hide resolved
}

export async function findPlayer(where: Prisma.PlayerWhereInput) {
const player = await prisma.player.findFirst({
where,
include: { greenbox: { orderBy: { id: "desc" }, take: 1 } },
});
if (!player) return null;
return { ...player, greenbox: player.greenbox.at(0) ?? null };
}

type FoundPlayer = Awaited<ReturnType<typeof findPlayer>>;

export async function identifyPlayer(
input: string,
): Promise<string | [Player<true>, FoundPlayer]> {
// Check if this is a discord mention
if (input.match(/^<@\d+>$/)) {
const knownPlayer = await findPlayer({ discordId: input.slice(2, -1) });

if (knownPlayer === null) {
return "That user hasn't claimed a KoL account.";
}

return [
await kolClient.players.fetch(knownPlayer.playerId, true),
knownPlayer,
];
}

// Validate if the string identifies a KoL player, either as a player ID or a user name
if (typeof input === "string" && !validPlayerIdentifier(input)) {
return "Come now, you know that isn't a player. Can't believe you'd try and trick me like this. After all we've been through? 😔";
}

const player = await kolClient.players.fetch(input, true);

if (!player)
return `According to KoL, player ${typeof input === "number" ? "#" : ""}${input} does not exist.`;

const knownPlayer = await findPlayer({ playerId: player.id });

return [player, knownPlayer];
}
50 changes: 18 additions & 32 deletions packages/oaf/src/commands/clan/brainiac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "discord.js";

import { prisma } from "../../clients/database.js";
import { kolClient } from "../../clients/kol.js";
import { identifyPlayer } from "../_player.js";

export const data = new SlashCommandBuilder()
.setName("brainiac")
Expand All @@ -26,46 +26,32 @@ export const data = new SlashCommandBuilder()
);

export async function execute(interaction: ChatInputCommandInteraction) {
const playerName = interaction.options.getString("player", true);
const input = interaction.options.getString("player", true);
const available = interaction.options.getBoolean("available", false) ?? true;

await interaction.deferReply();

const existing = await prisma.player.findFirst({
where: {
playerName: {
equals: playerName,
mode: "insensitive",
},
},
});
const identification = await identifyPlayer(input);

if (!existing) {
const player = await kolClient.players.fetch(playerName);
if (typeof identification === "string") {
await interaction.editReply(identification);
return;
}

if (!player) {
await interaction.editReply(
`User ${italic(playerName)} could not be found`,
);
return;
}
const [player] = identification;

await prisma.player.create({
data: {
playerId: player.id,
playerName: player.name,
brainiac: available,
},
});
} else {
await prisma.player.update({
where: { playerId: existing.playerId },
data: { brainiac: available },
});
}
await prisma.player.upsert({
where: { playerId: player.id },
create: {
playerId: player.id,
playerName: player.name,
brainiac: available,
},
update: { brainiac: available },
});

await interaction.editReply(
`${available ? "Added" : "Removed"} user ${italic(playerName)} ${
`${available ? "Added" : "Removed"} user ${italic(player.name)} ${
available ? "to" : "from"
} the list of players always available to help with skills.`,
);
Expand Down
46 changes: 18 additions & 28 deletions packages/oaf/src/commands/clan/done.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "discord.js";

import { prisma } from "../../clients/database.js";
import { kolClient } from "../../clients/kol.js";
import { identifyPlayer } from "../_player.js";

export const data = new SlashCommandBuilder()
.setName("done")
Expand All @@ -24,42 +24,32 @@ export const data = new SlashCommandBuilder()
);

export async function execute(interaction: ChatInputCommandInteraction) {
const playerName = interaction.options.getString("player", true);
const input = interaction.options.getString("player", true);
const doneWithSkills = interaction.options.getBoolean("done", false) ?? true;

await interaction.deferReply();

const existing = await prisma.player.findFirst({
where: {
playerName: {
equals: playerName,
mode: "insensitive",
},
},
});
const identification = await identifyPlayer(input);

if (!existing) {
const player = await kolClient.players.fetch(playerName);
if (typeof identification === "string") {
await interaction.editReply(identification);
return;
}

if (!player) {
await interaction.editReply(
`User ${italic(playerName)} could not be found`,
);
return;
}
const [player] = identification;

await prisma.player.create({
data: { playerId: player.id, playerName: player.name, doneWithSkills },
});
} else {
await prisma.player.update({
where: { playerId: existing.playerId },
data: { doneWithSkills },
});
}
await prisma.player.upsert({
where: { playerId: player.id },
create: {
playerId: player.id,
playerName: player.name,
doneWithSkills,
},
update: { doneWithSkills },
});

await interaction.editReply(
`${doneWithSkills ? "Added" : "Removed"} user ${italic(playerName)} ${
`${doneWithSkills ? "Added" : "Removed"} user ${italic(player.name)} ${
doneWithSkills ? "to" : "from"
} the list of players done with skills.`,
);
Expand Down
13 changes: 8 additions & 5 deletions packages/oaf/src/commands/clan/whitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {

import { kolClient } from "../../clients/kol.js";
import { config } from "../../config.js";
import { identifyPlayer } from "../_player.js";
import { ALL_CLANS } from "./_clans.js";

const PERMITTED_ROLE_IDS = config.WHITELIST_ROLE_IDS.split(",");
Expand Down Expand Up @@ -41,17 +42,19 @@ export async function execute(interaction: ChatInputCommandInteraction) {
return;
}

const playerNameOrId = interaction.options.getString("player", true);

await interaction.deferReply();

const player = await kolClient.players.fetch(playerNameOrId);
const input = interaction.options.getString("player", true);

const identification = await identifyPlayer(input);

if (!player) {
interaction.editReply({ content: "Player not found." });
if (typeof identification === "string") {
interaction.editReply(identification);
return;
}

const [player] = identification;

for (const clan of ALL_CLANS) {
await kolClient.addToWhitelist(player.id, clan.id);
}
Expand Down
61 changes: 6 additions & 55 deletions packages/oaf/src/commands/kol/whois.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Prisma } from "@prisma/client";
import {
APIEmbedField,
AttachmentBuilder,
Expand All @@ -13,10 +12,10 @@ import {

import { prisma } from "../../clients/database.js";
import { createEmbed, discordClient } from "../../clients/discord.js";
import { kolClient } from "../../clients/kol.js";
import { snapshotClient } from "../../clients/snapshot.js";
import { renderSvg } from "../../svgConverter.js";
import { toKoldbLink, toMuseumLink } from "../../utils.js";
import { findPlayer, identifyPlayer } from "../_player.js";

export const data = new SlashCommandBuilder()
.setName("whois")
Expand All @@ -31,68 +30,20 @@ export const data = new SlashCommandBuilder()
.setMaxLength(30),
);

export function validPlayerIdentifier(identifier: string) {
// If a player id: a number!
// If a username: 3 to 30 alphanumeric characters, starting with alpha, may contain underscores or spaces
return /^([a-zA-Z][a-zA-Z0-9_ ]{2,29})|[0-9]+$/.test(identifier);
}

async function findPlayer(where: Prisma.PlayerWhereInput) {
const player = await prisma.player.findFirst({
where,
include: { greenbox: { orderBy: { id: "desc" }, take: 1 } },
});
if (!player) return null;
return { ...player, greenbox: player.greenbox.at(0) ?? null };
}

export async function execute(interaction: ChatInputCommandInteraction) {
const input = interaction.options.getString("player", true);

await interaction.deferReply();

// If the input is a Discord mention, we'll try to set a player identifier there. Otherwise this just gets set to
// the same value as input.
let playerIdentifier;
const identification = await identifyPlayer(input);

// Whatever happens we'll try to ascertain whether this is a known player. Because we either do so at the start or
// at the end, declare a null variable here.
let knownPlayer: Awaited<ReturnType<typeof findPlayer>> = null;

// Check if this is a mention first of all
if (input.match(/^<@\d+>$/)) {
knownPlayer = await findPlayer({ discordId: input.slice(2, -1) });

if (knownPlayer === null) {
await interaction.editReply(`That user hasn't claimed a KoL account.`);
return;
}

playerIdentifier = knownPlayer.playerId;
} else {
playerIdentifier = input;
}

if (
typeof playerIdentifier === "string" &&
!validPlayerIdentifier(playerIdentifier)
) {
await interaction.editReply(
"Come now, you know that isn't a player. Can't believe you'd try and trick me like this. After all we've been through? 😔",
);
if (typeof identification === "string") {
await interaction.editReply(identification);
return;
}

const player = await kolClient.players.fetch(playerIdentifier, true);

if (!player) {
await interaction.editReply(
`According to KoL, player ${
typeof playerIdentifier === "number" ? "#" : ""
}${playerIdentifier} does not exist.`,
);
return;
}
const player = identification[0];
let knownPlayer = identification[1];

const fields: APIEmbedField[] = [
{ name: "Class", value: player.kolClass || "Unlisted" },
Expand Down