Files
website/bot/src/discord/commands/invite.command.js
wtclaude 12d50fd615
All checks were successful
PR Checks / bot-install (pull_request) Successful in 13s
PR Checks / client-build (pull_request) Successful in 22s
PR Checks / server-tests (pull_request) Successful in 11m13s
chore(quality): resolve SonarQube code smells across website
Clears the 124 CODE_SMELL findings from the SonarQube scan (server, client,
and bot). All changes are behaviour-preserving refactors — no route, protocol,
schema, or config changes — verified against the full server (381) and client
(43) test suites plus a clean client build.

By rule:
- S3776 (20, cognitive complexity): extract helpers/handlers so each function
  drops under the threshold — shard model upsert builders, page/wiki update,
  block validation, notification stream mapping (dispatch table), SSO mobile
  login, shard ingest deps, uo-link socket backfill/connect, the bot slash-
  command dispatchers + discord manager, and the Shard/UserDetail/HeroEditor/
  CharacterStats React components.
- S4624 (34, nested template literals): pull inner templates into locals /
  a withQs() helper; rewrite shardEvents.describe() as a formatter table.
- S3358 (35, nested ternaries): lift to if/else vars, lookup maps, small
  components, or guarded JSX expressions.
- S6479 (12, array-index React keys): key by stable content instead of index
  (two in-editor lists left as-is; index matches their by-index edit model).
- S6353 (6): [0-9]/[^0-9] -> \d/\D.  S125 (5): reword state-shape comments that
  parsed as code.  S3800/S3782 (botScore): JSDoc-type PATH_WEIGHTS tuples.
- S6481 (2): memoize Auth/Site context values (and SiteContext brand).
- S4144: dedupe HeroEditor upload handler into useImageUpload().
- S1126 (2), S6035, S5869 (redundant A-Z under /i), S5843 (town-name regex ->
  prefix list): assorted one-liners.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 04:35:39 -05:00

88 lines
3.3 KiB
JavaScript

const { PermissionFlagsBits, ApplicationCommandOptionType, ChannelType } = require('discord.js')
const guildConfig = require('../../model/guildConfig')
const inviteLog = require('../../model/inviteLog')
const inviteRotator = require('../../invites/inviteRotator')
// Per-subcommand handlers, split out of execute() so the dispatch stays flat.
async function handleChannel(interaction) {
const channel = interaction.options.getChannel('channel')
if (!channel) {
const currentId = await guildConfig.getInviteChannelId(interaction.guildId)
const content = currentId ? `Invites are created in <#${currentId}>.` : 'No invite channel is set yet.'
await interaction.reply({ content, ephemeral: true })
return
}
await guildConfig.setInviteChannelId(interaction.guildId, channel.id)
await interaction.reply({ content: `Invite channel set to ${channel}.`, ephemeral: true })
}
async function handleRotate(interaction) {
await interaction.deferReply({ ephemeral: true })
try {
const invite = await inviteRotator.rotate(interaction.client, interaction.guildId, {
triggeredBy: interaction.user.id,
triggeredByTag: interaction.user.tag,
})
await interaction.editReply({ content: `New invite: https://discord.gg/${invite.code}` })
} catch (err) {
await interaction.editReply({ content: `Couldn't rotate the invite: ${err.message}` })
}
}
async function handleLog(interaction) {
const rows = await inviteLog.list(interaction.guildId, 10)
if (rows.length === 0) {
await interaction.reply({ content: 'No invite rotations logged yet.', ephemeral: true })
return
}
const lines = rows.map((r) => {
const who = r.triggered_by_tag || 'automatic (scheduled)'
const status = r.revoked_at ? `revoked ${new Date(r.revoked_at).toLocaleString()}` : 'active'
return `\`${r.invite_code}\` — by ${who} on ${new Date(r.created_at).toLocaleString()} (${status})`
})
await interaction.reply({ content: lines.join('\n'), ephemeral: true })
}
module.exports = {
data: {
name: 'invite',
description: 'Manage the auto-rotating primary server invite.',
default_member_permissions: PermissionFlagsBits.ManageGuild.toString(),
options: [
{
name: 'channel',
description: 'View or set the channel new invites are created in.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'channel',
description: 'Channel to create invites in. Omit to view the current setting.',
type: ApplicationCommandOptionType.Channel,
channel_types: [ChannelType.GuildText],
required: false,
},
],
},
{
name: 'rotate',
description: 'Revoke the current invite and generate a new one now.',
type: ApplicationCommandOptionType.Subcommand,
options: [],
},
{
name: 'log',
description: 'Show recent invite rotation history.',
type: ApplicationCommandOptionType.Subcommand,
options: [],
},
],
},
async execute(interaction) {
const sub = interaction.options.getSubcommand()
if (sub === 'channel') return handleChannel(interaction)
if (sub === 'rotate') return handleRotate(interaction)
if (sub === 'log') return handleLog(interaction)
},
}