← back to Dw Slideshow Gen
postiz: per-provider settings in draft body (hosted API requires them even for drafts) + idempotent/paced bulk staging (posted.json marker, --restage)
0b44dfbbb1015c24f7727500fdab611432b05eeb · 2026-06-29 13:10:39 -0700 · Steve Abrams
Files touched
M index.jsM lib/post.jsM lib/postiz.js
Diff
commit 0b44dfbbb1015c24f7727500fdab611432b05eeb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 29 13:10:39 2026 -0700
postiz: per-provider settings in draft body (hosted API requires them even for drafts) + idempotent/paced bulk staging (posted.json marker, --restage)
---
index.js | 32 ++++++++++++++++++++++----------
lib/post.js | 9 ++++-----
lib/postiz.js | 22 ++++++++++++++++++----
3 files changed, 44 insertions(+), 19 deletions(-)
diff --git a/index.js b/index.js
index a1e7da4..a62d698 100644
--- a/index.js
+++ b/index.js
@@ -42,6 +42,7 @@ function parseArgs(argv) {
else if (t === '--no-photos') a.photos = false
else if (t === '--confirm') a.confirm = true
else if (t === '--postiz') a.postiz = true
+ else if (t === '--restage') a.restage = true
else if (t === '--channel') a.channel = argv[++i]
else if (t === '--schedule') a.schedule = argv[++i]
else a._.push(t)
@@ -99,19 +100,30 @@ if (cmd === 'post') {
: []
}
if (!dirs.length) { console.error('no render dirs found — run `render`/`catalog`/`all` first'); process.exit(1) }
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
+ let staged = 0, skipped = 0, failed = 0, i = 0
for (const dir of dirs) {
- const pkg = packageDraft(dir, { zip: true })
- console.error(` [post] ${pkg.spec_id}: local draft package (${pkg.slides} slides) -> ${pkg.zipPath || pkg.pkgDir} ($0, no network)`)
- if (!a.postiz) continue
+ i++
+ if (!a.postiz) { // local-package mode (no network)
+ const pkg = packageDraft(dir, { zip: true })
+ console.error(` [post] ${pkg.spec_id}: local draft package (${pkg.slides} slides) -> ${pkg.zipPath || pkg.pkgDir} ($0)`)
+ continue
+ }
+ const marker = path.join(dir, 'posted.json')
+ const id = path.basename(dir)
+ if (!a.restage && fs.existsSync(marker)) { skipped++; continue } // idempotent: already staged
const r = await stageToPostiz(dir, process.env, { confirm: a.confirm, channel: a.channel, schedule: a.schedule })
- const chNames = (r.channels || []).map(c => `${c.identifier}(${c.name})`).join(', ')
- if (r.gated) console.error(` [post] ${pkg.spec_id}: Postiz skipped — ${r.reason}`)
- else if (r.error) console.error(` [post] ${pkg.spec_id}: Postiz error — ${r.error}`)
- else if (r.dryRun) {
- console.error(` [post] ${pkg.spec_id}: DRY-RUN ${r.type} → ${chNames}, ${r.images} images. Add --confirm to stage. Body:`)
- console.error(' ' + JSON.stringify(r.body).replace(/\n/g, ' ').slice(0, 700))
- } else if (r.staged) console.error(` [post] ${pkg.spec_id}: Postiz ${r.type} STAGED → ${chNames} postIds=${(r.postIds || []).join(',')} — review/publish at platform.postiz.com`)
+ const chNames = (r.channels || []).map(c => c.identifier).join('+')
+ if (r.staged) {
+ staged++
+ writeJSON(marker, { postIds: r.postIds, channels: (r.channels || []).map(c => c.identifier), type: r.type, at: new Date().toISOString() })
+ console.error(` [post ${i}/${dirs.length}] STAGED ${id} → ${chNames} (${(r.postIds || []).length} entries)`)
+ await sleep(a.confirm ? 700 : 0) // pace API to avoid throttling
+ } else if (r.dryRun) {
+ console.error(` [post ${i}/${dirs.length}] DRY-RUN ${r.type} → ${chNames}, ${r.images} imgs (add --confirm to stage)`)
+ } else { failed++; console.error(` [post ${i}/${dirs.length}] ${r.gated ? 'SKIP' : 'FAIL'} ${id}: ${r.reason || r.error}`) }
}
+ if (a.postiz) console.error(`\n [post] done — staged ${staged}, skipped(already) ${skipped}, failed ${failed} of ${dirs.length}. Review at platform.postiz.com`)
console.error([
'',
' ── GATE ─────────────────────────────────────────────',
diff --git a/lib/post.js b/lib/post.js
index 840385c..0313eb4 100644
--- a/lib/post.js
+++ b/lib/post.js
@@ -12,7 +12,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { execFileSync } from 'node:child_process'
import { readJSON, ensureDir, exists } from './util.js'
-import { Postiz, buildDraftBody, buildDraftBodyMulti, buildScheduleBody, pickIntegration, pickIntegrations } from './postiz.js'
+import { Postiz, buildDraftBodyMulti, buildScheduleBody, pickIntegration, pickIntegrations } from './postiz.js'
function loadManifest(renderDir) {
const mp = path.join(renderDir, 'render-manifest.json')
@@ -81,11 +81,10 @@ export async function stageToPostiz(renderDir, env, opts = {}) {
} catch (e) { return { staged: false, error: `integrations: ${e.message}` } }
if (!chosen.length) return { staged: false, gated: true, reason: 'no connected channels — connect a channel in Postiz first' }
- const ids = chosen.map(c => c.id)
- // schedule needs per-provider settings, so it only supports ONE channel here; draft is multi.
+ // schedule needs a single provider's settings; draft is multi-channel (each gets its own settings)
const mkBody = media => opts.schedule
- ? buildScheduleBody({ integrationId: ids[0], identifier: chosen[0].identifier, content, media, dateISO, tiktok: opts.tiktok })
- : buildDraftBodyMulti({ integrationIds: ids, content, media, dateISO })
+ ? buildScheduleBody({ integrationId: chosen[0].id, identifier: chosen[0].identifier, content, media, dateISO, tiktok: opts.tiktok })
+ : buildDraftBodyMulti({ integrations: chosen, content, media, dateISO })
// dry-run: show the body with placeholder media, no upload, no post
if (!opts.confirm) {
diff --git a/lib/postiz.js b/lib/postiz.js
index 8cd8520..13bd90a 100644
--- a/lib/postiz.js
+++ b/lib/postiz.js
@@ -55,15 +55,29 @@ export class Postiz {
// ---- pure request-body builders (testable without a key, via --dry-run) ----
const imageRefs = media => media.map(m => ({ id: m.id, path: m.path, ...(m.alt ? { alt: m.alt } : {}) }))
-export function buildDraftBody({ integrationId, content, media, dateISO }) {
- return buildDraftBodyMulti({ integrationIds: [integrationId], content, media, dateISO })
+// per-provider settings — the hosted API requires these even for drafts
+export function settingsFor(identifier) {
+ const id = identifier || ''
+ if (id === 'tiktok') return {
+ __type: 'tiktok', title: '', privacy_level: 'SELF_ONLY',
+ duet: false, stitch: false, comment: true, autoAddMusic: 'no',
+ brand_content_toggle: false, brand_organic_toggle: false, video_made_with_ai: false,
+ content_posting_method: 'UPLOAD',
+ }
+ if (id.startsWith('instagram')) return { __type: id, post_type: 'post' }
+ if (id.startsWith('facebook')) return { __type: id, post_type: 'post' }
+ return { __type: id }
}
// stage ONE carousel to MANY channels in a single draft (posts[] = one entry per channel)
-export function buildDraftBodyMulti({ integrationIds, content, media, dateISO }) {
+export function buildDraftBodyMulti({ integrations, content, media, dateISO }) {
return {
type: 'draft', shortLink: false, date: dateISO, tags: [],
- posts: integrationIds.map(id => ({ integration: { id }, value: [{ content, image: imageRefs(media) }] })),
+ posts: integrations.map(i => ({
+ integration: { id: i.id },
+ value: [{ content, image: imageRefs(media) }],
+ settings: settingsFor(i.identifier),
+ })),
}
}
← 95bb63b postiz: multi-channel draft staging (--channel all|a,b), pic
·
back to Dw Slideshow Gen
·
RESUME.md: snapshot for restart — DW 500-SKU Postiz batch re cb523f9 →