Skip to content
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
52 changes: 52 additions & 0 deletions src/bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,64 @@ The `/flower` command opens an interactive modal form where users can submit mes
- **Content Validation**: Simple profanity filter to keep messages positive and respectful
- **Public Posting**: Submissions are posted to the channel with a beautiful embed
- **Agreement**: By submitting, users agree to be featured on the BobaTalks website
- **Admin Logging**: All submissions are logged to the `flowers-mod` channel for spam prevention and auditing

**Example Uses:**

- "I finally landed my first internship!"
- "Shoutout to Eileen for being so supportive at my event last weekend!"

#### 🔐 Setting Up the Flowers-Mod Channel (Admin Only)

To enable admin logging and prevent spam/abuse of the flower command, create a private moderation channel.

##### Step-by-Step Setup Instructions:

**1. Create a channel named "flowers-mod" in your Discord server**

**2. Configure channel permissions to make it private:**

- Go to Channel Settings → Permissions
- Click on `@everyone` and set "View Channel" to ❌ (red X/deny)
- Click "+ Add members or roles"
- Select your admin/moderator role(s)
- For each admin role, grant these permissions (set to ✅ green checkmark):
- View Channel
- Read Message History (so they can see past logs)
- Send Messages (optional, for adding notes)

**3. Ensure the bot has permissions in this channel:**

- In the same Permissions settings, click "+ Add members or roles"
- Add your bot's role
- Grant the bot these permissions (set to ✅ green checkmark):
- View Channel
- Send Messages
- Embed Links

##### Permission Visibility Explanation:

Discord channels use permission overwrites to control access. When you remove `@everyone`'s "View Channel" permission, the channel becomes **hidden from regular members**. Only users/roles with explicit "View Channel" permission can see it. This ensures that only admins/mods you designate can view the flower usage logs.

##### What Gets Logged:

Every time someone uses `/flower`, the bot sends a streamlined log to `flowers-mod` containing:

- **Clickable title** that links directly to the public flower message (click "🔍 Jump to message →")
- Full username (to identify who sent it)
- Display name (shows if they're anonymous or what name they provided)
- Exact timestamp (for tracking submission patterns)
- Full message content (to detect spam or inappropriate content)
- **Embedded image** (the actual image displayed right in the log for easy debugging)
- Flower ID from Google Sheets (for cross-referencing)

##### Why This Matters:

- **Spam Prevention**: Track if someone is overusing the command
- **Anonymous Accountability**: Even anonymous submissions are logged for admins
- **Audit Trail**: Full traceability for moderation purposes
- **Privacy**: Only designated admins can see this sensitive information

---

## 📚 Resources
Expand Down
133 changes: 130 additions & 3 deletions src/bot/commands/flower.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
ButtonBuilder,
ButtonStyle,
ButtonInteraction,
TextChannel,
Guild,
} from 'discord.js';
import { RegExpMatcher, englishDataset, englishRecommendedTransformers } from 'obscenity';

Expand Down Expand Up @@ -41,6 +43,90 @@
return matcher.hasMatch(text);
}

/**
* Logs flower command usage to the private flowers-mod channel for admin auditing.
* This helps prevent spam and tracks anonymous submissions.
*/
async function logFlowerUsageToModChannel(
guild: Guild | null,
logData: {
username: string;
discriminator: string;
timestamp: Date;
message: string;
nameProvided?: string;
flowerId?: string;
messageUrl?: string;
imageUrl?: string;
},
) {
if (!guild) {
console.warn('Cannot log flower usage: guild is null');
return;
}

try {
// Find the flowers-mod channel
const modChannel = guild.channels.cache.find(
(channel) => channel.name === 'flowers-mod' && channel.isTextBased(),
) as TextChannel | undefined;

if (!modChannel) {
console.warn(
'flowers-mod channel not found. Please create a private channel named "flowers-mod" for logging.',
);
return;
}

// Create audit log embed
const logEmbed = new EmbedBuilder()
.setColor('#FFA500') // Orange color for mod logs
.setTitle(logData.messageUrl ? '🔍 Jump to message →' : '🔍 Flower Command Usage Log')
.setURL(logData.messageUrl || null)
.addFields(
{
name: '📛 Username',
value:
logData.discriminator === '0'
? logData.username
: `${logData.username}#${logData.discriminator}`,
inline: true,
},
{
name: '🏷️ Display Name',
value: logData.nameProvided || '_(Anonymous)_',
inline: true,
},
{
name: '⏰ Timestamp',
value: `<t:${Math.floor(logData.timestamp.getTime() / 1000)}:F>`,
inline: false,
},
{
name: '📝 Message Content',
value:
logData.message.length > 1024
? logData.message.substring(0, 1021) + '...'
: logData.message,
inline: false,
},
)
.setFooter({
text: `Flower ID: ${logData.flowerId || 'N/A'} | For admin use only`,
})
.setTimestamp();

// Embed the image if one was attached
if (logData.imageUrl) {
logEmbed.setImage(logData.imageUrl);
}

await modChannel.send({ embeds: [logEmbed] });
} catch (error) {
console.error('Error logging flower usage to mod channel:', error);
}
}

