@praeko/cli 0.1.0 → 0.2.0
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/CHANGELOG.md +10 -2
- package/README.md +2 -2
- package/dist/chunks/{adaptadores-X3NUBMLT.mjs → adaptadores-73I3A475.mjs} +5 -5
- package/dist/chunks/{buffer-5TUDOBVN.mjs → buffer-7UXG3MU4.mjs} +5 -5
- package/dist/chunks/{chunk-QDZLRBTP.mjs → chunk-FZ2EWUHA.mjs} +133 -57
- package/dist/chunks/{chunk-SAHLBARV.mjs → chunk-K5RD3QB7.mjs} +12 -12
- package/dist/chunks/{chunk-Z325UM27.mjs → chunk-SL4UDIFM.mjs} +1 -1
- package/dist/chunks/{chunk-IZEX746E.mjs → chunk-SPELUAPP.mjs} +15 -15
- package/dist/chunks/chunk-SSYEOFS5.mjs +56 -0
- package/dist/chunks/{chunk-LLXYUCTT.mjs → chunk-TDQGKU7I.mjs} +9 -8
- package/dist/chunks/{chunk-UK3UKRPR.mjs → chunk-V3GB2IUZ.mjs} +20 -3
- package/dist/chunks/{instagram-GNY73ESY.mjs → instagram-3P4ROM45.mjs} +1 -1
- package/dist/chunks/{instagram-meta-COY6BTNX.mjs → instagram-meta-ANEWEJV7.mjs} +2 -1
- package/dist/chunks/{local-EUJHQ76T.mjs → local-QWOY7WI5.mjs} +3 -3
- package/dist/chunks/{registro-FL2CVMYM.mjs → registro-2EE55WTS.mjs} +1 -1
- package/dist/chunks/{remoto-OAB5Y2MS.mjs → remoto-HPELHT4F.mjs} +2 -2
- package/dist/chunks/{tiktok-QOUHOWDU.mjs → tiktok-DVG5JNJE.mjs} +1 -1
- package/dist/chunks/{tiktok-api-36DV2H43.mjs → tiktok-api-2CYJNS73.mjs} +1 -1
- package/dist/chunks/{youtube-RZ4XTUZW.mjs → youtube-EZL4SNMO.mjs} +4 -2
- package/dist/chunks/{youtube-analytics-3KIW2ZIW.mjs → youtube-analytics-74ZDYCY6.mjs} +2 -2
- package/dist/chunks/{youtube-api-Z2RP5D2V.mjs → youtube-api-QVEOU2L7.mjs} +3 -1
- package/dist/chunks/{youtube-comunidade-OAK5GGK4.mjs → youtube-comunidade-GJBG2XR7.mjs} +2 -2
- package/dist/praeko.mjs +851 -8
- package/package.json +5 -5
- package/dist/arauto.mjs +0 -11
- package/dist/chunks/chunk-6YARHJZ2.mjs +0 -56
- package/dist/chunks/chunk-XQO6MI7C.mjs +0 -851
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/) e [SemVer](https://semver.org/lang/pt-BR/).
|
|
4
4
|
|
|
5
|
-
## [0.
|
|
5
|
+
## [0.2.0] - 2026-11-07
|
|
6
|
+
|
|
7
|
+
Sem mudança de comportamento: o código da CLI (e do `@praeko/core` que ela embute) agora é TypeScript (`strict`), empacotado pelo esbuild como antes. Mesmo `dist/praeko.mjs`, zero dependências.
|
|
8
|
+
|
|
9
|
+
## [0.1.1] - 2026-11-07
|
|
10
|
+
|
|
11
|
+
- Um só comando, `praeko` (o alias `arauto` saiu do pacote). Com dois comandos o `npx @praeko/cli` não sabia qual rodar e falhava com "could not determine executable to run"; agora `npx @praeko/cli --help` funciona. Quem usou a 0.1.0 pode rodar `npx -p @praeko/cli praeko`.
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-11-07
|
|
6
14
|
|
|
7
15
|
Primeira versão preparada para o npm.
|
|
8
|
-
- Pacote `@praeko/cli`:
|
|
16
|
+
- Pacote `@praeko/cli`: comando `praeko`, empacotado em `dist/` com o core e o SDK embutidos (sem dependências); `--version`; `praeko studio` avisa quando o Studio não está instalado.
|
|
9
17
|
- Licença MIT (arquivo `LICENSE` no pacote) e repositório público de código-cliente (`Praeko/praeko-js`).
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @praeko/cli
|
|
2
2
|
|
|
3
|
-
Linha de comando do [Praeko](https://praeko.app): publica e agenda vídeos curtos no YouTube, Instagram e TikTok, e fala com a API do serviço (posts, fila, canais, chaves, insights). Comando `praeko` (
|
|
3
|
+
Linha de comando do [Praeko](https://praeko.app): publica e agenda vídeos curtos no YouTube, Instagram e TikTok, e fala com a API do serviço (posts, fila, canais, chaves, insights). Comando `praeko` (antes chamado `arauto`). Node 22 ou superior; `ffmpeg`/`ffprobe` só para os comandos que mexem em capa e vídeo.
|
|
4
4
|
|
|
5
5
|
## Instalação
|
|
6
6
|
|
|
@@ -52,7 +52,7 @@ A chave vem de `PRAEKO_KEY`. Sem ela, a CLI procura no Chaveiro do macOS (só no
|
|
|
52
52
|
|
|
53
53
|
## Links
|
|
54
54
|
|
|
55
|
-
Documentação: <https://praeko.app/docs> · Código
|
|
55
|
+
Documentação: <https://praeko.app/docs> · Código: <https://github.com/Praeko/praeko-js> · Suporte: contato@praeko.app · SDK: [`@praeko/sdk`](https://www.npmjs.com/package/@praeko/sdk)
|
|
56
56
|
|
|
57
57
|
## Licença
|
|
58
58
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
buffer_exports
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-K5RD3QB7.mjs";
|
|
5
5
|
import {
|
|
6
6
|
local_exports
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-SPELUAPP.mjs";
|
|
8
|
+
import "./chunk-SSYEOFS5.mjs";
|
|
9
9
|
import {
|
|
10
10
|
remoto_exports
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-FZ2EWUHA.mjs";
|
|
12
|
+
import "./chunk-SL4UDIFM.mjs";
|
|
13
13
|
import "./chunk-LCFK6X55.mjs";
|
|
14
14
|
export {
|
|
15
15
|
buffer_exports as buffer,
|
|
@@ -2,11 +2,11 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
2
2
|
import {
|
|
3
3
|
REF_CHAVE,
|
|
4
4
|
criar
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-K5RD3QB7.mjs";
|
|
6
|
+
import "./chunk-SPELUAPP.mjs";
|
|
7
|
+
import "./chunk-SSYEOFS5.mjs";
|
|
8
|
+
import "./chunk-FZ2EWUHA.mjs";
|
|
9
|
+
import "./chunk-SL4UDIFM.mjs";
|
|
10
10
|
import "./chunk-LCFK6X55.mjs";
|
|
11
11
|
export {
|
|
12
12
|
REF_CHAVE,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
validarPedido
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SL4UDIFM.mjs";
|
|
5
5
|
import {
|
|
6
6
|
__export
|
|
7
7
|
} from "./chunk-LCFK6X55.mjs";
|
|
8
8
|
|
|
9
|
-
// ../core/src/adaptadores/remoto.
|
|
9
|
+
// ../core/src/adaptadores/remoto.ts
|
|
10
10
|
var remoto_exports = {};
|
|
11
11
|
__export(remoto_exports, {
|
|
12
12
|
REF_CHAVE: () => REF_CHAVE,
|
|
@@ -14,14 +14,19 @@ __export(remoto_exports, {
|
|
|
14
14
|
itemDoContrato: () => itemDoContrato
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
// ../sdk/
|
|
17
|
+
// ../sdk/dist/legacy.js
|
|
18
18
|
var enc = (x) => encodeURIComponent(x);
|
|
19
19
|
function fonte(x) {
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
20
|
+
const a = x;
|
|
21
|
+
if (a && typeof a.read === "function" && typeof a.size === "number")
|
|
22
|
+
return a;
|
|
23
|
+
if (a && typeof a.ler === "function" && typeof a.tamanho === "number")
|
|
24
|
+
return { size: a.tamanho, read: (i, n) => a.ler(i, n) };
|
|
25
|
+
if (typeof Blob !== "undefined" && x instanceof Blob)
|
|
26
|
+
return { size: x.size, read: async (i, n) => new Uint8Array(await x.slice(i, i + n).arrayBuffer()) };
|
|
23
27
|
const u8 = x instanceof Uint8Array ? x : x instanceof ArrayBuffer ? new Uint8Array(x) : null;
|
|
24
|
-
if (!u8)
|
|
28
|
+
if (!u8)
|
|
29
|
+
throw new TypeError("fonte inv\xE1lida: use Uint8Array, ArrayBuffer, Blob ou { tamanho, ler(inicio, n) }");
|
|
25
30
|
return { size: u8.length, read: async (i, n) => u8.subarray(i, i + n) };
|
|
26
31
|
}
|
|
27
32
|
function addLegacy(a) {
|
|
@@ -37,7 +42,8 @@ function addLegacy(a) {
|
|
|
37
42
|
/** Uploads a whole media (legacy shapes: { id, … } with Portuguese fields). Prefer media.upload. */
|
|
38
43
|
enviar: async (origem, { tipo, nome, mime, parte = 16 * 1024 * 1024, aoProgredir } = {}) => {
|
|
39
44
|
const s = fonte(origem);
|
|
40
|
-
if (!tipo || !nome || !mime)
|
|
45
|
+
if (!tipo || !nome || !mime)
|
|
46
|
+
throw new Error("midia.enviar: informe tipo, nome e mime");
|
|
41
47
|
if (s.size <= 32 * 1024 * 1024) {
|
|
42
48
|
const m2 = await a.midia.iniciar({ tipo, nome, mime, tamanho: s.size, simples: true });
|
|
43
49
|
const r = await a.midia.enviarConteudo(m2.id, await s.read(0, s.size));
|
|
@@ -105,7 +111,7 @@ function addLegacy(a) {
|
|
|
105
111
|
a.saude = () => L("GET", "/saude", { semChave: true });
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
// ../sdk/
|
|
114
|
+
// ../sdk/dist/index.js
|
|
109
115
|
var PraekoError = class extends Error {
|
|
110
116
|
constructor(status, code, message, details) {
|
|
111
117
|
super(message);
|
|
@@ -131,23 +137,29 @@ var SIMPLE_MAX = 32 * 1024 * 1024;
|
|
|
131
137
|
var DEFAULT_PART = 16 * 1024 * 1024;
|
|
132
138
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
133
139
|
function asSource(x) {
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
140
|
+
const a = x;
|
|
141
|
+
if (a && typeof a.read === "function" && typeof a.size === "number")
|
|
142
|
+
return a;
|
|
143
|
+
if (a && typeof a.ler === "function" && typeof a.tamanho === "number")
|
|
144
|
+
return { size: a.tamanho, read: (i, n) => a.ler(i, n) };
|
|
145
|
+
if (typeof Blob !== "undefined" && x instanceof Blob)
|
|
146
|
+
return { size: x.size, read: async (i, n) => new Uint8Array(await x.slice(i, i + n).arrayBuffer()) };
|
|
137
147
|
const u8 = x instanceof Uint8Array ? x : x instanceof ArrayBuffer ? new Uint8Array(x) : null;
|
|
138
|
-
if (!u8)
|
|
148
|
+
if (!u8)
|
|
149
|
+
throw new TypeError("invalid source: use Uint8Array, ArrayBuffer, Blob or { size, read(start, n) }");
|
|
139
150
|
return { size: u8.length, read: async (i, n) => u8.subarray(i, i + n) };
|
|
140
151
|
}
|
|
141
152
|
var ref = (id) => encodeURIComponent(id);
|
|
142
153
|
var idem = (o) => o && o.idempotencyKey ? { idempotencyKey: o.idempotencyKey } : {};
|
|
143
154
|
var uuid = () => globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`;
|
|
144
155
|
var Praeko = class {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
constructor(o = {}) {
|
|
156
|
+
constructor(o) {
|
|
157
|
+
o = o ?? {};
|
|
148
158
|
const { url, key = o.chave, cookies = false, fetch: f = globalThis.fetch, retries = o.tentativas ?? 3, wait = o.espera ?? sleep } = o;
|
|
149
|
-
if (!url)
|
|
150
|
-
|
|
159
|
+
if (!url)
|
|
160
|
+
throw new Error("Praeko: missing service url (falta a url do servi\xE7o)");
|
|
161
|
+
if (!key && !cookies)
|
|
162
|
+
throw new Error("Praeko: missing API key praeko_\u2026 (falta a chave)");
|
|
151
163
|
this.url = String(url).replace(/\/+$/, "");
|
|
152
164
|
this._key = key;
|
|
153
165
|
this._cookies = !key && cookies;
|
|
@@ -166,7 +178,8 @@ var Praeko = class {
|
|
|
166
178
|
/** Uploads a whole file (one PUT or parts, depending on size). Returns the ready media { id, … }. */
|
|
167
179
|
upload: async (source, { type, name, mime, part = DEFAULT_PART, onProgress } = {}) => {
|
|
168
180
|
const f2 = asSource(source);
|
|
169
|
-
if (!type || !name || !mime)
|
|
181
|
+
if (!type || !name || !mime)
|
|
182
|
+
throw new Error("media.upload: provide type, name and mime");
|
|
170
183
|
if (f2.size <= SIMPLE_MAX) {
|
|
171
184
|
const m2 = await a.media.create({ type, name, mime, size: f2.size, simple: true });
|
|
172
185
|
const r = await a.media.uploadContent(m2.id, await f2.read(0, f2.size));
|
|
@@ -258,6 +271,10 @@ var Praeko = class {
|
|
|
258
271
|
get: () => a._req("GET", "/v1/preferences"),
|
|
259
272
|
put: ({ theme, language, notifications } = {}) => a._req("PUT", "/v1/preferences", { body: { theme, language, notifications } })
|
|
260
273
|
};
|
|
274
|
+
this.notifications = {
|
|
275
|
+
list: ({ unread, limit, cursor } = {}) => a._req("GET", "/v1/notifications", { query: { unread, limit, cursor } }),
|
|
276
|
+
markRead: ({ ids, all } = {}) => a._req("POST", "/v1/notifications/read", { body: { ids, all } })
|
|
277
|
+
};
|
|
261
278
|
this.webhooks = {
|
|
262
279
|
list: () => a._req("GET", "/v1/webhooks"),
|
|
263
280
|
create: ({ url: url2, events, active, idempotencyKey } = {}) => a._req("POST", "/v1/webhooks", { body: { url: url2, events, active }, ...idem({ idempotencyKey }) }),
|
|
@@ -291,16 +308,19 @@ var Praeko = class {
|
|
|
291
308
|
/**
|
|
292
309
|
* One HTTP call. `legacy: true` (only for the deprecated Portuguese aliases) skips the `X-Praeko-Contract: en` header, so the shared paths
|
|
293
310
|
* (/v1/posts, /v1/insights, /v1/youtube/videos) answer in the old shape.
|
|
311
|
+
* @internal
|
|
294
312
|
*/
|
|
295
|
-
async _req(method, path, { body, bytes, query, retry = method === "GET", noKey = false, legacy = false, idempotencyKey } = {}) {
|
|
296
|
-
|
|
313
|
+
async _req(method, path, { body, bytes, query, retry = method === "GET", noKey = false, legacy = false, idempotencyKey: idemOpt } = {}) {
|
|
314
|
+
let idempotencyKey;
|
|
315
|
+
if (idemOpt) {
|
|
297
316
|
retry = true;
|
|
298
|
-
idempotencyKey =
|
|
317
|
+
idempotencyKey = idemOpt === true ? uuid() : String(idemOpt);
|
|
299
318
|
}
|
|
300
319
|
const q = query ? Object.entries(query).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&") : "";
|
|
301
320
|
const url = `${this.url}${path}${q ? "?" + q : ""}`;
|
|
302
321
|
const init = { method, headers: { ...noKey || this._cookies ? {} : { authorization: `Bearer ${this._key}` }, ...legacy ? {} : { "x-praeko-contract": "en" } } };
|
|
303
|
-
if (idempotencyKey)
|
|
322
|
+
if (idempotencyKey)
|
|
323
|
+
init.headers["idempotency-key"] = idempotencyKey;
|
|
304
324
|
if (bytes !== void 0) {
|
|
305
325
|
init.body = bytes;
|
|
306
326
|
init.headers["content-type"] = "application/octet-stream";
|
|
@@ -310,7 +330,8 @@ var Praeko = class {
|
|
|
310
330
|
}
|
|
311
331
|
let last;
|
|
312
332
|
for (let i = 0; i < (retry ? this._retries : 1); i++) {
|
|
313
|
-
if (i)
|
|
333
|
+
if (i)
|
|
334
|
+
await this._wait(Math.min(8e3, 500 * 2 ** i));
|
|
314
335
|
let r;
|
|
315
336
|
try {
|
|
316
337
|
r = await this._fetch(url, init);
|
|
@@ -324,16 +345,18 @@ var Praeko = class {
|
|
|
324
345
|
j = txt ? JSON.parse(txt) : null;
|
|
325
346
|
} catch {
|
|
326
347
|
}
|
|
327
|
-
if (r.ok)
|
|
348
|
+
if (r.ok)
|
|
349
|
+
return j;
|
|
328
350
|
const e = j?.error ?? (j?.erro && { code: j.erro.codigo, message: j.erro.mensagem, details: j.erro.detalhes });
|
|
329
351
|
last = new PraekoError(r.status, e?.code || "http_error", e?.message || `HTTP ${r.status}`, e?.details);
|
|
330
|
-
if (!(r.status === 429 || r.status >= 500))
|
|
352
|
+
if (!(r.status === 429 || r.status >= 500))
|
|
353
|
+
throw last;
|
|
331
354
|
}
|
|
332
355
|
throw last;
|
|
333
356
|
}
|
|
334
357
|
};
|
|
335
358
|
|
|
336
|
-
// ../sdk/
|
|
359
|
+
// ../sdk/dist/node.js
|
|
337
360
|
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
338
361
|
import { basename, extname } from "node:path";
|
|
339
362
|
var MIME = { ".mp4": "video/mp4", ".mov": "video/quicktime", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg" };
|
|
@@ -351,16 +374,18 @@ function fileSource(path) {
|
|
|
351
374
|
async function uploadFile(cliente, path, { type, onProgress, tipo, aoProgredir } = {}) {
|
|
352
375
|
const f = fileSource(path);
|
|
353
376
|
try {
|
|
354
|
-
if (!f.mime)
|
|
377
|
+
if (!f.mime)
|
|
378
|
+
throw new Error(`n\xE3o sei o tipo de ${path} (use .mp4, .mov, .png ou .jpg)`);
|
|
355
379
|
const t = type || tipo || (f.mime.startsWith("video/") ? "video" : "cover"), cb = onProgress || aoProgredir;
|
|
356
|
-
if (cliente.media?.upload)
|
|
380
|
+
if (cliente.media?.upload)
|
|
381
|
+
return await cliente.media.upload(f, { type: t === "capa" ? "cover" : t, name: f.name, mime: f.mime, onProgress: cb });
|
|
357
382
|
return await cliente.midia.enviar(f, { tipo: t === "cover" ? "capa" : t, nome: f.name, mime: f.mime, aoProgredir: cb });
|
|
358
383
|
} finally {
|
|
359
384
|
f.close();
|
|
360
385
|
}
|
|
361
386
|
}
|
|
362
387
|
|
|
363
|
-
// ../sdk/
|
|
388
|
+
// ../sdk/dist/contract.js
|
|
364
389
|
var ROUTES = [
|
|
365
390
|
{ key: "health", method: "GET", path: "/health", legacy: "/saude", scope: "public", summary: "Liveness probe" },
|
|
366
391
|
{ key: "health.v1", method: "GET", path: "/v1/health", legacy: "/v1/saude", scope: "public", summary: "Liveness probe (versioned path)" },
|
|
@@ -392,7 +417,7 @@ var ROUTES = [
|
|
|
392
417
|
{ key: "posts.adopt", method: "POST", path: "/v1/posts/adopt", legacy: "/v1/posts/adotar", scope: "write", body: "json", summary: "Register a Reel already scheduled in Buffer (does not call Buffer)" },
|
|
393
418
|
{ key: "posts.list", method: "GET", path: "/v1/posts", legacy: "/v1/posts", scope: "read", summary: "List posts (filters: state, channel, tag)" },
|
|
394
419
|
{ key: "posts.get", method: "GET", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "read", summary: "One post with its items and actions" },
|
|
395
|
-
{ key: "posts.update", method: "PATCH", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "write", body: "json", summary: "Edit title, texts, media, tags, items; move to draft/queue" },
|
|
420
|
+
{ key: "posts.update", method: "PATCH", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "write", body: "json", summary: "Edit title, texts, media, tags, items; move to draft/queue; reschedule/edit a YouTube video already uploaded and still scheduled" },
|
|
396
421
|
{ key: "posts.cancel", method: "DELETE", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "write", summary: "Cancel the pending items of a post" },
|
|
397
422
|
{ key: "posts.publishNow", method: "POST", path: "/v1/posts/:id/publish-now", legacy: "/v1/posts/:id/publicar-agora", scope: "write", summary: "Take pending items out of the queue and publish now (?network=)" },
|
|
398
423
|
{ key: "posts.youtubeSchedule", method: "POST", path: "/v1/posts/:id/youtube/schedule", legacy: "/v1/posts/:id/youtube/agendar", scope: "write", body: "json", summary: "Schedule publication of a video already uploaded to YouTube (private)" },
|
|
@@ -425,7 +450,9 @@ var ROUTES = [
|
|
|
425
450
|
{ key: "usage.get", method: "GET", path: "/v1/usage", legacy: "/v1/usage", scope: "read", summary: "API usage of this workspace over the last `days` (1-30, default 7, UTC): total, succeeded (< 400), errors, p95 duration, peak requests in one minute vs the per-minute limit, `byDay`, `byHour` (only with days=1: the last 24 hours) and `byKey` (`session` = the panel)" },
|
|
426
451
|
{ key: "usage.log", method: "GET", path: "/v1/usage/log", legacy: "/v1/usage/log", scope: "read", summary: "Request log, newest first, kept 30 days; metadata only (method, route template without ids, status, duration, key), never bodies, headers or query. Filters: `key` (id or `session`), `status` (`2xx`|`3xx`|`4xx`|`5xx` or a code), `route` (substring), `method`, `days` (1-30); `limit` (1-200, default 50) and `cursor` (the `next` of the previous page)" },
|
|
427
452
|
{ key: "preferences.get", method: "GET", path: "/v1/preferences", legacy: "/v1/preferences", scope: "read", summary: "Panel preferences of the signed-in user in this workspace (an API key reads the workspace row): `theme` (light, dark, system), `language` (pt-BR, en) and `notifications` per event (`postSent`, `postFailed`, `scheduledFail`, `weeklySummary`, `newComment`) with `email` and `panel` flags. Missing values come back as the defaults (dark, pt-BR, no e-mail, panel on)" },
|
|
428
|
-
{ key: "preferences.put", method: "PUT", path: "/v1/preferences", legacy: "/v1/preferences", scope: "write", body: "json", summary: "Change preferences; partial (only the fields sent change) and strict (unknown field or value is 422 invalid_field). Returns the full preferences.
|
|
453
|
+
{ key: "preferences.put", method: "PUT", path: "/v1/preferences", legacy: "/v1/preferences", scope: "write", body: "json", summary: "Change preferences; partial (only the fields sent change) and strict (unknown field or value is 422 invalid_field). Returns the full preferences. With `email` true the server e-mails `postSent`, `postFailed`, `scheduledFail`, the `weeklySummary` (Mondays 08:00 America/Sao_Paulo) and `newComment` (batched, at most one e-mail per hour); with `panel` true it adds the event to the notification center (`GET /v1/notifications`)" },
|
|
454
|
+
{ key: "notifications.list", method: "GET", path: "/v1/notifications", legacy: "/v1/notifications", scope: "read", summary: "In-panel notification center of the signed-in user in this workspace (the bell), newest first: `notifications` (`id`, `event` = postSent, postFailed, scheduledFail, weeklySummary, newComment, `data` with the facts of the event, `link` to where it leads, `read`, `readAt`, `createdAt`), `unreadCount` and `next`. A notification exists only when the user's `panel` preference for that event was on when it happened. Filters: `unread` (true), `limit` (1-50, default 20) and `cursor` (the `next` of the previous page). Kept 30 days. An API key has no user and reads an empty list" },
|
|
455
|
+
{ key: "notifications.read", method: "POST", path: "/v1/notifications/read", legacy: "/v1/notifications/read", scope: "write", body: "json", summary: "Mark notifications as read: `ids` (up to 100) or `all: true`. Ids that are not yours or do not exist are ignored and marking again is harmless. Returns `marked` (how many changed) and the new `unreadCount`" },
|
|
429
456
|
{ key: "webhooks.list", method: "GET", path: "/v1/webhooks", legacy: "/v1/webhooks", scope: "admin", summary: "Outgoing webhooks of this workspace (never the secret: only `secretHint`), with the delivery success of the last 7 days (`recentDeliveries`) and the subscribable `events`" },
|
|
430
457
|
{ key: "webhooks.create", method: "POST", path: "/v1/webhooks", legacy: "/v1/webhooks", scope: "admin", body: "json", summary: "Create a webhook: `url` (https, public host; private IPs, localhost and cloud metadata are refused with unsafe_url), `events` (post.sent, post.failed, post.scheduled-fail; default the first two), `active`. The signing `secret` is returned ONCE. Max 10 per workspace" },
|
|
431
458
|
{ key: "webhooks.get", method: "GET", path: "/v1/webhooks/:id", legacy: "/v1/webhooks/:id", scope: "admin", summary: "One webhook (no secret)" },
|
|
@@ -443,14 +470,21 @@ var ROUTES = [
|
|
|
443
470
|
];
|
|
444
471
|
var toRegExp = (tpl) => new RegExp("^" + tpl.replace(/:[A-Za-z_]+/g, "([^/]+)").replace(/\//g, "\\/") + "$");
|
|
445
472
|
var NEW_RE = ROUTES.map((r) => ({ r, re: toRegExp(r.path) }));
|
|
446
|
-
var OLD_RE = ROUTES.
|
|
447
|
-
var
|
|
473
|
+
var OLD_RE = ROUTES.flatMap((r) => r.legacy ? [{ r, re: toRegExp(r.legacy) }] : []);
|
|
474
|
+
var KEY_ROWS = [
|
|
448
475
|
["abs", "absolute"],
|
|
449
476
|
["acao", "action"],
|
|
450
477
|
["acoes", "actions"],
|
|
451
478
|
["adotado", "adopted"],
|
|
452
479
|
["agregadoAtualizadoEm", "aggregateUpdatedAt"],
|
|
453
480
|
["agregadoBuffer", "bufferAggregate"],
|
|
481
|
+
["aplicado", "applied"],
|
|
482
|
+
["notas", "notes", "res"],
|
|
483
|
+
["tagsVideo", "videoTags", "req"],
|
|
484
|
+
["campo", "field"],
|
|
485
|
+
["campos", "fields"],
|
|
486
|
+
["naoAplicados", "notApplied"],
|
|
487
|
+
["resultadosItens", "itemResults"],
|
|
454
488
|
["alcance", "reach"],
|
|
455
489
|
["anterior", "previous"],
|
|
456
490
|
["antes", "before"],
|
|
@@ -671,6 +705,7 @@ var KEYS = [
|
|
|
671
705
|
["metodo", "method"],
|
|
672
706
|
["rota", "route"],
|
|
673
707
|
["duracaoMs", "durationMs"],
|
|
708
|
+
["duracao", "duration"],
|
|
674
709
|
["largura", "width"],
|
|
675
710
|
["altura", "height"],
|
|
676
711
|
["chaveNome", "keyName"],
|
|
@@ -694,6 +729,19 @@ var KEYS = [
|
|
|
694
729
|
["ideias", "ideas"],
|
|
695
730
|
["nota", "note"],
|
|
696
731
|
["grupo", "group"],
|
|
732
|
+
// central de avisos (/v1/notifications)
|
|
733
|
+
["naoLidos", "unreadCount"],
|
|
734
|
+
["lido", "read"],
|
|
735
|
+
["lido_em", "readAt"],
|
|
736
|
+
["dados", "data"],
|
|
737
|
+
["marcados", "marked"],
|
|
738
|
+
["vaiTentar", "willRetry"],
|
|
739
|
+
["quantidade", "count"],
|
|
740
|
+
["enviados", "sent"],
|
|
741
|
+
["falhas", "failed"],
|
|
742
|
+
["esperando", "waiting"],
|
|
743
|
+
["topTitulo", "topTitle"],
|
|
744
|
+
["topRede", "topNetwork"],
|
|
697
745
|
// retenção, impressões e CTR (/v1/insights/retention)
|
|
698
746
|
["retencao", "retention"],
|
|
699
747
|
["curva", "curve"],
|
|
@@ -706,9 +754,11 @@ var KEYS = [
|
|
|
706
754
|
["desde", "since"],
|
|
707
755
|
["pontos", "points"],
|
|
708
756
|
["coletadoEm", "collectedAt"]
|
|
709
|
-
]
|
|
757
|
+
];
|
|
758
|
+
var KEYS = KEY_ROWS.filter((x, i, a) => a.findIndex((y) => y[0] === x[0]) === i);
|
|
710
759
|
var SCOPED_KEYS = { reorganizados: [["de", "from"], ["para", "to"]], leitura48h: [["destinoId", "itemId"]] };
|
|
711
|
-
var
|
|
760
|
+
var SAME_LIST = ["id", "ctr", "item", "url", "views", "tags", "meta", "host", "token", "userId", "ok", "total", "pct", "manual", "custom", "studio", "video", "mime", "etag", "avatar", "youtube", "instagram", "tiktok", "publishAt", "parentId", "permalink", "thumbnail", "posts", "topPosts", "videos", "redirect", "chunkMB", "via", "videoId", "postId", "threadId", "limit", "max", "publishId", "status", "p95Ms", "cursor", "webhooks", "theme", "language", "notifications", "email", "panel", "postSent", "postFailed", "scheduledFail", "weeklySummary", "newComment", "unread", "ids", "all", "link", "topViews"];
|
|
761
|
+
var SAME_KEYS = new Set(SAME_LIST);
|
|
712
762
|
var VALUES = {
|
|
713
763
|
estado: [
|
|
714
764
|
["rascunho", "draft"],
|
|
@@ -786,22 +836,27 @@ var index = (rows, dir) => {
|
|
|
786
836
|
const mk = (kind) => {
|
|
787
837
|
const m = /* @__PURE__ */ new Map();
|
|
788
838
|
for (const [pt, en, only] of rows) {
|
|
789
|
-
if (only && only !== kind)
|
|
839
|
+
if (only && only !== kind)
|
|
840
|
+
continue;
|
|
790
841
|
if (dir === "toEN") {
|
|
791
|
-
if (!m.has(pt))
|
|
792
|
-
|
|
842
|
+
if (!m.has(pt))
|
|
843
|
+
m.set(pt, en);
|
|
844
|
+
} else if (!m.has(en))
|
|
845
|
+
m.set(en, pt);
|
|
793
846
|
}
|
|
794
847
|
return m;
|
|
795
848
|
};
|
|
796
849
|
return { req: mk("req"), res: mk("res") };
|
|
797
850
|
};
|
|
798
|
-
var
|
|
799
|
-
var
|
|
800
|
-
var
|
|
801
|
-
var
|
|
851
|
+
var both = (rows) => ({ toEN: index(rows, "toEN"), toPT: index(rows, "toPT") });
|
|
852
|
+
var KEY_IDX = both(KEYS);
|
|
853
|
+
var VAL_IDX = Object.fromEntries(Object.entries(VALUES).map(([k, rows]) => [k, both(rows)]));
|
|
854
|
+
var SKEY_IDX = Object.fromEntries(Object.entries(SCOPED_KEYS).map(([k, rows]) => [k, both(rows)]));
|
|
855
|
+
var SVAL_IDX = Object.fromEntries(Object.entries(SCOPED_VALUES).map(([p, fields]) => [p, Object.fromEntries(Object.entries(fields).map(([k, rows]) => [k, both(rows)]))]));
|
|
802
856
|
var PT_ONLY_KEYS = new Set(KEYS.filter(([pt, en]) => pt !== en).map(([pt]) => pt));
|
|
803
857
|
var EN_KEYS = new Set(KEYS.map(([, en]) => en));
|
|
804
|
-
for (const k of EN_KEYS)
|
|
858
|
+
for (const k of EN_KEYS)
|
|
859
|
+
PT_ONLY_KEYS.delete(k);
|
|
805
860
|
var EN_ALSO = new Set(Object.entries(VALUES).flatMap(([campo, rows]) => rows.map((r) => `${campo}|${r[1]}`)));
|
|
806
861
|
var PtKeyError = class extends Error {
|
|
807
862
|
constructor(key, value) {
|
|
@@ -816,28 +871,34 @@ for (const [campo, rows] of Object.entries(VALUES)) {
|
|
|
816
871
|
const en = new Set(rows.map((r) => r[1]));
|
|
817
872
|
PT_ONLY_VALUES[campo] = new Set(rows.filter((r) => !r[2] || r[2] === "req").map((r) => r[0]).filter((pt) => !en.has(pt)));
|
|
818
873
|
}
|
|
819
|
-
for (const [pai, campos] of Object.entries(SCOPED_VALUES))
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
}
|
|
874
|
+
for (const [pai, campos] of Object.entries(SCOPED_VALUES))
|
|
875
|
+
for (const [campo, rows] of Object.entries(campos)) {
|
|
876
|
+
const en = new Set(rows.map((r) => r[1]));
|
|
877
|
+
PT_ONLY_VALUES[`${pai}.${campo}`] ||= new Set(rows.map((r) => r[0]).filter((pt) => !en.has(pt)));
|
|
878
|
+
}
|
|
823
879
|
function translate(value, o, container = null, own = null) {
|
|
824
880
|
const { dir, kind } = o;
|
|
825
|
-
if (Array.isArray(value))
|
|
881
|
+
if (Array.isArray(value))
|
|
882
|
+
return value.map((x) => translate(x, o, container, own));
|
|
826
883
|
if (value && typeof value === "object") {
|
|
827
|
-
const out = {}, scoped = SKEY_IDX[own]?.[dir]?.[kind];
|
|
884
|
+
const out = {}, scoped = own === null ? void 0 : SKEY_IDX[own]?.[dir]?.[kind];
|
|
828
885
|
for (const [k, v] of Object.entries(value)) {
|
|
829
886
|
const hit = scoped?.get(k) ?? KEY_IDX[dir][kind].get(k);
|
|
830
|
-
if (hit === void 0 && o.strict && dir === "toPT" && PT_ONLY_KEYS.has(k) && !SAME_KEYS.has(k))
|
|
887
|
+
if (hit === void 0 && o.strict && dir === "toPT" && PT_ONLY_KEYS.has(k) && !SAME_KEYS.has(k))
|
|
888
|
+
throw new PtKeyError(k);
|
|
831
889
|
const outKey = hit ?? k, ptKey = dir === "toEN" ? k : outKey;
|
|
832
890
|
out[outKey] = translate(v, o, own, ptKey);
|
|
833
891
|
}
|
|
834
892
|
return out;
|
|
835
893
|
}
|
|
836
894
|
if (typeof value === "string" && own !== null) {
|
|
837
|
-
const idx = SVAL_IDX[container]?.[own] ?? VAL_IDX[own], mapped = idx?.[dir]?.[kind]?.get(value);
|
|
838
|
-
if (mapped !== void 0)
|
|
839
|
-
|
|
840
|
-
if (o.
|
|
895
|
+
const idx = (container === null ? void 0 : SVAL_IDX[container]?.[own]) ?? VAL_IDX[own], mapped = idx?.[dir]?.[kind]?.get(value);
|
|
896
|
+
if (mapped !== void 0)
|
|
897
|
+
return mapped;
|
|
898
|
+
if (o.strict && dir === "toPT" && kind === "req" && PT_ONLY_VALUES[own]?.has(value) && !EN_ALSO.has(`${own}|${value}`))
|
|
899
|
+
throw new PtKeyError(own, value);
|
|
900
|
+
if (o.paths && PATH_KEYS.has(own))
|
|
901
|
+
return rewritePath(value, dir);
|
|
841
902
|
}
|
|
842
903
|
return value;
|
|
843
904
|
}
|
|
@@ -873,6 +934,17 @@ var ERROR_CODES = {
|
|
|
873
934
|
horario_passado: "time_in_past",
|
|
874
935
|
sem_vagas: "no_slots",
|
|
875
936
|
sem_vaga_livre: "no_free_slot",
|
|
937
|
+
email_em_uso: "email_in_use",
|
|
938
|
+
email_de_revisao: "review_account_email",
|
|
939
|
+
usuario_sem_senha: "user_without_password",
|
|
940
|
+
redefinicao_recente: "reset_recently_sent",
|
|
941
|
+
email_falhou: "email_send_failed",
|
|
942
|
+
email_nao_configurado: "email_not_configured",
|
|
943
|
+
conta_revisao: "review_account_restricted",
|
|
944
|
+
conta_revisao_expirada: "review_account_expired",
|
|
945
|
+
conta_revisao_revogada: "review_account_revoked",
|
|
946
|
+
conta_revisao_limite: "review_account_limit",
|
|
947
|
+
conta_revisao_inexistente: "review_account_not_found",
|
|
876
948
|
post_inexistente: "post_not_found",
|
|
877
949
|
destino_inexistente: "item_not_found",
|
|
878
950
|
acao_inexistente: "action_not_found",
|
|
@@ -898,6 +970,10 @@ var ERROR_CODES = {
|
|
|
898
970
|
workspace_da_plataforma: "platform_workspace",
|
|
899
971
|
migracao_pendente: "migration_pending",
|
|
900
972
|
chave_em_uso: "api_key_in_use",
|
|
973
|
+
token_invalido: "invalid_token",
|
|
974
|
+
pedido_inexistente: "request_not_found",
|
|
975
|
+
pedido_de_outro_navegador: "request_from_another_browser",
|
|
976
|
+
conexao_inexistente: "connection_not_found",
|
|
901
977
|
video_inexistente: "video_not_found",
|
|
902
978
|
sem_video: "no_video",
|
|
903
979
|
sem_avatar: "no_avatar",
|
|
@@ -958,7 +1034,7 @@ var ERROR_CODES = {
|
|
|
958
1034
|
};
|
|
959
1035
|
var ERROR_CODES_REVERSE = Object.fromEntries(Object.entries(ERROR_CODES).map(([pt, en]) => [en, pt]));
|
|
960
1036
|
|
|
961
|
-
// ../core/src/segredos.
|
|
1037
|
+
// ../core/src/segredos.ts
|
|
962
1038
|
import { execFileSync } from "node:child_process";
|
|
963
1039
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
964
1040
|
import { homedir } from "node:os";
|
|
@@ -1044,7 +1120,7 @@ function migrar(loja = segredos, refs = REFS_CONHECIDAS) {
|
|
|
1044
1120
|
return r;
|
|
1045
1121
|
}
|
|
1046
1122
|
|
|
1047
|
-
// ../core/src/adaptadores/remoto.
|
|
1123
|
+
// ../core/src/adaptadores/remoto.ts
|
|
1048
1124
|
var REF_CHAVE = "praeko/chave";
|
|
1049
1125
|
var itemDoContrato = (i) => ({ rede: i.rede, id: i.id, estado: i.estado, quando: i.quando, ...i.url ? { url: i.url } : {}, ...i.studio ? { studio: i.studio } : {}, ...i.aviso ? { aviso: i.aviso } : {}, ...i.custom ? { custom: true } : {} });
|
|
1050
1126
|
function criar({ config, ctx, praeko, arauto } = {}) {
|
|
@@ -3,18 +3,18 @@ import {
|
|
|
3
3
|
caminhoEstado,
|
|
4
4
|
caminhoLeitura,
|
|
5
5
|
pastaEstado
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SPELUAPP.mjs";
|
|
7
7
|
import {
|
|
8
8
|
segredos
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FZ2EWUHA.mjs";
|
|
10
10
|
import {
|
|
11
11
|
validarPedido
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SL4UDIFM.mjs";
|
|
13
13
|
import {
|
|
14
14
|
__export
|
|
15
15
|
} from "./chunk-LCFK6X55.mjs";
|
|
16
16
|
|
|
17
|
-
// ../core/src/adaptadores/buffer.
|
|
17
|
+
// ../core/src/adaptadores/buffer.ts
|
|
18
18
|
var buffer_exports = {};
|
|
19
19
|
__export(buffer_exports, {
|
|
20
20
|
REF_CHAVE: () => REF_CHAVE,
|
|
@@ -22,7 +22,7 @@ __export(buffer_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
24
24
|
|
|
25
|
-
// ../core/src/hospedagem.
|
|
25
|
+
// ../core/src/hospedagem.ts
|
|
26
26
|
import { randomBytes } from "node:crypto";
|
|
27
27
|
import { spawnSync } from "node:child_process";
|
|
28
28
|
import { basename } from "node:path";
|
|
@@ -48,7 +48,7 @@ function apagar({ h, chave, cwd, rodar = spawnSync }) {
|
|
|
48
48
|
return r.status === 0;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// ../core/src/capa-quadro.
|
|
51
|
+
// ../core/src/capa-quadro.ts
|
|
52
52
|
var capa_quadro_exports = {};
|
|
53
53
|
__export(capa_quadro_exports, {
|
|
54
54
|
argsFfmpeg: () => argsFfmpeg,
|
|
@@ -89,7 +89,7 @@ function embutirCapa({ video, capa, saida, quadros = 2, rodar = spawnSync2 }) {
|
|
|
89
89
|
return { saida, quadros };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
// ../core/src/config.
|
|
92
|
+
// ../core/src/config.ts
|
|
93
93
|
import { existsSync, readFileSync } from "node:fs";
|
|
94
94
|
import { dirname, join, resolve } from "node:path";
|
|
95
95
|
var ARQUIVOS_CONFIG = ["praeko.json", "arauto.json", "marca.json"];
|
|
@@ -113,7 +113,7 @@ function acharConfig(de = process.cwd()) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// ../core/src/contexto.
|
|
116
|
+
// ../core/src/contexto.ts
|
|
117
117
|
function criarContexto({ config, raiz = process.cwd(), log = console.log, fetchFn = globalThis.fetch, segredos: segredos2 = segredos, agora = () => /* @__PURE__ */ new Date() } = {}) {
|
|
118
118
|
const h = () => configHospedagem(config);
|
|
119
119
|
return {
|
|
@@ -129,7 +129,7 @@ function criarContexto({ config, raiz = process.cwd(), log = console.log, fetchF
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
// ../core/src/index.
|
|
132
|
+
// ../core/src/index.ts
|
|
133
133
|
var PADRAO = {
|
|
134
134
|
utm: { instagram: ["instagram", "bio"], youtube: ["youtube", "short"], tiktok: ["tiktok", "bio"] },
|
|
135
135
|
limites: { youtubeTitulo: 100, youtubeDescricao: 5e3, instagram: 2200, tiktok: 2200, hashtagsInstagram: 30 },
|
|
@@ -191,7 +191,7 @@ ${tags(ep.hashtags?.tiktok)}`;
|
|
|
191
191
|
return { erros, avisos };
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
// ../core/src/buffer-api.
|
|
194
|
+
// ../core/src/buffer-api.ts
|
|
195
195
|
var ENDPOINT = "https://api.buffer.com";
|
|
196
196
|
var ANTECEDENCIA_MIN_MS = 5 * 6e4;
|
|
197
197
|
async function gql(consulta, variaveis, { chave, fetchFn = fetch } = {}) {
|
|
@@ -232,7 +232,7 @@ async function apagarPost(id, op) {
|
|
|
232
232
|
return true;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
// ../core/src/buffer.
|
|
235
|
+
// ../core/src/buffer.ts
|
|
236
236
|
var tags2 = (l) => (l || []).join(" ");
|
|
237
237
|
function parseQuando(txt, { fuso = "-03:00", agora = /* @__PURE__ */ new Date() } = {}) {
|
|
238
238
|
const s = String(txt).trim();
|
|
@@ -307,7 +307,7 @@ function escolherCanais({ config, canais, redes }) {
|
|
|
307
307
|
return { canais: saida, faltam };
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
// ../core/src/adaptadores/buffer.
|
|
310
|
+
// ../core/src/adaptadores/buffer.ts
|
|
311
311
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
312
312
|
var REF_CHAVE = "buffer/principal";
|
|
313
313
|
function criar({ config, ctx }) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
|
|
3
|
-
// ../core/src/contrato.
|
|
3
|
+
// ../core/src/contrato.ts
|
|
4
4
|
var REDES = { yt: "youtube", youtube: "youtube", ig: "instagram", instagram: "instagram", tt: "tiktok", tiktok: "tiktok" };
|
|
5
5
|
var REDES_VALIDAS = ["youtube", "instagram", "tiktok"];
|
|
6
6
|
var MODOS = { youtube: ["privado", "agendado", "publico"], tiktok: ["rascunho", "direto"], instagram: ["agora"] };
|