EasySocialAutopilot · Bedienhandbuch
Dieses Handbuch begleitet dich durch Installation, Channel-Anbindung und Betrieb des Plugins EasySocialAutopilot v0.4.0 in der Ausbaustufe Phase 4 — Auto-Enqueue + 20 Channels. Es beschreibt den realen Funktionsumfang dieser Version — ohne Versprechungen, die der Code noch nicht einlöst.
Worum geht's?
Was EasySocialAutopilot heute tut, warum es existiert und für wen sich diese Phase-4-Version lohnt.
EasySocialAutopilot ist ein WordPress-Plugin, das deine Blog-Beiträge automatisiert an Social-Media-Plattformen verteilt. Die Architektur ist von Anfang an als Multi-Channel-Pipeline gebaut: jeder Kanal (Mastodon, Bluesky, Telegram, Discord, LinkedIn, Reddit, Pinterest, …) ist ein eigener Driver hinter einem stabilen ChannelDriver-Interface. Du verbindest beliebig viele Accounts pro Kanal, deine WordPress-Posts wandern in eine Queue, ein Cron-Worker greift sich pending Jobs alle fünf Minuten und ruft den jeweiligen Driver auf.
In der vorliegenden Version 0.4.0 sind 20 Channel-Driver registriert. Elf davon sind voll funktional und posten in Produktion (Mastodon, Bluesky, Telegram, Discord, Webhook, LinkedIn, Reddit, Pinterest, Tumblr, WordPress.com, Google Business Profile, Blogger). Fünf weitere haben einen funktionierenden OAuth-Connect, aber der Posting-Pfad ist bis zu einer externen Freigabe (App-Review oder Paid-Tier) blockiert — Facebook Pages, Instagram, Threads, TikTok, X (Twitter). YouTube läuft im description_update-Modus produktiv, video_upload und Community Posts geben eine ehrliche Failure zurück. Medium läuft per Integration-Token (kein OAuth-Onboarding mehr möglich). XING ist ein dokumentierter Platzhalter — die öffentliche API wurde 2022 abgeschaltet.
Phase 4 ist live: Der Hook auf transition_post_status legt beim Publish eines Posts automatisch Queue-Jobs für alle aktiven Channel-Accounts an. Per Post-Meta _esa_selected_accounts kannst du die Zielauswahl pro Post überschreiben, mit _esa_skip_distribution=1 ein einzelner Post komplett aus der Verteilung herausgenommen werden. Idempotenz ist eingebaut: QueueRepository::hasActiveJob verhindert Duplikate, wenn ein Post mehrfach gespeichert wird.
Die zentrale Designentscheidung bleibt: kein Phone-Home, kein Lizenz-Server, keine SaaS-Abhängigkeit. Tokens liegen AES-256-GCM-verschlüsselt in deiner WordPress-Datenbank. Wenn du das Plugin morgen ausschaltest, ist alles auf deinem Server — nichts auf einem fremden.
Zielgruppe der aktuellen Version sind Plugin-Entwickler, Self-Hoster, Agenturen und Power-User, die mit einer ehrlich beschriebenen Phase-4-Reife produktiv testen oder produktiv betreiben wollen. End-User mit Komfortanspruch sollten auf Phase 5 (Gutenberg-Sidebar-UI für die Per-Post-Selection) warten.
Voraussetzungen
Welche Plattform, welche WordPress-Konfiguration und welche externen Accounts du brauchst, bevor du startest.
| Anforderung | Mindestversion | Hinweis |
|---|---|---|
| WordPress | 6.0 | Niedrigere Versionen werden weder geprüft noch supportet. |
| PHP | 8.0 | Nutzt declare(strict_types=1), readonly-Properties, Named Arguments. |
| OpenSSL | beliebig moderne Version | Erforderlich für aes-256-gcm (Token-Verschlüsselung). |
| MySQL/MariaDB | dbDelta-kompatibel | Standard-WordPress-Schema, keine Sonderwünsche. |
| Ausgehendes HTTPS | – | Server muss die gewünschten Channel-Endpoints (Port 443) erreichen. In 99 % aller WordPress-Hosts gegeben. |
| WP-Cron oder System-Cron | aktiv | Ohne Cron läuft die Queue nicht. Bei deaktiviertem WP-Cron (DISABLE_WP_CRON) musst du wp-cron.php selbst anstoßen. |
| WP-Salts gesetzt | – | AUTH_KEY, SECURE_AUTH_SALT, NONCE_KEY aus wp-config.php werden zur Key-Derivation für die Token-Verschlüsselung genutzt. |
Channel-spezifische Voraussetzungen
Für die meisten Channels brauchst du auf der Plattform-Seite mindestens ein normales Nutzerkonto. Manche Channels verlangen zusätzlich, dass du als Plugin-Betreiber eine eigene Developer-App registrierst (OAuth-Channels) — Channel-spezifische Setup-Schritte stehen in Kapitel 5b. Eine grobe Vorab-Übersicht:
| Anforderung | Channels |
|---|---|
| Nur Account auf der Plattform | Mastodon, Bluesky |
| Bot-/Webhook-/App-Token (kein Developer-Account) | Telegram, Discord, Webhook, Medium |
| Eigene OAuth-App registrieren | LinkedIn, Reddit, Pinterest, Tumblr, WordPress.com |
| Google-Cloud-Projekt + OAuth-Consent-Screen | Google Business Profile, YouTube, Blogger |
| Meta-Developer-App + App Review | Facebook Pages, Instagram, Threads |
| Plattform-Developer-Subscription | TikTok, X (Twitter) |
| Nicht verfügbar | XING (API 2022 abgeschaltet) |
Installation
Drei Wege zur Installation — wähle den, der zu deiner Umgebung passt.
Variante A: ZIP-Upload (Endnutzer)
Empfohlen, wenn du ein klassisches Managed-WordPress-Hosting nutzt.
- Lade das Release-ZIP von GitHub (Releases-Tab) oder von einer Build-Pipeline.
- WordPress-Admin → Plugins → Hochladen → ZIP wählen → Jetzt installieren.
- Nach dem Upload Plugin aktivieren klicken.
Variante B: Git-Clone (Entwickler)
cd wp-content/plugins
git clone https://github.com/uh8888/easysocialautopilot.git
cd easysocialautopilot
composer install --no-dev --optimize-autoloader
Anschließend im WP-Admin aktivieren.
Variante C: Bind-Mount im Docker-Setup (Self-Hoster)
Wenn dein WordPress in einem Docker-Container läuft, mountest du das Plugin-Verzeichnis direkt ins wp-content/plugins-Volume. Beispiel-Snippet für docker-compose.yml:
services:
wordpress:
image: wordpress:latest
volumes:
- ./easysocialautopilot:/var/www/html/wp-content/plugins/easysocialautopilot:ro
Vorteil: Code-Updates sind ein git pull weit weg, ohne ZIP-Roundtrip.
Was bei der Aktivierung passiert
Der Activator (EasySocialAutopilot\Core\Activator::activate) führt folgende Schritte aus:
| Schritt | Aktion |
|---|---|
| 1 | Registriert einen Custom-Cron-Schedule esa_five_minutes (Intervall 300 s). |
| 2 | Führt Migrations::run() aus — legt fünf DB-Tabellen via dbDelta an. |
| 3 | Plant den Cron-Hook esa_process_queue auf 60 Sekunden in der Zukunft. |
| 4 | Schreibt esa_db_version in die wp_options-Tabelle. |
Die fünf Tabellen (Prefix wp_esa_ bei Standard-WP-Prefix):
| Tabelle | Inhalt |
|---|---|
wp_esa_channels |
Per-Channel-Flags (is_enabled) und globale Settings. |
wp_esa_channel_accounts |
Verbundene Accounts inkl. AES-verschlüsseltem Credential-Blob. |
wp_esa_queue |
Pending/Processing-Jobs mit Backoff-State. |
wp_esa_jobs |
Job-History (success + terminal failure). |
wp_esa_logs |
Application-Log-Stream der UI. |
Erster Login & Tour der UI
Was du nach der Aktivierung im Admin siehst — und welche Sidebar-Items in Phase 4 wirklich gefüllt sind.
Nach der Aktivierung erscheint im linken WordPress-Sidebar-Menü der Eintrag Social Autopilot. Klick darauf — du landest auf einer einseitigen React-SPA mit Hash-Router (#/dashboard, #/channels, …) im klassischen ContentForge-Schwarz mit Lime-Akzenten.
Die SPA hat sechs Pages, die du oben über die Sub-Navigation erreichst:
| Page | Hash-Route | Status in Phase 4 | Was du dort siehst |
|---|---|---|---|
| Dashboard | #/ |
Funktionsfähig (/dashboard/stats-Endpoint neu) |
Vier StatTiles (Channels Connected, Pending Jobs, Sent Today, Failed Today) plus eine Tabelle der letzten 8 Jobs. |
| Channels | #/channels |
Voll funktionsfähig | Karten-Grid aller registrierten Channel-Driver — in Phase 4: 20 Karten. Connect-Button, verbundene Accounts mit Avatar + Status-Pill, Disconnect-Button. |
| Queue | #/queue |
Funktionsfähig (lesend + Retry/Cancel + Cron-Trigger) | Tabelle aller pending/processing-Jobs. Spalten: #, Channel, Post, Status-Pill, Scheduled, Attempts, Aktionen Retry/Cancel. Plus Button Cron jetzt ausführen. |
| History | #/history |
Funktionsfähig | Tabelle aller terminierten Jobs (done + failed) mit Link auf den Remote-Post (remote_post_url). |
| Logs | #/logs |
Funktionsfähig | Application-Log-Stream aus wp_esa_logs mit Level-Filter (All / Debug / Info / Warn / Error). |
| Settings | #/settings |
Placeholder | Zeigt einen EmptyState. Per-Channel-Settings werden via Constants in wp-config.php gesetzt — siehe Kapitel 5b. |
Die SPA lädt ihre Daten ausschließlich über den REST-Namespace esa/v1. Jeder Request trägt einen X-WP-Nonce-Header, der von der RestApi::authorize-Permission-Callback gegen die wp_rest-Action verifiziert wird. Capability-Check: manage_options — also nur Administratoren.
Bundle-Loading-Hinweis
Das React-Bundle wird als ESM-Modul geladen, um die WordPress-wp-Global-Kollision zu umgehen. Vite ist mit base: './' gebaut — so funktionieren auch relative Font-Pfade in Subdirectory-Installationen. Wenn du dein eigenes Bundle baust: npm run build im Plugin-Root.
Channels verbinden — der gemeinsame Flow
Das Hauptkapitel: Schritt-für-Schritt durch den Connect-Flow, den die meisten Channels gemeinsam haben. Channel-spezifische Setup-Schritte stehen direkt danach in Kapitel 5b.
Was technisch passiert (Kurzfassung)
Der Connect-Flow folgt einem stabilen Muster, egal ob OAuth, App-Password oder Token:
| Phase | Was passiert | Wo |
|---|---|---|
| 1 | UI sendet POST /wp-json/esa/v1/channels/<id>/connect/start mit dem passenden context (z. B. Instance-URL, Webhook-URL, Bot-Token, App-Password). |
WP REST |
| 2 | Driver bereitet alles vor: registriert ggf. dynamisch eine App (Mastodon), baut die Authorize-URL (OAuth), validiert das Token sofort (App-Password / Webhook). | Driver-spezifisch |
| 3 | Bei OAuth: Browser geht zur Plattform, du klickst Authorize. Bei Token-Flows: Driver verifiziert das Credential synchron im selben Request. | Plattform-seitig |
| 4 | Bei OAuth-Callback: admin-post.php?action=esa_oauth_callback&channel=<id>&code=…&state=… tauscht den Code gegen den Access-Token. |
OAuthHandler |
| 5 | Credentials werden mit AES-256-GCM verschlüsselt und in wp_esa_channel_accounts abgelegt. UI bekommt einen Toast und die neue Account-Karte erscheint. |
DB |
Schritt für Schritt im Browser
1. Öffne die Channels-Page. Sidebar → Social Autopilot → Tab Channels. Du siehst 20 Karten, jeweils mit Channel-Logo, Status (verbunden / nicht verbunden), Account-Counter und einem Connect-Button.
2. Klick auf Connect. Es öffnet sich ein modaler Dialog (ConnectModal) mit den Feldern, die der jeweilige Driver verlangt — von einer einzigen Instance-URL (Mastodon) bis zu drei Feldern (Bluesky: Handle, App-Password, optional PDS).
3. Fülle aus & sende ab. Bei OAuth-Channels wirst du auf die Plattform redirected. Bei Token-Channels macht der Driver eine Live-Verifizierung — wenn das Token nicht stimmt, siehst du sofort einen Fehler im Dialog.
4. Erfolg. Du landest auf #/channels?connected=1&account_id=<neue-id>, ein Success-Toast erscheint, die Account-Karte rendert sich mit Avatar, Anzeigename und Aktiv-Pill.
Mehrere Accounts pro Channel
Du kannst pro Channel beliebig viele Accounts verbinden — auch über mehrere Instanzen / Pages / Subreddits / Bot-Identitäten hinweg. Der Connect-Button heißt nach dem ersten verbundenen Account Weiteren Account verbinden. Jeder Account hat eine eigene account_id, einen eigenen Credential-Blob und kann einzeln getrennt werden.
Was passiert beim Trennen?
Trennen heißt: Row in wp_esa_channel_accounts wird gelöscht. Externe App-Autorisierungen bleiben in der Regel bestehen — sauber widerrufen kannst du sie auf der jeweiligen Plattform (Mastodon: Authorized Apps; Google: Drittanbieter-Apps mit Zugriff; Meta: Business-Einstellungen → Verbundene Apps; etc.). Pending Queue-Jobs, die auf den gelöschten Account verweisen, schlagen beim nächsten Cron-Run mit "account not found" terminal fehl.
Channels im Detail
Pro Channel: kurze Beschreibung, Setup-Schritte als Tabelle und Verweis auf die ausführliche README im Repo unter src/Channels/<Name>/README.md. Wo eine Plattform-seitige Freigabe fehlt, ist das ehrlich als "blockiert pending external approval" markiert.
5b.1 Simple Auth — kein Developer-Account nötig
Diese fünf Channels sind die schnellsten zum Verbinden: keine eigene App-Registrierung, keine Developer-Subscription, kein Verifizierungsprozess.
Mastodon
Voll funktional. Die OAuth-App wird beim ersten Connect dynamisch via POST /api/v1/apps auf der gewählten Mastodon-Instance registriert — du musst weder bei Mastodon noch beim Plugin-Hersteller etwas einrichten. Der Driver normalisiert Instance-Eingaben (Schema-Strip, Lowercase, Pfad weg), unterstützt Featured-Image-Upload und sendet Idempotency-Key-Header zur Doppel-Post-Vermeidung.
| Schritt | Aktion |
|---|---|
| 1 | Mastodon-Account auf einer Instance deiner Wahl haben (mastodon.social, chaos.social, troet.cafe, eigener Server, …). |
| 2 | In ESA: Channels → Connect Mastodon → Instance eintragen (z. B. mastodon.social). |
| 3 | Auf der Authorize-Seite Authorize klicken. Fertig. |
Per-Post-Optionen: visibility (public/unlisted/private/direct), language, sensitive, spoiler_text.
Bluesky
Voll funktional. Authentifiziert mit einem App-Password (nicht dem Account-Passwort). Der Driver speichert accessJwt (~1 h) und refreshJwt (~2 Monate), rotiert den Access-Token automatisch, und fällt bei Doppel-Expire auf erneutes createSession zurück. Link-Facets werden automatisch erkannt, Featured Image als app.bsky.embed.images mit max. 1 MB eingebettet.
| Schritt | Aktion |
|---|---|
| 1 | https://bsky.app → Settings → Privacy and security → App passwords → neues App-Password anlegen. |
| 2 | In ESA Connect-Dialog: identifier (Handle wie alice.bsky.social), app_password, optional pds_url (Default https://bsky.social). |
Per-Post-Optionen: langs (Default en). Hard-Limit: 300 Zeichen. Details: src/Channels/Bluesky/README.md.
Telegram
Voll funktional. Nutzt einen Bot-Token statt OAuth. Mit Featured Image wird sendPhoto mit Caption gesendet (1024-Zeichen-Limit), ohne wird sendMessage mit HTML-formatiertem Body gesendet (4096-Zeichen-Limit).
| Schritt | Aktion |
|---|---|
| 1 | In Telegram @BotFather anschreiben, /newbot senden, Token im Format 1234567890:ABCdef... kopieren. |
| 2 | Ziel-Chat-ID besorgen — bei Personal Chat: @userinfobot nach numerischer ID fragen. Bei Channel: Bot als Admin mit "Post Messages"-Recht hinzufügen, @channel_username nutzen. Bei Group: Bot adden, getUpdates-Call gegen die API für die Chat-ID. |
| 3 | In ESA Connect-Dialog: bot_token und default_chat_id eintragen. |
Per-Post-Optionen: chat_id (Override), parse_mode (HTML/MarkdownV2), disable_link_preview. Details: src/Channels/Telegram/README.md.
Discord
Voll funktional. Postet via Channel-Webhook — kein Bot-Account, kein OAuth-Scope. Sendet pro Post einen Rich-Embed mit Title, Description, URL, Featured-Image und Discord-Blurple als Farbe.
| Schritt | Aktion |
|---|---|
| 1 | Discord → Server Settings → Integrations → Webhooks → New Webhook → Channel wählen, Name/Avatar setzen, Copy Webhook URL klicken. |
| 2 | In ESA Connect-Dialog: url einfügen. Optional: username / avatar_url Override. |
Per-Post-Optionen: content (Message über dem Embed, max. 2000 Zeichen). Details: src/Channels/Discord/README.md.
Webhook
Voll funktional. Generischer JSON-POST an einen beliebigen HTTPS-Endpoint — perfekt für Zapier, n8n, Make, Custom-CRMs, Log-Aggregatoren. Optional mit HMAC-SHA256-Signatur per Shared-Secret.
| Schritt | Aktion |
|---|---|
| 1 | Empfangs-URL im Ziel-System einrichten. |
| 2 | In ESA Connect-Dialog: url, optional secret (HMAC), optional headers (z. B. X-API-Key). |
Payload-Schema enthält id, title, content, excerpt, link, featured_image, published_at, author. Bei gesetztem Secret kommt ein X-ESA-Signature: sha256=<hex> Header mit. Details: src/Channels/Webhook/README.md.
5b.2 OAuth 2.0 Standard — User registriert eigene App
Sechs Channels mit klassischem OAuth-2-Authorization-Code-Flow. Du brauchst pro Channel eine eigene Developer-App beim Provider und musst Client-ID/Secret als WP-Konstanten setzen.
Voll funktional. Postet auf Member-Feeds und optional auf Company Pages via REST Posts API (/rest/posts, Version 202405).
| Schritt | Aktion |
|---|---|
| 1 | App anlegen auf https://www.linkedin.com/developers/apps . Redirect-URL: https://YOUR-SITE/wp-admin/admin.php?page=esa-channels&esa_oauth=linkedin |
| 2 | Products anfordern: Sign In with LinkedIn using OpenID Connect, Share on LinkedIn, optional Community Management API (für Pages). |
| 3 | In wp-config.php: ESA_LINKEDIN_APP_ID + ESA_LINKEDIN_APP_SECRET. |
| 4 | In ESA Channels → Connect LinkedIn → OAuth-Consent zustimmen. |
| 5 | Für Company Pages: erneut verbinden mit Toggle Also connect Company Pages — Pages mit ADMINISTRATOR/CONTENT_ADMIN-Rolle werden gelistet. |
Scopes: openid profile w_member_social (Member) bzw. zusätzlich w_organization_social r_organization_social rw_organization_admin (Pages). Per-Post: visibility (PUBLIC/CONNECTIONS), reshare_disabled. Details: src/Channels/LinkedIn/README.md.
Voll funktional. Submitted Link- oder Self-Posts via POST /api/submit auf oauth.reddit.com mit duration=permanent (Refresh-Token).
| Schritt | Aktion |
|---|---|
| 1 | https://www.reddit.com/prefs/apps → create another app... → Type web app. Redirect-URI wie bei LinkedIn-Muster. |
| 2 | Client-ID (kurzer String unter App-Name) + Secret kopieren. |
| 3 | In wp-config.php: ESA_REDDIT_APP_ID + ESA_REDDIT_APP_SECRET. |
| 4 | In ESA Connect → Reddit-Account-Consent. |
Scopes: identity submit flair mysubreddits read. Per-Post: subreddit (Pflicht, ohne r/-Prefix), kind (link/self), text, flair_id, nsfw, spoiler. Reddit verlangt einen unique User-Agent — der Driver sendet EasySocialAutopilot/<version> by <reddit-username>. Details: src/Channels/Reddit/README.md.
Voll funktional. Erzeugt Pins via Pinterest API v5 (POST /v5/pins). Pinterest erfordert ein Bild pro Pin — Posts ohne Featured Image failen mit klarer Fehlermeldung.
| Schritt | Aktion |
|---|---|
| 1 | https://developers.pinterest.com/ → My apps → Create app. App-Type: Standard-Tier (Production). |
| 2 | Redirect-URI eintragen, App-ID + App-Secret kopieren. |
| 3 | In wp-config.php: ESA_PINTEREST_APP_ID + ESA_PINTEREST_APP_SECRET. |
| 4 | In ESA Connect → Pinterest-Consent. Beim ersten Connect wird das erste Board als Default gecached; in Per-Account-Settings änderbar. |
Scopes: boards:read,pins:read,pins:write,user_accounts:read. Per-Post: board_id, section_id, alt_text. Access-Token ~30 Tage, Refresh ~60 Tage (Driver refreshed automatisch). Details: src/Channels/Pinterest/README.md.
Tumblr
Voll funktional. Nutzt OAuth 1.0a HMAC-SHA1 (historisch bedingt). Postet als Tumblr-Text-Post auf den Primary-Blog.
| Schritt | Aktion |
|---|---|
| 1 | App registrieren unter https://www.tumblr.com/oauth/apps . Callback-URL: ESA-OAuth-Callback. |
| 2 | In wp-config.php: ESA_TUMBLR_CONSUMER_KEY + ESA_TUMBLR_CONSUMER_SECRET. |
| 3 | In ESA Connect → Tumblr-Consent → Primary-Blog wird per /v2/user/info ermittelt. |
Per-Post: state (published/draft/queue/private), format (html/markdown), tags. Driver nutzt die stabile Legacy-form-encoded-Variante, nicht NPF. Details: src/Channels/Tumblr/README.md.
WordPress.com
Voll funktional. Postet auf WordPress.com-Blogs oder Jetpack-verbundene Self-Hosted-Sites via REST API v1.1.
| Schritt | Aktion |
|---|---|
| 1 | App registrieren unter https://developer.wordpress.com/apps/ . |
| 2 | In wp-config.php: ESA_WPCOM_CLIENT_ID + ESA_WPCOM_CLIENT_SECRET. |
| 3 | In ESA Connect → WordPress.com-Consent. Multi-Site: Tokens resolven via /me/sites. |
Per-Post: status, tags, categories, sticky. Achtung: Bearer-Tokens haben ~2 Wochen TTL, kein Refresh-Token — bei Expire status='expired', User muss neu autorisieren. Details: src/Channels/WordPressCom/README.md.
Medium
Funktional via Integration-Token-Pfad (OAuth ist seit März 2023 für neue Partner geschlossen und das API-Repo archiviert). Wenn POST /v1/users/{id}/posts mal 404/410 zurückgibt, zeigt der Driver einen ehrlichen "Medium API is no longer publicly accessible"-Fehler.
| Schritt | Aktion |
|---|---|
| 1 | https://medium.com/me/settings → Security & apps → Integration tokens → Token-Description (z. B. EasySocialAutopilot) → Get integration token. |
| 2 | Hex-Token kopieren. Wenn der Settings-Bereich fehlt: Account nicht eligible, Channel nicht nutzbar. |
| 3 | In ESA: Connect Medium → Token einfügen. Driver verifiziert per GET /v1/me. |
Per-Post: publish_status (public/draft/unlisted), tags (max. 5), canonical_url (Default true, gut für SEO-Credit), license, notify_followers. Details: src/Channels/Medium/README.md.
5b.3 Google Ecosystem — ein Cloud-Projekt, drei Channels
Google Business Profile, YouTube und Blogger teilen sich einen Google-Cloud-Projekt und einen OAuth-Client. Du registrierst einmal, aktivierst dann pro Channel die passende API. Konstanten in wp-config.php:
define( 'ESA_GOOGLE_APP_ID', '...apps.googleusercontent.com' );
define( 'ESA_GOOGLE_APP_SECRET', '...' );
Gemeinsames Google-Setup
| Schritt | Aktion |
|---|---|
| 1 | https://console.cloud.google.com → Projekt anlegen oder bestehendes wählen. |
| 2 | APIs & Services → Enabled APIs → die gewünschten APIs aktivieren (Business Profile API + My Business Account Management + My Business Business Information für GBP; YouTube Data API v3 für YouTube; Blogger API v3 für Blogger). |
| 3 | OAuth consent screen → User-Type External → die nötigen Scopes hinzufügen (siehe Channel-Sektionen). Während der Verifizierung sich selbst als Test-User adden — Production-Posting erfordert Google-App-Verification (Wochen Wartezeit). |
| 4 | Credentials → Create OAuth client ID → Application type Web application → Authorized redirect URI: https://YOUR-SITE/wp-admin/admin-post.php?action=esa_oauth_callback&channel=<channel-id> (eine URI pro Channel-ID). |
| 5 | Client-ID + Secret in wp-config.php setzen. |
Google Business Profile
Voll funktional. Postet in den Location-Feed (eine ChannelAccount pro Location).
Scope: https://www.googleapis.com/auth/business.manage. Per-Post: language_code, cta_action_type (LEARN_MORE/BOOK/ORDER/SHOP/SIGN_UP/CALL), cta_url. Limits: Summary max. 1500 Zeichen, Featured Image als media[0].sourceUrl (muss öffentlich erreichbar sein), Posts laufen nach 7 Tagen automatisch ab (GBP-Policy). Details: src/Channels/GoogleBusinessProfile/README.md.
YouTube
description_update-Modus voll funktional — perfekt um z. B. Show-Notes oder Affiliate-Links automatisiert aus einem WP-Post-Update an ein bestehendes Video anzuhängen. video_upload und community_post returnen ehrlich SendResult::failure.
Scope: https://www.googleapis.com/auth/youtube. Per-Post: mode (description_update Default), video_id (Pflicht), prepend (Bool — Default ist Append). Warum die anderen Modi blockiert sind: video_upload braucht den sensitiven youtube.upload-Scope + Google-Branding-Verification + Resumable-Multipart-Upload; community_post hat keine öffentliche API, nur YouTube Studio. Details: src/Channels/YouTube/README.md.
Blogger
Voll funktional. Postet einen WordPress-Post als neuen Blogger-v3-Post. Eine ChannelAccount pro Blogger-Blog — eine Google-Identität kann mehrere Blogs haben, jeden separat verbinden.
Scope: https://www.googleapis.com/auth/blogger. Per-Post: labels (Tags), draft (Bool), title (Override). post_content läuft durch the_content-Filter, Blogger nimmt HTML direkt an. Details: src/Channels/Blogger/README.md.
5b.4 Meta + Paid Tier — OAuth klappt, Posten blockiert
Fünf Channels mit funktionierendem OAuth-Connect, aber blockiertem Posting-Pfad. Du kannst jetzt schon die Accounts verbinden und in der UI sehen — sobald die externe Freigabe da ist, wird der POSTING_BLOCKED-Stub in send() entfernt und das Posten geht live.
Drei davon (Facebook Pages, Instagram, Threads) teilen sich eine Meta-Developer-App. Konstanten:
define( 'ESA_META_APP_ID', '...' );
define( 'ESA_META_APP_SECRET', '...' );
Facebook Pages
Connect funktional. Posten BLOCKIERT pending Facebook App Review für pages_manage_posts.
Setup: Meta Developer Portal → App → Scopes pages_show_list, pages_read_engagement, pages_manage_posts, business_management anfordern → in Live-Mode + App Review schicken. OAuth: Short-Lived-User-Token → Long-Lived-User-Token (~60 d) → GET /me/accounts liefert pro Page einen perpetual Page-Access-Token. Pro Page ein ChannelAccount. Posting-Flow (vorbereitet, aber blockiert): POST graph.facebook.com/v21.0/{page-id}/feed. Details: src/Channels/Facebook/README.md.
Connect funktional. Posten BLOCKIERT pending Instagram Business Account + Facebook App Review für instagram_content_publish.
Voraussetzung: User verwaltet mindestens eine FB-Page, die mit einem Instagram-Business-Konto verknüpft ist. Scopes: pages_show_list, pages_read_engagement, business_management, instagram_basic, instagram_content_publish. Discovery: GET /me/accounts?fields=instagram_business_account. Posting-Flow (geplant): 2-Step Create-Media → Publish-Media. Details: src/Channels/Instagram/README.md.
Threads
Connect funktional. Posten BLOCKIERT pending Threads-API-Access (derzeit Limited Rollout).
OAuth läuft über threads.net/oauth/authorize (nicht facebook.com), Token-Exchange auf graph.threads.net. Long-lived-Exchange ?grant_type=th_exchange_token ergibt ~60-Tage-Token. Scopes: threads_basic, threads_content_publish. Posting-Flow analog Instagram (2-Step). Details: src/Channels/Threads/README.md.
TikTok
Connect funktional. Posten BLOCKIERT pending TikTok Developer Sandbox + Production Review für video.publish.
TikTok hat keine Text-only Posts — jeder Publish braucht ein Video. Der Driver fail-fast auf fehlendes options['video_url'] (auch vor dem Posting-Block). Konstanten: ESA_TIKTOK_CLIENT_KEY + ESA_TIKTOK_CLIENT_SECRET. Scopes: user.info.basic, video.publish. Refresh-Token-Flow implementiert. Posting-Flow (geplant): Async 3-Step Init → PUT-Upload → Status-Poll. Details: src/Channels/TikTok/README.md.
X (Twitter)
Connect funktional. Posten BLOCKIERT pending X API v2 Basic-Tier ($100+/Monat).
OAuth 2.0 mit PKCE. Konstanten: ESA_X_CLIENT_ID (optional ESA_X_CLIENT_SECRET für Confidential-Client). Scopes: tweet.read, tweet.write, users.read, offline.access. PKCE: code_verifier (64 random Bytes, base64url), code_challenge = SHA256(verifier) (S256). Refresh-Token-Flow implementiert. Posting-Flow (geplant): POST /2/tweets mit max. 280 Zeichen Free / 25.000 Premium. Details: src/Channels/X/README.md.
5b.5 Discontinued — XING
Platzhalter — nicht funktional. XING hat seine öffentliche Developer-API (dev.xing.com) 2022 abgeschaltet. Der OAuth 1.0a Flow und der /v1/users/me/status_message-Endpoint sind nicht mehr erreichbar, kein Ersatz wurde veröffentlicht.
Was der Driver tut: Er registriert den Channel in der Connector-Liste, alle anderen Methoden werfen einen klaren RuntimeException mit "XING public API was discontinued in 2022; this channel is a placeholder pending an alternative integration path." refreshIfNeeded() markiert Accounts als status='expired'.
Manueller Workaround: XING Business Manager für Company-Page-Scheduling nutzen, persönliche Status-Updates aus WordPress per Copy-Paste übertragen. Wenn die API zurückkommt: der Stub-Code in src/Channels/Xing/ ist so gebaut, dass man die Real-Implementierung 1:1 austauschen kann (OAuth 1.0a kann von \EasySocialAutopilot\Channels\Tumblr\TumblrOAuth1 recycelt werden). Details: src/Channels/Xing/README.md.
Einen Post veröffentlichen (Phase-4-Flow)
Wie ein WordPress-Post automatisch in die Queue kommt — und welche Hebel du pro Post hast.
Auto-Enqueue ist live
Phase 4 liefert das Stück, das in Phase 3 noch gefehlt hat: einen Listener auf transition_post_status. Sobald du einen Post von draft auf publish setzt (egal ob im Block-Editor, Classic-Editor, per REST-API oder per WP-CLI), passiert Folgendes:
| Schritt | Aktion |
|---|---|
| 1 | PostHookListener::onTransition feuert. |
| 2 | Liest alle aktiven Accounts aus wp_esa_channel_accounts (status='active'). |
| 3 | Wendet die Per-Post-Selection an (siehe unten). |
| 4 | Pro Account: QueueRepository::hasActiveJob($post_id, $account_id)-Check — wenn schon ein pending/processing-Job existiert, wird kein Duplikat angelegt (Idempotenz beim Re-Save). |
| 5 | Pro Account: ein Insert in wp_esa_queue mit status='pending', scheduled_for=now(). |
| 6 | Beim nächsten Cron-Tick (max. 5 Min später, oder sofort per Button — siehe unten) greift QueueProcessor::run die Jobs auf und dispatcht. |
Per-Post-Override via Post-Meta
Zwei Post-Meta-Keys steuern die Verteilung pro Post:
| Meta-Key | Typ | Wirkung |
|---|---|---|
_esa_selected_accounts |
Array von account_ids |
Wenn gesetzt: nur diese Accounts kriegen einen Queue-Job. Wenn leer/nicht gesetzt: alle aktiven Accounts (Default). |
_esa_skip_distribution |
1 / 0 |
Wenn 1: dieser Post wird gar nicht verteilt — kein Queue-Insert, egal was sonst. |
Heute setzt du diese Meta-Keys per Code (z. B. ACF, add_post_meta, REST-API, wp post meta add). Eine UI-Meta-Box / Gutenberg-Sidebar dafür kommt in Phase 5 — bis dahin ist die Verteilung "alle aktiven Accounts kriegen alles, außer du sagst per Meta explizit was anderes".
Skip-Distribution-Beispiel
# Diesen Post nicht verteilen
wp post meta add 123 _esa_skip_distribution 1
# Nur an Mastodon-Account #5 und Bluesky-Account #8
wp post meta update 123 _esa_selected_accounts '[5,8]' --format=json
Cron jetzt ausführen — der manuelle Trigger
Wenn du nicht 5 Minuten warten willst, gibt es auf der Queue-Page einen Button Cron jetzt ausführen. Klick → POST /wp-json/esa/v1/cron/run triggert QueueProcessor::run synchron im Request-Kontext. Im Toast siehst du Before/After-Counts (z. B. "3 jobs processed: 7 → 4 pending"). Praktisch beim Testen, Demos und für ungeduldige Redakteure. Capability-geschützt: nur manage_options.
Alternativ über WP-CLI:
wp cron event run esa_process_queue
Oder per curl gegen den WP-Cron-Endpoint:
curl https://<deine-site>/wp-cron.php?doing_wp_cron
Was geschieht beim Send
Pro Queue-Job ruft QueueProcessor den passenden ChannelDriver::send($post, $account, $payload). Was der Driver pro Channel tut, steht in Kapitel 5b. Erfolg → Job-Eintrag in wp_esa_jobs mit remote_post_id + remote_post_url. Misserfolg → Retry mit Backoff (siehe nächstes Kapitel).
Queue verstehen
Was die Queue-Page zeigt, wie Retry funktioniert und welche Backoff-Strategie der Cron-Processor fährt.
Die Queue-Page (#/queue) liest defaultmäßig pending Jobs (Status-Filter via REST-Query-Param status=processing|done|failed). Tabellen-Spalten:
| Spalte | Quelle | Inhalt |
|---|---|---|
# |
wp_esa_queue.id |
Primary-Key, monospace. |
| Channel | wp_esa_queue.channel_id |
UPPERCASE-Anzeige. |
| Post | Lookup wp_posts.post_title |
Titel des zu postenden Beitrags. |
| Status | wp_esa_queue.status |
Pill: pending / processing / done / failed. |
| Scheduled | wp_esa_queue.scheduled_for |
Lokales Datums-/Zeitformat. |
| Attempts | wp_esa_queue.attempts |
Anzahl bisheriger Versuche (0–4). |
| Aktionen | – | Buttons Retry und Cancel. |
Status-Werte
| Status | Bedeutung |
|---|---|
pending |
Wartet auf den nächsten Cron-Tick (scheduled_for <= now()). |
processing |
Cron-Worker hat ihn gegriffen und ruft gerade den Driver. |
done |
Erfolgreich publiziert, Eintrag liegt in wp_esa_jobs. |
failed |
Terminal fehlgeschlagen (max. Attempts erreicht oder unbedingt-final-Fehler). Failure-Eintrag in wp_esa_jobs. |
Retry- und Backoff-Strategie
| Versuch | Backoff bis zum nächsten Versuch |
|---|---|
| 1 (gerade fehlgeschlagen) | 5 Minuten (300 s) |
| 2 | 15 Minuten (900 s) |
| 3 | 60 Minuten (3 600 s) |
| 4 | 180 Minuten (10 800 s) |
| nach Versuch 4 | terminal failed, kein weiterer Retry |
Konstanten: QueueProcessor::BACKOFF = [1=>300, 2=>900, 3=>3600, 4=>10800], QueueRepository::MAX_ATTEMPTS = 4.
Retry-Button
Setzt manuell status='pending', attempts=0, last_error=NULL, scheduled_for=now(). Auch terminal failed-Einträge können so reanimiert werden — sinnvoll, wenn z. B. eine API kurzfristig down war.
Cancel-Button
Löscht den Queue-Eintrag. Keine History-Spur in wp_esa_jobs.
Batch-Größe
Pro Cron-Tick verarbeitet QueueProcessor::run maximal BATCH_SIZE = 10 Jobs. Größere Backlogs werden seriell über mehrere Ticks abgearbeitet — das schont Remote-Rate-Limits.
Mehrere Channels gleichzeitig
Der typische Phase-4-Workflow: ein Post fließt automatisch an alle aktiven Accounts.
Standard-Workflow
- Du verbindest in Kapitel 5/5b z. B. einen Mastodon-Account, einen Bluesky-Account, einen LinkedIn-Account und einen Telegram-Bot.
- Du veröffentlichst einen WP-Post.
PostHookListenerlegt vier Queue-Jobs an — einen pro aktivem Account.- Der nächste Cron-Tick (oder Button-Klick) dispatcht alle vier parallel an die jeweiligen APIs.
- Im History-Tab siehst du vier Outcome-Einträge mit Links auf den jeweiligen Remote-Post.
Mit Per-Post-Override
Wenn du nur einen Channel pro Post willst:
wp post meta update 999 _esa_selected_accounts '[12]' --format=json
Nur Account 12 kriegt einen Job. Die anderen aktiven Accounts werden für diesen Post übersprungen.
Performance-Hinweise
| Aspekt | Wert / Verhalten |
|---|---|
| Cron-Intervall | 5 Minuten (esa_five_minutes) |
| Batch-Size pro Tick | 10 Jobs |
| Backoff | 5 → 15 → 60 → 180 Minuten |
| Max-Attempts | 4 |
| Idempotenz | hasActiveJob-Check verhindert Duplikate beim Re-Save |
| Manueller Trigger | Button Cron jetzt ausführen auf #/queue |
Bei großen Backlogs (z. B. nach Mass-Republish): mehrfach Cron jetzt ausführen drücken oder per WP-CLI wp cron event run esa_process_queue schleifen lassen.
History
Was in wp_esa_jobs landet, was bewusst nicht geloggt wird und wie du Remote-Posts wiederfindest.
Die History-Page (#/history) listet alle Jobs aus wp_esa_jobs — also Einträge mit Status success oder terminal failed. Retries werden bewusst NICHT in History geschrieben, nur das finale Outcome. Das hält die History rauscharm.
Spalten
| Spalte | Quelle | Hinweis |
|---|---|---|
# |
wp_esa_jobs.id |
Primary-Key. |
| Channel | wp_esa_jobs.channel_id |
UPPERCASE. |
| Post | Lookup wp_posts.post_title |
Titel des Beitrags. |
| Status | wp_esa_jobs.status |
Pill: success (grün) oder failed (rot). |
| Completed | wp_esa_jobs.executed_at |
UTC, in lokale Zeit konvertiert. |
| Remote | wp_esa_jobs.remote_post_url |
Externer Link, öffnet in neuem Tab. |
Datenfelder, die in wp_esa_jobs landen
| Spalte | Inhalt |
|---|---|
queue_id |
Verweis auf den ursprünglichen Queue-Eintrag. |
channel_id |
Z. B. mastodon, bluesky, linkedin. |
account_id |
Welcher Account gepostet hat. |
remote_post_id |
ID auf der Remote-Plattform. |
remote_post_url |
Klickbare URL. |
response_data |
Komplette API-Response als JSON-Blob (Forensik). |
status |
success oder failed. |
executed_at |
UTC-Timestamp des Cron-Runs. |
Bei terminal failed-Einträgen ist remote_post_id leer und response_data enthält den letzten Fehlerkontext.
Logs & Debugging
Wo du nachschaust, wenn etwas klemmt.
Logs-Page
#/logs zeigt den Application-Log-Stream aus wp_esa_logs. Filter-Pills oben (All / Debug / Info / Warn / Error). Limit 200 Einträge pro Reload. Wird von HTTP-Schicht (HttpClient) und Channel-Driver-Code populated.
Apache-Error-Log
Das eigentlich interessante Log liegt im Web-Server-Container:
docker exec wp-esa tail -f /var/log/apache2/error.log
Suche nach Prefixes:
| Prefix | Quelle | Bedeutet |
|---|---|---|
[ESA HTTP] |
HttpClient Retry-Logik bei 429/5xx |
Outbound-HTTP-Calls Richtung Channel-APIs. |
[ESA OAuth] |
OAuthHandler::handle |
OAuth-Callbacks, die nicht durchgegangen sind. |
[ESA <Channel>] |
Driver-interne Hinweise | Z. B. [ESA Mastodon], [ESA LinkedIn]. |
[ESA Hook] |
PostHookListener |
Auto-Enqueue-Trace beim transition_post_status. |
[ESA Cron] |
QueueProcessor |
Batch-Run-Trace mit Job-Count + Outcome. |
Häufige Fehler
| Fehlermeldung | Ursache | Lösung |
|---|---|---|
Failed to register Mastodon app on <instance> (status …) |
Instance falsch geschrieben, nicht erreichbar. | Eingabe prüfen. |
<Channel> OAuth state is unknown or expired. |
Transient ist abgelaufen (TTL 600 s). | Connect neu starten. |
<Channel> token exchange failed (status 400/401) |
Client-Secret nicht akzeptiert oder Redirect-Mismatch. | App-Konfiguration prüfen, Redirect-URI vergleichen. |
<Channel> verify_credentials failed (status 401) |
Token von der Plattform widerrufen. | Account trennen und neu verbinden. |
no driver registered for channel: <id> |
Queue-Eintrag verweist auf nicht-existenten Channel. | channel_id-Spalte prüfen. |
account not found: <id> |
Account getrennt, aber Queue-Eintrag existiert noch. | Queue-Eintrag löschen. |
Cannot decrypt credentials: … |
wp-config.php-Salts wurden rotiert. |
Account löschen, neu verbinden. |
POSTING_BLOCKED (Facebook/Instagram/Threads/TikTok/X) |
Plattform-seitige Freigabe fehlt. | Siehe "Unlock"-Sektion der jeweiligen Channel-README. |
Pinterest: featured image required |
Post hat kein Featured Image. | Featured Image setzen oder Pinterest aus Verteilung ausschließen. |
TikTok: video_url required |
Post hat kein video_url in den Options. |
Per-Post-Option setzen oder TikTok ausschließen. |
Sicherheit
Wie das Plugin mit Tokens, Permissions und Nonces umgeht.
Token-Verschlüsselung
Access-Tokens / Refresh-Tokens / App-Passwords / Bot-Tokens / Webhook-URLs / HMAC-Secrets — alles wird nie im Klartext gespeichert. Helpers\Encryption nutzt AES-256-GCM mit einem zur Laufzeit aus drei WordPress-Salts abgeleiteten Schlüssel:
| Bestandteil | Quelle |
|---|---|
| Schlüssel-Material | SHA-256 über AUTH_KEY + SECURE_AUTH_SALT + NONCE_KEY aus wp-config.php |
| IV-Länge | 12 Bytes (per random_bytes) |
| Tag-Länge | 16 Bytes |
| Payload-Format | base64( IV ‖ TAG ‖ CIPHERTEXT ) |
Wichtige Konsequenz: Wenn du deine wp-config.php-Salts rotierst, werden alle vorhandenen Token-Blobs unentschlüsselbar. Bei der nächsten Validierung fliegt Cannot decrypt credentials und der Account muss neu verbunden werden. Bewusstes Sicherheitsfeature, kein Bug.
Capability-Checks
Alle Admin-Pages und alle REST-Endpoints prüfen current_user_can('manage_options'). Editoren, Autoren oder Subscribers haben keinerlei Zugriff. Eine Custom-Capability-Filter (z. B. damit Editoren auch posten dürfen) ist in Phase 4 nicht eingebaut.
Nonces
Alle mutierenden REST-Routen verlangen einen X-WP-Nonce-Header mit der Action wp_rest. Die React-SPA bekommt den Nonce beim Admin-Asset-Enqueue und sendet ihn automatisch mit.
Kein Phone-Home, kein Tracking
- Keine Aktivierungs-Beacons.
- Keine Lizenz-Server-Checks.
- Keine Telemetrie über genutzte Channels.
- Keine externen JS-Bundles (alles wird mit der React-SPA gebundled und ausgeliefert).
- Outbound-Traffic geht ausschließlich zu Channel-APIs, mit denen du dich aktiv verbunden hast.
Idempotency-Keys
Wo die Plattform es unterstützt (Mastodon prominent), sendet der Driver einen Idempotency-Key-Header der Form esa-<account_id>-<post_id>-<md5_prefix>. Damit verhindert der Remote-Server, dass identische Posts bei einem versehentlichen Retry doppelt erscheinen. Auf der ESA-Seite tut zusätzlich QueueRepository::hasActiveJob denselben Dienst.
Roadmap (Phase 5+)
Was als Nächstes kommt.
Phase 5 — Per-Post-UI
| Feature | Beschreibung |
|---|---|
| Gutenberg-Sidebar-Panel | Per-Post-Channel-/Account-Selection direkt im Block-Editor. Setzt _esa_selected_accounts und _esa_skip_distribution. |
| Classic-Editor Meta-Box | Pendant für Classic-Editor-Nutzer. |
| Per-Post Custom-Text | Statt Auto-Format pro Channel ein eigener Text-Override. |
| Default-Selection global | Settings-Page-Sektion: welche Channels sind per Default aktiv für neue Posts. |
Phase 6 — Scheduling
| Feature | Beschreibung |
|---|---|
| UI-Date-Picker im Editor | scheduled_for per Post setzen, statt nur Sofort-Publish. |
| Calendar-View | Wochen-/Monats-Kalender für Editorial-Pläne. |
| Re-Share-Scheduling | Älteren Post nach X Wochen automatisch nochmal posten. |
Phase 7 — White-Label
| Feature | Beschreibung |
|---|---|
| Per-Workspace Branding | Eigener App-Name auf Mastodon-Authorize-Page, eigene Logos. |
| Multi-Site Network | Network-Activate mit Site-isolierten Channel-Settings. |
| Per-Tenant API-Keys | Eine Plugin-Installation, mehrere mandantenfähige Channel-Pools. |
Phase 8 — Bulk-Ops & Analytics
| Feature | Beschreibung |
|---|---|
| Multi-Account Bulk-Ops | "Trenne alle Mastodon-Accounts", "Verbinde alle neu". |
| Per-Channel-Quotas | Rate-Limiting pro Account / Stunde / Tag. |
| Engagement-Analytics | Likes/Reposts/Replies pro Outbound-Post via Channel-Read-APIs. |
| Best-Time-to-Post | Heuristik aus historischen Engagement-Daten. |
Technische Referenz
Architektur, DB-Schema, REST-Endpoints — für Entwickler, die eigene Driver oder Integrationen bauen wollen.
Architektur in Stichworten
- Autoload: PSR-4 unter
EasySocialAutopilot\aussrc/(siehecomposer.json). - Bootstrap:
Plugin::boot()läuft aufplugins_loaded, registriert Loader, AdminMenu, OAuthHandler, RestApi, QueueProcessor, PostHookListener. - Channel-Driver-Contract: abstrakter
ChannelDrivermit acht abstract methods —getId,getName,getIcon,getOAuthStartUrl,handleOAuthCallback,refreshIfNeeded,validate,send. Jeder neue Channel ist eine konkrete Subklasse. - Registry-Singleton:
Channels\Registry::getInstance()feuert beim ersten Zugriff die Actionesa_register_channels, an die sich Built-ins (viaChannels\Bootstrap) und Drittanbieter ihre eigenen Drivers hängen können. Das ist der offizielle Extension-Point. - HttpClient: zentraler Wrapper um
wp_remote_*. Eingebaute Retry-Semantik für 429 und 5xx (HonoringRetry-After), Multipart-Upload, JSON-Body. - PostHookListener: Hook auf
transition_post_status. Honoriert_esa_skip_distributionund_esa_selected_accounts. Idempotent viahasActiveJob. - DTOs:
ChannelAccount,SendResult— immutable, mitfromDbRow/toPublicArray/success/failure-Factories. - Migrations:
dbDelta-basiert, idempotent, versioniert überwp_options['esa_db_version'].
Wie der Bootstrap die 20 Driver registriert
Channels\Bootstrap::registerBuiltins hängt sich an esa_register_channels und instanziiert die mitgelieferten Drivers. Vereinfacht:
| Aktion | Was passiert |
|---|---|
do_action('esa_register_channels', $registry) |
Wird vom Registry beim ersten getInstance()-Zugriff gefeuert. |
Bootstrap::registerBuiltins |
Default-Listener. Registriert die 20 Built-in-Drivers. |
| Third-Party-Listener | Eigene Drivers per add_action('esa_register_channels', fn($r) => $r->register(new MyDriver())). |
Bedingung für einen Custom-Driver: extends ChannelDriver, alle acht abstract methods implementiert, getId() returnt einen global eindeutigen Slug. Beim nächsten UI-Reload erscheint die Karte im Channels-Grid.
DB-Schema (kompakt)
| Tabelle | Wichtigste Spalten |
|---|---|
wp_esa_channels |
id, channel_id UNIQUE, is_enabled TINYINT, settings LONGTEXT |
wp_esa_channel_accounts |
id, channel_id, remote_user_id, remote_username, avatar_url, credentials_encrypted LONGTEXT, scopes, status ENUM(active,expired,revoked) |
wp_esa_queue |
id, post_id, channel_id, account_id, payload, status ENUM(pending,processing,done,failed), attempts, priority, scheduled_for, last_attempt_at, last_error |
wp_esa_jobs |
id, queue_id, channel_id, account_id, remote_post_id, remote_post_url, response_data LONGTEXT, status ENUM(success,failed), executed_at |
wp_esa_logs |
id, level ENUM(debug,info,warning,error), context, message, meta, created_at |
REST-Endpoints (Namespace esa/v1)
| Methode | Pfad | Zweck |
|---|---|---|
| GET | /dashboard/stats |
Aggregierte StatTile-Zahlen (Phase-4 neu). |
| GET | /channels |
Liste aller registrierten Channel-Driver inkl. account_count. |
| GET | /channels/{id}/accounts |
Accounts pro Channel. |
| POST | /channels/{id}/connect/start |
OAuth-/Token-Connect-Start. |
| POST | /channels/{id}/connect/callback |
API-driven Callback (UI nutzt admin-post.php). |
| DELETE | /accounts/{id} |
Account trennen. |
| GET | /queue?status=pending&limit=50 |
Queue-Einträge nach Status. |
| POST | /queue/{id}/retry |
Job zurück auf pending. |
| DELETE | /queue/{id} |
Job-Eintrag löschen. |
| POST | /cron/run |
Cron sofort triggern (Phase-4 neu). |
| GET | /jobs?account_id=&limit=50 |
History aus wp_esa_jobs. |
| GET | /logs?level=&limit=100 |
Application-Log-Stream. |
Browser-Callback-Endpoint (keine REST-Route, sondern admin-post.php):
| Pfad | Zweck |
|---|---|
/wp-admin/admin-post.php?action=esa_oauth_callback&channel={id} |
Stabile Redirect-URI für OAuth-Provider. |
Cron
| Hook | Schedule | Interval | Worker |
|---|---|---|---|
esa_process_queue |
esa_five_minutes |
300 s | QueueProcessor::run |
Glossar
Kurzdefinitionen der wichtigsten Begriffe aus diesem Handbuch.
| Begriff | Definition |
|---|---|
| OAuth | Autorisierungsprotokoll, das einer Drittanwendung Zugriff auf ein Nutzerkonto gewährt, ohne dass das Passwort weitergegeben wird. EasySocialAutopilot nutzt OAuth-2-Authorization-Code-Flow (LinkedIn, Reddit, Pinterest, WordPress.com, Google-Family, Meta-Family, X) und OAuth-1.0a HMAC-SHA1 (Tumblr). |
| PKCE | Proof Key for Code Exchange. OAuth-2-Erweiterung mit code_verifier/code_challenge, schützt Public-Clients gegen Authorization-Code-Interception. ESA nutzt PKCE bei X (Twitter). |
| Confidential vs Public OAuth Client | Confidential Client besitzt ein Client-Secret und kann es geheim halten (Server-Side). Public Client nicht (Mobile, SPA). ESA-Drivers laufen serverseitig — alle sind Confidential. |
| App-Password | Provider-generiertes Sekundärpasswort, das man Apps gibt, ohne das Hauptpasswort preiszugeben. ESA nutzt App-Password bei Bluesky. |
| Bot-Token | Pre-shared Secret, das einem programmatischen Account-Identifier zugeordnet ist. ESA: Telegram. |
| Webhook | HTTPS-Endpoint, der unsolicited eingehende POSTs entgegennimmt. ESA-Webhook-Channel sendet generischen JSON-Payload — Empfänger entscheidet, was er damit tut. |
| Bearer | HTTP-Authentication-Schema: Authorization: Bearer <token>. Der Token-Holder wird unmodifiziert als Auth akzeptiert. |
| Long-Lived Token | Token mit Wochen- bis Monatslaufzeit, oft per Exchange aus einem Short-Lived Token erzeugt. ESA nutzt z. B. bei Meta und Threads. |
| Token | Access-Token, das der OAuth-Provider nach erfolgreicher Autorisierung ausstellt. Wird als Bearer-Header an die API mitgesendet. |
| Scope | Berechtigungsumfang eines Tokens. Pro Channel verschieden, dokumentiert in den jeweiligen READMEs. |
| App-Review | Plattform-seitiger Verifizierungsprozess (Meta, TikTok), in dem die App-Funktionalität geprüft wird, bevor Production-Scopes freigeschaltet werden. Wochen bis Monate Wartezeit üblich. |
| AES-256-GCM | Symmetrisches Verschlüsselungsverfahren mit integriertem Authentizitäts-Tag. Standard für moderne Token-At-Rest-Verschlüsselung. |
| Cron | Geplanter Hintergrund-Job. WordPress kennt einen "WP-Cron" (Request-getrieben) und System-Cron (zeitgesteuert per Server). |
| Queue | Persistente Tabelle pending Jobs (wp_esa_queue), die der Cron-Worker abarbeitet. |
| Backoff | Wartezeit zwischen Retry-Versuchen, die mit jedem Fehlversuch wächst. Hier: 5 → 15 → 60 → 180 Minuten. |
| dbDelta | WordPress-Funktion zum idempotenten Anlegen/Migrieren von DB-Tabellen aus CREATE-TABLE-Statements. |
| Idempotency-Key | HTTP-Header, der dem Server signalisiert, dass identische Requests nur einmal verarbeitet werden sollen. Verhindert Doppel-Posts bei Retries. |
| Federation | Architektur des Fediverse: viele eigenständige Mastodon-/Pleroma-/Misskey-Instanzen, die über ActivityPub kommunizieren. |
| AT Protocol | Authenticated Transfer Protocol. Föderiertes Protokoll hinter Bluesky. PDS = Personal Data Server, hostet die User-Daten. |
| Toot / Status | Mastodon-Begriffe für einen einzelnen Beitrag. Toot ist die historische, Status die aktuelle API-Bezeichnung. |
| Instance | Eigenständiger Mastodon-Server (z. B. mastodon.social, chaos.social). |
| PDS | Personal Data Server im AT-Protocol — Bluesky-Pendant zur Mastodon-Instance. Default: https://bsky.social. |
| Transient | Kurzlebige Key-Value-Speicherung in WordPress, hier genutzt für OAuth-State (10 Min TTL). |
| PSR-4 | PHP-Autoload-Standard, der Klassen-Namespaces auf Datei-Pfade abbildet. |
| HMAC-SHA256 | Hashed Message Authentication Code. ESA-Webhook-Channel signiert Payloads optional mit HMAC-SHA256(secret, body). |
| POSTING_BLOCKED | Marker im Code: OAuth-Connect funktioniert, aber send() returnt eine Failure, weil eine externe Freigabe fehlt. Siehe Kapitel 5b.4. |
Über dieses Plugin
EasySocialAutopilot ist eine Open-Source-Initiative, getragen von Uwe Hiltmann (Internet-Unternehmensberater & KI-Consultant) und Contributors. Ziel: eine WordPress-Social-Distribution-Pipeline, die ohne Phone-Home, ohne Lizenz-Server und ohne SaaS-Abhängigkeit funktioniert.
| Detail | Wert |
|---|---|
| Plugin-Version | 0.4.0 (Phase 4 — Auto-Enqueue + 20 Channels) |
| Lizenz | GPL-2.0+ |
| Repository | github.com/uh8888/easysocialautopilot |
| Text-Domain | easy-social-autopilot |
| Mindest-WordPress | 6.0 |
| Mindest-PHP | 8.0 |
Feedback, Bug-Reports und Pull-Requests sind willkommen — nutze dafür den Issue-Tracker im GitHub-Repository.
Stand dieses Handbuchs: Phase 4. Auto-Enqueue produktiv, 20 Channel-Drivers registriert, 11 davon voll funktional posting-fähig, 5 Posting-blockiert pending external approval, 1 description-only (YouTube), 1 token-only (Medium), 1 Platzhalter (XING). Phase 5 bringt das Per-Post-UI im Gutenberg-Editor.