TG-Spam

repository·master·Indexed 19 days ago

https://github.com/umputun/tg-spam

A self-hosted, multi-layered anti-spam solution for Telegram groups. It automates message deletion and user banning using a variety of detection methods, including LLMs (OpenAI GPT and Google Gemini), Combot Anti-Spam System (CAS) integration, stop words, emoji counts, and metadata analysis. TG-Spam is extensible via custom Lua plugins and can be deployed as a Docker container, a standalone binary, a Telegram bot, an HTTP API server, or as a library.

Tokens
48.7K
Snippets
95
Records
185
Agent score
65%

What's inside tg-spam

  1. Overview of simplechat - Toy Chat Server with Spam Protection

    master

    simplechat is a toy chat server designed to demonstrate how to integrate the tg-spam library for spam protection. It serves as a practical example of implementing spam detection and reporting within a chat application.

    Key features include:

    • Spam Protection: Demonstrates real-world usage of tg-spam for checking and reporting spam.
    • Authentication: Uses a simple in-memory session to authenticate users.
    • Persistence: Stores chat messages in an SQLite database.
    • Concurrency: Supports multiple synchronized clients and dynamic updates.
  2. Overview of TG-Spam

    master

    TG-Spam is a self-hosted anti-spam bot designed specifically for Telegram groups. It monitors messages and automatically deletes spammy content and bans the offending users.

    It is highly flexible and can be deployed as a Docker container on various architectures (amd64, arm64, armv7) or run as a standalone binary on Linux, macOS, and Windows.

    Key features include:

    • Automated Actions: Immediate message deletion and user banning.
    • Multi-faceted Detection: Uses message analysis, similarity checks, stop words, emoji counts, and meta-checks (links, images, etc.).
    • External Integrations: Cross-references with Combot Anti-Spam System (CAS) and supports LLM-based detection via OpenAI (GPT) and Google Gemini.
    • Extensibility: Supports custom logic via Lua plugins.
    • Multiple Modes of Operation: Can run as a Telegram bot, an HTTP API server, or be used as a library in your own code.
  3. Overview of the DB-backed configuration feature (PR #294)

    master

    The feature-db-configuration branch introduces a significant architectural change to how tg-spam manages settings. Key features include:

    • Nested Domain Model: Replaces the flat webapi.Settings view-model with a structured, nested app/config.Settings domain model.
    • DB-backed Config Store: Settings are persisted in a database with encryption support.
    • --confdb Mode: A new CLI mode for loading and saving configurations via the database.
    • Encryption: Configuration data is wrapped by a Crypter before being stored as a JSON blob in the database.

    This architecture allows for more complex, grouped settings (e.g., LLM providers, report thresholds, duplicate detection) that are easier to manage and persist across restarts.

  4. Understand the configuration persistence model

    master

    The application persists settings as a single JSON blob per row in the database.

    • Encryption: Sensitive string fields (like Telegram.Token, OpenAI.Token, Gemini.Token, and Server.AuthHash) are encrypted individually with an ENC: prefix when --confdb-encrypt-key is provided. Other fields are stored in plaintext within the JSON blob.
    • Schema Compatibility: The system is strictly additive. Adding new fields to the *config.Settings struct does not require migrations; old blobs will simply decode with zero-value defaults for the new fields.
    • Cleanup: When Save is called, the entire blob is overwritten. Any JSON keys present in the database that no longer exist in the current code's struct will be dropped on the next write.
  5. How the Spam Reporting Flow Works

    master

    The reporting lifecycle follows these steps:

    1. Submission: A user replies to a message with /report. The system validates that neither the reporter nor the reported user is a superuser.
    2. Rate Limiting: The system checks if the reporter has exceeded the report.rate-limit within the report.rate-period.
    3. Storage: The report is recorded in the database. If the report is for an image, the caption is used as the message text.
    4. Threshold Check: The system checks if the number of reports for this specific message has reached the report.threshold.
    5. Notification:
      • If the threshold is reached for the first time, a new notification is sent to the admin chat.
      • If the threshold was already met and more reports arrive, the existing admin notification is updated (edited) to include the new reporters and an updated count.
    6. Resolution: Once an admin acts (Approve/Reject/Ban), the reports are cleared from the database and the admin notification is updated to reflect the outcome (e.g., "banned by admin").
  6. How Lua Plugins work in tg-spam

    master

    Lua plugins allow you to implement custom spam detection logic without modifying the Go source code. Each plugin is a standalone .lua file that must define a check function. This function is called by tg-spam for incoming requests and determines if a message should be flagged.

    To be valid, the check function must return exactly two values:

    1. A boolean: true if the message is spam, false otherwise.
    2. A string: Details or a reason for the spam classification.
    function check(request)
        -- logic here
        return true, "reason for spam"
    end
  7. Understand the Settings data model

    master

    The application configuration is organized into a hierarchical Settings struct, grouped by functional domains. This structure is used regardless of whether settings are loaded from CLI or a database.

    Core Domains:

    • Telegram: Telegram-specific settings (e.g., Token).
    • OpenAI / Gemini / LLM: AI provider settings and tokens.
    • Admin: Administrative settings.
    • Server: Server runtime settings (e.g., Listen address).
    • Files: File system paths (e.g., DynamicDataPath).
    • Spam Detection: Flat fields like SimilarityThreshold, MinMsgLen, MaxEmoji, and MinSpamProbability.

    Transient Settings: Certain fields are marked as Transient and are never persisted to the database. These include connection details (DataBaseURL), debug flags (Dbg, TGDbg), and the encryption key (ConfigDBEncryptKey).

    // Example of the Settings structure hierarchy
    type Settings struct {
        InstanceID string `json:"instance_id"` 
        Telegram   TelegramSettings `json:"telegram"` 
        OpenAI     OpenAISettings   `json:"openai"` 
        // ... other domains
        SimilarityThreshold float64 `json:"similarity_threshold"` 
        Transient           TransientSettings `json:"-"` 
    }
  8. How the admin message fallback mechanism works

    master

    When an admin forwards a spam message to the admin chat, the bot normally uses a Locator to find the original message's metadata. If the Locator lookup fails (e.g., due to a bot restart or network gap), the bot now enters a degraded fallback path if the original sender's identity is available via ForwardOrigin.

    Fallback Logic Flow

    If locator.Message() fails, the bot checks the fwdID (extracted from ForwardOrigin):

    1. If fwdID != 0 (Sender is a User):

      • Super-user Check: The bot verifies if the user is a super-user; if so, it ignores the request.
      • Remove Approved: Calls a.bot.RemoveApprovedUser(fwdID) (errors are logged but do not stop the process).
      • Detection Results: Calls a.bot.OnMessage(...) to display detection results to the admin.
      • Spam Update: If not in dry run, calls a.bot.UpdateSpam(msgTxt).
      • Ban: If not in dry run, calls banUserOrChannel() using the fwdID and respects the current trainingMode.
      • Admin Warning: Sends a warning to the admin chat stating that the original message must be deleted manually, providing the username, user ID, and a text snippet for identification.
      • Note: Because the original msgID is unavailable, the fallback path does not include an unban button.
    2. If fwdID == 0 (Hidden User, Channel, or no ForwardOrigin):

      • The operation aborts and returns an error, as the bot cannot identify the target for banning or spam updates.
  9. Resolve multiple LLM results with LLM Consensus

    master

    If multiple LLM providers (OpenAI and Gemini) are configured, use --llm.consensus to decide the final outcome:

    • any (default): If any eligible LLM disagrees with the base decision, the decision flips.
    • all: All eligible LLMs must agree before the decision flips.

    Each request is subject to --llm.request-timeout (default: 30s).

  10. How quote handling works for /spam and /report commands

    master

    When using the /spam (admin) or /report (user) commands on a message that contains quoted text (a reply to an external channel), the system is designed to process both the main message text and the quoted text to ensure the spam classifier receives the full context.

    To ensure correct behavior, the quoted text is concatenated to the main message text using a newline separator.

    Important Implementation Detail: To avoid losing data from media messages (like images with captions), the quote concatenation must occur after the transform fallback logic. If the main text is empty, the system first attempts to extract text via a transform function; the quoted text is then appended to that result.

    // 1. Initial text assignment
    msgTxt := origMsg.Text
    
    // 2. Transform fallback (handles empty text/captions)
    if msgTxt == "" {
        m := transform(origMsg)
        msgTxt = m.Text
    }
    
    // 3. Quote concatenation (MUST happen after transform)
    if origMsg.Quote != nil && origMsg.Quote.Text != "" {
        msgTxt = msgTxt + "\n" + origMsg.Quote.Text
    }
  11. How max-short-msg-count detection works

    master

    The max-short-msg-count check is a stateless mechanism that calculates the number of "non-graduating" messages a user has sent. It identifies users who are sending many messages that fail to move them into the "approved" state.

    A user is flagged if the following conditions are met simultaneously:

    1. Message Count Threshold: (Total messages from user - Approved count) >= MaxShortMsgCount.
    2. Unapproved Status: The user's approvedCount is less than the configured FirstMessagesCount.
    3. Message Length: The current message is considered "short" (its length in runes is less than the detector's MinMsgLen).
    4. Not Check-Only: The request is not a CheckOnly request (meaning it is allowed to perform actions like banning/deleting).

    This logic relies on the fact that every successful "long" ham message increments the approvedUsers[id].Count. By subtracting this from the total message count, the system isolates the number of short or unclassified messages sent by the user.

  12. Configure tg-spam using the database mode (--confdb)

    master

    To run tg-spam using a database as the source of truth for configuration, use the --confdb flag. When this flag is present, the application expects settings to be loaded from the specified database.

    Key behaviors:

    • Strict Loading: If --confdb is specified but the database contains no settings, the application will exit with an error: failed to load configuration from database: no settings found in database.
    • Persistence: Settings modified via the Web UI (e.g., via PUT /config) are persisted to the database.
    • Encryption: Sensitive credentials like Gemini.Token are encrypted at rest using the ENC: prefix. The encryption key is provided via the CONFDB_ENCRYPT_KEY environment variable or the --confdb-encrypt-key flag.
    • Precedence: CLI flags follow the same precedence rules as other credentials (e.g., Gemini.Token provided via CLI will override the value stored in the database).
    CONFDB_ENCRYPT_KEY=test-key-test-key-test-key /tmp/tg-spam --confdb --db=/tmp/tg-spam-smoke.db --dry --token=dummy --server.enabled