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

Defense channels and rolemapping #301

Merged
merged 17 commits into from
Jul 2, 2024
204 changes: 123 additions & 81 deletions discord-scripts/invite-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import {
ApplicationCommandOptionType,
Client,
ChannelType,
Collection,
TextChannel,
GuildMember,
} from "discord.js"
import { DAY, MILLISECOND, WEEK } from "../lib/globals.ts"

const guildInvites = new Collection()
const guildInvites: { [guildId: string]: { [inviteCode: string]: number } } = {}

async function createInvite(
channel: TextChannel,
Expand All @@ -31,19 +30,19 @@ async function createInvite(

async function listInvites(discordClient: Client, robot: Robot): Promise<void> {
discordClient.guilds.cache.forEach(async (guild) => {
const fetchInvites = await guild.invites.fetch().catch((error) => {
try {
const fetchInvites = await guild.invites.fetch()
if (fetchInvites) {
guildInvites[guild.id] = guildInvites[guild.id] || {}
zuuring marked this conversation as resolved.
Show resolved Hide resolved

fetchInvites.forEach((invite) => {
guildInvites[guild.id][invite.code] = invite.uses ?? 0
})
}
} catch (error) {
robot.logger.error(
`Failed to fetch invites for guild ${guild.name}: ${error}`,
)
})

if (fetchInvites) {
guildInvites.set(
guild.id,
new Collection(
fetchInvites.map((invite) => [invite.code, invite.uses]),
),
)
}
})
}
Expand Down Expand Up @@ -81,9 +80,10 @@ export default async function sendInvite(discordClient: Client, robot: Robot) {
description: "Creates Defense audit channels",
options: [
{
name: "client-name",
name: "audit-name",
type: ApplicationCommandOptionType.String,
description: "The name of the client to create the channels for",
description:
"The name of the audit/client to create the channels for.",
required: true,
},
],
Expand Down Expand Up @@ -153,13 +153,13 @@ export default async function sendInvite(discordClient: Client, robot: Robot) {
) {
await interaction.reply({
content:
"This command can only be run in channels under the 'defense' category.",
"This command can only be run in chat channels under the 'Defense' category.",
ephemeral: true,
})
return
}

const clientName = interaction.options.get("client-name")
const clientName = interaction.options.get("audit-name")
if (!clientName) {
await interaction.reply({
content: "Client name is required for the defense-audit command.",
Expand All @@ -172,13 +172,13 @@ export default async function sendInvite(discordClient: Client, robot: Robot) {
if (typeof clientName.value === "string") {
await interaction.deferReply({ ephemeral: true })
const normalizedClientName = clientName.value
.replace(/\s+/g, "-")
.replace(/[^a-zA-Z0-9]/g, "-")
.toLowerCase()
const internalChannelName = `int-${normalizedClientName}-audit`
const externalChannelName = `ext-${normalizedClientName}-audit`
const internalChannelName = `🔒int-${normalizedClientName}-audit`
const externalChannelName = `🔒ext-${normalizedClientName}-audit`
zuuring marked this conversation as resolved.
Show resolved Hide resolved

const defenseCategory = interaction.guild.channels.cache.find(
(c) => c.name === "defense",
(category) => category.name === "defense",
)

if (!defenseCategory) {
Expand All @@ -189,49 +189,98 @@ export default async function sendInvite(discordClient: Client, robot: Robot) {
return
}

const internalChannel = await interaction.guild.channels.create({
name: internalChannelName,
type: ChannelType.GuildText,
parent: defenseCategory.id,
})

const externalChannel = await interaction.guild.channels.create({
name: externalChannelName,
type: ChannelType.GuildText,
parent: defenseCategory.id,
})
// Internal channel setup
let internalChannel = interaction.guild.channels.cache.find(
(channel) => channel.name === internalChannelName,
) as TextChannel
const internalChannelCreated = !internalChannel
if (internalChannelCreated) {
zuuring marked this conversation as resolved.
Show resolved Hide resolved
internalChannel = await interaction.guild.channels.create({
name: internalChannelName,
type: ChannelType.GuildText,
parent: defenseCategory.id,
})
}

const internalRoleName = `Defense Internal: ${clientName.value}`
const externalRoleName = `Defense External: ${clientName.value}`
const internalRole = await interaction.guild.roles.create({
name: internalRoleName,
reason: "Role for internal audit channel",
})
const externalRole = await interaction.guild.roles.create({
name: externalRoleName,
reason: "Role for external audit channel",
})

await internalChannel.permissionOverwrites.create(internalRole, {
ViewChannel: true,
})
await externalChannel.permissionOverwrites.create(externalRole, {
ViewChannel: true,
})
await externalChannel.permissionOverwrites.create(internalRole, {
ViewChannel: true,
})
let internalRole = interaction.guild.roles.cache.find(
(r) => r.name === internalRoleName,
)
if (!internalRole) {
internalRole = await interaction.guild.roles.create({
name: internalRoleName,
reason: "Role for internal audit channel",
})
}

if (internalChannel) {
await internalChannel.permissionOverwrites.create(internalRole, {
ViewChannel: true,
})
await internalChannel.send(
`@here **Welcome to the ${clientName.value} Internal Audit Channel!**`,
zuuring marked this conversation as resolved.
Show resolved Hide resolved
)
}
const internalInvite = await createInvite(internalChannel)

// External channel setup
let externalChannel = interaction.guild.channels.cache.find(
(channel) => channel.name === externalChannelName,
) as TextChannel
const externalChannelCreated = !externalChannel
if (externalChannelCreated) {
externalChannel = await interaction.guild.channels.create({
name: externalChannelName,
type: ChannelType.GuildText,
parent: defenseCategory.id,
})
}

const externalRoleName = `Defense External: ${clientName.value}`
let externalRole = interaction.guild.roles.cache.find(
(r) => r.name === externalRoleName,
)
if (!externalRole) {
externalRole = await interaction.guild.roles.create({
name: externalRoleName,
reason: "Role for external audit channel",
})
}

if (externalChannel) {
await externalChannel.permissionOverwrites.create(externalRole, {
ViewChannel: true,
})
await externalChannel.permissionOverwrites.create(internalRole, {
ViewChannel: true,
})
await externalChannel.send(
`@here **Welcome to the ${clientName.value} External Audit Channel!**`,
)
}
const externalInvite = await createInvite(externalChannel)
zuuring marked this conversation as resolved.
Show resolved Hide resolved

await interaction.editReply({
content:
`**Defense audit setup complete for: ${clientName.value}**\n\n` +
`Internal Channel: <#${internalChannel.id}> (Invite: ${internalInvite.url})\n` +
`External Channel: <#${externalChannel.id}> (Invite: ${externalInvite.url})\n\n` +
`Roles created: <@&${internalRole.id}>, <@&${externalRole.id}>`,
})
// Final interaction response
if (internalChannelCreated || externalChannelCreated) {
await interaction.editReply({
content:
`**Defense audit setup complete for: ${clientName.value}**\n\n` +
`Internal Channel: <#${internalChannel.id}> - Invite: \`${internalInvite.url}\`\n` +
`External Channel: <#${externalChannel.id}> - Invite: \`${externalInvite.url}\`\n\n` +
`Roles created: <@&${internalRole.id}>, <@&${externalRole.id}>`,
})
} else {
await interaction.editReply({
content:
`**Defense audit channels already set up for: ${clientName.value}**\n\n` +
"These channels were found here:\n" +
`- Internal Channel: <#${internalChannel.id}> - Invite: \`${internalInvite.url}\`\n` +
`- External Channel: <#${externalChannel.id}> - Invite: \`${externalInvite.url}\`\n\n` +
"We've updated permissions to these roles:\n" +
`- Internal Role: <@&${internalRole.id}>\n` +
`- External Role: <@&${externalRole.id}>`,
})
}
}
} catch (error) {
robot.logger.error(error)
Expand All @@ -244,25 +293,20 @@ export default async function sendInvite(discordClient: Client, robot: Robot) {

// Check list of invites and compare when a new user joins which invite code has been used, then assign role based on channel.name.match TO DO: Modify this to work with potentially all invites
discordClient.on("guildMemberAdd", async (member: GuildMember) => {
const oldInvites =
(guildInvites.get(member.guild.id) as Collection<
string,
{ uses: number }
>) || new Collection<string, { uses: number }>()
const oldInvites = guildInvites[member.guild.id] || {}
const fetchedInvites = await member.guild.invites.fetch()
const newInvites = new Collection<string, number>(
fetchedInvites.map((invite) => [invite.code, invite.uses ?? 0]),
)
guildInvites.set(member.guild.id, newInvites)

const usedInvite = fetchedInvites.find((fetchedInvite) => {
const oldInvite = oldInvites.get(fetchedInvite.code)
const oldUses =
typeof oldInvite === "object" ? oldInvite.uses : oldInvite
return (fetchedInvite.uses ?? 0) > (oldUses ?? 0)
const newInvites: { [code: string]: number } = {}
fetchedInvites.forEach((invite) => {
newInvites[invite.code] = invite.uses ?? 0
})

robot.logger.info(`Used Invite: ${usedInvite ? usedInvite.code : "None"}`)
guildInvites[member.guild.id] = newInvites

const usedInvite = fetchedInvites.find((fetchedInvite) => {
const oldUses = oldInvites[fetchedInvite.code] || 0
return (fetchedInvite.uses ?? 0) > oldUses
})

if (usedInvite && usedInvite.channelId) {
const channel = member.guild.channels.cache.get(
Expand All @@ -281,26 +325,24 @@ export default async function sendInvite(discordClient: Client, robot: Robot) {
.join(" ")
: ""

robot.logger.info(`Channel Name: ${channel.name}`)

if (channelTypeMatch) {
const auditType =
channelTypeMatch[1] === "ext" ? "External" : "Internal"
robot.logger.info(`Audit Channel Type: ${auditType}`)
const roleName = `Defense ${auditType}: ${clientName}`

const role = member.guild.roles.cache.find(
(r) => r.name.toLowerCase() === roleName.toLowerCase(),
)

if (role) {
await member.roles.add(role)
robot.logger.info(
`Assigned role ${roleName} to ${member.displayName}`,
)
} else {
robot.logger.info(`Role ${roleName} not found in guild.`)
}
robot.logger.info(
`Invite code used: ${
usedInvite ? usedInvite.code : "None"
}, Username joined: ${
member.displayName
}, Role assignments: ${roleName}`,
)
}
}
} else {
Expand Down
Loading