Skip to content
Closed
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: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ MODERATORS_ROLE_IDS= # Comma separated list of role IDs that are Moderators(Mods
REPEL_LOG_CHANNEL_ID= # Channel ID where the bot will log repel actions
REPEL_ROLE_ID= # Role ID assigned to users who can use the repel command
GUIDES_CHANNEL_ID="" # The ID of the channel where guides will be posted

ONBOARDING_CHANNEL_ID= # Channel ID where onboarding messages will be sent
ONBOARDING_ROLE_ID= # Role ID assigned to new members upon onboarding
7 changes: 7 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SERVER_ID=1403539983821963274
GUIDES_CHANNEL_ID=1426998962635804712
REPEL_LOG_CHANNEL_ID=1403540596035027045
REPEL_ROLE_ID=1403545113904414842
MODERATORS_ROLE_IDS=tbd
ONBOARDING_CHANNEL_ID=tbd
ONBOARDING_ROLE_ID=tbd
12 changes: 5 additions & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,15 @@ jobs:
export NODE_VERSION=$(cat .nvmrc | sed 's/v//')
echo "Using Node version: $NODE_VERSION"

# Create .env.local file with secrets
cat > .env.local << EOF
# Create .env.secrets file with secrets
cat > .env.secrets << EOF
DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }}
CLIENT_ID=${{ secrets.CLIENT_ID }}
GUIDES_CHANNEL_ID=${{ secrets.GUIDES_CHANNEL_ID }}
SERVER_ID=${{ secrets.SERVER_ID }}
REPEL_LOG_CHANNEL_ID=${{ secrets.REPEL_LOG_CHANNEL_ID }}
REPEL_ROLE_ID=${{ secrets.REPEL_ROLE_ID }}
MODERATORS_ROLE_IDS=${{ secrets.MODERATORS_ROLE_IDS }}
EOF

# copy .env.production file
cp .env.production .env.production

# Stop any existing containers
docker compose down || true

Expand Down
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ services:
container_name: webdev-bot-prod
restart: unless-stopped
env_file:
- .env.local
- .env.production
- .env.secrets
environment:
- NODE_ENV=production
- GUIDES_TRACKER_PATH=/app/data/guides-tracker.json
volumes:
# Persist guides tracker data
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dependencies": {
"discord.js": "^14.22.1",
"typescript": "^5.9.3",
"web-features": "^3.3.0"
"web-features": "^3.7.0"
},
"devDependencies": {
"@biomejs/biome": "2.2.4",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion src/commands/docs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ export const executeDocCommand = async (
}
};

export const NON_BASELINE_FEATURES = ['numeric-seperators', 'single-color-gradients'];
export const NON_BASELINE_FEATURES = [
'numeric-seperators',
'open-closed',
'single-color-gradients',
];
export const getBaselineFeatures = (
originalFeatures: Record<string, unknown>,
nonFeatureKeys: string[] = NON_BASELINE_FEATURES
Expand Down
11 changes: 10 additions & 1 deletion src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ import { docsCommands } from './docs/index.js';
import { guidesCommand } from './guides/index.js';
import cacheMessages from './moderation/cache-messages.js';
import { repelCommand } from './moderation/repel.js';
import { onboardingCommand } from './onboarding/index.js';
import { pingCommand } from './ping.js';
import { tipsCommands } from './tips/index.js';
import type { Command } from './types.js';

export const commands = new Map<string, Command>(
[pingCommand, guidesCommand, docsCommands, tipsCommands, repelCommand, cacheMessages]
[
pingCommand,
guidesCommand,
docsCommands,
tipsCommands,
repelCommand,
cacheMessages,
onboardingCommand,
]
.flat()
.map((cmd) => [cmd.data.name, cmd])
);
2 changes: 1 addition & 1 deletion src/commands/moderation/repel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
} from 'discord.js';
import { HOUR, MINUTE, timeToString } from '../../constants/time.js';
import { config } from '../../env.js';
import { logToChannel } from '../../util/channel-logging.js';
import { getPublicChannels } from '../../util/channel.js';
import { logToChannel } from '../../util/channel-logging.js';
import { buildCommandString, createCommand } from '../../util/commands.js';

