Community Add-ons & Extras

A collection of community-contributed scripts, add-ons, and extensions for Script Archive programs.

Contributions from users who extended and enhanced the original scripts.

About Community Extras

Over the years, the Script Archive community created numerous add-ons, modifications, and utility scripts that extended the functionality of the core CGI programs. These contributions ranged from simple enhancements to complete rewrites with new features.

FormMail Extensions

Auto-reply emails, database logging, fax integration, and file sending capabilities.

WWWBoard Enhancements

Multiple boards, search, preview, subscriptions, digests, and access control.

Utility Scripts

Database converters, duplicate removers, bookmark importers, and more.

FormMail Extensions

Add-on Author Description Features
BFormMail Brian Sietz Enhanced FormMail v1.6 with multiple additions Auto-reply Database Fax Support
MailFile Script Mike Wheeler Send multiple files by email from web requests File Attachment Multi-file
SendCGI SMH Software Email text files to users from web forms Text Email Simple Form
Modern Alternative: PHP Mailer
<?php
// Modern email with auto-reply using PHPMailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

function processFormWithAutoReply($formData) {
    // Send main notification
    $mail = new PHPMailer(true);

    try {
        $mail->isSMTP();
        $mail->Host = 'smtp.example.com';
        $mail->SMTPAuth = true;
        $mail->Username = '[email protected]';
        $mail->Password = 'password';
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
        $mail->Port = 587;

        $mail->setFrom('[email protected]', 'Website');
        $mail->addAddress('[email protected]');
        $mail->Subject = 'New Form Submission';
        $mail->Body = formatFormData($formData);
        $mail->send();

        // Send auto-reply to visitor
        $autoReply = new PHPMailer(true);
        $autoReply->isSMTP();
        // ... same SMTP config
        $autoReply->setFrom('[email protected]', 'Website');
        $autoReply->addAddress($formData['email']);
        $autoReply->Subject = 'Thank you for your message';
        $autoReply->Body = "Dear {$formData['name']},\n\nThank you for contacting us...";
        $autoReply->send();

        // Log to database
        logToDatabase($formData);

        return true;
    } catch (Exception $e) {
        error_log("Mail Error: " . $mail->ErrorInfo);
        return false;
    }
}

function logToDatabase($data) {
    $pdo = new PDO('mysql:host=localhost;dbname=forms', 'user', 'pass');
    $stmt = $pdo->prepare("INSERT INTO submissions (name, email, message, created_at) VALUES (?, ?, ?, NOW())");
    $stmt->execute([$data['name'], $data['email'], $data['message']]);
}
?>

WWWBoard Enhancements

Craig D. Horton of DBASICS created an extensive collection of WWWBoard modifications that added enterprise-level features to the basic discussion board.

Multiple Boards

Run multiple separate discussion boards using a single wwwboard.cgi script installation.

  • Shared codebase reduces maintenance
  • Configuration per board
  • Independent message numbering
  • Separate styling per board
Board Search

Add search functionality to find messages across all WWWBoard posts.

  • Full-text search
  • Search by author
  • Date range filtering
  • Results pagination
Message Preview

Allow users to preview their message before final submission.

  • See formatted output
  • Check for errors
  • Edit before posting
  • Reduce spam posts
Access Control

Restrict posting to authorized users or block troublesome posters.

  • Email whitelist/blacklist
  • IP-based blocking
  • Private boards
  • Spam prevention
Subscriptions

Email notifications when new messages are posted.

  • Subscribe/unsubscribe
  • Instant notifications
  • Manage subscriber list
  • Thread subscriptions
Daily Digest

Send daily digest emails instead of individual notifications.

  • Scheduled delivery
  • Reduced email volume
  • Summary format
  • Cron job integration
Modern Implementation: Forum Features
<?php
// Modern forum subscription system
class ForumSubscription {
    private $pdo;

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

