PHP Classes

File: bin/update-domains

Recommend this page to a friend!
  Packages of Raul   Disposable Email Blocker Core   bin/update-domains   Download  
File: bin/update-domains
Role: Example script
Content type: text/plain
Description: Example script
Class: Disposable Email Blocker Core
Detect disposable or temporary email addresses
Author: By
Last change:
Date: 1 month ago
Size: 10,157 bytes
 

Contents

Class file image Download
#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
 * Updates the bundled disposable domains list from external sources.
 *
 * Usage:
 * ./bin/update-domains [--dry-run] [--source=name] [--output=path]
 *
 * Options:
 * --dry-run Show what would be done without writing
 * --source=name Only fetch from specific source (can be repeated)
 * --output=path Custom output path (default: src/Resources/disposable_domains.txt)
 * --config=path Custom config file path
 * --no-config Ignore config file
 * --verbose Show progress details
 *
 * Configuration:
 * Create a file named "disposable-blocker.php" in your project root:
 *
 * ```php
 * return [
 * 'sources' => [
 * new UrlSource('https://example.com/domains.txt', 'my-source'),
 * ],
 * 'exclude_sources' => ['fakefilter'],
 * 'output_path' => 'storage/disposable_domains.txt',
 * ];
 * ```
 */

// Find autoloader
$autoloadPaths = [
   
__DIR__ . '/../vendor/autoload.php', // Running from package directly
   
__DIR__ . '/../../../autoload.php', // Running from vendor/bin
   
getcwd() . '/vendor/autoload.php', // Running from project root
];

$autoloaded = false;
foreach (
$autoloadPaths as $autoloadPath) {
    if (
is_file($autoloadPath)) {
        require_once
$autoloadPath;
       
$autoloaded = true;
        break;
    }
}

if (!
$autoloaded) {
   
fwrite(STDERR, "Could not find autoload.php. Run composer install first.\n");
    exit(
1);
}

use
Raul3k\DisposableBlocker\Core\Config\Configuration;
use
Raul3k\DisposableBlocker\Core\Sources\SourceRegistry;

// Parse arguments
$options = getopt('', ['dry-run', 'source:', 'output:', 'config:', 'no-config', 'verbose', 'help']);
$args = array_slice($argv, 1);

if (isset(
$options['help']) || in_array('--help', $args) || in_array('-h', $args)) {
    echo <<<HELP
Update Disposable Domains List

Usage:
  ./bin/update-domains [options]
  vendor/bin/update-domains [options]

Options:
  --dry-run Show statistics without writing the file
  --source=name Only fetch from specific source (can be repeated)
  --output=path Custom output path
  --config=path Custom config file path
  --no-config Ignore configuration file
  --verbose Show detailed progress
  --help Show this help message

Configuration File:
  Create "disposable-blocker.php" in your project root:

  <?php
  use Raul3k\DisposableBlocker\Core\Sources\UrlSource;
  use Raul3k\DisposableBlocker\Core\Sources\FileSource;

  return [
      // Add custom sources
      'sources' => [
          new UrlSource(
              url: 'https://example.com/domains.txt',
              name: 'my-custom-source'
          ),
          new FileSource(
              path: __DIR__ . '/my-domains.txt',
              name: 'local-domains'
          ),
      ],

      // Exclude built-in sources (optional)
      'exclude_sources' => ['fakefilter'],

      // Custom output path (optional)
      'output_path' => __DIR__ . '/storage/disposable_domains.txt',
  ];

Built-in Sources:
  - disposable-email-domains
  - burner-email-providers
  - mailchecker
  - ivolo-disposable
  - fakefilter

Examples:
  ./bin/update-domains
  ./bin/update-domains --dry-run
  ./bin/update-domains --source=disposable-email-domains --source=burner-email-providers
  ./bin/update-domains --output=storage/domains.txt
  ./bin/update-domains --config=custom-config.php

HELP;
    exit(
0);
}

$dryRun = isset($options['dry-run']) || in_array('--dry-run', $args);
$verbose = isset($options['verbose']) || in_array('--verbose', $args);
$noConfig = isset($options['no-config']) || in_array('--no-config', $args);
$configPath = $options['config'] ?? null;

// Output helpers
function info(string $message): void {
    echo
"\033[34m[INFO]\033[0m $message\n";
}

function
success(string $message): void {
    echo
"\033[32m[OK]\033[0m $message\n";
}

function
warning(string $message): void {
    echo
"\033[33m[WARN]\033[0m $message\n";
}

function
error(string $message): void {
    echo
"\033[31m[ERROR]\033[0m $message\n";
}

function
progress(string $message): void {
    global
$verbose;
    if (
$verbose) {
        echo
" ? $message\n";
    }
}

// Main
echo "\n";
echo
"??????????????????????????????????????????????????????????????\n";
echo
"? Disposable Domains List Updater ?\n";
echo
"??????????????????????????????????????????????????????????????\n";
echo
"\n";

// Load configuration
$config = null;
if (!
$noConfig) {
    try {
        if (
$configPath !== null) {
           
$config = Configuration::fromFile($configPath);
           
info("Using config: $configPath");
        } else {
           
$config = new Configuration();
            if (
$config->isLoaded()) {
               
info("Using config: " . $config->getConfigPath());
            }
        }
    } catch (
Throwable $e) {
       
error("Failed to load config: " . $e->getMessage());
        exit(
1);
    }
}

