Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Shaw

Shaw is a small Discord spoiler bot I originally built for a Silksong community.

The first version sounded pretty simple: keep a list of spoiler words and delete messages that contain them.

Then the false positives started.

A word should be allowed inside ||Discord spoiler tags||. It should not be detected because it appears inside a URL. Markdown links caused another problem because the visible text and the URL do not behave the same way. I ended up spending more time deciding when not to flag a word than detecting the word itself.

That became the interesting part of the project.

What Shaw Does

Shaw watches selected Discord channels for configured spoiler terms.

When it finds an unhidden spoiler, it:

  • deletes the message;
  • sends the user a temporary warning;
  • logs the detected term for staff;
  • increments the user's warning count; and
  • sends an extra staff log when a configured warning threshold is reached.

Users with whitelisted roles can be ignored, and staff have slash commands for checking warning counts and correcting them when needed.

Avoiding False Positives

A normal word filter can check whether a message contains a banned word. Shaw has to care about where the match appears in the message.

Before checking spoiler terms, the bot finds several protected parts of the text:

  • Discord spoiler spans: ||like this||
  • URLs
  • the visible text inside Markdown links

Each configured spoiler term is compiled into a case-insensitive regular expression. When the bot finds a match, it checks the start and end positions of that match against the protected spans.

If the match falls inside one of them, Shaw ignores it.

In simplified form:

message
   |
   +--> find Discord spoiler spans
   |
   +--> find URL spans
   |
   +--> find Markdown link-text spans
   |
   v
search for configured spoiler terms
   |
   v
is the match inside a protected span?
   |
   +--> yes: ignore it
   |
   +--> no: flag the message

This means a term can be flagged in normal text while still being allowed in situations where deleting the message would be a false positive.

For example:

Lace

would be detected if Lace is configured as a spoiler term.

But:

||Lace||

is already hidden by Discord and should be allowed.

The same idea applies to matches that only appear as part of a URL or protected Markdown text.

Warning Tracking

Shaw keeps a warning count for each user in a local JSON file.

Count changes use an asynchronous lock so two updates do not modify the in-memory warning state at the same time.

This is deliberately simple persistence. Shaw was built for one server and does not use a database. Warning counts survive normal restarts as long as the persistence file remains available, but they can be lost if the deployment environment clears local storage.

Staff Commands

Command What it does
/top-spoilers Shows users with the highest spoiler warning counts
/spoiler-check Checks one user's current warning count
/clear-user Lets authorized staff reset or override a warning count

/clear-user requires a configured staff role and asks the staff member to confirm the change before applying it. Confirmed changes are written to the moderation log.

Configuration

Most server-specific settings are currently stored near the top of main.py.

Setting Purpose
ALLOWED_CHANNEL_IDS Channels checked for spoilers
ALLOWED_STAFF Staff roles allowed to use moderation commands
WHITELIST_ROLE_IDS Roles ignored by the spoiler checker
LOG_CHANNEL_ID Channel used for warning and audit logs
WARN_THRESHOLD Warning count that triggers an additional staff log
AUTO_DELETE_BOT_REPLY_SECONDS How long temporary warning messages remain visible
PERSISTENCE_FILE Local JSON file used for warning counts

The spoiler vocabulary is also currently stored directly in main.py.

Running Shaw

Shaw requires Python 3.10 or newer.

Install the dependencies:

pip install -r requirements.txt

Set the Discord bot token:

export BOT_TOKEN="your bot token here"

On Windows PowerShell:

$env:BOT_TOKEN="your bot token here"

Then run:

python main.py

The Discord application needs Message Content Intent enabled and enough server permissions to read and delete messages, send warnings, use slash commands, and post moderation logs.

Current Limitations

Shaw is a small server-specific project, not a general moderation framework.

The main limitations are intentional or architectural:

  • server configuration is hardcoded in main.py;
  • the spoiler vocabulary is stored directly in the source;
  • warning counts use local JSON persistence instead of a database; and
  • matching, Discord events, commands, and persistence currently live in one Python module.

If I continued developing Shaw, the first things I would separate are the spoiler matcher and persistence layer. The matcher is the part I would especially want isolated and tested because most of the interesting edge cases happen there.

Why Shaw Is Public

This is a repost of an older project from my previous GitHub account.

It is a much smaller bot than projects such as Avenue Guard or Nauta, but I kept it public because I like the problem it ended up solving. What started as a basic word filter became mostly an exercise in avoiding bad matches.

License

Shaw is available under the MIT License.

About

A small Discord bot for catching unhidden Silksong spoilers

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages