An automation system for planning, generating, approving, publishing, and monitoring LinkedIn content using Python scripts, GitHub Actions, Discord bots/webhooks, Google Sheets, and Supabase.
This repository uses a WAT-style split:
- Workflows (
workflows/*.md) for SOP/instructions - Agents/orchestration (
orchestrator.py, Discord bots) - Tools (
tools/*.py) for deterministic execution
- Reads pending topics from Google Sheets Topic Bank (
tools/generate_posts.pyviatools/sheets_helper.py) - Generates up to 5 posts for next Mon–Fri with Groq (
GROQ_MODEL, defaultllama-3.3-70b-versatile) - Auto-assigns audience/category logic by weekday (engineer vs founder)
- Writes back to Sheets columns including:
post_text,hook,bullet_points,headline,audience,status,scheduled_date
- Duplicate-week guard: skips generation if the upcoming week already has scheduled content
- Includes
--dry-runmode for integration checks without writing production content
tools/generate_images.pyrenders branded 1080x1350 PNGs using Playwright + Chromium from HTML templates:templates/technical_post.htmltemplates/business_post.htmltemplates/story_post.html
- Uses extracted
hook/headline/bullet_pointsfor infographic layout - Uploads PNGs to Supabase Storage bucket (
SUPABASE_IMAGE_BUCKET, defaultpost-images) - Writes public image URL back to Sheets
image_path - Falls back gracefully if render/upload fails (post can still proceed text-only path)
- In
#ready-posts,tools/ready_post_intake.py:- Uses pasted post text as-is (no rewriting)
- Extracts structure (hook/headline/bullets/category/audience) with Groq
- Renders image with Playwright
- Stores post in Supabase
- Queues next available weekday in Sheets
- Sends Discord preview with Approve & Schedule button
- Includes idempotent dedup by Discord message ID (
topic=ready:{message_id})
- In
#stories,tools/story_intake.py:- Validates story length and deduplicates submissions
- Uses Groq to transform raw story into publish-ready post + extracted points
- Saves story + post in Supabase
- Schedules next available Saturday slot in Sheets
- Responds with preview and scheduling status in Discord
tools/publish_post.pypublishes approved content to LinkedIn UGC API- Supports:
- Registering LinkedIn image upload
- Uploading image bytes (from URL or local path)
- Posting text-only fallback if image upload fails
- Sheets status updates to
published - Supabase record insertion/update for published posts
- Discord publish confirmations and error alerts
- Includes token handling:
- 401 handling with refresh-token attempt
- 429 rate-limit wait-and-retry
tools/triage_comments.py:- Pulls comments for recently published posts (lookback window)
- Deduplicates via
processed_comments - Classifies comments in Groq batches into A/B/C:
- A = lead (Discord alert + drafted DM)
- B = content idea (append to Sheets backlog)
- C = noise (logged only)
- Operational safeguard:
- Handles LinkedIn comments API 403 (Marketing Developer Platform (MDP) restriction) with one-per-week warning gate using
configtable
- Handles LinkedIn comments API 403 (Marketing Developer Platform (MDP) restriction) with one-per-week warning gate using
tools/search_leads.py:- Uses Apify LinkedIn post search actor + multiple query patterns
- Filters/deduplicates against
leads_seen - Uses Groq to score lead quality and draft outreach comment + DM
- Sends alerts for qualified leads to Discord webhook
- Includes retry/delay logic for 429 and transient failures
tools/weekly_digest.py:- Queries published posts from Supabase
- Computes weekly totals and weighted engagement score
- Compares against previous week (WoW deltas)
- Sends digest to Discord
- Updates
config.top_performers(best-effort)
watchers/discord_watcher.pycommand bot for:!approve,!reject,!queue,!status,!pause,!resume
tools/dm_ghostwriter.pyslash-command bot for DM drafting:/draft,/biz,/tech,/follow,/reply,/comment,/retry
- Vault-based orchestration (
orchestrator.py+vault_sync.py) for task lifecycle in Obsidian-style folders (Needs_Action,Pending_Approval,Done, etc.)
- Retries / backoff: present for Groq, Apify, LinkedIn, and Supabase query paths
- Rate-limit handling: explicit handling for 429 in multiple scripts
- Error handling: non-fatal handling where possible + Discord notifications + vault logs
- Scheduling: GitHub Actions cron + persistent PM2 processes for bots/orchestrator
- Deduplication: comments, stories, lead URLs, ready-post intake all have dedup logic
- Not implemented in codebase: proxy rotation, CAPTCHA solving, and interactive 2FA automation
tools/linkedin_auth.pyexists but main flow is currently TODO/incompletetools/generate_post.py,tools/generate_image.py, andtools/publish_linkedin.pyprovide an alternate/legacy DB-first pipeline (still usable scripts, but main scheduled path currently usesgenerate_posts.py+generate_images.py+publish_post.py)mcp_servers/*are stubs/partial integration surfaces
- Python 3.11 (GitHub Actions workflows pin this version)
- Node.js/PM2 configs for long-running process management (
pm2_ecosystem*.config.js)
- LLM:
groq,anthropic,openai,google-genai - Data/API:
requests,python-dotenv - Database/Storage:
supabase - Google integration:
gspread,google-auth,google-auth-httplib2,google-api-python-client - Discord:
discord.py - Automation/rendering:
playwright - Images:
Pillow - Utilities:
pytz,python-frontmatter
- LinkedIn API v2 (UGC posting, social actions endpoints)
- Supabase Postgres + Storage
- Google Sheets API
- Discord bot + webhooks
- Apify (lead search source)
- GitHub Actions (scheduled/dispatch automation)
- HTML/CSS templates for social image rendering (Playwright screenshot pipeline)
- Discord embeds/buttons for approval and operational control
- Local HTTP callback scaffold for LinkedIn OAuth exists (
http.serverusage inlinkedin_auth.py)
- GitHub Actions workflow automation (
.github/workflows/*.yml) - PM2 process manager configs for persistent services
- Deploy scripts for remote host sync/restart (
deploy.sh, deploy workflows)
postsstoriesprocessed_commentsconfigcontent_backlogleads_seenvault_items(insupabase/vault_items.sql)
update_updated_at()trigger for posts (legacy schema file)update_config_updated_at()trigger for configset_vault_items_updated_at()trigger for vault_items
Note: The repo currently contains both
schema/supabase_schema.sql(broader current schema) andsupabase/schema.sql(legacy/alternate shape). In practice, treatschema/supabase_schema.sqlas the primary source andsupabase/schema.sqlas deprecated legacy to avoid schema drift.
.
├── .github/workflows/ # Scheduled/manual GitHub Actions jobs
├── tools/ # Core automation scripts
├── watchers/ # Discord command watcher + notifications
├── templates/ # HTML image templates
├── workflows/ # SOP docs / workflow specs
├── schema/ # Main Supabase SQL schema
├── supabase/ # Additional SQL (legacy + vault items)
├── orchestrator.py # Vault/task orchestrator loop
├── vault_sync.py # Vault bridge + logging/dashboard helpers
├── pm2_ecosystem*.config.js # PM2 process configs
└── .env.example # Environment variable template
Primary entry points used in practice:
tools/generate_posts.pytools/generate_images.pytools/publish_post.pytools/triage_comments.pytools/search_leads.pytools/weekly_digest.pytools/dm_ghostwriter.pywatchers/discord_watcher.pyorchestrator.py
git clone https://github.com/usmankhan045/linkedin-automation.git
cd linkedin-automation
python -m pip install -r requirements.txtplaywright install chromium --with-depscp .env.example .envPopulate required values (see Configuration section below).
- Provide service account JSON via one of:
GOOGLE_SERVICE_ACCOUNT_JSON(raw JSON string; common in CI)GOOGLE_SERVICE_ACCOUNT_JSON_PATH(path to localcredentials.json)
- Share the target sheet with the service account email.
- Run schema SQL (choose your target schema file and stay consistent)
- Create storage bucket (default:
post-images) - Set
SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEY
- Start long-running bots/orchestrator with PM2 using provided config files.
The repository’s Python code reads the following env vars:
GROQ_API_KEY,GROQ_MODELOPENAI_API_KEYANTHROPIC_API_KEYGEMINI_API_KEY
LINKEDIN_ACCESS_TOKENLINKEDIN_REFRESH_TOKENLINKEDIN_CLIENT_IDLINKEDIN_CLIENT_SECRETLINKEDIN_PERSON_URN
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEYSUPABASE_IMAGE_BUCKET
GOOGLE_SHEETS_SPREADSHEET_IDGOOGLE_SERVICE_ACCOUNT_JSONGOOGLE_SERVICE_ACCOUNT_JSON_PATHSHEETS_TOPIC_BANK_TABSHEETS_CONTENT_BACKLOG_TAB
DISCORD_BOT_TOKENDISCORD_GHOSTWRITER_CHANNEL_IDDISCORD_STORIES_CHANNEL_IDDISCORD_READY_POSTS_CHANNEL_IDDISCORD_COMMANDS_CHANNEL_IDDISCORD_WEBHOOK_CONFIRMATIONSDISCORD_WEBHOOK_NEEDS_ACTIONDISCORD_WEBHOOK_APPROVALDISCORD_WEBHOOK_LINKEDINDISCORD_WEBHOOK_ERRORSDISCORD_WEBHOOK_BRIEFINGSDISCORD_WEBHOOK_COMMANDSDISCORD_WEBHOOK_READY_POSTSDISCORD_LEADS_WEBHOOK_URLDISCORD_LEAD_HUNTER_WEBHOOK_URLDISCORD_DIGEST_WEBHOOK_URL
APIFY_API_TOKENVAULT_PATHGIT_SYNC
Generate weekly drafts:
python tools/generate_posts.pyGenerate images for scheduled pending posts:
python tools/generate_images.pyPublish today’s approved post:
python tools/publish_post.pyRun comment triage manually:
python tools/triage_comments.pyRun lead hunter manually:
python tools/search_leads.pySend weekly digest now:
python tools/weekly_digest.pyRun DM ghostwriter bot:
python tools/dm_ghostwriter.pyRun command watcher bot:
python watchers/discord_watcher.pyOrchestrator dry run:
python orchestrator.py --once --dry-rundaily_publish.yml— scheduled weekdays publishweekly_digest.yml— scheduled weekly digestlead_hunter.yml— scheduled lead searchweekly_generation.yml— currently manual (workflow_dispatch)comment_triage.yml— currently manual (workflow_dispatch)deploy.yml/sync_secrets.yml— deployment/ops
- LinkedIn API access has platform restrictions (notably comments/analytics 403 without elevated program access in some cases).
- Automating posting, scraping, or engagement can violate LinkedIn ToS depending on account/app permissions and usage patterns.
- Use conservative rate limits, avoid spam behavior, and keep human approval in the loop for outbound actions.
- You are responsible for compliance, account health, credential security, and regional/legal requirements.
Current repository includes integration/smoke style scripts/docs (e.g., INTEGRATION_TEST.md) but does not include a formal pytest test suite in this clone.