Standalone bot/ service (its own package.json/Dockerfile) managed entirely through a new admin-only Discord Bot panel — token stored encrypted in the DB and pushed to the bot process in-memory, never an env var. Built in phases, each independently verified against a live Discord guild: - Bot skeleton: gateway connection, internal shared-secret API, self-heals on its own restart by pulling config from the site - Moderation core: /ban /kick /mute /warn /warnings + mod-log channel - Word/invite/spam filtering with leetspeak-resistant normalization and a staff role/channel allowlist - Scheduled messages: recurring (cron) and one-off channel posts - Role assignment: button role menus, auto-role on join, temp roles, bulk role ops - Auto-rotating primary invite with an audit log - Site integration: news-publish -> Discord announce webhook, manual /announce, read-only /wiki search Also fixes a pre-existing bug in both DB pools (server + bot): the mariadb driver defaulted to timezone 'local', silently mis-serializing bound Date params by the host's local offset instead of the DB's UTC session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
48 lines
1.6 KiB
JavaScript
48 lines
1.6 KiB
JavaScript
// Per-guild key/value config the bot owns (see guild_config in
|
|
// server/db/schema.sql). Generic get/set now; filters/schedules/role-menu
|
|
// config reuses this same table in later phases.
|
|
const db = require('../db')
|
|
|
|
const MOD_LOG_CHANNEL_KEY = 'mod_log_channel_id'
|
|
const AUTO_ROLE_KEY = 'auto_role_id'
|
|
const INVITE_CHANNEL_KEY = 'invite_channel_id'
|
|
const NEWS_CHANNEL_KEY = 'news_channel_id'
|
|
|
|
async function get(guildId, key) {
|
|
const rows = await db.query('SELECT value FROM guild_config WHERE guild_id = ? AND `key` = ? LIMIT 1', [guildId, key])
|
|
return rows[0] ? rows[0].value : null
|
|
}
|
|
|
|
async function set(guildId, key, value) {
|
|
await db.query(
|
|
`INSERT INTO guild_config (guild_id, \`key\`, value) VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE value = VALUES(value)`,
|
|
[guildId, key, value],
|
|
)
|
|
}
|
|
|
|
const getModLogChannelId = (guildId) => get(guildId, MOD_LOG_CHANNEL_KEY)
|
|
const setModLogChannelId = (guildId, channelId) => set(guildId, MOD_LOG_CHANNEL_KEY, channelId)
|
|
|
|
const getAutoRoleId = (guildId) => get(guildId, AUTO_ROLE_KEY)
|
|
const setAutoRoleId = (guildId, roleId) => set(guildId, AUTO_ROLE_KEY, roleId)
|
|
|
|
const getInviteChannelId = (guildId) => get(guildId, INVITE_CHANNEL_KEY)
|
|
const setInviteChannelId = (guildId, channelId) => set(guildId, INVITE_CHANNEL_KEY, channelId)
|
|
|
|
const getNewsChannelId = (guildId) => get(guildId, NEWS_CHANNEL_KEY)
|
|
const setNewsChannelId = (guildId, channelId) => set(guildId, NEWS_CHANNEL_KEY, channelId)
|
|
|
|
module.exports = {
|
|
get,
|
|
set,
|
|
getModLogChannelId,
|
|
setModLogChannelId,
|
|
getAutoRoleId,
|
|
setAutoRoleId,
|
|
getInviteChannelId,
|
|
setInviteChannelId,
|
|
getNewsChannelId,
|
|
setNewsChannelId,
|
|
}
|