{
  "id": "1tR0c9AtnzjrWq5w",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "BlueSky Suite: Schedule BlueSky posts and threads from Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "247a7105-ed40-4051-bf26-b65d1c23a6ca",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -576,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ce6a92cb-b874-4589-8955-3d3e93be959b",
      "name": "Get row(s) in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        272,
        0
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "930d47a1-75a7-4b19-babc-3af2f571f437",
      "name": "Sort",
      "type": "n8n-nodes-base.sort",
      "position": [
        880,
        0
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "Thread ID"
            },
            {
              "fieldName": "Sequence"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "16b10c78-8e3f-4764-abbd-5a76ad1d930c",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1168,
        0
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "f049c216-68dd-45a8-99c6-cd507affee50",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        1472,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d8125374-12b8-49cc-b1fe-4e6229186f82",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json[\"Image URL\"] }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "cd9bb8fd-18f0-4d26-b3a5-80552c3eeb27",
      "name": "HTTP Download Image",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1648,
        -128
      ],
      "parameters": {
        "url": "={{ $json[\"Image URL\"] }}",
        "options": {}
      },
      "typeVersion": 4.3
    },
    {
      "id": "d1bcfb1c-3d1c-4f75-8012-ea7dfea41639",
      "name": "Update row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3440,
        16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Posted",
            "Post Link": "={{ $json.postLink }}",
            "Posted At": "={{ $now.setZone($('Configuration').first().json.timezone || 'UTC').toFormat('yyyy-MM-dd HH:mm') }}",
            "row_number": "={{ $('Loop Over Items').item.json.row_number }}"
          },
          "schema": [
            {
              "id": "Content",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Content",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Thread ID",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Thread ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sequence",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Sequence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Image URL",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Image URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Scheduled Time",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Scheduled Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Posted At",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Posted At",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post Link",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Post Link",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post URI",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Post URI",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Like Count",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Like Count",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Repost Count",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Repost Count",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reply Count",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Reply Count",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "abe0d905-1908-46b6-915b-c6685a817b3b",
      "name": "Construct Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        2576,
        16
      ],
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const json = item.json;\n  \n  // --- 1. SETUP VARIABLES ---\n  // Now safe to access because we fixed the data flow!\n  const threadId = json[\"Thread ID\"]; \n  const sequence = parseInt(json[\"Sequence\"] || 1);\n  const did = $('BlueSky Auth').first().json.did;\n  const postBody = json[\"Content\"]; // Using the correct column name from your sheet\n\n  // --- 2. CONSTRUCT BASIC RECORD ---\n  let record = {\n    text: postBody,\n    createdAt: new Date().toISOString(),\n    $type: \"app.bsky.feed.post\"\n  };\n\n  // --- 3. HANDLE IMAGES (THE FIX) ---\n  // We now check if the CURRENT item's JSON has the 'blob' data.\n  // This data exists only if it came through the \"Combine Data\" merge node.\n  if (json.blob) {\n    record.embed = {\n      $type: \"app.bsky.embed.images\",\n      images: [{\n        alt: json[\"Alt Text\"] || \"\", // Default to empty string if missing\n        image: json.blob // Use the blob directly from the item's data\n      }]\n    };\n  }\n\n  // --- 4. HANDLE THREADING (THE FIX) ---\n  // This logic now works because 'sequence' and 'threadId' are correctly retrieved.\n  if (sequence === 1) {\n    // New thread: reset global pointers\n    staticData.currentRoot = null;\n    staticData.currentParent = null;\n    staticData.currentThreadId = threadId;\n  } \n  else if (sequence > 1 && staticData.currentThreadId == threadId) {\n    // Reply: check if we have parent/root info from previous loop iteration\n    if (staticData.currentParent && staticData.currentRoot) {\n      record.reply = {\n        root: staticData.currentRoot,\n        parent: staticData.currentParent\n      };\n    }\n  }\n\n  // --- 5. FINALIZE ---\n  results.push({\n    json: {\n      repo: did,\n      collection: \"app.bsky.feed.post\",\n      record: record\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "aba87f26-ec9d-4c60-9c58-cfc2c3d85571",
      "name": "BlueSky Auth",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -96,
        0
      ],
      "parameters": {
        "url": "https://bsky.social/xrpc/com.atproto.server.createSession",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"identifier\":\"{{$('Configuration').first().json.bluesky_handle}}\",\n  \"password\": \"{{ $('Configuration').first().json.app_password }}\"\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.3
    },
    {
      "id": "35cf5776-c5e9-4b28-a63f-6902dec747c8",
      "name": "Create Post",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2896,
        16
      ],
      "parameters": {
        "url": "https://bsky.social/xrpc/com.atproto.repo.createRecord",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ $node[\"Construct Payload\"].data }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('BlueSky Auth').first().json.accessJwt }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "57359aaa-c587-41bd-bb87-d621bcaa3ac8",
      "name": "Update Thread State",
      "type": "n8n-nodes-base.code",
      "position": [
        3184,
        16
      ],
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\n// Get the response from the Create Post node\nconst lastPost = $('Create Post').first().json; \nconst handle = $('BlueSky Auth').first().json.handle || $('BlueSky Auth').first().json.identifier; // Get username\n\n// --- 1. EXTRACT ID ---\n// The URI looks like: at://did:plc:1234/app.bsky.feed.post/3lb234...\n// We need the last part (\"3lb234...\") to build the web link\nconst uriParts = lastPost.uri.split('/');\nconst rkey = uriParts[uriParts.length - 1];\n\n// --- 2. BUILD WEB URL ---\n// Format: https://bsky.app/profile/{handle}/post/{id}\nconst webUrl = `https://bsky.app/profile/${handle}/post/${rkey}`;\n\n// --- 3. SAVE THREAD STATE (Existing Logic) ---\nconst newRef = {\n  uri: lastPost.uri,\n  cid: lastPost.cid\n};\n\nif (!staticData.currentRoot) {\n  staticData.currentRoot = newRef;\n}\nstaticData.currentParent = newRef;\n\n// --- 4. OUTPUT DATA FOR SHEET ---\n// We pass the original sheet data + our new links\nreturn [{\n  json: {\n    ...$input.first().json, // Keep original sheet data\n    postUri: lastPost.uri,  // The technical ID\n    postLink: webUrl        // The clickable link\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "0b3ab7f8-88ce-4b85-9f5b-1dbab47af201",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -336,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4e2b2be1-d052-4dc8-96e8-60054b1cba1d",
              "name": "bluesky_handle",
              "type": "string",
              "value": ""
            },
            {
              "id": "b336da17-f730-49e6-85c9-4f8e9b3f8522",
              "name": "app_password",
              "type": "string",
              "value": ""
            },
            {
              "id": "8054c068-b9a2-4c82-975e-23499fbf0465",
              "name": "timezone",
              "type": "string",
              "value": "Asia/Kolkata"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9a06d0c9-6f8a-4e2f-a304-606aa0f4b13a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1520,
        -288
      ],
      "parameters": {
        "width": 832,
        "height": 624,
        "content": "# \ud83d\udcd8 Post Scheduler - How To Use\n**Goal:** Auto-post threads and images from Google Sheets to BlueSky.\n\n**Step 1: Setup Credentials**\nOpen the \"Configuration\" node (first green node) and enter your BlueSky Handle and App Password.\n\n**Step 2: Prepare Your Google Sheet** - Here is a [**Sample Google Sheet**](https://docs.google.com/spreadsheets/d/1Mg04gK1K5DBtJHrWw3ePRFc_JjkxwAp0deGjapVl2q0/edit?usp=sharing) \n\nEnsure your sheet has these columns:\n1. **Content:** The text of your post.\n2. **Thread ID:** Group posts together using the same ID (e.g., \"1002 or Thread-A\"). **>> Imp - Even if its a single post (not a thread), still you must add a unique thread id <<**\n3. **Sequence:** Order of the thread (1, 2, 3...). **>> Imp: Enter 1 even if it is a single post <<**\n4. **Image URL:** (Optional) Direct link to an image. **Must be valid image ending with png or jpg etc**\n5. **Status:** Mark rows as \"Ready\" to post them.\n6. **Scheduled Time (Column type - Plain Text):** Workflow checks if this time has passed, then creates a post. **Format YYYY-MM-DD HH:mm**\n\n\n**Step 3: Connect your google account**\nIf you already have google account connected already, use that or connect new account in the \"Get row(s) insheet\" Google sheet node. [More details about Google Sheets node here](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlesheets/)\n\n**Step 4: Activate**\nTurn the workflow to \"Active\". It will run every hour (or your set interval), pick up \"Ready\" rows, post them, and update the status to \"Posted\"."
      },
      "typeVersion": 1
    },
    {
      "id": "a233dfba-973d-49e5-8662-e05e3f8e1de4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -432,
        -208
      ],
      "parameters": {
        "width": 288,
        "height": 192,
        "content": "### 1- START HERE \nEnter your BlueSky Handle (e.g., steve.bsky.social), App Password and timezone (eg: America/Los_Angeles or Europe/Berlin etc) here.\n\n[Find your timezone name here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)"
      },
      "typeVersion": 1
    },
    {
      "id": "aac4a7c0-1898-4c47-9b2f-bf93cd97c866",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        160
      ],
      "parameters": {
        "width": 304,
        "height": 272,
        "content": "### 3- Google sheets rows \nFetcher Grabs all rows where Status column is \"Ready\". If you want to test, manually add a row with \"Ready\" in your sheet first.\n**Must have columns -**\nContent | Thread ID | Sequence | Image URL | Scheduled Time | Status | Posted At | Post Link\n\n[**Sample Google Sheet**](https://docs.google.com/spreadsheets/d/1Mg04gK1K5DBtJHrWw3ePRFc_JjkxwAp0deGjapVl2q0/edit?usp=sharing)\nScheduled Time - Use column type as \"Plain Text\" to avoid errors"
      },
      "typeVersion": 1
    },
    {
      "id": "28dba190-c8ee-4c2b-a9b7-74cf4bc4545d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        160
      ],
      "parameters": {
        "height": 176,
        "content": "### 2- Get access token\nLogin to BlueSky using handle and app password to get the access token. This will be used in all future BlueSky api calls."
      },
      "typeVersion": 1
    },
    {
      "id": "f3297a25-02b8-497a-a9cb-7d701db9747c",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -192
      ],
      "parameters": {
        "height": 176,
        "content": "### 4- The Thread Organizer\n**Critically important!** This ensures \"Post 1\" runs before \"Post 2\" so threads are built in the correct order."
      },
      "typeVersion": 1
    },
    {
      "id": "bd26db50-83ca-49f9-93f2-26b581d845dd",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        -176
      ],
      "parameters": {
        "content": "### 5- The Batch Processor\nProcesses posts one by one to ensure we handle image uploads and thread linking correctly."
      },
      "typeVersion": 1
    },
    {
      "id": "8819caed-b005-4cbc-9196-42d618ee0077",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        -320
      ],
      "parameters": {
        "content": "### 6i- Download Image \nIf an image URL is found, we download it to re-upload it to BlueSky."
      },
      "typeVersion": 1
    },
    {
      "id": "b010ea61-1685-4b54-bfd6-b544f9461204",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1792,
        -320
      ],
      "parameters": {
        "content": "### 6ii- Upload Image \nUpload it to BlueSky's servers to get a \"Blob Link\" before posting."
      },
      "typeVersion": 1
    },
    {
      "id": "633901dd-5069-4683-90b4-0b990748f569",
      "name": "Upload Blob",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1856,
        -128
      ],
      "parameters": {
        "url": "https://bsky.social/xrpc/com.atproto.repo.uploadBlob",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "binaryData",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('BlueSky Auth').first().json.accessJwt }}"
            },
            {
              "name": "Content-Type",
              "value": "={{ $binary.data.mimeType }}"
            }
          ]
        },
        "inputDataFieldName": "data"
      },
      "typeVersion": 4.3
    },
    {
      "id": "182c0acf-5f32-4870-924d-b7b29d6645ec",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        -240
      ],
      "parameters": {
        "width": 288,
        "height": 208,
        "content": "### 8- Construct Payload\n**The Logic Brain** This script builds the final data packet. It handles:\n\n- Attaching images (if any).\n- Linking replies (if Sequence > 1, it finds the parent ID).\n- Resetting memory for new threads."
      },
      "typeVersion": 1
    },
    {
      "id": "2a721a10-a65d-44ee-b9a0-7d9610fa8b92",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2816,
        -160
      ],
      "parameters": {
        "content": "### 9- Create Post\nCall BlueSky api to create a new post or reply to a post (if its a sequence in a thread)"
      },
      "typeVersion": 1
    },
    {
      "id": "34a9917c-7dd3-40ea-b977-bfe280a70293",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3072,
        -160
      ],
      "parameters": {
        "content": "### 10- Update Thread State\n**Memory Keeper** After a successful post, this saves the Post ID (uri and cid) into memory. The next item in the loop will look here to know which post to reply to."
      },
      "typeVersion": 1
    },
    {
      "id": "358c9ff0-8ecd-423e-95ed-5e9586b20a07",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3344,
        -160
      ],
      "parameters": {
        "content": "### 11- Update Google Sheets \n**The Closer** Writes \"Posted\" back to your sheet and saves the direct link to the live post so you have a record."
      },
      "typeVersion": 1
    },
    {
      "id": "52b4b478-cb17-421d-a9d0-54ac5ce925cd",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2784,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 816,
        "content": "# Create post and update Google sheet"
      },
      "typeVersion": 1
    },
    {
      "id": "8cb827c7-a323-4bf4-8e14-7eefc48718ec",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 816,
        "content": "# Inputs"
      },
      "typeVersion": 1
    },
    {
      "id": "5540c559-3aea-41d6-a799-f44a652da048",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 2016,
        "height": 816,
        "content": "# Posting Logic"
      },
      "typeVersion": 1
    },
    {
      "id": "73100de1-eeb8-48e0-a353-3d9c7b63e725",
      "name": "Attach Image Blob",
      "type": "n8n-nodes-base.merge",
      "position": [
        2160,
        -112
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "ed94e650-f36e-4f24-8aea-2b42931a572c",
      "name": "Consolidate Streams",
      "type": "n8n-nodes-base.merge",
      "position": [
        2352,
        16
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "8f1c1597-e6b3-4a97-90df-2a61580fe22f",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2064,
        -336
      ],
      "parameters": {
        "width": 272,
        "height": 208,
        "content": "### 6iii- Re-assembling the Data \nThis node merges the \"Blob\" (the technical image file from BlueSky) back with your original Google Sheet data (Text, Thread ID, etc.).\n\nWithout this, the next step would have the image but forget what text goes with it!"
      },
      "typeVersion": 1
    },
    {
      "id": "f6a33102-6593-4d0e-b6e3-49aa645b1b9f",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        160
      ],
      "parameters": {
        "width": 288,
        "height": 208,
        "content": "### 7- The Reunion\nThis brings your \"Image Posts\" (top path) and \"Text-Only Posts\" (bottom path) back together into a single list.\n\nThis allows the **\"Construct Payload\"** node to handle all posts in one single script, keeping your workflow clean and DRY (Don't Repeat Yourself)."
      },
      "typeVersion": 1
    },
    {
      "id": "7b50984a-0e71-4f4c-bbc5-28f31bdeaa00",
      "name": "Sticky Note17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1408,
        144
      ],
      "parameters": {
        "content": "### 6- If post contains image\n\nIf we got image url, then download it, upload to BlueSky and then return the combined data.\nElse continue."
      },
      "typeVersion": 1
    },
    {
      "id": "82b6857a-3698-4477-97ea-cd604f25a5ab",
      "name": "Filter",
      "type": "n8n-nodes-base.filter",
      "position": [
        496,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1b57a1d9-892b-47a1-a4ae-313dfd71a9ae",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Ready"
            },
            {
              "id": "078a3331-6e90-4414-98af-a2c654b63866",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json[\"Scheduled Time\"] }}",
              "rightValue": "={{ $now }}"
            },
            {
              "id": "419fce85-1314-46f8-a06b-6984aae78644",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              },
              "leftValue": "={{ DateTime.fromFormat($json['Scheduled Time'], 'yyyy-MM-dd HH:mm', { zone: $('Configuration').first().json.timezone }) }}",
              "rightValue": "={{ $now.setZone($('Configuration').first().json.timezone) }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "ad3ac0d7-f1d0-463b-a1ea-e3525589fbcb",
      "name": "Sticky Note18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        -208
      ],
      "parameters": {
        "width": 272,
        "height": 192,
        "content": "### 4- Checks Schedule Time \n**Format:** \nYYYY-MM-DD HH:mm\neg: 2025-12-25 14:30\n\n**Important:** Set this column to **\"Plain Text\"** in Google Sheets to prevent errors.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "2722c82f-10f2-4e34-945f-99aee6880228",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "HTTP Download Image",
            "type": "main",
            "index": 0
          },
          {
            "node": "Attach Image Blob",
            "type": "main",
            "index": 1
          }
        ],
        [
          {
            "node": "Consolidate Streams",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Sort": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter": {
      "main": [
        [
          {
            "node": "Sort",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Post": {
      "main": [
        [
          {
            "node": "Update Thread State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Blob": {
      "main": [
        [
          {
            "node": "Attach Image Blob",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "BlueSky Auth": {
      "main": [
        [
          {
            "node": "Get row(s) in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "BlueSky Auth",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attach Image Blob": {
      "main": [
        [
          {
            "node": "Consolidate Streams",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Construct Payload": {
      "main": [
        [
          {
            "node": "Create Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Consolidate Streams": {
      "main": [
        [
          {
            "node": "Construct Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s) in sheet": {
      "main": [
        [
          {
            "node": "Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Download Image": {
      "main": [
        [
          {
            "node": "Upload Blob",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Thread State": {
      "main": [
        [
          {
            "node": "Update row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update row in sheet": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}