Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@
"flushIntervalMs": 5000
}
},
"starboard": {
"enabled": false,
"channelId": null,
"threshold": 3,
"emoji": "⭐",
"selfStarAllowed": false,
"ignoredChannels": []
},
"permissions": {
"enabled": true,
"adminRoleId": null,
Expand Down
27 changes: 27 additions & 0 deletions migrations/002_starboard-posts.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Migration: starboard_posts table
*
* Tracks which messages have been reposted to the starboard channel,
* enabling dedup (update instead of repost) and star-count syncing.
*/

/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
exports.up = (pgm) => {
pgm.sql(`
CREATE TABLE IF NOT EXISTS starboard_posts (
id SERIAL PRIMARY KEY,
guild_id TEXT NOT NULL,
source_message_id TEXT NOT NULL UNIQUE,
source_channel_id TEXT NOT NULL,
starboard_message_id TEXT NOT NULL,
star_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
)
`);
pgm.sql('CREATE INDEX IF NOT EXISTS idx_starboard_source ON starboard_posts(source_message_id)');
};

/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
exports.down = (pgm) => {
pgm.sql('DROP TABLE IF EXISTS starboard_posts CASCADE');
};
70 changes: 70 additions & 0 deletions src/modules/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getConfig } from './config.js';
import { checkLinks } from './linkFilter.js';
import { checkRateLimit } from './rateLimit.js';
import { isSpam, sendSpamAlert } from './spam.js';
import { handleReactionAdd, handleReactionRemove } from './starboard.js';
import { accumulateMessage, evaluateNow } from './triage.js';
import { recordCommunityActivity, sendWelcomeMessage } from './welcome.js';

Expand Down Expand Up @@ -214,6 +215,74 @@ export function registerMessageCreateHandler(client, _config, healthMonitor) {
});
}

/**
* Register reaction event handlers for the starboard feature.
* Listens to both MessageReactionAdd and MessageReactionRemove to
* post, update, or remove starboard embeds based on star count.
*
* @param {Client} client - Discord client instance
* @param {Object} _config - Unused (kept for API compatibility); handler resolves per-guild config via getConfig().
*/
export function registerReactionHandlers(client, _config) {
client.on(Events.MessageReactionAdd, async (reaction, user) => {
// Ignore bot reactions
if (user.bot) return;

// Resolve guild — reactions in DMs have no guild
const guild =
(reaction.message.guild ?? reaction.message.partial) ? null : reaction.message.guild;
if (!guild && reaction.message.partial) {
try {
await reaction.message.fetch();
} catch {
return;
}
}
const guildId = reaction.message.guild?.id;
if (!guildId) return;

const guildConfig = getConfig(guildId);
if (!guildConfig.starboard?.enabled) return;

try {
await handleReactionAdd(reaction, user, client, guildConfig);
} catch (err) {
logError('Starboard reaction add handler failed', {
messageId: reaction.message.id,
error: err.message,
});
}
});

client.on(Events.MessageReactionRemove, async (reaction, user) => {
if (user.bot) return;

const guild =
(reaction.message.guild ?? reaction.message.partial) ? null : reaction.message.guild;
if (!guild && reaction.message.partial) {
try {
await reaction.message.fetch();
} catch {
return;
}
}
const guildId = reaction.message.guild?.id;
if (!guildId) return;

const guildConfig = getConfig(guildId);
if (!guildConfig.starboard?.enabled) return;

try {
await handleReactionRemove(reaction, user, client, guildConfig);
} catch (err) {
logError('Starboard reaction remove handler failed', {
messageId: reaction.message.id,
error: err.message,
});
}
});
}

/**
* Register error event handlers
* @param {Client} client - Discord client
Expand Down Expand Up @@ -241,5 +310,6 @@ export function registerEventHandlers(client, config, healthMonitor) {
registerReadyHandler(client, config, healthMonitor);
registerGuildMemberAddHandler(client, config);
registerMessageCreateHandler(client, config, healthMonitor);
registerReactionHandlers(client, config);
registerErrorHandlers(client);
}
Loading
Loading