const DEFAULT_LOOK_BACK_MS = 10 * MINUTE;
Expand Down
21 changes: 21 additions & 0 deletions src/commands/onboarding/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ContainerBuilder,
type MessageActionRowComponentBuilder,
} from 'discord.js';

const containerComponent = new ContainerBuilder();

const actionRowComponent = new ActionRowBuilder<MessageActionRowComponentBuilder>();

const buttonComponent = new ButtonBuilder()
.setCustomId('onboarding_add_role')
.setLabel('Add role')
.setStyle(ButtonStyle.Primary);

actionRowComponent.addComponents(buttonComponent);
containerComponent.addActionRowComponents(actionRowComponent);

export { containerComponent };
65 changes: 65 additions & 0 deletions src/commands/onboarding/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ApplicationCommandType, MessageFlags } from 'discord.js';
import { config } from '../../env.js';
import { createCommand } from '../../util/commands.js';
import { containerComponent } from './component.js';

export const onboardingCommand = createCommand({
data: {
name: 'onboarding',
description: 'Manage onboarding settings',
type: ApplicationCommandType.ChatInput,
},
execute: async (interaction) => {
const guild = interaction.guild;
if (!guild) {
await interaction.reply({
content: 'This command can only be used in a server.',
flags: MessageFlags.Ephemeral,
});
return;
}
const onboardingRole = guild.roles.cache.get(config.onboarding.roleId);
if (!onboardingRole) {
await interaction.reply({
content: 'Onboarding role not found. Please check the configuration.',
flags: MessageFlags.Ephemeral,
});
return;
}
const onboardingChannel = guild.channels.cache.get(config.onboarding.channelId);
if (!onboardingChannel || !onboardingChannel.isSendable()) {
await interaction.reply({
content:
'Onboarding channel not found or is not a text channel. Please check the configuration.',
flags: MessageFlags.Ephemeral,
});
return;
}

const onboardingMessage = await interaction.reply({
components: [containerComponent],
flags: MessageFlags.IsComponentsV2,
});

const collector = onboardingMessage.createMessageComponentCollector({});

collector.on('collect', async (componentInteraction) => {
if (componentInteraction.customId === 'onboarding_add_role') {
const member = await guild.members.fetch(componentInteraction.user.id);
const hasRole = member.roles.cache.has(onboardingRole.id);
if (hasRole) {
await componentInteraction.reply({
content: `You already have the ${onboardingRole.name} role.`,
flags: MessageFlags.Ephemeral,
});
} else {
await member.roles.add(onboardingRole);
await componentInteraction.reply({
content: `You have been given the ${onboardingRole.name} role!`,
flags: MessageFlags.Ephemeral,
});
}
}
});
},
});
4 changes: 4 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export const config = {
channelId: requireEnv('GUIDES_CHANNEL_ID'),
trackerPath: optionalEnv('GUIDES_TRACKER_PATH'),
},
onboarding: {
channelId: requireEnv('ONBOARDING_CHANNEL_ID'),
roleId: requireEnv('ONBOARDING_ROLE_ID'),
},
// Add more config sections as needed:
// database: {
// url: requireEnv('DATABASE_URL'),
Expand Down
6 changes: 5 additions & 1 deletion src/loadEnvFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ function loadEnvFile(filePath: string) {
}

// Load local environment file if it exists
loadEnvFile(join(process.cwd(), '.env.local'));
// In production, docker will use .env.production and .env.secrets automatically
const isProd = process.env.NODE_ENV === 'production';
if (!isProd) {
loadEnvFile(join(process.cwd(), '.env.local'));
}