Skip to main content

Clear spam and abuse from the Supporter Wall automatically

Premium Intermediate

Spam on the Supporter Wall does not usually look like the message board spam you are picturing. It arrives as a wall of characters in a script your audience does not read, or as a message whose only purpose is the link inside it. Both slip past a word list, because neither contains a word you thought to ban.

This recipe screens a message on save and clears it when it fails, using three checks that catch different things. It is the enforcement companion to Moderate or audit-log every Supporter Wall message, which covers the hook’s mechanics — read that one first if you have not.

The Three Checks

A word list. The obvious one, and the weakest on its own. The example ships an empty list on purpose — the terms you want are specific to your audience, your show’s tone and the languages your listeners write in, and a list shipped by us would be simultaneously too aggressive for one show and useless for another. WORDS is where yours go, lowercase, one per entry.

Script detection. A message written entirely in a script your show does not publish in is the single highest-signal indicator available, and it needs no vocabulary at all. The example flags a message that is predominantly Cyrillic or CJK. Set the threshold with your own audience in mind, and be honest about who you would be silencing — a genuine supporter writing in Russian or Chinese trips this exactly as a spammer does. If your show has listeners who write in those scripts, use this to flag for review rather than to clear.

Link density. Almost no honest “why I support” message contains a URL, and spam almost always does. Cheap, and the highest ratio of catches to false positives of the three.

Notes

Clearing from inside the handler does not re-enter the hook, because a direct update_user_meta() write is silent. That is what makes this safe to run on every save; the companion recipe explains the trade-off in full.

Check for an empty message first. The hook fires on clears as well as writes, so a subscriber deleting their own message would otherwise be screened, pass, and cost you the work for nothing.

Screening is not the same as deleting the subscriber. This clears one field. The person keeps their subscription, their feed and their access — reach for something heavier only deliberately, and not from inside a save handler.

Log what you cleared, and keep the original. A rule that silently eats a real supporter’s message is worse than the spam it was written to stop, and you will not find out unless you can look. The example writes the original to a second meta key before blanking the live one.

Code

<?php
add_action( 'benecaster_subscriber_wall_message', function ( int $user_id, string $message ): void {
    // The hook fires on clears too. Nothing to screen, so stop here.
    if ( '' === trim( $message ) ) {
        return;
    }

    // 1. Word list. Deliberately empty - add your own terms, lowercase.
    //    e.g. [ 'exampleterm', 'anotherterm' ]
    $words = [];

    $haystack = mb_strtolower( $message );
    foreach ( $words as $word ) {
        if ( str_contains( $haystack, $word ) ) {
            my_clear_wall_message( $user_id, $message, 'wordlist' );
            return;
        }
    }

    // 2. Predominantly non-Latin script. Tune the threshold to your audience,
    //    or drop this check entirely if your listeners write in these scripts.
    $cyrillic = preg_match_all( '/\p{Cyrillic}/u', $message );
    $cjk      = preg_match_all( '/\p{Han}|\p{Hiragana}|\p{Katakana}/u', $message );
    $letters  = max( 1, preg_match_all( '/\p{L}/u', $message ) );

    if ( ( $cyrillic + $cjk ) / $letters > 0.5 ) {
        my_clear_wall_message( $user_id, $message, 'script' );
        return;
    }

    // 3. Any URL at all. Honest messages rarely contain one.
    if ( preg_match( '#https?://|www\.|\b[a-z0-9-]+\.(com|net|ru|cn|xyz|top)\b#i', $message ) ) {
        my_clear_wall_message( $user_id, $message, 'link' );
    }
}, 10, 2 );

/**
 * Blank the live message, but keep the original so a false positive is
 * recoverable and reviewable. The direct write does not re-enter the hook.
 */
function my_clear_wall_message( int $user_id, string $original, string $reason ): void {
    update_user_meta( $user_id, '_benecaster_subscriber_wall_message', '' );
    update_user_meta( $user_id, '_my_wall_message_cleared', [
        'original' => $original,
        'reason'   => $reason,
        'at'       => current_time( 'mysql' ),
    ] );
}

View on GitHub →

Hooks Used

Need this built rather than just documented? See our services →