Skip to content

water-apps/waterapps-linkedin-publisher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WaterApps LinkedIn Publisher (Scaffold)

Local publishing workflow scaffold for WaterApps LinkedIn content.

Purpose:

  • keep post drafts in versioned files
  • validate copy + links + UTM parameters
  • preview final post text
  • support dry-run publishing now
  • add real LinkedIn API posting later (credentials/scopes required)

Status

This is a scaffold. It supports:

  • validate
  • preview
  • list (queue view: drafts/approved/published)
  • move (change post status and queue folder)
  • publish --dry-run
  • publish --live (API hook, requires LinkedIn app credentials and approved access)
  • GitHub Actions for validation and optional scheduled batch publishing

Quick Start

cd /Users/varunau/Projects/waterapps/instances/water-apps/waterapps-linkedin-publisher
python3 publisher.py validate posts/sample_audit_ready_cicd.json
python3 publisher.py preview posts/sample_audit_ready_cicd.json
python3 publisher.py list
python3 publisher.py move posts/sample_audit_ready_cicd.json --to drafts
python3 publisher.py publish posts/sample_audit_ready_cicd.json --dry-run
python3 linkedin_auth_helper.py auth-url --redirect-uri "https://YOUR_CALLBACK"

Post File Format

Posts are stored as JSON for zero-dependency parsing.

Queue folders are available under posts/:

  • posts/drafts/
  • posts/approved/
  • posts/published/
  • posts/logs/publish_log.jsonl (created on publish runs)

Required fields:

  • id
  • status
  • audience
  • headline
  • body

Optional fields:

  • cta_url
  • utm (object)
  • tags (array)
  • notes

See posts/sample_audit_ready_cicd.json.

LinkedIn API Notes

LinkedIn posting requires:

  • a LinkedIn app
  • approved product/scopes for posting
  • access token
  • author URN (profile or organization)

Set environment variables (see .env.example) before --live.

Detailed setup notes: docs/LINKEDIN_API_SETUP.md

OAuth / Token Helper

Use linkedin_auth_helper.py to assist with:

  • generating OAuth authorization URLs
  • exchanging an auth code for an access token
  • fetching /v2/me
  • printing urn:li:person:<id> for LINKEDIN_AUTHOR_URN

Examples:

# Start a local callback listener (recommended)
python3 linkedin_auth_helper.py listen-callback --host 127.0.0.1 --port 8080 --path /callback

# Generate auth URL (uses LINKEDIN_CLIENT_ID from env if not provided)
# IMPORTANT: register this exact redirect URI in LinkedIn app settings first
python3 linkedin_auth_helper.py auth-url --redirect-uri "http://127.0.0.1:8080/callback"

# Optional: include expected state verification
python3 linkedin_auth_helper.py auth-url --redirect-uri "http://127.0.0.1:8080/callback" --state "YOUR_STATE"
python3 linkedin_auth_helper.py listen-callback --host 127.0.0.1 --port 8080 --path /callback --expected-state "YOUR_STATE"

# Extract code from the full browser callback URL (recommended)
python3 linkedin_auth_helper.py extract-code --url "https://localhost/callback?code=REAL_CODE&state=REAL_STATE"

# Exchange auth code for token
python3 linkedin_auth_helper.py exchange-code \
  --client-id "$LINKEDIN_CLIENT_ID" \
  --client-secret "$LINKEDIN_CLIENT_SECRET" \
  --redirect-uri "https://localhost/callback" \
  --code "AUTH_CODE_FROM_CALLBACK"

# Resolve person URN from token (requires profile scope support on your app)
python3 linkedin_auth_helper.py person-urn --access-token "$LINKEDIN_ACCESS_TOKEN"

Common mistake to avoid:

  • The state value printed by auth-url is not the OAuth code.
  • Use extract-code on the full callback URL after LinkedIn redirects your browser.
  • redirect_uri in auth-url and exchange-code must match the registered LinkedIn app callback URL exactly.
  • Some LinkedIn apps are not authorized for openid/profile; the helper now defaults to w_member_social only.
  • If you need person-urn lookup and your app supports it, pass --scopes "openid profile w_member_social" (or an approved profile-read scope) explicitly.

GitHub Actions (Optional Automation)

Included workflows:

  • .github/workflows/linkedin-content-quality.yml
    • validates all queued post JSON files on push/PR
  • .github/workflows/linkedin-batch-publish.yml
    • manual workflow_dispatch batch publish (dry-run/live)
    • optional scheduled posting (disabled by default)

To enable unattended scheduled posting:

  1. Add repo secrets:
    • LINKEDIN_ACCESS_TOKEN
    • LINKEDIN_AUTHOR_URN
  2. Add repo variable:
    • LINKEDIN_AUTOPUBLISH_ENABLED=true
  3. (Optional) Add repo variable:
    • LINKEDIN_BATCH_LIMIT=1

Recommended rollout:

  • Start with workflow_dispatch + dry-run
  • Test workflow_dispatch + live
  • Only then enable scheduled autopublish

Safety Defaults

  • publish defaults to dry-run
  • copy validation warns on likely issues (length, missing UTM on WaterApps URLs)
  • no scraping or outreach automation features are included
  • live publish does not move files automatically unless --move-to-published is set

One-Command OAuth Setup (Recommended: Python)

Use scripts/linkedin_oauth_setup.py to run the local OAuth flow (browser approval still required), exchange the code, and update GitHub secrets/vars via gh.

export LINKEDIN_CLIENT_ID="<your-client-id>"
export LINKEDIN_CLIENT_SECRET="<your-client-secret>"
python3 scripts/linkedin_oauth_setup.py

It will attempt to resolve and store LINKEDIN_AUTHOR_URN automatically when the token has profile-read permissions.

Shell fallback (older implementation) is still available:

./scripts/linkedin_oauth_setup.sh

About

No description, website, or topics provided.

Resources

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors