Skip to content

Latest commit

 

History

History
222 lines (157 loc) · 7.16 KB

File metadata and controls

222 lines (157 loc) · 7.16 KB

Draft — This document was generated by AI and may not have been reviewed for accuracy.

Echomail Robots

Echomail robots watch echo areas for matching messages and automatically process them. The framework is designed to be generic and extensible: any number of robot rules can be configured, each pointing at an echo area with a processor that knows how to handle that type of message.


How It Works

A robot rule (echomail_robots table) links an echo area to a processor. When the runner executes, it:

  1. Queries the echo area for messages newer than its last-processed cursor (last_processed_echomail_id)
  2. Optionally filters by a case-insensitive subject substring (subject_pattern)
  3. Passes each matching message to the configured processor
  4. Advances the cursor and updates run statistics

Robots are stateless between runs — progress is tracked entirely in the database row.


Running Robots

Via cron (recommended)

* * * * * php /path/to/binkterm-php/scripts/echomail_robots.php --quiet

Manually from the command line

# Run all enabled robots
php scripts/echomail_robots.php

# Run a specific robot by ID
php scripts/echomail_robots.php --robot-id=3

# Preview without writing anything
php scripts/echomail_robots.php --dry-run

# Suppress output
php scripts/echomail_robots.php --quiet

Output for each robot:

[OK]    Robot #1 (FSX_DAT iBBS): examined=42 processed=38
[ERROR] Robot #2 (Bad Robot): examined=0 processed=0 error=Unknown processor type: fake_type

Via the admin UI

Navigate to Admin → Area Management → Echomail Robots. Click the ▶ Run Now button on any robot row to execute it immediately and see a summary toast.


Database Schema

echomail_robots

Column Type Description
id SERIAL PK
name VARCHAR(100) Human-readable label
echoarea_id INTEGER FK Echo area to watch
subject_pattern VARCHAR(255) Substring match on subject (case-insensitive, ILIKE). Leave null to process all messages.
processor_type VARCHAR(100) Matches a registered processor's getProcessorType()
processor_config JSONB Optional processor-specific configuration
enabled BOOLEAN Inactive robots are skipped by the runner
last_processed_echomail_id INTEGER Cursor — next run starts at id > this
last_run_at TIMESTAMPTZ Timestamp of last execution
messages_examined INTEGER Running total of messages seen
messages_processed INTEGER Running total of messages handled
last_error TEXT Last exception message, if any

Source Files

File Purpose
src/Robots/MessageProcessorInterface.php Interface all processors must implement
src/Robots/EchomailRobotRunner.php Fetches messages, dispatches to processors, updates cursors
src/Robots/Processors/IbbsLastCallProcessor.php Built-in processor for iBBS last-call announcements
scripts/echomail_robots.php CLI entry point

Writing a New Processor

Create a class in src/Robots/Processors/ that implements MessageProcessorInterface:

<?php

namespace BinktermPHP\Robots\Processors;

use BinktermPHP\Robots\MessageProcessorInterface;

class MyProcessor implements MessageProcessorInterface
{
    private \PDO $db;

    public function __construct(\PDO $db)
    {
        $this->db = $db;
    }

    public static function getProcessorType(): string
    {
        return 'my_processor';  // stored in echomail_robots.processor_type
    }

    public static function getDisplayName(): string
    {
        return 'My Processor';
    }

    public static function getDescription(): string
    {
        return 'Does something useful with matching messages.';
    }

    public function processMessage(array $message, array $robotConfig): bool
    {
        // $message contains echomail table columns:
        //   id, subject, from_name, to_name, message_text, echoarea_id,
        //   date, origin, etc.
        //
        // $robotConfig is the decoded processor_config JSONB from the robot rule.
        //
        // Return true if the message was handled, false to skip/ignore.

        $text = $message['message_text'] ?? '';
        if (empty($text)) {
            return false;
        }

        // ... do work ...

        return true;
    }
}

Then register it in EchomailRobotRunner::PROCESSORS:

private const PROCESSORS = [
    'ibbslastcall_rot47' => IbbsLastCallProcessor::class,
    'my_processor'       => \BinktermPHP\Robots\Processors\MyProcessor::class,
];

The new processor will immediately appear in the admin UI's processor-type dropdown.

The $message array

The array passed to processMessage() is a raw row from the echomail table. Commonly used columns:

Column Description
id Message ID
subject Message subject
from_name Author name
to_name Recipient name (All for broadcast messages)
message_text Full message body
echoarea_id Echo area ID
date Message date string
origin Origin line

Processor config

The processor_config JSONB column allows per-robot configuration for a processor. Pass anything you need — the decoded array is provided as $robotConfig to every processMessage() call. Currently the admin UI does not expose a config editor; values can be set directly in the database or added to the modal in a future update.


Built-in Processors

ibbslastcall_rot47 — iBBS Last Call (ROT47)

Handles BBS node announcement messages posted to the FSX_DAT echo area by iBBS software. These messages have the subject ibbslastcall-data and a ROT47-encoded body.

Recommended robot rule settings:

Field Value
Echo Area FSX_DAT (or whichever area carries these messages)
Subject Pattern ibbslastcall-data
Processor Type ibbslastcall_rot47

Message body format (after ROT47 decode, newline-separated):

Line 0  >>> BEGIN marker
Line 1  Sysop name
Line 2  BBS name
Line 3  Date (MM/DD/YY or YY/MM/DD)
Line 4  Time
Line 5  Location
Line 6  Operating system
Line 7  telnet-host:port
Line 8  >>> END marker

On a successful parse the processor upserts a row in bbs_directory. If the BBS name already exists (case-insensitive), the sysop, location, OS, and telnet address are updated and last_seen is refreshed. Manual entries (source = 'manual') are updated in place but their source flag is preserved.

ROT47 maps every printable ASCII character in the range 33–126 by rotating 47 positions: chr((ord(c) - 33 + 47) % 94 + 33). It is its own inverse — applying it twice returns the original text.


Future: Netmail Robots

The interface is designed to work for netmail as well. When needed:

  1. Create a netmail_robots table (same shape as echomail_robots, substituting to_address VARCHAR for echoarea_id)
  2. Add src/Robots/NetmailRobotRunner.php — identical logic, queries the netmail table
  3. Add scripts/netmail_robots.php CLI entry point

All existing MessageProcessorInterface implementations will work without modification; they receive the same $message array shape regardless of source table.