// Setup registry with configuration
$registry = new SourceRegistry();

if (
$config !== null && $config->isLoaded()) {
   
$config->applyTo($registry);

   
$customSources = $config->getSources();
    if (!empty(
$customSources)) {
       
info("Custom sources: " . count($customSources));
    }

   
$excluded = $config->getExcludeSources();
    if (!empty(
$excluded)) {
       
info("Excluded sources: " . implode(', ', $excluded));
    }
}

$availableSources = $registry->list();

// Determine output path
$defaultOutputPath = __DIR__ . '/../src/Resources/disposable_domains.txt';

if (isset(
$options['output'])) {
   
$outputPath = $options['output'];
} elseif (
$config !== null && $config->getOutputPath() !== null) {
   
$outputPath = $config->getOutputPath();
} else {
   
$outputPath = $defaultOutputPath;
}

// Handle multiple --source options
$selectedSources = [];
if (isset(
$options['source'])) {
   
$selectedSources = is_array($options['source']) ? $options['source'] : [$options['source']];
}

// Validate selected sources
if (!empty($selectedSources)) {
    foreach (
$selectedSources as $source) {
        if (!
in_array($source, $availableSources)) {
           
error("Unknown source: $source");
            echo
"Available sources: " . implode(', ', $availableSources) . "\n";
            exit(
1);
        }
    }
   
$sourcesToFetch = $selectedSources;
} else {
   
$sourcesToFetch = $availableSources;
}

echo
"\n";
info("Sources to fetch: " . implode(', ', $sourcesToFetch));
info("Output path: $outputPath");
echo
"\n";

$allDomains = [];
$stats = [];

foreach (
$sourcesToFetch as $sourceName) {
   
$source = $registry->get($sourceName);

    echo
"Fetching: \033[1m$sourceName\033[0m\n";
   
progress("URL: " . ($source->getUrl() ?? 'local file'));

   
$startTime = microtime(true);
   
$count = 0;

    try {
        foreach (
$source->fetch() as $domain) {
           
$domain = strtolower(trim($domain));
            if (
$domain !== '' && !str_starts_with($domain, '#')) {
               
$allDomains[$domain] = true;
               
$count++;
            }
        }

       
$elapsed = round(microtime(true) - $startTime, 2);
       
$stats[$sourceName] = ['count' => $count, 'time' => $elapsed, 'status' => 'ok'];
       
success("Fetched $count domains in {$elapsed}s");

    } catch (
Throwable $e) {
       
$stats[$sourceName] = ['count' => 0, 'time' => 0, 'status' => 'error', 'error' => $e->getMessage()];
       
error("Failed: " . $e->getMessage());
    }

    echo
"\n";
}

// Sort domains alphabetically
$domains = array_keys($allDomains);
sort($domains, SORT_STRING);

$totalDomains = count($domains);

// Statistics
echo "??????????????????????????????????????????????????????????????\n";
echo
"? Statistics ?\n";
echo
"??????????????????????????????????????????????????????????????\n";
echo
"\n";

if (!empty(
$stats)) {
   
$maxNameLen = max(array_map('strlen', array_keys($stats)));
    foreach (
$stats as $name => $stat) {
       
$namePadded = str_pad($name, $maxNameLen);
        if (
$stat['status'] === 'ok') {
           
$countFormatted = number_format($stat['count']);
            echo
" $namePadded : $countFormatted domains ({$stat['time']}s)\n";
        } else {
            echo
" $namePadded : \033[31mFAILED\033[0m - {$stat['error']}\n";
        }
    }

    echo
"\n";
    echo
" " . str_repeat('?', $maxNameLen + 30) . "\n";
}

echo
" \033[1mTotal unique domains: " . number_format($totalDomains) . "\033[0m\n";
echo
"\n";

if (
$dryRun) {
   
warning("Dry run - no file written");
    echo
"\n";
    exit(
0);
}

// Ensure output directory exists
$outputDir = dirname($outputPath);
if (!
is_dir($outputDir)) {
    if (!
mkdir($outputDir, 0755, true)) {
       
error("Failed to create output directory: $outputDir");
        exit(
1);
    }
}

// Write file
info("Writing to: $outputPath");

$header = <<<HEADER
# Disposable Email Domains
# Auto-generated by bin/update-domains
# Last updated: %s
# Total domains: %s
#
# Sources:
%s
#
# This file is part of disposable-email-blocker-core
# https://github.com/raul3k/disposable-email-blocker-core

HEADER;

$sourcesComment = implode("\n", array_map(
    fn(
$name) => "# - $name",
   
$sourcesToFetch
));

$content = sprintf(
   
$header,
   
date('Y-m-d H:i:s T'),
   
number_format($totalDomains),
   
$sourcesComment
);

$content .= implode("\n", $domains) . "\n";

$bytesWritten = file_put_contents($outputPath, $content);

if (
$bytesWritten === false) {
   
error("Failed to write file!");
    exit(
1);
}

$sizeKb = round($bytesWritten / 1024, 1);
success("Written $sizeKb KB to $outputPath");
echo
"\n";