// Temporary storage for image attachments and submission data (userId -> data)
const pendingSubmissions = new Map<
string,
Expand Down Expand Up @@ -269,8 +355,11 @@
// Update consent
submissionData.hasConsent = hasConsent;

// Defer the interaction to prevent token expiration during async operations
await interaction.deferUpdate();
// Show loading message immediately
await interaction.update({
content: '⏳ Processing your submission... (uploading image, saving to sheets, etc.)',
components: [], // Remove buttons
});

// Process the flower submission directly
await processFlowerSubmission(interaction, submissionData);
Expand Down Expand Up @@ -323,7 +412,7 @@
}
}

async function processFlowerSubmission(

Check warning on line 415 in src/bot/commands/flower.ts

View workflow job for this annotation

GitHub Actions / lint

Refactor this function to reduce its Cognitive Complexity from 34 to the 15 allowed

Check warning on line 415 in src/bot/commands/flower.ts

View workflow job for this annotation

GitHub Actions / lint

Refactor this function to reduce its Cognitive Complexity from 34 to the 15 allowed
interaction: ButtonInteraction,
submissionData: {
attachment?: { url: string; contentType: string; filename: string };
Expand Down Expand Up @@ -430,9 +519,47 @@
}

// Post the flower to the channel
let publicMessageUrl: string | undefined;
if (interaction.channel && 'send' in interaction.channel) {
await interaction.channel.send({ embeds: [embed] });
const publicMessage = await interaction.channel.send({ embeds: [embed] });
publicMessageUrl = publicMessage.url;
}

// Log to mod channel for auditing (after public message is sent to include the link)
const logData: {
username: string;
discriminator: string;
timestamp: Date;
message: string;
nameProvided?: string;
flowerId?: string;
messageUrl?: string;
imageUrl?: string;
} = {
username: interaction.user.username,
discriminator: interaction.user.discriminator,
timestamp: new Date(),
message: message,
};

// Only include optional fields if they exist
if (nameInput && nameInput.trim() !== '') {
logData.nameProvided = nameInput.trim();
}
if (createdFlowerId) {
logData.flowerId = createdFlowerId;
}
if (publicMessageUrl) {
logData.messageUrl = publicMessageUrl;
}
// Include the Drive image URL if available, otherwise the direct attachment URL
if (driveImageUrl) {
logData.imageUrl = driveImageUrl;
} else if (attachmentData) {
logData.imageUrl = attachmentData.url;
}

await logFlowerUsageToModChannel(interaction.guild, logData);
} catch (discordError) {
// If Discord message fails, rollback the sheet entry
console.error('Error sending Discord message, rolling back sheet entry:', discordError);
Expand All @@ -453,7 +580,7 @@
// If already deferred/replied, use editReply or followUp
if (interaction.deferred) {
await interaction.editReply({
content: '❌ An error occurred while submitting your flower. Please try again.',

Check warning on line 583 in src/bot/commands/flower.ts

View workflow job for this annotation

GitHub Actions / lint

Define a constant instead of duplicating this literal 3 times

Check warning on line 583 in src/bot/commands/flower.ts

View workflow job for this annotation

GitHub Actions / lint

Define a constant instead of duplicating this literal 3 times
components: [],
});
} else {
Expand Down
Loading