From d8baad0d9b3563c65fd97e01a6c2bfdcdc8d3767 Mon Sep 17 00:00:00 2001 From: nusquama Date: Wed, 12 Nov 2025 12:58:19 +0100 Subject: [PATCH] creation --- .../retweet_cleanup_with_scheduling_for_x_twitter.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 workflows/Retweet Cleanup with Scheduling for X-Twitter-9766/retweet_cleanup_with_scheduling_for_x_twitter.json diff --git a/workflows/Retweet Cleanup with Scheduling for X-Twitter-9766/retweet_cleanup_with_scheduling_for_x_twitter.json b/workflows/Retweet Cleanup with Scheduling for X-Twitter-9766/retweet_cleanup_with_scheduling_for_x_twitter.json new file mode 100644 index 000000000..7dd2fab57 --- /dev/null +++ b/workflows/Retweet Cleanup with Scheduling for X-Twitter-9766/retweet_cleanup_with_scheduling_for_x_twitter.json @@ -0,0 +1 @@ +{"id":"ayEitoNXtdYvLPfW","meta":{"instanceId":"15d6057a37b8367f33882dd60593ee5f6cc0c59310ff1dc66b626d726083b48d"},"name":"Automated Retweet Cleanup for X Accounts with Scheduling","tags":[],"nodes":[{"id":"69913088-8501-421a-b049-fa8d13f36485","name":"TRIGGER | Schedule (09:00 server time)","type":"n8n-nodes-base.scheduleTrigger","notes":"Runs daily at 09:00 (server timezone). Adjust cadence as needed.","position":[-192,560],"parameters":{"rule":{"interval":[{"triggerAtHour":9}]}},"typeVersion":1.2},{"id":"439b67e8-ba4a-4711-b3ab-e5b90d64cfc8","name":"CONFIG | User Variables (Set Fields)","type":"n8n-nodes-base.set","notes":"Centralizes user-editable variables for easy setup.","position":[112,560],"parameters":{"values":{"number":[{"name":"max_results","value":50},{"name":"batch_delay_minutes","value":1}],"string":[{"name":"target_username","value":"yourhandle"}]},"options":{},"keepOnlySet":true},"typeVersion":2},{"id":"90daaa08-e061-40a8-b127-352f41d3ccf6","name":"FETCH | Get user ID by username (X API)","type":"n8n-nodes-base.httpRequest","notes":"Resolves @handle β†’ user.id via X API. Uses OAuth2 Credentials (no tokens in node).","position":[336,560],"parameters":{"url":"={{ `https://api.twitter.com/2/users/by/username/${$node[\"CONFIG | User Variables (Set Fields)\"].json.target_username}` }}","options":{},"sendHeaders":true,"authentication":"oAuth2","headerParameters":{"parameters":[{"name":"User-Agent","value":"n8n-workflow"}]}},"typeVersion":4.2},{"id":"e7f8d1a4-4554-498c-bd08-608601de57dd","name":"FETCH | Latest tweets (X API)","type":"n8n-nodes-base.httpRequest","notes":"Fetches recent tweets for the user. Includes fields to detect retweets.","position":[592,560],"parameters":{"url":"={{ `https://api.twitter.com/2/users/${$json.data.id}/tweets` }}","options":{},"sendQuery":true,"authentication":"oAuth2","queryParameters":{"parameters":[{"name":"max_results","value":"={{ $node[\"CONFIG | User Variables (Set Fields)\"].json.max_results }}"},{"name":"tweet.fields","value":"created_at,referenced_tweets"}]}},"typeVersion":4.2},{"id":"237c5e8a-afde-4a31-a1f3-dadabfe4b57c","name":"SPLIT | One item per tweet","type":"n8n-nodes-base.splitOut","notes":"Converts the tweets array into one item per tweet.","position":[832,560],"parameters":{"options":{},"fieldToSplitOut":"data"},"typeVersion":1},{"id":"2f57969a-11e0-47ca-aee5-b137c57a5e62","name":"IF | Is retweet?","type":"n8n-nodes-base.if","notes":"Continue only when the first referenced_tweet is of type 'retweeted'.","position":[1072,560],"parameters":{"options":{},"conditions":{"options":{"version":2,"caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"cond-rt","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.referenced_tweets ? $json.referenced_tweets[0].type : '' }}","rightValue":"retweeted"}]}},"typeVersion":2.2},{"id":"e2727f92-2da8-4eda-8013-2aa9110651c8","name":"LOOP | Split in batches (rate-limit friendly)","type":"n8n-nodes-base.splitInBatches","notes":"Processes items in small batches to respect API limits.","onError":"continueRegularOutput","position":[1312,560],"parameters":{"options":{}},"typeVersion":3},{"id":"06dd0960-e777-4dd2-9a3a-0044208d5e46","name":"SEND | Unretweet (delete retweet)","type":"n8n-nodes-base.twitter","notes":"Deletes YOUR retweet; the original tweet remains.","position":[1536,480],"parameters":{"operation":"delete","tweetDeleteId":{"__rl":true,"mode":"id","value":"={{ $json.id }}"}},"typeVersion":2},{"id":"e8b4b141-caee-4df9-a132-614e82b287ea","name":"WAIT | Delay between API calls","type":"n8n-nodes-base.wait","notes":"Spaces API calls using CONFIG.batch_delay_minutes.","position":[1856,528],"webhookId":"62cb023c-c542-4636-8b33-07dc61bded3b","parameters":{"unit":"minutes","amount":"={{ $node[\"CONFIG | User Variables (Set Fields)\"].json.batch_delay_minutes }}"},"typeVersion":1.1},{"id":"b0712253-079a-4f14-b633-64510b75ac2f","name":"🟨 Sticky: Description (copy of page)","type":"n8n-nodes-base.stickyNote","position":[-864,176],"parameters":{"color":"yellow","width":540,"height":992,"content":"## Who’s it for\nSocial media managers, creators, and brand accounts that rely on retweets for reach but want an automated, hands-off cleanup after campaigns to keep profiles tidy and on-brand.\n\n## What it does / How it works\nThe workflow runs on a schedule, resolves your handle to a user ID, fetches recent tweets, filters retweets only, and unretweets them safely with batching and delays to respect rate limits. A dedicated CONFIG (Set Fields) node centralizes variables like target_username, max_results, and batch_delay_minutes, so you can adjust behavior without touching logic.\n\n## Use cases\n- Brand hygiene: Auto-unretweet promos after 48–72h.\n- Campaign cadence: Remove event retweets once the event ends.\n- Feed freshness: Clear low-priority retweets on a rolling basis.\n\n## How to set up\n1) Open CONFIG (Set Fields) and replace placeholders: { \"target_username\": \"yourhandle\", \"max_results\": 50, \"batch_delay_minutes\": 1 }\n2) In HTTP/Twitter nodes, assign your X (Twitter) OAuth2 credentials (never paste tokens into nodes).\n3) Adjust the schedule and run a test, then enable.\n\n## Requirements\n- n8n (cloud/self-hosted)\n- X developer app with OAuth2 + permissions to read tweets and delete your retweets\n\n## How to customize\n- Raise max_results (up to 100) or change the schedule (cron/interval).\n- Modify the IF condition to target replies/quotes.\n- Add logging/notifications (Slack/Email) after each unretweet.\n\n**Security:** No hardcoded API keys. Use Credentials. Keep delay β‰₯ 1 minute.\n\n## API endpoints used\n- **GET /2/users/by/username/{username}**\n- **GET /2/users/{id}/tweets** with `tweet.fields=created_at,referenced_tweets`\n\n### Example: GET /2/users/by/username/{username}\n```json\n{\n \"data\": { \"id\": \"2244994945\", \"name\": \"Twitter Dev\", \"username\": \"TwitterDev\" }\n}\n```\n\n### Example: GET /2/users/{id}/tweets (truncated)\n```json\n{\n \"data\": [\n {\n \"id\": \"1760000000000000000\",\n \"text\": \"RT @someone: …\",\n \"referenced_tweets\": [{ \"type\": \"retweeted\", \"id\": \"1759999999999999999\" }],\n \"created_at\": \"2025-01-15T09:10:11.000Z\"\n }\n ],\n \"meta\": { \"result_count\": 20 }\n}\n```"},"typeVersion":1},{"id":"8070f2cd-ded2-47d6-a7a1-261dda506d5f","name":"Sticky: Quick setup","type":"n8n-nodes-base.stickyNote","position":[752,1280],"parameters":{"color":"white","height":280,"content":"## Quick setup\n- Open **CONFIG (Set Fields)** and fill: `target_username`, `max_results`, `batch_delay_minutes`.\n- Assign **X OAuth2** credentials in HTTP/Twitter nodes.\n- Run once with a sample; then enable."},"typeVersion":1},{"id":"49537208-5044-4ebb-9e50-f79ca9287acc","name":"Sticky: Rules & customization","type":"n8n-nodes-base.stickyNote","position":[-864,1184],"parameters":{"color":"white","width":532,"height":280,"content":"## Rules & customization\n- Change schedule cadence in **TRIGGER**.\n- Adjust `max_results` up to 100.\n- Edit IF for replies/quotes.\n- Add **ON FAIL** routing (dead letter) + Slack/Email."},"typeVersion":1},{"id":"1f5027fc-0ff1-4a0c-a555-225c29314d60","name":"Sticky: Troubleshooting","type":"n8n-nodes-base.stickyNote","position":[144,1280],"parameters":{"color":"white","width":260,"height":456,"content":"## Troubleshooting\n- 401/403 β†’ credential scopes or expired token.\n- 404 β†’ invalid tweet_id or missing permission.\n- 429 β†’ add **Wait** / increase delay; consider smaller batches."},"typeVersion":1},{"id":"0b1a83f8-3bb0-4eae-a0bf-2be3bde1ef62","name":"Sticky: TRIGGER details","type":"n8n-nodes-base.stickyNote","position":[-256,160],"parameters":{"color":"white","height":516,"content":"## TRIGGER | Schedule\n- Runs daily at 09:00 server time.\n- Change cadence: cron or interval.\n- Testing tips: run once manually to push a sample through the whole pipeline.\n\n**Output:** emits a single empty item to start the flow.\n**Failure modes:** none (but cron misconfig = no runs)."},"typeVersion":1},{"id":"521b8703-ff91-49ba-9ae8-8da8e481827a","name":"Sticky: CONFIG details","type":"n8n-nodes-base.stickyNote","position":[0,0],"parameters":{"color":"white","width":224,"height":676,"content":"## CONFIG | User Variables (Set Fields)\nCentralizes user-editable fields.\n\n**Fields**\n- `target_username` (string) β€” your X handle without `@`.\n- `max_results` (number) β€” tweets to fetch (≀100).\n- `batch_delay_minutes` (number) β€” delay between API calls.\n\n**Output JSON**\n```json\n{\n \"target_username\":\"yourhandle\",\n \"max_results\":50,\n \"batch_delay_minutes\":1\n}\n```\n**Tip:** keep all user inputs here to avoid touching logic nodes."},"typeVersion":1},{"id":"9f368eb6-d8a8-4f2f-8c8d-b19f94a3d662","name":"Sticky: GET USER details","type":"n8n-nodes-base.stickyNote","position":[240,48],"parameters":{"color":"white","width":256,"height":628,"content":"## FETCH | Get user ID by username (HTTP)\n**Purpose:** Resolve `@handle` β†’ `user.id` via X API.\n\n**Auth:** OAuth2 (Credentials). No tokens in node.\n**Request:** `GET /2/users/by/username/{handle}`\n**Output shape:**\n```json\n{ \"data\": { \"id\": \"123\", \"username\": \"yourhandle\" } }\n```\n**Common errors:**\n- 401/403 β†’ invalid/expired OAuth2.\n- 404 β†’ handle not found or suspended.\n**Next:** feeds `data.id` to Fetch Tweets."},"typeVersion":1},{"id":"e3927387-c8f4-4a4f-a1a4-d95ee3e7efd9","name":"Sticky: FETCH TWEETS details","type":"n8n-nodes-base.stickyNote","position":[512,48],"parameters":{"color":"white","width":208,"height":628,"content":"## FETCH | Latest tweets (HTTP)\n**Purpose:** Pull recent tweets for the resolved user.\n\n**Query params:**\n- `max_results` ← from CONFIG\n- `tweet.fields=created_at,referenced_tweets` (retweet detection)\n\n**Output shape (truncated):**\n```json\n{ \"data\": [ {\"id\":\"...\",\"referenced_tweets\":[{\"type\":\"retweeted\"}]} ] }\n```\n**Edge cases:** empty `data` array; pagination not enabled in this template.\n**Next:** passes array to SPLIT."},"typeVersion":1},{"id":"9db3075f-3341-4177-b4c0-42a7f97c7cd6","name":"Sticky: SPLIT details","type":"n8n-nodes-base.stickyNote","position":[736,192],"parameters":{"color":"white","height":484,"content":"## SPLIT | One item per tweet\n**Purpose:** Convert list response to single items per tweet.\n\n**Input field:** `data`\n**Output:** each item = one tweet (enables per-item filtering & actions).\n**Tip:** If API changes payload keys, update `fieldToSplitOut`."},"typeVersion":1},{"id":"536b8ab0-3da5-4ffd-9029-93367c37a8ad","name":"Sticky: IF RT details","type":"n8n-nodes-base.stickyNote","position":[992,192],"parameters":{"color":"white","height":484,"content":"## IF | Is retweet?\n**Condition:**\n`$json.referenced_tweets ? $json.referenced_tweets[0].type : ''` equals `retweeted`.\n\n**True path:** retweets only β†’ proceed to LOOP.\n**False path:** non-retweets are dropped.\n**Edge cases:** missing `referenced_tweets` β†’ false branch.\n**Customize:** change to replies/quotes by checking `type`."},"typeVersion":1},{"id":"e7fe7eae-8b07-47d9-8003-eb02ee1960b6","name":"Sticky: LOOP details","type":"n8n-nodes-base.stickyNote","position":[1248,192],"parameters":{"color":"white","width":224,"height":484,"content":"## LOOP | Split in batches\n**Purpose:** Respect rate limits by chunking.\n\n**Behavior:** iterates through items in small groups.\n**Tip:** combine with WAIT for pacing; add error routing if needed.\n**Failure:** on API failure, downstream node can push to dead-letter (not included by default)."},"typeVersion":1},{"id":"aab64d61-9f7a-410a-b4c8-f654d9159848","name":"Sticky: UNRETWEET details","type":"n8n-nodes-base.stickyNote","position":[1488,192],"parameters":{"color":"white","width":320,"height":484,"content":"## SEND | Unretweet (delete retweet)\n**Purpose:** Delete YOUR retweet only.\n\n**Node:** Twitter β†’ operation: `delete`\n**Input:** `tweetDeleteId = $json.id`\n**Note:** original author’s tweet remains intact.\n**Auth:** Twitter Credential (OAuth2) selected in node.\n**Errors:** 404 when `id` is not a retweet; 403 without write scope."},"typeVersion":1},{"id":"70400462-8a65-4d7a-8c3a-c9a826087575","name":"Sticky: WAIT details","type":"n8n-nodes-base.stickyNote","position":[1824,192],"parameters":{"color":"white","width":320,"height":484,"content":"## WAIT | Delay between API calls\n**Purpose:** Space out requests to avoid 429 responses.\n\n**Delay:** uses `batch_delay_minutes` from CONFIG.\n**Recommendation:** keep β‰₯ 1 minute. Increase during heavy cleanup.\n**Alternative:** Use Wait β†’ `seconds` for finer control if needed."},"typeVersion":1},{"id":"d26b1bc9-6169-48c1-bb74-a6670bc3028d","name":"Sticky: Data flow map","type":"n8n-nodes-base.stickyNote","position":[416,1280],"parameters":{"color":"white","width":320,"height":484,"content":"## Data flow\nTRIGGER β†’ CONFIG β†’ GET USER β†’ FETCH TWEETS β†’ SPLIT β†’ IF RT β†’ LOOP β†’ UNRETWEET β†’ WAIT β†’ LOOP\n\n**Customize hooks:**\n- Add LOG node after UNRETWEET\n- Add ON FAIL branch from LOOP/UNRETWEET to Sheet/Slack\n- Extend IF for keyword/author filters in a prior ENRICH node\n\nNotify path: UNRETWEET β†’ LOG β†’ Slack (optional)\nError path: Error Trigger β†’ DLQ Set β†’ (Sheets/Email/Slack)"},"typeVersion":1},{"id":"f13bd3c3-5739-47bc-9b75-faf07f0c4ea9","name":"NOTIFY | Slack (optional)","type":"n8n-nodes-base.slack","notes":"Optional: Send a Slack message after each unretweet. Set credentials and channel.","position":[2016,528],"parameters":{"text":"={{ $json.message }}","channel":"={{ '#your-channel' }}","attachments":[],"otherOptions":{}},"typeVersion":1},{"id":"54dac7b0-8123-4580-a9a7-899a53b3ca4a","name":"ON ERROR | Capture","type":"n8n-nodes-base.errorTrigger","notes":"Catches workflow errors at runtime.","position":[16,1056],"parameters":{},"typeVersion":1},{"id":"832a1fed-ce14-4dc7-bad2-9260fe1d9833","name":"DLQ | Format error (Set)","type":"n8n-nodes-base.set","notes":"Formats an error record for a dead-letter destination (e.g., Google Sheets, Email, or Slack).","position":[288,1056],"parameters":{"values":{"string":[{"name":"failed_node","value":"={{ $json.execution.error.node.name }}"},{"name":"error_message","value":"={{ $json.execution.error.message }}"},{"name":"timestamp","value":"={{ new Date().toISOString() }}"}]},"options":{},"keepOnlySet":true},"typeVersion":2},{"id":"1ce4bb84-0bf9-4dcb-b1bc-6e4f81836d14","name":"Sticky: DLQ & Notify","type":"n8n-nodes-base.stickyNote","position":[-288,1280],"parameters":{"color":"white","width":420,"height":368,"content":"## Dead-letter & notifications (optional)\n- **Error Trigger** captures runtime errors and forwards to `DLQ | Format error (Set)`.\n- From there, connect to Google Sheets/Email/Slack to persist and alert.\n- **Notify path**: after `SEND | Unretweet`, a log message is built and can be sent to Slack.\n\n**Tip:** Use this to audit API failures (403/404/429) and track actions."},"typeVersion":1},{"id":"f86fbe64-7c65-439f-91cc-a78c57727adc","name":"Sticky: API endpoints","type":"n8n-nodes-base.stickyNote","position":[1008,1280],"parameters":{"color":"white","width":420,"height":300,"content":"## API endpoints (reference)\n- `GET https://api.twitter.com/2/users/by/username/{username}` β†’ resolve user id\n- `GET https://api.twitter.com/2/users/{id}/tweets?tweet.fields=created_at,referenced_tweets` β†’ list tweets\n\n**Response payloads** are shown in the yellow master sticky."},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"4d73778e-74ab-49ab-af4b-ec2aa8149324","connections":{"IF | Is retweet?":{"main":[[{"node":"LOOP | Split in batches (rate-limit friendly)","type":"main","index":0}]]},"ON ERROR | Capture":{"main":[[{"node":"DLQ | Format error (Set)","type":"main","index":0}]]},"NOTIFY | Slack (optional)":{"main":[[{"node":"LOOP | Split in batches (rate-limit friendly)","type":"main","index":0}]]},"SPLIT | One item per tweet":{"main":[[{"node":"IF | Is retweet?","type":"main","index":0}]]},"FETCH | Latest tweets (X API)":{"main":[[{"node":"SPLIT | One item per tweet","type":"main","index":0}]]},"WAIT | Delay between API calls":{"main":[[{"node":"NOTIFY | Slack (optional)","type":"main","index":0}]]},"SEND | Unretweet (delete retweet)":{"main":[[{"node":"WAIT | Delay between API calls","type":"main","index":0}]]},"CONFIG | User Variables (Set Fields)":{"main":[[{"node":"FETCH | Get user ID by username (X API)","type":"main","index":0}]]},"TRIGGER | Schedule (09:00 server time)":{"main":[[{"node":"CONFIG | User Variables (Set Fields)","type":"main","index":0}]]},"FETCH | Get user ID by username (X API)":{"main":[[{"node":"FETCH | Latest tweets (X API)","type":"main","index":0}]]},"LOOP | Split in batches (rate-limit friendly)":{"main":[[{"node":"SEND | Unretweet (delete retweet)","type":"main","index":0}]]}}} \ No newline at end of file