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

Invite management #297

Merged
merged 10 commits into from
Mar 14, 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
144 changes: 144 additions & 0 deletions discord-scripts/invite-management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Robot } from "hubot"
import { Client, TextChannel } from "discord.js"
import { DAY, MILLISECOND, WEEK } from "../lib/globals.ts"

const EXTERNAL_AUDIT_CHANNEL_REGEXP = /^ext-(?<client>.*)-audit$/

async function createInvite(
channel: TextChannel,
maxAge = (1 * WEEK) / MILLISECOND,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, but let's do this conversion inside, when we call channel.createInvite—so the caller can always pass things and receive things in terms of our base time unit (the ms) throughout the codebase, and we only convert when we touch the external entity that requires a different denomination.

maxUses = 10,
): Promise<{ url: string; maxAge: number; maxUses: number }> {
const invite = await channel.createInvite({
maxAge,
maxUses,
unique: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Wonder what happens if this is off lol.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Shadowfiend Looks like it defaults to 24 hours, infinite uses = 0 😁

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I just meant the unique property.

})

return {
url: invite.url,
maxAge,
maxUses,
}
}

export default async function sendInvite(discordClient: Client, robot: Robot) {
const { application } = discordClient

if (application) {
// Check if create-invite command already exists, if not create it
const existingInviteCommand = (await application.commands.fetch()).find(
(command) => command.name === "create-invite",
)
if (existingInviteCommand === undefined) {
robot.logger.info("No create-invite command found, creating it!")
await application.commands.set([
{
name: "create-invite",
description: "Creates a new invite",
},
])
robot.logger.info("create invite command set")
}
// Create an invite based of the command and channel where the command has been run
discordClient.on("interactionCreate", async (interaction) => {
if (
!interaction.isCommand() ||
interaction.commandName !== "create-invite"
) {
return
Shadowfiend marked this conversation as resolved.
Show resolved Hide resolved
}

if (!interaction.guild) {
await interaction.reply("This command can only be used in a server.")
return
}

try {
const { channel } = interaction
if (channel instanceof TextChannel) {
const invite = await createInvite(channel)
if (invite) {
await interaction.reply(
`Here is your invite link: ${
invite.url
}\nThis invite expires in ${
(invite.maxAge / DAY) * MILLISECOND
} days and has a maximum of ${invite.maxUses} uses.`,
)
}
} else {
await interaction.reply(
"Cannot create an invite for this type of channel.",
)
}
} catch (error) {
await interaction.reply("An error occurred while creating the invite.")
}
})

// Generates an invite if the channel name matches ext-*-audit format
discordClient.on("channelCreate", async (channel) => {
if (
channel.parent &&
channel.parent.name === "defense" &&
channel instanceof TextChannel &&
EXTERNAL_AUDIT_CHANNEL_REGEXP.test(channel.name)
) {
try {
const defenseInvite = await createInvite(channel)
if (defenseInvite) {
robot.logger.info(
`New invite created for defense audit channel: ${channel.name}, URL: ${defenseInvite.url}`,
)
channel.send(
`Here is your invite link: ${
defenseInvite.url
}\nThis invite expires in ${
(defenseInvite.maxAge / DAY) * MILLISECOND
} days and has a maximum of ${defenseInvite.maxUses} uses.`,
)
}
// Create a new role with the client name extracted and set permissions to that channel
const clientName = channel.name
.split("-")
.slice(1, -1)
.map(
(segment) =>
segment.substring(0, 1).toUpperCase() + segment.substring(1),
)
.join(" ")

if (clientName) {
const roleName = clientName
? `Defense: ${clientName}`
: `Defense: ${channel.name}`
Comment on lines +112 to +115
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have the if (clientName) check now, I think this can just be:

Suggested change
if (clientName) {
const roleName = clientName
? `Defense: ${clientName}`
: `Defense: ${channel.name}`
if (clientName) {
const roleName = `Defense: ${clientName}`


const role = await channel.guild.roles.create({
name: roleName,
reason: `Role for ${channel.name} channel`,
})

await channel.permissionOverwrites.create(role, {
ViewChannel: true,
})
channel.send(
`**${role.name}** role created and permissions set for **${channel.name}**`,
)
robot.logger.info(
`${role.name} role created and permissions set for channel ${channel.name}`,
)
} else {
robot.logger.info(
`Skipping role creation due to empty client name for channel ${channel.name}`,
)
}
} catch (error) {
robot.logger.error(
`An error occurred setting up the defense audit channel: ${error}`,
)
}
}
})
}
}
6 changes: 5 additions & 1 deletion lib/globals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const HOST = process.env.HUBOT_HOST
export const SECOND = 1000
export const MILLISECOND = 1000
export const SECOND = 1 * MILLISECOND
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realizing this should really be phrased as:

export const MILLISECOND = 1 // Base unit of time for our code.
export const SECOND = 1000 * MILLISECOND
...

export const MINUTE = 60 * SECOND
export const HOUR = 60 * MINUTE
export const DAY = 24 * HOUR
export const WEEK = 7 * DAY
4 changes: 2 additions & 2 deletions scripts/github-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
withConfigOrReportIssues,
issueReporterForRobot,
} from "../lib/config.ts"
import { HOST, MINUTE, SECOND } from "../lib/globals.ts"
import { HOST, MINUTE, MILLISECOND } from "../lib/globals.ts"

const PENDING_GITHUB_TOKENS_KEY = "pendingGitHubTokens"
const GITHUB_TOKENS_KEY = "gitHubTokens"
Expand Down Expand Up @@ -70,7 +70,7 @@ export default function setupGitHubAuth(robot: Robot) {
robot.brain.set(PENDING_GITHUB_TOKENS_KEY, pendingGitHubTokens)
}

setInterval(cleanPending, 30 * SECOND)
setInterval(cleanPending, 30 * MILLISECOND)
cleanPending()

robot.respond(/github auth/, (res) => {
Expand Down
Loading