@jiggai/kitchen-plugin-marketing 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/handler.js +437 -117
- package/dist/tabs/accounts.js +197 -157
- package/dist/tabs/content-library.js +226 -92
- package/package.json +1 -1
package/dist/api/handler.js
CHANGED
|
@@ -198,6 +198,13 @@ function encryptCredentials(credentials) {
|
|
|
198
198
|
encrypted += cipher.final("hex");
|
|
199
199
|
return Buffer.from(encrypted, "hex");
|
|
200
200
|
}
|
|
201
|
+
function decryptCredentials(encryptedData) {
|
|
202
|
+
const hash = (0, import_crypto.createHash)("sha256").update(ENCRYPTION_KEY).digest();
|
|
203
|
+
const decipher = (0, import_crypto.createDecipher)("aes-256-cbc", hash);
|
|
204
|
+
let decrypted = decipher.update(encryptedData.toString("hex"), "hex", "utf8");
|
|
205
|
+
decrypted += decipher.final("utf8");
|
|
206
|
+
return JSON.parse(decrypted);
|
|
207
|
+
}
|
|
201
208
|
function initializeDatabase(teamId) {
|
|
202
209
|
const { db, sqlite } = createDatabase(teamId);
|
|
203
210
|
try {
|
|
@@ -208,6 +215,311 @@ function initializeDatabase(teamId) {
|
|
|
208
215
|
return { db, sqlite };
|
|
209
216
|
}
|
|
210
217
|
|
|
218
|
+
// src/drivers/postiz-backend.ts
|
|
219
|
+
async function postizFetch(config, path, options) {
|
|
220
|
+
return fetch(`${config.baseUrl}${path}`, {
|
|
221
|
+
...options,
|
|
222
|
+
headers: {
|
|
223
|
+
"Authorization": config.apiKey,
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
...options?.headers || {}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function getPostizIntegrations(config) {
|
|
230
|
+
const res = await postizFetch(config, "/integrations");
|
|
231
|
+
if (!res.ok) return [];
|
|
232
|
+
const data = await res.json();
|
|
233
|
+
return Array.isArray(data) ? data : data.integrations || [];
|
|
234
|
+
}
|
|
235
|
+
async function postizPublish(config, integrationId, content, options) {
|
|
236
|
+
const payload = {
|
|
237
|
+
content,
|
|
238
|
+
integrationIds: [integrationId]
|
|
239
|
+
};
|
|
240
|
+
if (options?.scheduledAt) payload.date = options.scheduledAt;
|
|
241
|
+
if (options?.settings) payload.settings = options.settings;
|
|
242
|
+
if (options?.mediaUrls?.length) {
|
|
243
|
+
payload.media = options.mediaUrls.map((url) => ({ url }));
|
|
244
|
+
}
|
|
245
|
+
const res = await postizFetch(config, "/posts", {
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify(payload)
|
|
248
|
+
});
|
|
249
|
+
const data = await res.json().catch(() => null);
|
|
250
|
+
if (!res.ok) {
|
|
251
|
+
return { success: false, error: data?.message || `Postiz error ${res.status}`, meta: data };
|
|
252
|
+
}
|
|
253
|
+
return { success: true, postId: data?.id, meta: data };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/drivers/base-driver.ts
|
|
257
|
+
var BaseDriver = class {
|
|
258
|
+
config;
|
|
259
|
+
_postizIntegrationId = null;
|
|
260
|
+
_statusCache = null;
|
|
261
|
+
constructor(config) {
|
|
262
|
+
this.config = config;
|
|
263
|
+
}
|
|
264
|
+
/** Platform-specific character limit */
|
|
265
|
+
getMaxLength() {
|
|
266
|
+
return void 0;
|
|
267
|
+
}
|
|
268
|
+
/** Platform-specific supported media types */
|
|
269
|
+
getSupportedMedia() {
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
getCapabilities() {
|
|
273
|
+
const hasPostiz = !!this.config.postiz;
|
|
274
|
+
const hasGateway = !!this.config.gateway;
|
|
275
|
+
const hasDirect = !!this.config.direct;
|
|
276
|
+
return {
|
|
277
|
+
canPost: hasPostiz || hasGateway || hasDirect,
|
|
278
|
+
canSchedule: hasPostiz,
|
|
279
|
+
// only Postiz supports native scheduling
|
|
280
|
+
canDelete: false,
|
|
281
|
+
canUploadMedia: hasPostiz || hasDirect,
|
|
282
|
+
maxLength: this.getMaxLength(),
|
|
283
|
+
supportedMedia: this.getSupportedMedia()
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async getStatus() {
|
|
287
|
+
if (this._statusCache) return this._statusCache;
|
|
288
|
+
if (this.config.postiz) {
|
|
289
|
+
try {
|
|
290
|
+
const integrations = await getPostizIntegrations(this.config.postiz);
|
|
291
|
+
const match = integrations.find(
|
|
292
|
+
(i) => i.providerIdentifier === this.postizProvider && !i.disabled
|
|
293
|
+
);
|
|
294
|
+
if (match) {
|
|
295
|
+
this._postizIntegrationId = this.config.postiz.integrationId || match.id;
|
|
296
|
+
this._statusCache = {
|
|
297
|
+
connected: true,
|
|
298
|
+
backend: "postiz",
|
|
299
|
+
displayName: match.name || `${this.label} (Postiz)`,
|
|
300
|
+
username: match.username,
|
|
301
|
+
avatar: match.picture,
|
|
302
|
+
integrationId: match.id
|
|
303
|
+
};
|
|
304
|
+
return this._statusCache;
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (this.config.gateway) {
|
|
310
|
+
this._statusCache = {
|
|
311
|
+
connected: true,
|
|
312
|
+
backend: "gateway",
|
|
313
|
+
displayName: `${this.label} (via OpenClaw)`
|
|
314
|
+
};
|
|
315
|
+
return this._statusCache;
|
|
316
|
+
}
|
|
317
|
+
if (this.config.direct?.accessToken) {
|
|
318
|
+
this._statusCache = {
|
|
319
|
+
connected: true,
|
|
320
|
+
backend: "direct",
|
|
321
|
+
displayName: `${this.label} (Direct API)`
|
|
322
|
+
};
|
|
323
|
+
return this._statusCache;
|
|
324
|
+
}
|
|
325
|
+
this._statusCache = {
|
|
326
|
+
connected: false,
|
|
327
|
+
backend: "none",
|
|
328
|
+
displayName: this.label
|
|
329
|
+
};
|
|
330
|
+
return this._statusCache;
|
|
331
|
+
}
|
|
332
|
+
async publish(content) {
|
|
333
|
+
const status = await this.getStatus();
|
|
334
|
+
switch (status.backend) {
|
|
335
|
+
case "postiz":
|
|
336
|
+
return this.publishViaPostiz(content);
|
|
337
|
+
case "gateway":
|
|
338
|
+
return this.publishViaGateway(content);
|
|
339
|
+
case "direct":
|
|
340
|
+
return this.publishDirect(content);
|
|
341
|
+
default:
|
|
342
|
+
return { success: false, error: `No backend configured for ${this.label}` };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Publish through Postiz */
|
|
346
|
+
async publishViaPostiz(content) {
|
|
347
|
+
const cfg = this.config.postiz;
|
|
348
|
+
if (!cfg) return { success: false, error: "Postiz not configured" };
|
|
349
|
+
const integrationId = this._postizIntegrationId || cfg.integrationId;
|
|
350
|
+
if (!integrationId) return { success: false, error: "No Postiz integration found for " + this.platform };
|
|
351
|
+
const result = await postizPublish(cfg, integrationId, content.text, {
|
|
352
|
+
scheduledAt: content.scheduledAt,
|
|
353
|
+
mediaUrls: content.mediaUrls,
|
|
354
|
+
settings: content.settings
|
|
355
|
+
});
|
|
356
|
+
return {
|
|
357
|
+
success: result.success,
|
|
358
|
+
postId: result.postId,
|
|
359
|
+
error: result.error,
|
|
360
|
+
scheduledAt: content.scheduledAt,
|
|
361
|
+
meta: result.meta
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/** Publish through OpenClaw gateway messaging. Override for platform-specific formatting. */
|
|
365
|
+
async publishViaGateway(_content) {
|
|
366
|
+
return { success: false, error: `Gateway publishing not implemented for ${this.label}` };
|
|
367
|
+
}
|
|
368
|
+
/** Direct API publish. Override per platform. */
|
|
369
|
+
async publishDirect(_content) {
|
|
370
|
+
return { success: false, error: `Direct API publishing not implemented for ${this.label}` };
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/drivers/x-driver.ts
|
|
375
|
+
var XDriver = class extends BaseDriver {
|
|
376
|
+
platform = "x";
|
|
377
|
+
label = "X (Twitter)";
|
|
378
|
+
icon = "\u{1D54F}";
|
|
379
|
+
postizProvider = "x";
|
|
380
|
+
getMaxLength() {
|
|
381
|
+
return 280;
|
|
382
|
+
}
|
|
383
|
+
getSupportedMedia() {
|
|
384
|
+
return ["image/jpeg", "image/png", "image/gif", "video/mp4"];
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/drivers/instagram-driver.ts
|
|
389
|
+
var InstagramDriver = class extends BaseDriver {
|
|
390
|
+
platform = "instagram";
|
|
391
|
+
label = "Instagram";
|
|
392
|
+
icon = "\u{1F4F7}";
|
|
393
|
+
postizProvider = "instagram";
|
|
394
|
+
getMaxLength() {
|
|
395
|
+
return 2200;
|
|
396
|
+
}
|
|
397
|
+
getSupportedMedia() {
|
|
398
|
+
return ["image/jpeg", "image/png", "video/mp4"];
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// src/drivers/facebook-driver.ts
|
|
403
|
+
var FacebookDriver = class extends BaseDriver {
|
|
404
|
+
platform = "facebook";
|
|
405
|
+
label = "Facebook";
|
|
406
|
+
icon = "\u{1F4D8}";
|
|
407
|
+
postizProvider = "facebook";
|
|
408
|
+
getMaxLength() {
|
|
409
|
+
return 63206;
|
|
410
|
+
}
|
|
411
|
+
getSupportedMedia() {
|
|
412
|
+
return ["image/jpeg", "image/png", "image/gif", "video/mp4"];
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/drivers/linkedin-driver.ts
|
|
417
|
+
var LinkedInDriver = class extends BaseDriver {
|
|
418
|
+
platform = "linkedin";
|
|
419
|
+
label = "LinkedIn";
|
|
420
|
+
icon = "\u{1F4BC}";
|
|
421
|
+
postizProvider = "linkedin";
|
|
422
|
+
getMaxLength() {
|
|
423
|
+
return 3e3;
|
|
424
|
+
}
|
|
425
|
+
getSupportedMedia() {
|
|
426
|
+
return ["image/jpeg", "image/png", "image/gif", "video/mp4"];
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/drivers/tiktok-driver.ts
|
|
431
|
+
var TikTokDriver = class extends BaseDriver {
|
|
432
|
+
platform = "tiktok";
|
|
433
|
+
label = "TikTok";
|
|
434
|
+
icon = "\u{1F3B5}";
|
|
435
|
+
postizProvider = "tiktok";
|
|
436
|
+
getMaxLength() {
|
|
437
|
+
return 2200;
|
|
438
|
+
}
|
|
439
|
+
getSupportedMedia() {
|
|
440
|
+
return ["video/mp4"];
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/drivers/discord-driver.ts
|
|
445
|
+
var DiscordDriver = class extends BaseDriver {
|
|
446
|
+
platform = "discord";
|
|
447
|
+
label = "Discord";
|
|
448
|
+
icon = "\u{1F4AC}";
|
|
449
|
+
postizProvider = "discord";
|
|
450
|
+
getMaxLength() {
|
|
451
|
+
return 2e3;
|
|
452
|
+
}
|
|
453
|
+
/** Discord posting via OpenClaw gateway message tool */
|
|
454
|
+
async publishViaGateway(content) {
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
error: "Gateway publishing requires OpenClaw message routing \u2014 use the scheduler or workflow"
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// src/drivers/telegram-driver.ts
|
|
463
|
+
var TelegramDriver = class extends BaseDriver {
|
|
464
|
+
platform = "telegram";
|
|
465
|
+
label = "Telegram";
|
|
466
|
+
icon = "\u2708\uFE0F";
|
|
467
|
+
postizProvider = "telegram";
|
|
468
|
+
getMaxLength() {
|
|
469
|
+
return 4096;
|
|
470
|
+
}
|
|
471
|
+
/** Telegram posting via OpenClaw gateway message tool */
|
|
472
|
+
async publishViaGateway(content) {
|
|
473
|
+
return {
|
|
474
|
+
success: false,
|
|
475
|
+
error: "Gateway publishing requires OpenClaw message routing \u2014 use the scheduler or workflow"
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/drivers/index.ts
|
|
481
|
+
var DRIVER_MAP = {
|
|
482
|
+
x: XDriver,
|
|
483
|
+
twitter: XDriver,
|
|
484
|
+
instagram: InstagramDriver,
|
|
485
|
+
facebook: FacebookDriver,
|
|
486
|
+
linkedin: LinkedInDriver,
|
|
487
|
+
tiktok: TikTokDriver,
|
|
488
|
+
discord: DiscordDriver,
|
|
489
|
+
telegram: TelegramDriver
|
|
490
|
+
};
|
|
491
|
+
function getPlatforms() {
|
|
492
|
+
return ["x", "instagram", "facebook", "linkedin", "tiktok", "discord", "telegram"];
|
|
493
|
+
}
|
|
494
|
+
function createDriver(platform, config) {
|
|
495
|
+
const Cls = DRIVER_MAP[platform.toLowerCase()];
|
|
496
|
+
if (!Cls) return null;
|
|
497
|
+
return new Cls(config);
|
|
498
|
+
}
|
|
499
|
+
function createAllDrivers(sources) {
|
|
500
|
+
const platforms = getPlatforms();
|
|
501
|
+
const drivers = [];
|
|
502
|
+
for (const platform of platforms) {
|
|
503
|
+
const config = {};
|
|
504
|
+
if (sources.postiz) {
|
|
505
|
+
config.postiz = {
|
|
506
|
+
apiKey: sources.postiz.apiKey,
|
|
507
|
+
baseUrl: sources.postiz.baseUrl
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (sources.gatewayChannels?.includes(platform)) {
|
|
511
|
+
config.gateway = { channel: platform };
|
|
512
|
+
}
|
|
513
|
+
const stored = sources.storedAccounts?.find((a) => a.platform === platform);
|
|
514
|
+
if (stored) {
|
|
515
|
+
config.direct = stored.credentials;
|
|
516
|
+
}
|
|
517
|
+
const driver = createDriver(platform, config);
|
|
518
|
+
if (driver) drivers.push(driver);
|
|
519
|
+
}
|
|
520
|
+
return drivers;
|
|
521
|
+
}
|
|
522
|
+
|
|
211
523
|
// src/api/handler.ts
|
|
212
524
|
function apiError(status, error, message, details) {
|
|
213
525
|
const payload = { error, message, details };
|
|
@@ -224,139 +536,151 @@ function getTeamId(req) {
|
|
|
224
536
|
function getUserId(req) {
|
|
225
537
|
return req.headers["x-user-id"] || "system";
|
|
226
538
|
}
|
|
227
|
-
function
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
async function postizFetch(config, path, options) {
|
|
234
|
-
return fetch(`${config.baseUrl}${path}`, {
|
|
235
|
-
...options,
|
|
236
|
-
headers: {
|
|
237
|
-
"Authorization": config.apiKey,
|
|
238
|
-
"Content-Type": "application/json",
|
|
239
|
-
...options?.headers || {}
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
async function detectProviders(req, teamId) {
|
|
244
|
-
const providers = [];
|
|
245
|
-
const postizCfg = getPostizConfig(req);
|
|
246
|
-
if (postizCfg) {
|
|
247
|
-
try {
|
|
248
|
-
const res = await postizFetch(postizCfg, "/integrations");
|
|
249
|
-
if (res.ok) {
|
|
250
|
-
const data = await res.json();
|
|
251
|
-
const integrations = Array.isArray(data) ? data : data.integrations || [];
|
|
252
|
-
for (const integ of integrations) {
|
|
253
|
-
providers.push({
|
|
254
|
-
id: `postiz:${integ.id}`,
|
|
255
|
-
type: "postiz",
|
|
256
|
-
platform: integ.providerIdentifier || integ.provider || "unknown",
|
|
257
|
-
displayName: integ.name || integ.providerIdentifier || "Postiz account",
|
|
258
|
-
username: integ.username || void 0,
|
|
259
|
-
avatar: integ.picture || integ.avatar || void 0,
|
|
260
|
-
isActive: !integ.disabled,
|
|
261
|
-
capabilities: ["post", "schedule"],
|
|
262
|
-
meta: { postizId: integ.id, provider: integ.providerIdentifier }
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
} catch {
|
|
267
|
-
}
|
|
539
|
+
function getBackendSources(req, teamId) {
|
|
540
|
+
const sources = {};
|
|
541
|
+
const postizKey = req.query.postizApiKey || req.headers["x-postiz-api-key"];
|
|
542
|
+
if (postizKey) {
|
|
543
|
+
const baseUrl = req.query.postizBaseUrl || req.headers["x-postiz-base-url"] || "https://api.postiz.com/public/v1";
|
|
544
|
+
sources.postiz = { apiKey: postizKey, baseUrl: baseUrl.replace(/\/+$/, "") };
|
|
268
545
|
}
|
|
269
546
|
try {
|
|
270
|
-
const fs =
|
|
271
|
-
const path =
|
|
272
|
-
const os =
|
|
547
|
+
const fs = require("fs");
|
|
548
|
+
const path = require("path");
|
|
549
|
+
const os = require("os");
|
|
273
550
|
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
274
551
|
if (fs.existsSync(configPath)) {
|
|
275
552
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
276
553
|
const plugins = cfg?.plugins?.entries || {};
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
platform: "discord",
|
|
282
|
-
displayName: "Discord (via OpenClaw)",
|
|
283
|
-
isActive: true,
|
|
284
|
-
capabilities: ["post"],
|
|
285
|
-
meta: { channel: "discord" }
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
if (plugins.telegram?.enabled) {
|
|
289
|
-
providers.push({
|
|
290
|
-
id: "gateway:telegram",
|
|
291
|
-
type: "gateway",
|
|
292
|
-
platform: "telegram",
|
|
293
|
-
displayName: "Telegram (via OpenClaw)",
|
|
294
|
-
isActive: true,
|
|
295
|
-
capabilities: ["post"],
|
|
296
|
-
meta: { channel: "telegram" }
|
|
297
|
-
});
|
|
298
|
-
}
|
|
554
|
+
const channels = [];
|
|
555
|
+
if (plugins.discord?.enabled) channels.push("discord");
|
|
556
|
+
if (plugins.telegram?.enabled) channels.push("telegram");
|
|
557
|
+
sources.gatewayChannels = channels;
|
|
299
558
|
}
|
|
300
559
|
} catch {
|
|
301
560
|
}
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
payload.date = body.scheduledAt;
|
|
311
|
-
}
|
|
312
|
-
if (body.settings) {
|
|
313
|
-
payload.settings = body.settings;
|
|
314
|
-
}
|
|
315
|
-
if (body.mediaUrls && body.mediaUrls.length > 0) {
|
|
316
|
-
payload.media = body.mediaUrls.map((url) => ({ url }));
|
|
317
|
-
}
|
|
318
|
-
const res = await postizFetch(config, "/posts", {
|
|
319
|
-
method: "POST",
|
|
320
|
-
body: JSON.stringify(payload)
|
|
321
|
-
});
|
|
322
|
-
const data = await res.json().catch(() => null);
|
|
323
|
-
if (!res.ok) {
|
|
324
|
-
return apiError(res.status, "POSTIZ_ERROR", data?.message || `Postiz returned ${res.status}`, data);
|
|
561
|
+
try {
|
|
562
|
+
const { db } = initializeDatabase(teamId);
|
|
563
|
+
const accounts = db.select().from(socialAccounts).where((0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(socialAccounts.teamId, teamId), (0, import_drizzle_orm2.eq)(socialAccounts.isActive, true))).all();
|
|
564
|
+
sources.storedAccounts = accounts.map((a) => ({
|
|
565
|
+
platform: a.platform,
|
|
566
|
+
credentials: decryptCredentials(a.credentials)
|
|
567
|
+
}));
|
|
568
|
+
} catch {
|
|
325
569
|
}
|
|
326
|
-
return
|
|
570
|
+
return sources;
|
|
327
571
|
}
|
|
328
572
|
async function handleRequest(req, ctx) {
|
|
329
573
|
const teamId = getTeamId(req);
|
|
330
|
-
if (req.path === "/
|
|
574
|
+
if (req.path === "/drivers" && req.method === "GET") {
|
|
331
575
|
try {
|
|
332
|
-
const
|
|
333
|
-
|
|
576
|
+
const sources = getBackendSources(req, teamId);
|
|
577
|
+
const drivers = createAllDrivers(sources);
|
|
578
|
+
const results = await Promise.all(
|
|
579
|
+
drivers.map(async (d) => {
|
|
580
|
+
const status = await d.getStatus();
|
|
581
|
+
const caps = d.getCapabilities();
|
|
582
|
+
return {
|
|
583
|
+
platform: d.platform,
|
|
584
|
+
label: d.label,
|
|
585
|
+
icon: d.icon,
|
|
586
|
+
...status,
|
|
587
|
+
capabilities: caps
|
|
588
|
+
};
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
return { status: 200, data: { drivers: results } };
|
|
334
592
|
} catch (error) {
|
|
335
|
-
return apiError(500, "
|
|
593
|
+
return apiError(500, "DRIVER_ERROR", error?.message || "Failed to load drivers");
|
|
336
594
|
}
|
|
337
595
|
}
|
|
338
|
-
if (req.path
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
596
|
+
if (req.path.match(/^\/drivers\/([^/]+)\/status$/) && req.method === "GET") {
|
|
597
|
+
const platform = req.path.split("/")[2];
|
|
598
|
+
const sources = getBackendSources(req, teamId);
|
|
599
|
+
const driver = createDriver(platform, {
|
|
600
|
+
postiz: sources.postiz,
|
|
601
|
+
gateway: sources.gatewayChannels?.includes(platform) ? { channel: platform } : void 0,
|
|
602
|
+
direct: sources.storedAccounts?.find((a) => a.platform === platform)?.credentials
|
|
603
|
+
});
|
|
604
|
+
if (!driver) return apiError(404, "NOT_FOUND", `No driver for platform: ${platform}`);
|
|
605
|
+
const status = await driver.getStatus();
|
|
606
|
+
const caps = driver.getCapabilities();
|
|
607
|
+
return { status: 200, data: { platform, ...status, capabilities: caps } };
|
|
348
608
|
}
|
|
349
609
|
if (req.path === "/publish" && req.method === "POST") {
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
610
|
+
const body = req.body;
|
|
611
|
+
if (!body?.content || !body?.platforms?.length) {
|
|
612
|
+
return apiError(400, "VALIDATION_ERROR", "content and platforms[] are required");
|
|
613
|
+
}
|
|
614
|
+
const sources = getBackendSources(req, teamId);
|
|
615
|
+
const results = [];
|
|
616
|
+
for (const platform of body.platforms) {
|
|
617
|
+
const driver = createDriver(platform, {
|
|
618
|
+
postiz: sources.postiz,
|
|
619
|
+
gateway: sources.gatewayChannels?.includes(platform) ? { channel: platform } : void 0,
|
|
620
|
+
direct: sources.storedAccounts?.find((a) => a.platform === platform)?.credentials
|
|
621
|
+
});
|
|
622
|
+
if (!driver) {
|
|
623
|
+
results.push({ platform, success: false, error: `No driver for ${platform}` });
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
const status = await driver.getStatus();
|
|
627
|
+
if (!status.connected) {
|
|
628
|
+
results.push({ platform, success: false, error: `Not connected`, backend: status.backend });
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const postContent = {
|
|
632
|
+
text: body.content,
|
|
633
|
+
mediaUrls: body.mediaUrls,
|
|
634
|
+
scheduledAt: body.scheduledAt,
|
|
635
|
+
settings: body.settings?.[platform]
|
|
636
|
+
};
|
|
637
|
+
const result = await driver.publish(postContent);
|
|
638
|
+
results.push({
|
|
639
|
+
platform,
|
|
640
|
+
success: result.success,
|
|
641
|
+
postId: result.postId,
|
|
642
|
+
error: result.error,
|
|
643
|
+
backend: status.backend
|
|
644
|
+
});
|
|
355
645
|
}
|
|
646
|
+
const allOk = results.every((r) => r.success);
|
|
647
|
+
return { status: allOk ? 201 : 207, data: { results } };
|
|
648
|
+
}
|
|
649
|
+
if (req.path === "/platforms" && req.method === "GET") {
|
|
650
|
+
const sources = getBackendSources(req, teamId);
|
|
651
|
+
const drivers = createAllDrivers(sources);
|
|
652
|
+
const platforms = drivers.map((d) => ({
|
|
653
|
+
platform: d.platform,
|
|
654
|
+
label: d.label,
|
|
655
|
+
icon: d.icon,
|
|
656
|
+
capabilities: d.getCapabilities()
|
|
657
|
+
}));
|
|
658
|
+
return { status: 200, data: { platforms } };
|
|
659
|
+
}
|
|
660
|
+
if (req.path === "/providers" && req.method === "GET") {
|
|
356
661
|
try {
|
|
357
|
-
|
|
662
|
+
const sources = getBackendSources(req, teamId);
|
|
663
|
+
const drivers = createAllDrivers(sources);
|
|
664
|
+
const providers = await Promise.all(
|
|
665
|
+
drivers.map(async (d) => {
|
|
666
|
+
const status = await d.getStatus();
|
|
667
|
+
if (!status.connected) return null;
|
|
668
|
+
return {
|
|
669
|
+
id: `${status.backend}:${d.platform}`,
|
|
670
|
+
type: status.backend,
|
|
671
|
+
platform: d.platform,
|
|
672
|
+
displayName: status.displayName,
|
|
673
|
+
username: status.username,
|
|
674
|
+
avatar: status.avatar,
|
|
675
|
+
isActive: status.connected,
|
|
676
|
+
capabilities: status.backend === "postiz" ? ["post", "schedule"] : ["post"],
|
|
677
|
+
meta: { integrationId: status.integrationId }
|
|
678
|
+
};
|
|
679
|
+
})
|
|
680
|
+
);
|
|
681
|
+
return { status: 200, data: { providers: providers.filter(Boolean) } };
|
|
358
682
|
} catch (error) {
|
|
359
|
-
return apiError(
|
|
683
|
+
return apiError(500, "DETECT_ERROR", error?.message || "Failed to detect providers");
|
|
360
684
|
}
|
|
361
685
|
}
|
|
362
686
|
if (req.path === "/posts" && req.method === "GET") {
|
|
@@ -364,12 +688,8 @@ async function handleRequest(req, ctx) {
|
|
|
364
688
|
const { db } = initializeDatabase(teamId);
|
|
365
689
|
const { limit, offset } = parsePagination(req.query);
|
|
366
690
|
const conditions = [(0, import_drizzle_orm2.eq)(posts.teamId, teamId)];
|
|
367
|
-
if (req.query.status)
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
if (req.query.platform) {
|
|
371
|
-
conditions.push((0, import_drizzle_orm2.like)(posts.platforms, `%"${req.query.platform}"%`));
|
|
372
|
-
}
|
|
691
|
+
if (req.query.status) conditions.push((0, import_drizzle_orm2.eq)(posts.status, String(req.query.status)));
|
|
692
|
+
if (req.query.platform) conditions.push((0, import_drizzle_orm2.like)(posts.platforms, `%"${req.query.platform}"%`));
|
|
373
693
|
const totalResult = await db.select({ count: import_drizzle_orm2.sql`count(*)` }).from(posts).where((0, import_drizzle_orm2.and)(...conditions));
|
|
374
694
|
const total = totalResult[0]?.count ?? 0;
|
|
375
695
|
const posts2 = await db.select().from(posts).where((0, import_drizzle_orm2.and)(...conditions)).orderBy((0, import_drizzle_orm2.desc)(posts.createdAt)).limit(limit).offset(offset);
|