    public function subscribe($userId, $threadId, $type = 'instant') {
        $stmt = $this->pdo->prepare("
            INSERT INTO subscriptions (user_id, thread_id, notification_type, created_at)
            VALUES (?, ?, ?, NOW())
            ON DUPLICATE KEY UPDATE notification_type = ?
        ");
        return $stmt->execute([$userId, $threadId, $type, $type]);
    }

    public function unsubscribe($userId, $threadId) {
        $stmt = $this->pdo->prepare("
            DELETE FROM subscriptions WHERE user_id = ? AND thread_id = ?
        ");
        return $stmt->execute([$userId, $threadId]);
    }

    public function notifySubscribers($threadId, $newPost) {
        // Get instant notification subscribers
        $stmt = $this->pdo->prepare("
            SELECT u.email, u.name, s.notification_type
            FROM subscriptions s
            JOIN users u ON s.user_id = u.id
            WHERE s.thread_id = ? AND s.notification_type = 'instant'
        ");
        $stmt->execute([$threadId]);

        foreach ($stmt->fetchAll() as $subscriber) {
            $this->sendNotification($subscriber, $newPost);
        }
    }

    public function sendDailyDigest() {
        // Get digest subscribers and their threads
        $stmt = $this->pdo->prepare("
            SELECT u.id, u.email, u.name
            FROM users u
            JOIN subscriptions s ON u.id = s.user_id
            WHERE s.notification_type = 'digest'
            GROUP BY u.id
        ");
        $stmt->execute();

        foreach ($stmt->fetchAll() as $user) {
            $posts = $this->getTodaysPosts($user['id']);
            if (!empty($posts)) {
                $this->sendDigestEmail($user, $posts);
            }
        }
    }
}
?>
// Real-time forum notifications with WebSockets
class ForumNotifications {
    constructor(userId) {
        this.userId = userId;
        this.socket = null;
        this.subscriptions = new Set();
    }

    connect() {
        this.socket = new WebSocket('wss://example.com/ws/forum');

        this.socket.onopen = () => {
            this.socket.send(JSON.stringify({
                type: 'auth',
                userId: this.userId
            }));
        };

        this.socket.onmessage = (event) => {
            const data = JSON.parse(event.data);

            switch (data.type) {
                case 'new_post':
                    this.handleNewPost(data);
                    break;
                case 'subscription_confirmed':
                    this.subscriptions.add(data.threadId);
                    break;
            }
        };
    }

    subscribe(threadId) {
        this.socket.send(JSON.stringify({
            type: 'subscribe',
            threadId: threadId
        }));
    }

    unsubscribe(threadId) {
        this.socket.send(JSON.stringify({
            type: 'unsubscribe',
            threadId: threadId
        }));
        this.subscriptions.delete(threadId);
    }

    handleNewPost(data) {
        // Show browser notification
        if (Notification.permission === 'granted') {
            new Notification('New Reply', {
                body: `${data.author} replied to "${data.threadTitle}"`,
                icon: '/images/forum-icon.png'
            });
        }

        // Update UI
        this.updateThreadBadge(data.threadId);
        this.showToast(`New reply in ${data.threadTitle}`);
    }

    showToast(message) {
        const toast = document.createElement('div');
        toast.className = 'toast show position-fixed bottom-0 end-0 m-3';
        toast.innerHTML = `
            
${message}
`; document.body.appendChild(toast); setTimeout(() => toast.remove(), 5000); } } // Initialize const notifications = new ForumNotifications(currentUserId); notifications.connect();

Utility Scripts

Author: Jude Lynell Hollins

A freeware Perl script for interactive sorting of bibliographic citations on the web. Based on the Free For All Link concept, it allows users to submit and organize citations by category.

Features:
  • Sort citations by section
  • Save to flat-file in BASIC format
  • Web-based submission form
  • Category organization

Version: 1.0 (December 15, 1995)

Converts a Free For All link page into a database format suitable for the Random Link Generator.

#!/usr/bin/perl
# make_list.pl - Convert FFA links to Random Link database
# Usage: perl make_list.pl links.html > urls.txt

use strict;
use warnings;

my $input_file = shift || 'links.html';

open(my $fh, '<', $input_file) or die "Cannot open $input_file: $!";

while (my $line = <$fh>) {
    # Extract URLs from anchor tags
    while ($line =~ /href\s*=\s*["']?([^"'\s>]+)/gi) {
        my $url = $1;
        # Skip internal links
        next if $url =~ /^#/;
        next if $url =~ /^mailto:/i;
        print "$url\n";
    }
}

close($fh);

Author: Usama Wazeer

Language: C

Automatically removes duplicate links from your links.html file. Reports all duplicate URLs and creates a clean output file.

Modern PHP Equivalent:
<?php
// Remove duplicate links from HTML file
function removeDuplicateLinks($inputFile, $outputFile) {
    $content = file_get_contents($inputFile);
    $urls = [];
    $duplicates = [];
    $lineNum = 0;

    $lines = explode("\n", $content);
    $cleanLines = [];

    foreach ($lines as $line) {
        $lineNum++;

        // Check for URLs in this line
        if (preg_match_all('/href\s*=\s*["\']?([^"\'>\s]+)/i', $line, $matches)) {
            foreach ($matches[1] as $url) {
                $normalizedUrl = strtolower(trim($url));

                if (isset($urls[$normalizedUrl])) {
                    $duplicates[] = [
                        'url' => $url,
                        'first_line' => $urls[$normalizedUrl],
                        'duplicate_line' => $lineNum
                    ];
                    continue 2; // Skip this entire line
                }

                $urls[$normalizedUrl] = $lineNum;
            }
        }

        $cleanLines[] = $line;
    }

    // Write clean file
    file_put_contents($outputFile, implode("\n", $cleanLines));

    // Report duplicates
    if (!empty($duplicates)) {
        echo "Found " . count($duplicates) . " duplicate URLs:\n";
        foreach ($duplicates as $dup) {
            echo "  {$dup['url']} - First: line {$dup['first_line']}, Duplicate: line {$dup['duplicate_line']}\n";
        }
    }

    return count($duplicates);
}

// Usage
removeDuplicateLinks('links.html', 'links_clean.html');
?>

Author: Jon Wichmann Hansen

Converts Netscape Navigator 1.2 - 2.0 beta bookmark files into a format readable by the Random Link Generator.

Modern JavaScript Equivalent:
// Convert browser bookmarks (HTML) to various formats
class BookmarkConverter {
    constructor(htmlContent) {
        this.parser = new DOMParser();
        this.doc = this.parser.parseFromString(htmlContent, 'text/html');
        this.bookmarks = [];
        this.parseBookmarks(this.doc.body);
    }

    parseBookmarks(element, folder = 'root') {
        const links = element.querySelectorAll('a');

        links.forEach(link => {
            if (link.href && !link.href.startsWith('javascript:')) {
                this.bookmarks.push({
                    title: link.textContent.trim(),
                    url: link.href,
                    folder: folder,
                    addDate: link.getAttribute('add_date'),
                    icon: link.getAttribute('icon')
                });
            }
        });

        // Parse folders
        const folders = element.querySelectorAll('h3');
        folders.forEach(h3 => {
            const folderName = h3.textContent.trim();
            const dl = h3.nextElementSibling;
            if (dl && dl.tagName === 'DL') {
                this.parseBookmarks(dl, folderName);
            }
        });
    }

    toRandomLinkFormat() {
        // Format: URL|Title (pipe-delimited)
        return this.bookmarks
            .map(b => `${b.url}|${b.title}`)
            .join('\n');
    }

    toJSON() {
        return JSON.stringify(this.bookmarks, null, 2);
    }

    toCSV() {
        const header = 'Title,URL,Folder\n';
        const rows = this.bookmarks
            .map(b => `"${b.title.replace(/"/g, '""')}","${b.url}","${b.folder}"`)
            .join('\n');
        return header + rows;
    }
}

// Usage with file input
document.getElementById('bookmarkFile').addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const content = await file.text();

    const converter = new BookmarkConverter(content);

    console.log('Random Link Format:');
    console.log(converter.toRandomLinkFormat());

    console.log('\nJSON Format:');
    console.log(converter.toJSON());
});

Community Contributors

These add-ons were made possible by generous contributions from the Script Archive community:

Top Contributors
  • Craig D. Horton (DBASICS) 9 scripts
  • Brian Sietz 1 script
  • Mike Wheeler 1 script
  • Jude Lynell Hollins 1 script
  • SMH Software 1 script
  • Usama Wazeer 1 script
  • Jon Wichmann Hansen 1 script
Modern Open Source Alternatives

Frequently Asked Questions

Most of these scripts were hosted on personal websites that no longer exist. However, some may be found on archive.org's Wayback Machine. The concepts and techniques demonstrated in these scripts can be implemented using modern programming languages and frameworks with improved security.

While Perl is still available on most servers, using these legacy scripts is not recommended due to security vulnerabilities. The original scripts were written before modern security practices like input validation, SQL injection prevention, and XSS protection were standard. We recommend using modern alternatives or rewriting the functionality with current security best practices.

BFormMail added three key features: automatic courtesy reply emails sent to visitors after form submission, the ability to append form data to a flat-file database for record keeping, and integration with fax-to-email services for offline notification. These features addressed common business needs that the basic FormMail didn't handle.

WWWBoard was extremely popular as one of the first web-based discussion systems. As communities grew, they needed features like search, email notifications, spam blocking, and multi-board support. Craig D. Horton at DBASICS recognized these needs and created a full set of add-ons that became popular for serious WWWBoard sites.

These scripts used flat-file storage, typically text files with one subscriber email per line. For digests, a cron job would run daily, read the day's posts from the message files, compile them into a single email, and send to the subscriber list. While simple, this approach worked well for smaller communities but didn't scale efficiently.

Modern forum software like phpBB, Discourse, or Flarum includes all these features built-in: email notifications, digests, search, user management, and spam prevention. For custom implementations, frameworks like Laravel (PHP), Django (Python), or Rails (Ruby) provide the tools to build these features with proper security, scalability, and real-time capabilities via WebSockets.

In the mid-1990s, C programs were often used for performance-critical tasks. Processing large HTML files to find duplicates was faster in compiled C than interpreted Perl, especially on the limited hardware of the era. Today, this task can be easily handled by scripting languages, and most systems can process millions of URLs in seconds.

The Script Archive is maintained as a historical resource and educational reference. While we don't accept new script submissions, the spirit of open source contribution lives on through platforms like GitHub, GitLab, and npm. You can share your modern scripts there and contribute to the thousands of open source projects that continue the tradition of free, community-developed software.

Related Scripts

FormMail

The original form-to-email script that many add-ons extended.

WWWBoard

The discussion board that inspired numerous community enhancements.

Random Link Generator

The script that utility tools like bookmark converters targeted.