Skip to content

Commit

Permalink
Merge pull request #193 from MrLotU/associate_player_command
Browse files Browse the repository at this point in the history
Add associate player slash command
  • Loading branch information
slmnio authored Sep 3, 2023
2 parents 41b0ca1 + bbac068 commit 0841b9b
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 19 deletions.
2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"body-parser": "^1.19.0",
"chalk": "^4.1.0",
"cors": "^2.8.5",
"discord.js": "^14.11.0",
"discord.js": "^14.12.0-dev.1688904265-df40dcd.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"libsodium-wrappers": "^0.7.10",
Expand Down
2 changes: 1 addition & 1 deletion server/src/action-utils/action-manager-models.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Action {
}) {
return this.handler(args, auth)
.then(data => {
console.log(`[actions] Success in ${this.key}`, data);
console.log(`[actions] Success in ${this.key}`);
success(data);
},
e => {
Expand Down
7 changes: 6 additions & 1 deletion server/src/action-utils/action-permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ async function isEventStaffOrHasRole(user, event, role, websiteRoles) {
return false;
}

function canUpdateUserDetails(user) {
// TODO: Better / specific permission?
return (user.airtable?.website_settings ?? []).includes("Full broadcast permissions");
}

module.exports = {
canEditMatch, isEventStaffOrHasRole
canEditMatch, isEventStaffOrHasRole, canUpdateUserDetails
};
55 changes: 55 additions & 0 deletions server/src/actions/match-discord-slmngg-player.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const { canUpdateUserDetails } = require("../action-utils/action-permissions");
const { log } = require("../discord/slmngg-log");
const { User } = require("discord.js");
const Cache = require("../cache");

module.exports = {
key: "match-discord-slmngg-player",
requiredParams: ["discordData"],
auth: ["user"],
/***
* @param {User} discordData
* @param {UserData} user
* @returns {Promise<UserData[]>}
*/
async handler({ discordData }, { user }) {
if (!canUpdateUserDetails(user)) {
throw {
errorMessage: "You don't have permission update user details",
errorCode: 403
};
}

let players = await Promise.all((await Cache.get("Players")).ids.map(id => (Cache.get(id.slice(3)))));

if (await Cache.auth.getPlayer(discordData.id)) {
throw {
errorMessage: "This user is already linked to a SLMN.GG profile",
errorCode: 400
};
}

const isNewUsername = discordData.discriminator === "0";

let searchValues = [discordData.username, discordData.globalName, !isNewUsername && `${discordData.username}#${discordData.discriminator}`]
.filter(Boolean)
.map((value) => value.toLocaleLowerCase());

let potentials = players.filter((player) => {
if (player.discord_id) return false;

const discordSimple = player.discord_tag?.split("#")[0].toLocaleLowerCase();

return searchValues.includes(discordSimple) || searchValues.includes(player.discord_tag?.toLocaleLowerCase()) || searchValues.includes(player.name?.toLocaleLowerCase());
});

if (potentials.length === 0) {
throw {
errorMessage: "Unable to find a SLMN.GG user that matches that Discord account.",
errorCode: 404
};
}

return potentials;
}
};
45 changes: 45 additions & 0 deletions server/src/actions/update-player-discord-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { canUpdateUserDetails } = require("../action-utils/action-permissions");
const { log } = require("../discord/slmngg-log");
const {
User,
userMention
} = require("discord.js");
const Cache = require("../cache");
const { cleanID } = require("../action-utils/action-utils");

module.exports = {
key: "update-player-discord-id",
requiredParams: ["discordData", "slmnggId"],
auth: ["user"],
/***
* @param {User} discordData
* @param {string} slmnggId
* @param {UserData} user
* @returns {Promise<string>}
*/
async handler({ discordData, slmnggId }, { user }) {
if (!canUpdateUserDetails(user)) {
throw {
errorMessage: "You don't have permission update user details",
errorCode: 403
};
}

const targetUser = await Cache.get(cleanID(slmnggId));

if (!targetUser) throw { errorMessage: "No user found to send to Airtable" };

let response = await this.helpers.updateRecord("Players", targetUser, {
"Discord ID": discordData.id,
"Discord Tag": discordData.username
});

log(`[Profile] ${user.airtable.name} ${userMention(user.discord.id)} ${cleanID(user.airtable.id)} is linking Discord account for ${userMention(discordData.id)} ${discordData.id} to ${targetUser.name} ${cleanID(targetUser.id)} https://slmn.gg/player/${cleanID(targetUser.id)}`);

if (response?.error) {
console.error("Airtable error", response.error);
throw "Airtable error";
}
return targetUser.name;
}
};
2 changes: 1 addition & 1 deletion server/src/actions/update-profile-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module.exports = {
validatedData["Profile Picture Theme"] = [dirtyID(profileData.profile_picture_theme)];
}

console.log("[profile]", user.airtable.name, user.airtable.id, "is setting", validatedData);
console.log("[Profile]", user.airtable.name, user.airtable.id, "is setting", validatedData);
let response = await this.helpers.updateRecord("Players", user.airtable, {
...validatedData
});
Expand Down
148 changes: 148 additions & 0 deletions server/src/discord/commands/admin/set-user-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
const {
ContextMenuCommandBuilder,
userMention,
ApplicationCommandType,
ActionRowBuilder,
StringSelectMenuBuilder,
StringSelectMenuOptionBuilder,
ButtonBuilder,
ButtonStyle,
} = require("discord.js");
const Cache = require("../../../cache.js");

const { getInternalManager } = require("../../../action-utils/action-manager");
const { cleanID } = require("../../../action-utils/action-utils");


function generatePlayerDescription(player) {
let descriptionItems = [];
// descriptionItems.push(`${player["#_of_teams"]} team${player["#_of_teams"] === 1 ? "" : "s"}`);
if (player.pronouns) descriptionItems.push(`${player.pronouns}`);
if (player.discord_tag) descriptionItems.push(`Discord: ${player.discord_tag}`);
if (player.battletag) descriptionItems.push(`Battletag: ${player.battletag}`);
if (!descriptionItems?.length) return "No additional info";
return descriptionItems.join(", ");
}

function selectUserUI(targetUser, users, selectedUser) {
let content;

const rows = [];

if (users.length === 1) {
selectedUser = users[0];
content = `Found match for ${userMention(targetUser.id)}: [${selectedUser.name}](https://slmn.gg/player/${cleanID(selectedUser.id)})`;
} else {
const options = users.map((p) => {
const option = new StringSelectMenuOptionBuilder()
.setLabel(p.name + ` (${p["#_of_teams"]} team${p["#_of_teams"] === 1 ? "" : "s"})`)
.setDescription(generatePlayerDescription(p))
.setValue(p.id);
if (p.id === selectedUser?.id) {
option.setDefault(true);
}
return option;
});
const dropdown = new StringSelectMenuBuilder()
.setCustomId("slmngg-player")
.setPlaceholder("Select SLMNGG player")
.addOptions(options);
rows.push(new ActionRowBuilder().addComponents(dropdown));
content = "Please select the SLMN.GG user to link this account to.";
}

if (selectedUser) {
const confirmButton = new ButtonBuilder()
.setCustomId("confirm")
.setLabel("Confirm")
.setStyle(ButtonStyle.Success);

const cancelButton = new ButtonBuilder()
.setCustomId("cancel")
.setLabel("Cancel")
.setStyle(ButtonStyle.Secondary);

rows.push(new ActionRowBuilder().addComponents(cancelButton, confirmButton));
}

return {
components: rows,
content
};
}

module.exports = {
data: new ContextMenuCommandBuilder()
.setName("Associate with SLMN.GG player")
.setDMPermission(false)
.setType(ApplicationCommandType.User),
async execute(interaction) {
const ephemeral = true;

await interaction.deferReply({ ephemeral });

const { token } = await Cache.auth.startRawDiscordAuth(interaction.user);

if (!token) {
return interaction.followUp({ ephemeral, content: "Could not authenticate you with SLMN.GG." });
}

const internalManager = getInternalManager();
if (!internalManager) {
return interaction.followUp({ ephemeral, content: "No action handlers can process your request." });
}

const potentials = await internalManager.runAction("match-discord-slmngg-player", { discordData: interaction.targetUser }, token);
let selectedUser = potentials[0];

let response = await interaction.followUp({ ephemeral, ...selectUserUI(interaction.targetUser, potentials, null) });

const collector = await response.createMessageComponentCollector({ time: 3_600_000 });

collector.on("collect", async event => {
// console.log("collection", event);

/* String menu selection */
if (event.componentType === 3) {
let selectedUserID = event.values[0];
selectedUser = potentials.find(u => u.id === selectedUserID);
console.log("collector setting selected user", selectedUser);
console.table(event.values);

let menuOptions = { ephemeral, ...selectUserUI(interaction.targetUser, potentials, selectedUser) };
console.log(menuOptions);
event.update({}); // tell discord we've handled this
await interaction.editReply(menuOptions);

return;
}

/* Button */
if (event.componentType === 2) {
event.update({}); // tell discord we've handled this

if (event.customId === "cancel") {
await interaction.followUp({ ephemeral, content: "Cancelled action." });
collector.stop();
} else if (event.customId === "confirm" && selectedUser !== null) {
await internalManager.runAction("update-player-discord-id", { discordData: interaction.targetUser, slmnggId: selectedUser?.id }, token)
.then(async slmnggUser => {
await interaction.followUp({ ephemeral, content: `Linked discord user ${userMention(interaction.targetUser.id)} to SLMN.GG user ${slmnggUser}` });
collector.stop();
})
.catch(async ({
errorCode,
errorMessage
}) => {
console.error({ errorCode, errorMessage });
await interaction.followUp({
ephemeral, content: `Action failed: ${errorMessage}`
});
collector.stop();
});
}
}

});
}
};
8 changes: 3 additions & 5 deletions server/src/discord/slash-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ for (const folder of commandFolders) {
}

client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;

const command = interaction.client.commands.get(interaction.commandName);

if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
if (interaction.commandName) console.error(`No command matching ${interaction.commandName} was found.`);
return;
}

Expand All @@ -41,12 +39,12 @@ client.on(Events.InteractionCreate, async (interaction) => {
console.error(error);
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: "There was an error while executing this command!",
content: `There was an error while executing this command: \n> ${error.errorMessage}`,
ephemeral: true
});
} else {
await interaction.reply({
content: "There was an error while executing this command!",
content: `There was an error while executing this command: \n> ${error.errorMessage}`,
ephemeral: true
});
}
Expand Down
30 changes: 20 additions & 10 deletions server/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@
fast-deep-equal "^3.1.3"
lodash "^4.17.21"

"@sapphire/snowflake@^3.4.2":
"@sapphire/snowflake@^3.4.2", "@sapphire/snowflake@^3.5.1":
version "3.5.1"
resolved "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz"
integrity sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==
Expand Down Expand Up @@ -977,24 +977,29 @@ discord-api-types@^0.37.37, discord-api-types@^0.37.41:
resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.45.tgz"
integrity sha512-r9m/g+YQfo7XWMrl645jvMlYoWF8lvns/ch4NCxsz/FbingrECu97LFSD2zKOvgHaSc90BHP8wgshaMcA2/c6Q==

discord.js@^14.11.0:
version "14.11.0"
resolved "https://registry.npmjs.org/discord.js/-/discord.js-14.11.0.tgz"
integrity sha512-CkueWYFQ28U38YPR8HgsBR/QT35oPpMbEsTNM30Fs8loBIhnA4s70AwQEoy6JvLcpWWJO7GY0y2BUzZmuBMepQ==
discord-api-types@^0.37.45:
version "0.37.47"
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.47.tgz#d622d5f5629a71ca2cd534442ae2800844e1888d"
integrity sha512-rNif8IAv6duS2z47BMXq/V9kkrLfkAoiwpFY3sLxxbyKprk065zqf3HLTg4bEoxRSmi+Lhc7yqGDrG8C3j8GFA==

[email protected]:
version "14.12.0-dev.1688904265-df40dcd.0"
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.12.0-dev.1688904265-df40dcd.0.tgz#94c7100827ac1672af5d3a041b70331d90497c1e"
integrity sha512-4TomFy6bRIvU7uUAO/cFqhSY3LtiQkSJPYQHKkO+5eZQJZhXQl2fTNu807V5xJpzpElno7oEl+4SEoPItIAVvg==
dependencies:
"@discordjs/builders" "^1.6.3"
"@discordjs/collection" "^1.5.1"
"@discordjs/formatters" "^0.3.1"
"@discordjs/rest" "^1.7.1"
"@discordjs/util" "^0.3.1"
"@discordjs/ws" "^0.8.3"
"@sapphire/snowflake" "^3.4.2"
"@sapphire/snowflake" "^3.5.1"
"@types/ws" "^8.5.4"
discord-api-types "^0.37.41"
discord-api-types "^0.37.45"
fast-deep-equal "^3.1.3"
lodash.snakecase "^4.1.1"
tslib "^2.5.0"
undici "^5.22.0"
tslib "^2.5.2"
undici "^5.22.1"
ws "^8.13.0"

doctrine@^3.0.0:
Expand Down Expand Up @@ -3094,6 +3099,11 @@ tslib@^2.5.0:
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz"
integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==

tslib@^2.5.2:
version "2.6.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3"
integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==

tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz"
Expand Down Expand Up @@ -3150,7 +3160,7 @@ undefsafe@^2.0.3:
dependencies:
debug "^2.2.0"

undici@^5.22.0:
undici@^5.22.0, undici@^5.22.1:
version "5.22.1"
resolved "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz"
integrity sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==
Expand Down

0 comments on commit 0841b9b

Please sign in to comment.