Skip to content

feat: add 1984 audit-logging module#95

Merged
BagToad merged 2 commits into
mainfrom
feature/1984-audit-log
Apr 18, 2026
Merged

feat: add 1984 audit-logging module#95
BagToad merged 2 commits into
mainfrom
feature/1984-audit-log

Conversation

@BagToad
Copy link
Copy Markdown
Owner

@BagToad BagToad commented Apr 18, 2026

Adds a new 1984 module that logs every message activity and voice-channel event across all visible channels to a single configured audit channel.

What's logged

  • Message create / edit / delete (with diff for edits)
  • Reaction add / remove
  • Voice channel create / rename
  • Voice channel status updates (via raw gateway event, since discordgo doesn't model this typed)

Edits render as a unified diff inside a ```diff fenced block. Full before/after/diff are also attached as files for completeness.

Discord API safety

A single buildPayload chokepoint enforces:

  • content length (1900 chars)
  • attachment count (10)
  • per-file size (8 MB)

Overflow is automatically offloaded to text attachments. Truncation is UTF-8 rune-aware so multi-byte runes (emoji / CJK) are never split.

Edge cases handled

  • MESSAGE_UPDATE is a partial payload: requires EditedTimestamp != nil and falls back to BeforeUpdate.Author when the payload omits it (avoids false "content cleared" entries from embed unfurls).
  • CHANNEL_UPDATE is also partial: requires a cached BeforeUpdate to confirm an actual rename before logging.
  • Sticker / poll / component-only message creates are not silently dropped.
  • Skips the log channel itself, DMs, and the bot's own messages.

Configuration

New config key: gamerpals_1984_log_channel_id (accessor + example yaml + /config list-keys entry).

Module is wired in bot.go with session.State.MaxMessageCount = 500 so discordgo populates BeforeUpdate / BeforeDelete from cache.

Testing

Table-driven tests at ~94% coverage including:

  • combinatorial sweep over content/attachment shapes
  • long-diff stress case asserting payload stays within Discord limits
  • UTF-8 boundary truncation tests
  • partial-payload scenarios for MESSAGE_UPDATE and CHANNEL_UPDATE

Local go build, go vet, go test ./... all pass.

Adds a new "1984" module that logs every message activity and voice-channel
event across all visible channels to a single configured audit channel:

- Message create / edit / delete (with diff for edits)
- Reaction add / remove
- Voice channel create / rename
- Voice channel status updates (via raw gateway event)

Edits render as a unified diff inside a ```diff``` fenced block, with
full before/after/diff also attached as files.

Discord API safety: a single buildPayload chokepoint enforces content
length (1900 chars), attachment count (10), and per-file size (8MB);
overflow is offloaded to text attachments. Truncation is UTF-8
rune-aware so multi-byte runes (emoji/CJK) are never split.

Edge cases handled:
- MESSAGE_UPDATE is partial: requires EditedTimestamp != nil and falls
  back to BeforeUpdate.Author when payload omits it (avoids false
  "content cleared" entries from embed unfurls etc).
- CHANNEL_UPDATE is also partial: requires cached BeforeUpdate to
  confirm an actual rename before logging.
- Sticker / poll / component-only message creates are not silently
  dropped.
- Skips the log channel itself, DMs, and the bot's own messages.

Configuration via new key gamerpals_1984_log_channel_id.

Module is wired up in bot.go with session.State.MaxMessageCount=500 so
discordgo populates BeforeUpdate / BeforeDelete from cache.

Tests are table-driven with ~94% coverage including a long-diff stress
case that asserts the final payload stays within Discord's limits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 18, 2026 19:04
@BagToad BagToad added the deploy-dev Request a deployment to the dev bot label Apr 18, 2026
@github-actions github-actions Bot removed the deploy-dev Request a deployment to the dev bot label Apr 18, 2026
@github-actions
Copy link
Copy Markdown

✅ Development deployment succeeded!

View workflow run

On successful deployments, you'll need to wait a moment for the bot to fully boot up AND refresh/reload your Discord client if you see a message about invalid integration ID.

Thanks!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new 1984 (“nineteeneightyfour”) command module that passively audit-logs message activity, reactions, and select voice-channel events into a configured Discord channel, plus the config wiring and bot event-handler wiring needed to support it.

Changes:

  • Introduces the nineteeneightyfour module with Discord event handlers and a payload builder that enforces Discord message/attachment limits.
  • Wires the module into the bot runtime (handlers + Discord state message cache sizing) and module registration.
  • Adds new config key gamerpals_1984_log_channel_id (accessor, example YAML, /config list-keys entry) and adds go-difflib for unified diff rendering.
Show a summary per file
File Description
internal/commands/modules/nineteeneightyfour/nineteeneightyfour.go New audit-logging module: handlers, diff rendering, payload sizing/attachment logic.
internal/commands/modules/nineteeneightyfour/nineteeneightyfour_test.go New table-driven tests covering payload building, truncation, and event handler behavior.
internal/bot/bot.go Enables message caching for before/after and registers 1984 event handlers (including raw events).
internal/commands/module_handler.go Registers the new module under key "1984".
internal/config/config_accessors.go Adds accessor for gamerpals_1984_log_channel_id.
internal/commands/modules/config/config.go Adds the new config key to /config list-keys.
config.example.yaml Documents the new config key and how to disable the module.
go.mod Promotes github.com/pmezard/go-difflib to a direct dependency.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 5

Comment on lines +545 to +550
if len(a.content) > inlineContentCap {
files = append(files, fileSpec{
name: a.name,
content: clampBytes([]byte(a.content), maxAttachmentBytes),
})
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildPayload only uploads a provided attachment when len(a.content) > inlineContentCap. This means before/after/diff (and content.txt) will not be attached for typical small edits/messages, which conflicts with the PR description claiming the full artifacts are attached “for completeness”. If the intent is to always attach these artifacts, buildPayload likely needs to upload all non-empty attachments (or support a force-upload flag).

Copilot uses AI. Check for mistakes.
Comment on lines +415 to +419
if author != nil {
fmt.Fprintf(&b, "**User:** `%s` (ID: `%s`)\n", author.Username, author.ID)
} else {
b.WriteString("**User:** `(unknown)`\n")
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildHeader wraps author.Username in backticks but does not escape backticks/markdown-sensitive characters. A username (or channel name elsewhere) containing ` will break the formatting of the audit message. Consider sanitizing these values (e.g., replace backticks or apply a small markdown-escape helper) before embedding them in inline code spans.

Copilot uses AI. Check for mistakes.
Comment on lines +1237 to +1257
tests := []struct {
name string
before string
after string
wantSubstrs []string
wantEmpty bool
}{
{
name: "single line change shows minus and plus",
before: "hello",
after: "world",
wantSubstrs: []string{"-hello", "+world", "@@"},
},
{
name: "added line",
before: "a\n",
after: "a\nb\n",
wantSubstrs: []string{"+b"},
},
{
name: "identical inputs produce empty diff",
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section of the new test file appears not gofmt-formatted (indentation missing). Please run gofmt on the file to match repo conventions and keep the tests readable.

Suggested change
tests := []struct {
name string
before string
after string
wantSubstrs []string
wantEmpty bool
}{
{
name: "single line change shows minus and plus",
before: "hello",
after: "world",
wantSubstrs: []string{"-hello", "+world", "@@"},
},
{
name: "added line",
before: "a\n",
after: "a\nb\n",
wantSubstrs: []string{"+b"},
},
{
name: "identical inputs produce empty diff",
tests := []struct {
name string
before string
after string
wantSubstrs []string
wantEmpty bool
}{
{
name: "single line change shows minus and plus",
before: "hello",
after: "world",
wantSubstrs: []string{"-hello", "+world", "@@"},
},
{
name: "added line",
before: "a\n",
after: "a\nb\n",
wantSubstrs: []string{"+b"},
},
{
name: "identical inputs produce empty diff",

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +83
body := strings.TrimSpace(e.Content)
// A message create is worth logging if it has any user-visible payload:
// text, attachments, embeds, stickers, polls, or interactive components.
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnMessageCreate uses strings.TrimSpace(e.Content), which alters the audited content (leading/trailing whitespace is removed) and makes whitespace-only messages look empty (and then skipped by the “no user-visible payload” check). For an audit logger, consider preserving the exact raw content for logging/attachments and only trimming for a separate emptiness check if needed.

Suggested change
body := strings.TrimSpace(e.Content)
// A message create is worth logging if it has any user-visible payload:
// text, attachments, embeds, stickers, polls, or interactive components.
body := e.Content
// A message create is worth logging if it has any payload:
// raw text (including whitespace-only messages), attachments, embeds,
// stickers, polls, or interactive components.

Copilot uses AI. Check for mistakes.
Comment on lines +328 to +333
func (m *Module) shouldLog(s *discordgo.Session, guildID, channelID string, author *discordgo.User) bool {
if guildID == "" {
return false
}
if !m.shouldLogChannel(channelID) {
return false
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldLog only checks guildID != "" and does not scope logging to the configured GamerPals server ID. If the bot is in multiple guilds, this can route events from other guilds into the configured audit channel (cross-guild data leak / permission confusion). Consider adding an optional guard: if cfg.GetGamerPalsServerID() is set, require guildID to match before logging (and apply similarly to channel/raw events).

Copilot uses AI. Check for mistakes.
When a logged message has image attachments, fetch them via a hookable
HTTP client and re-upload them to the log channel so the evidence
survives the original message being edited or deleted (the Discord CDN
URL is short-lived).

- Add fetchImage hook to Module (default: 15s http.Client, image/* only,
  capped at maxAttachmentBytes).
- Add isImageAttachment helper (content-type then extension fallback).
- Refactor buildPayload to take extraFiles []fileSpec; binary files take
  priority for the 10-attachment slot cap.
- Replace the combined.txt overflow merge with drop-and-note (the merge
  would corrupt mixed binary/text content).
- defaultDispatchLog now picks per-file Content-Type via
  http.DetectContentType so re-hosted images render inline.
- Tests for isImageAttachment, rehostImages (success / failure / oversize
  / non-image / mixed / nil), OnMessageCreate rehosting integration,
  fetch failure tolerance, binary priority on overflow, and
  detectContentType.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@BagToad BagToad added the deploy-dev Request a deployment to the dev bot label Apr 18, 2026
@github-actions github-actions Bot removed the deploy-dev Request a deployment to the dev bot label Apr 18, 2026
@github-actions
Copy link
Copy Markdown

✅ Development deployment succeeded!

View workflow run

On successful deployments, you'll need to wait a moment for the bot to fully boot up AND refresh/reload your Discord client if you see a message about invalid integration ID.

Thanks!

@BagToad BagToad merged commit 3204d43 into main Apr 18, 2026
6 checks passed
@BagToad BagToad deleted the feature/1984-audit-log branch April 18, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants