Search by

atannex / news-publisher

Atanga1920

Headless Laravel news ingestion, editorial review and safe Facebook Page publishing.

Package info

github.com/atannex/news-publisher

pkg:composer/atannex/news-publisher

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-11-03 18:00 UTC

This package is auto-updated.

Last update: 2026-11-03 18:02:44 UTC


README

A PHP 8.3+ Laravel 12/13 package that discovers authorized news, applies editorial rules and publishes attributed posts to Facebook Pages through the official Meta Graph API. Run it synchronously from GitHub Actions every 10 minutes. The default runtime requires no database, migrations, queue worker, Redis, web server, paid news API or AI service.

Publishing starts disabled. Routine posts can run automatically after you explicitly configure source permissions, credentials, publishing rules and storage. Sensitive stories stay on hold until reviewed. A feed or news API subscription alone is not permission to redistribute photographs.

The default US-based global trending profile prioritizes the United States, Europe, the Middle East and major global events. Africa-focused coverage is excluded by a configurable geographic profile. Articles are collected, independently corroborated, clustered, scored and selected before publication. A single fresh article is not automatically considered trending. Without authorized popularity data, rankings are explicitly labeled inferred news prominence.

Use php artisan news:trending --json (or --region=united_states --limit=10) for calculated rankings without publishing. Automatic significance rules require no article scores. Momentum uses optional persisted observations and remains unavailable on cold starts. Reports expose evidence coverage, missing signals and effective weights separately from score.

See trending configuration and scoring before enabling publishing. Configure at least two independent, reliable publishers. Add trust.reliable=true after review and use trend.publisher_group for shared ownership/syndication. primary_region describes a genuinely regional feed, not its publisher's headquarters. The default six-hour window and minimum score of 70 are configurable.

Install

Install in a Laravel application, not directly in this Composer library:

composer require atannex/news-publisher
php artisan news:install
NEWS_STATE_DRIVER=memory php artisan news:doctor --json

This initial disabled local check explicitly uses memory. Production publishing uses the default GitHub state driver after repository/token initialization.

Laravel discovers the provider automatically. No migration or APP_KEY is required by this package's default runtime. Set CACHE_STORE=array, SESSION_DRIVER=array, and QUEUE_CONNECTION=sync when the host application otherwise defaults to database-backed services. The package never dispatches jobs in default mode. A complete console-only application is in examples/laravel, and tests/Runtime demonstrates real Laravel boot/execution with the database connection unavailable.

Configure authorized sources and a Page

In config/news-publisher.php, configure these arrays alongside the published defaults:

'sources' => [[
    'id' => 'newsroom',
    'name' => 'Original Publisher',
    'driver' => 'rss', // atom, json-feed, json-api, newsapi, or registered custom driver
    'url' => env('NEWS_RSS_URL'),
    'enabled' => true,
    'language' => 'en',
    'primary_region' => 'united_states', // only for a genuinely US-focused feed
    'trust' => [
        'identity_confirmed' => true, // set only after checking the publisher
        'reliable' => true,
        'feed_authorized' => true,
        'article_hosts' => [env('NEWS_ARTICLE_HOST')],
    ],
    'permissions' => [
        'headline_reuse' => true, // confirm actual terms before setting these permissions
        'linking' => true,
        'media_grants' => [],
    ],
]],
'destinations' => [[
    'id' => 'main-page',
    'page_id' => env('FACEBOOK_PAGE_ID'),
    'enabled' => true,
    'style' => 'general',
    'upload_media' => true,
    'multi_photo_supported' => false,
    'credentials' => [
        'access_token' => env('FACEBOOK_PAGE_ACCESS_TOKEN'),
        'app_id' => env('FACEBOOK_APP_ID'),
        'app_access_token' => env('FACEBOOK_APP_ACCESS_TOKEN'),
    ],
]],

Use a Meta developer app, a Page access token for the intended Page, and the required Page management permissions. Configure an explicit FACEBOOK_GRAPH_VERSION supported by your app. Check identity, token expiry, app identity and scopes with:

php artisan news:facebook:check --destination=main-page --json
php artisan news:run --sync --dry-run --json

See Facebook setup. Secrets belong in environment variables, GitHub Actions secrets or a replacement PageCredentials adapter. Never commit them. Caption templates preserve publisher and original URL; the default does not reproduce full articles. Optional AI output must be an exact source sentence and requires summary reuse permission.

Choose publication history

Driver History lifetime Notes
null Current invocation only No durable history; active reservations are still protected in process
memory Current process Deduplicates during the process; each Actions runner starts empty
file Local filesystem Atomic replacement and locking; mark persistent only on genuinely durable storage
github (production default) Across runners Recommended for Actions; dedicated branch and Contents API compare-and-swap
cache Configured cache lifetime Requires locks; eviction/lost cache fails closed; array cache is ephemeral
database Configured database Optional Query Builder adapter, explicit schema initialization

Story ranking also applies corroboration, geographic and diversity rules; it does not publish every discovered article. Automatic publishing with default daily limits, minimum intervals or required cross-run deduplication needs persistent history. Configuration validation refuses memory/null/ephemeral file/cache when those rules are enabled. To deliberately operate without cross-run guarantees, explicitly set require_persistent_history=false, max_per_day=0, and minimum_interval_minutes=0. Within-run duplicate checks still apply.

For GitHub history, configure:

NEWS_STATE_DRIVER=github
NEWS_STATE_OWNER=your-organization
NEWS_STATE_REPOSITORY=your-laravel-app
NEWS_STATE_BRANCH=news-publication-state
NEWS_STATE_PATH=state/publications.json
NEWS_STATE_TOKEN=your-scoped-token

Then initialize deliberately once, inspect and dry-run:

php artisan news:state:init --json
php artisan news:state:inspect --json
php artisan news:run --sync --dry-run --json

The initializer creates a dedicated branch from the default branch if needed, and a bounded versioned JSON journal. Normal runs never create an empty history when state is missing or corrupt. Do not reset history to fix errors. Protect the state branch against deletion/force-push while permitting the intended bot to update its state file. State and recovery details.

Enable automation

After checking source terms, Page credentials, a dry run and a live app/version smoke test, set NEWS_PUBLISHER_ENABLED=true. Default limits are one attempt per run, 30 successful publications per America/New_York editorial day, and at least 15 minutes between normal successful publications. Uncertain outcomes retain reserved capacity until reconciled. DST-aware boundaries and persisted counters are enforced through the GitHub journal. Set filters and overrides under publishing and each destination's policy.

php artisan news:github:install

This installs the workflow into the Laravel application's repository. It runs every 10 minutes, allows manual dry runs, uses PHP 8.3, installs Composer dependencies, enforces repository concurrency, runs news:run --sync, writes execution summaries and propagates failure exit codes. Configure the referenced variables and secrets; the example application's opt-in source authorization variables are included. It uses contents: write for the GitHub journal; narrow this to read if using a separately scoped state token. No local file or Actions cache is treated as authoritative persistent history. GitHub Actions setup.

Safety and recovery

The pipeline validates HTTPS URLs, pins public DNS answers, checks redirects, bounds downloads, parses XML without external entities, sanitizes text and isolates source errors. Uploads require an explicit exact-URL media grant. Invalid, expired, unlicensed or AI-generated media falls back to the attributed article link. Single images, supported multi-photo posts, text posts, link posts and bounded authorized MP4 uploads are implemented. Facebook controls generated previews. Media configuration.

A persistent reservation is written before any final Facebook write. If Facebook succeeds and saving its receipt fails, that reservation remains and blocks automatic reposting. Known remote IDs are reconciled using Page ownership and caption hash; missing IDs require an operator to supply a confirmed ID. Ambiguous final writes are quarantined, never blindly retried. There is no exactly-once guarantee across independent APIs. Troubleshooting.

Sensitive-topic holds include a review key in the execution report. An accountable reviewer can add sources[N].approvals[review-key] = 'reviewer-identity' after checking the story. The key covers URL, headline, description and media rights, so changes require another review. Filtering, freshness, source validation and media authorization remain enforced. No database dashboard is needed.

Optional existing database engine

The model-based source/editorial APIs, queue jobs, verification evidence and audit tables are preserved. Existing deployments must explicitly set mode=database (NEWS_PUBLISHER_MODE=database) and database.load_migrations=true before using their existing commands and migrations. Existing data is not automatically imported into JSON history. The default JSON runtime's optional state.driver=database is separate and does not enable the legacy engine. Migration path.

Validation and extension

composer validate --strict
vendor/bin/phpunit
vendor/bin/phpstan analyse --debug --no-progress --memory-limit=1G
vendor/bin/pint --test

Tests mock Meta/GitHub HTTP and cover both engines. No production Facebook credentials or database service is needed. Live Facebook publishing has not been verified. Meta's official documentation returned HTTP 429 during this implementation; confirm your app/version capabilities against the official references linked in the setup guide.

The source, caption, editorial, destination, credential and state contracts are replaceable. See extension contracts, architecture, dependency audit, commands, and configuration.

The automatic intelligence implementation report lists changed files, algorithms, validation results and exact verification commands.

See publishing schedule for the ten-minute workflow, 30-post cap, DST behavior, breaking priority and recovery rules. The cap is a maximum, never a requirement to fill a quota.