Skip to content

Split modlogformats into a package of event-keyed renderers - #293

Merged
celveren merged 8 commits into
masterfrom
copilot/separate-modlogformats-file
Oct 7, 2026
Merged

celveren merged 8 commits into
masterfrom
copilot/separate-modlogformats-file

Conversation

Copilot AI commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

modlogformats.py was one 600-line file with three parallel classes (EmojiFormat, MinimalisticFormat, EmbedFormat) of static methods, and the modlog cog repeated an if/elif chain per event. This replaces them with one renderer per event, in the style of the AutoMod Punishment registry. Each renderer is keyed by the event model.

  • Package layout (lightning/utils/modlogformats/)

    • base.py: FormatContext, Renderer, the @renders registry and get_renderer.
    • member.py: join, leave, screening, nick change and role change.
    • moderation.py: ModAction, timed-action expiry, timeout expiry, infraction update and delete.
    • commands.py: command ran.
    • __init__.py re-exports the public helpers, so existing imports keep working.
  • Renderer API

    • Each event has one renderer with emoji(), minimal(ctx), embed() and mentions().
    • get_renderer(event) walks the event's MRO, so a subclass gets its own renderer if it has one and its parent's otherwise.
    • Mod actions are keyed on Action. The cog passes event.action, which also covers automod events.
    • FormatContext.from_setting() parses the stored format string into a format name plus with_timestamp. An unknown format raises ValueError.
    @renders(MemberJoinEvent)
    class MemberJoin(Renderer[MemberJoinEvent]):
        def emoji(self) -> str: ...
        def minimal(self, ctx: FormatContext) -> str: ...
        def embed(self) -> discord.Embed: ...
    
    # in the cog
    await self._emit(emitter, record, MemberJoinEvent(member))
  • Events (lightning/events.py)

    • Added MemberJoinEvent, MemberLeaveEvent, MemberScreeningEvent and TimedActionExpiredEvent.
    • CommandEvent is now used. The new events are built by the modlog cog and aren't dispatched anywhere.
    • Renderers take a single event, so the per-format argument-order differences (e.g. embed timed_action_expired) go away.
  • Helpers (lightning/formatters.py)

    • base_user_format, escape_markdown_and_mentions, action_format, format_timestamp and construct_dm_message moved here.
    • This breaks the events ↔ modlogformats import cycle.
  • Modlog cog

    • Every listener now makes one _emit(emitter, record, event) call. _emit picks the renderer and sends a message or embed.
    • It applies allowed_mentions only for the emoji format.
  • Behaviour changes to check

    • Nick change with neither nickname set no longer raises.
    • Removed the stray ( in the emoji screening output.
    • Fixed the missing space in the timestamp-less minimal role change.
    • Added the missing newline before the moderator line in the emoji timeout-expired output.
    • Join/leave still skips minimal without timestamp, as before.
    • Removed bot_addition, which had no callers.
    • Removed EmojiFormat, MinimalisticFormat, EmbedFormat and BaseFormat.
  • Misc

    • License headers on the new files use the current LICENSE.header.
    • __all__ lists one name per line.
    • Added tests/test_modlogformats.py.

Copilot AI and others added 6 commits October 5, 2026 14:18
Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
…sses

Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
@sourcery-ai

sourcery-ai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR decomposes modlog formatting into a package of event-keyed renderers registered by event type, adds a shared emission pipeline and supporting event models/helpers, and covers registry behavior plus output regressions with focused tests while preserving public helper imports.

Sequence diagram for unified modlog emission

sequenceDiagram
    participant Listener as Modlog listener
    participant Cog as ModlogCog
    participant Registry as get_renderer
    participant Renderer as Event renderer
    participant Emitter as TextChannelEmitter

    Listener->>Cog: _emit(emitter, record, event)
    Cog->>FormatContext: from_setting(record.format)
    FormatContext-->>Cog: FormatContext
    Cog->>Registry: get_renderer(event)
    Registry-->>Cog: Renderer(event)
    Cog->>Renderer: render(ctx)
    Renderer-->>Cog: str or discord.Embed
    Cog->>Renderer: mentions()
    Cog->>Emitter: put(rendered output, optional embeds and mentions)
Loading

Flow diagram for renderer lookup and format selection

flowchart TD
    Start[Event supplied to get_renderer] --> MRO[Walk event class MRO]
    MRO --> Match{Registered renderer found?}
    Match -- No --> Error[Raise LookupError]
    Match -- Yes --> Renderer[Instantiate event renderer]
    Renderer --> Setting[FormatContext.from_setting]
    Setting --> Valid{Format setting valid?}
    Valid -- No --> ValueError[Raise ValueError]
    Valid -- Yes --> Format{Selected format}
    Format --> Emoji[renderer.emoji]
    Format --> Minimal["renderer.minimal(ctx)"]
    Format --> Embed[renderer.embed]
Loading

File-Level Changes

Change Details Files
Replace the monolithic format classes and cog-side format branching with an event-keyed renderer registry.
  • Add FormatContext parsing, generic Renderer API, decorator registration, and MRO-based lookup.
  • Implement dedicated member, moderation, and command renderers for emoji, minimal, embed, and mention behavior.
  • Route every modlog listener through a shared _emit path, including automod actions keyed by Action.
lightning/modlogformats/__init__.py
lightning/modlogformats/base.py
lightning/modlogformats/member.py
lightning/modlogformats/moderation.py
lightning/modlogformats/commands.py
lightning/cogs/modlog/cog.py
Introduce renderer-specific event models and relocate shared formatting helpers to remove the events/modlogformats import cycle.
  • Add join, leave, screening, and timed-action-expiry event classes while adopting CommandEvent.
  • Move user, action, escaping, timestamp, and DM message helpers into lightning.formatters.
  • Preserve public helper imports through the new modlogformats package exports.
lightning/events.py
lightning/formatters.py
lightning/modlogformats/__init__.py
lightning/cogs/emoji.py
lightning/cogs/infractions/cog.py
lightning/cogs/infractions/ui.py
lightning/cogs/mod/cog.py
lightning/cogs/reports/ui.py
lightning/cogs/stats.py
Preserve and test modlog output behavior while correcting identified formatting edge cases.
  • Handle nickname changes with no old or new nickname without failing.
  • Retain join/leave behavior for timestampless minimal output and apply allowed mentions only to emoji messages.
  • Fix screening punctuation, role spacing, and timeout-expiry line breaks; remove unused bot_addition and legacy format classes.
  • Add registry, format parsing, renderer output, inheritance, mention, expiry, and regression tests.
lightning/cogs/modlog/cog.py
lightning/modlogformats/member.py
lightning/modlogformats/moderation.py
lightning/utils/modlogformats.py
tests/test_modlogformats.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Co-authored-by: celveren <46062298+celveren@users.noreply.github.com>
Copilot AI requested a review from celveren October 6, 2026 14:05
@celveren
celveren marked this pull request as ready for review October 6, 2026 14:17

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="lightning/modlogformats/moderation.py" line_range="215-216" />
<code_context>
+        base = [f"\N{MEMO} **Infraction update**: ID: {event.after.id}"]
+
+        if self.moderator_changed:
+            base.append(f"\n__Old Moderator__: {escape_markdown_and_mentions(event.before.moderator)}"
+                        f"\n__New Moderator__: {escape_markdown_and_mentions(event.after.moderator)}")
+
+        if self.reason_changed:
</code_context>
<issue_to_address>
**Infraction updates stop logging**

When an infraction's moderator changes and emoji format is configured, `InfractionUpdate.emoji()` passes moderator objects to `escape_markdown_and_mentions()`, whose `escape_markdown()` expects text and raises `TypeError`, so the update is not logged.

Convert the moderator values to text before passing them to `escape_markdown_and_mentions()`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and this replaces the modlog formatting and dispatch path across many event types, so a renderer or format regression could send incorrect notifications to Discord channels. Reverting restores the old behavior but cannot retract messages that were already sent; subsequent messages would need to be corrected normally.

Blocking findings: lightning/modlogformats/moderation.py:216


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread lightning/modlogformats/moderation.py Resolved
@celveren
celveren merged commit ef69653 into master Oct 7, 2026
5 checks passed
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