@praeko/cli 0.0.0-stage → 0.1.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 +9 -0
- package/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/arauto.mjs +11 -0
- package/dist/chunks/adaptadores-X3NUBMLT.mjs +18 -0
- package/dist/chunks/buffer-5TUDOBVN.mjs +14 -0
- package/dist/chunks/chunk-6YARHJZ2.mjs +56 -0
- package/dist/chunks/chunk-IZEX746E.mjs +238 -0
- package/dist/chunks/chunk-LCFK6X55.mjs +16 -0
- package/dist/chunks/chunk-LLXYUCTT.mjs +184 -0
- package/dist/chunks/chunk-QDZLRBTP.mjs +1125 -0
- package/dist/chunks/chunk-SAHLBARV.mjs +415 -0
- package/dist/chunks/chunk-UK3UKRPR.mjs +363 -0
- package/dist/chunks/chunk-XQO6MI7C.mjs +851 -0
- package/dist/chunks/chunk-Z325UM27.mjs +34 -0
- package/dist/chunks/instagram-GNY73ESY.mjs +293 -0
- package/dist/chunks/instagram-meta-COY6BTNX.mjs +412 -0
- package/dist/chunks/local-EUJHQ76T.mjs +14 -0
- package/dist/chunks/registro-FL2CVMYM.mjs +12 -0
- package/dist/chunks/remoto-OAB5Y2MS.mjs +13 -0
- package/dist/chunks/tiktok-QOUHOWDU.mjs +311 -0
- package/dist/chunks/tiktok-api-36DV2H43.mjs +309 -0
- package/dist/chunks/youtube-RZ4XTUZW.mjs +199 -0
- package/dist/chunks/youtube-analytics-3KIW2ZIW.mjs +109 -0
- package/dist/chunks/youtube-api-Z2RP5D2V.mjs +94 -0
- package/dist/chunks/youtube-comunidade-OAK5GGK4.mjs +25 -0
- package/dist/praeko.mjs +11 -0
- package/package.json +48 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../core/src/contrato.mjs
|
|
4
|
+
var REDES = { yt: "youtube", youtube: "youtube", ig: "instagram", instagram: "instagram", tt: "tiktok", tiktok: "tiktok" };
|
|
5
|
+
var REDES_VALIDAS = ["youtube", "instagram", "tiktok"];
|
|
6
|
+
var MODOS = { youtube: ["privado", "agendado", "publico"], tiktok: ["rascunho", "direto"], instagram: ["agora"] };
|
|
7
|
+
var PROVEDORES = ["buffer", "local", "remoto"];
|
|
8
|
+
function validarPedido(p) {
|
|
9
|
+
const e = [];
|
|
10
|
+
if (!p || typeof p !== "object") return ["pedido vazio"];
|
|
11
|
+
if (!p.midia?.video) e.push("midia.video \xE9 obrigat\xF3rio");
|
|
12
|
+
if (!p.titulo && !p.textos?.youtube?.titulo) e.push("falta o t\xEDtulo");
|
|
13
|
+
if (!Array.isArray(p.destinos) || !p.destinos.length) e.push("destinos vazio");
|
|
14
|
+
for (const d of p.destinos || []) {
|
|
15
|
+
if (!REDES_VALIDAS.includes(d.rede)) {
|
|
16
|
+
e.push(`rede desconhecida: ${d.rede}`);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!p.textos?.[d.rede]?.texto) e.push(`falta textos.${d.rede}.texto`);
|
|
20
|
+
if (d.quando != null && Number.isNaN(Date.parse(d.quando))) e.push(`${d.rede}: quando inv\xE1lido (${d.quando})`);
|
|
21
|
+
if (d.midia != null && (typeof d.midia !== "object" || Array.isArray(d.midia) || ["video", "capa"].some((k) => d.midia[k] != null && typeof d.midia[k] !== "string"))) e.push(`${d.rede}: midia do destino precisa ser { video?: <id>, capa?: <id> }`);
|
|
22
|
+
if (d.modo && !MODOS[d.rede].includes(d.modo)) e.push(`${d.rede}: modo "${d.modo}" inv\xE1lido (use ${MODOS[d.rede].join(", ")})`);
|
|
23
|
+
}
|
|
24
|
+
const todos = JSON.stringify(p.textos || {});
|
|
25
|
+
const marc = [...new Set(todos.match(/\[[A-Z][A-Z_]{3,}\]/g) || [])];
|
|
26
|
+
if (marc.length) e.push(`texto com marcador n\xE3o preenchido: ${marc.join(", ")}`);
|
|
27
|
+
return e;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
REDES,
|
|
32
|
+
PROVEDORES,
|
|
33
|
+
validarPedido
|
|
34
|
+
};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import "./chunk-LCFK6X55.mjs";
|
|
3
|
+
|
|
4
|
+
// ../core/src/provedores/instagram.mjs
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync, statSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { basename, extname, join } from "node:path";
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
10
|
+
var nome = "instagram";
|
|
11
|
+
var rede = "instagram";
|
|
12
|
+
var antecedenciaMs = 15 * 6e4;
|
|
13
|
+
var HOST = "https://graph.instagram.com";
|
|
14
|
+
var REF = "instagram/token";
|
|
15
|
+
var DIA = 864e5;
|
|
16
|
+
var LIMITES = { legenda: 2200, hashtags: 30, duracaoMin: 3, duracaoMax: 900, bytes: 300 * 1048576, fpsMin: 23, fpsMax: 60 };
|
|
17
|
+
var VERSAO_PADRAO = "v23.0";
|
|
18
|
+
var versao = (ctx) => ctx.config?.instagram?.versao || VERSAO_PADRAO;
|
|
19
|
+
var agora = (ctx) => ctx.agora ? ctx.agora() : /* @__PURE__ */ new Date();
|
|
20
|
+
var pausa = (ctx, ms) => ctx.pausa ? ctx.pausa(ms) : new Promise((r) => setTimeout(r, ms));
|
|
21
|
+
async function api(ctx, metodo, caminho, params = {}, { semVersao = false, host } = {}) {
|
|
22
|
+
const base = host || lerToken(ctx)?.host || HOST;
|
|
23
|
+
const url = new URL(`${base}/${semVersao ? "" : versao(ctx) + "/"}${caminho}`);
|
|
24
|
+
let init = { method: metodo };
|
|
25
|
+
if (metodo === "GET") for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
|
|
26
|
+
else {
|
|
27
|
+
init.headers = { "content-type": "application/x-www-form-urlencoded" };
|
|
28
|
+
init.body = new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)])).toString();
|
|
29
|
+
}
|
|
30
|
+
const r = await (ctx.fetchFn || fetch)(url.toString(), init);
|
|
31
|
+
const txt = await r.text();
|
|
32
|
+
let j;
|
|
33
|
+
try {
|
|
34
|
+
j = JSON.parse(txt);
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(`Instagram respondeu ${r.status}: ${txt.slice(0, 200)}`);
|
|
37
|
+
}
|
|
38
|
+
if (j.error) throw new Error(`Instagram: ${j.error.message}${j.error.code ? ` (c\xF3digo ${j.error.code}${j.error.error_subcode ? "/" + j.error.error_subcode : ""})` : ""}`);
|
|
39
|
+
if (!r.ok) throw new Error(`Instagram respondeu ${r.status}: ${txt.slice(0, 200)}`);
|
|
40
|
+
return j;
|
|
41
|
+
}
|
|
42
|
+
function lerToken(ctx) {
|
|
43
|
+
const bruto = ctx.segredos.obter(REF);
|
|
44
|
+
if (!bruto) return null;
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(bruto);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var guardarToken = (ctx, t) => ctx.segredos.guardar(REF, JSON.stringify(t));
|
|
52
|
+
var diasRestantes = (t, d = /* @__PURE__ */ new Date()) => t.expira ? (Date.parse(t.expira) - +d) / DIA : Infinity;
|
|
53
|
+
async function tokenValido(ctx) {
|
|
54
|
+
const t = lerToken(ctx);
|
|
55
|
+
if (!t?.token) throw new Error("Instagram n\xE3o conectado: praeko conta instagram conectar --token \u2026");
|
|
56
|
+
const dias = diasRestantes(t, agora(ctx));
|
|
57
|
+
if (dias <= 0) throw new Error("o token do Instagram expirou: gere outro no painel da Meta e rode praeko conta instagram conectar --token \u2026");
|
|
58
|
+
if (t.expira && dias < 15) {
|
|
59
|
+
try {
|
|
60
|
+
const r = await api(ctx, "GET", "refresh_access_token", { grant_type: "ig_refresh_token", access_token: t.token }, { semVersao: true });
|
|
61
|
+
const novo = { ...t, token: r.access_token, expira: new Date(+agora(ctx) + r.expires_in * 1e3).toISOString() };
|
|
62
|
+
guardarToken(ctx, novo);
|
|
63
|
+
ctx.log?.(`\u2713 token do Instagram renovado (vale mais ${Math.round(r.expires_in / 86400)} dias)`);
|
|
64
|
+
return novo;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
ctx.log?.(`\u26A0 n\xE3o consegui renovar o token do Instagram (${e.message}); faltam ${Math.floor(dias)} dias`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return t;
|
|
70
|
+
}
|
|
71
|
+
async function lerEntrada(args, ctx) {
|
|
72
|
+
const i = args.indexOf("--token");
|
|
73
|
+
if (i >= 0 && args[i + 1]) return args[i + 1].trim();
|
|
74
|
+
if (!process.stdin.isTTY) {
|
|
75
|
+
const ch = [];
|
|
76
|
+
for await (const c of process.stdin) ch.push(c);
|
|
77
|
+
const s = Buffer.concat(ch).toString().trim();
|
|
78
|
+
if (s) return s;
|
|
79
|
+
}
|
|
80
|
+
if (process.stdin.isTTY) {
|
|
81
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
82
|
+
const s = await new Promise((ok) => rl.question("Cole o token de acesso do Instagram (painel da Meta \u2192 Instagram \u2192 API setup \u2192 Generate token): ", ok));
|
|
83
|
+
rl.close();
|
|
84
|
+
return s.trim();
|
|
85
|
+
}
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
async function conectar({ ctx, args = [] }) {
|
|
89
|
+
const token = await lerEntrada(args, ctx);
|
|
90
|
+
if (!token) throw new Error("sem token: praeko conta instagram conectar --token <token> (ou por stdin)");
|
|
91
|
+
const j = args.indexOf("--app-secret"), segredoApp = j >= 0 ? args[j + 1] : null;
|
|
92
|
+
const flag = (k) => {
|
|
93
|
+
const i = args.indexOf(k);
|
|
94
|
+
return i >= 0 ? args[i + 1] : void 0;
|
|
95
|
+
};
|
|
96
|
+
const userIdArg = flag("--user-id"), hostArg = flag("--host");
|
|
97
|
+
let eu, host = null;
|
|
98
|
+
if (hostArg || !userIdArg) {
|
|
99
|
+
try {
|
|
100
|
+
eu = await api(ctx, "GET", "me", { fields: "user_id,username,account_type", access_token: token }, { host: hostArg ? `https://${hostArg.replace(/^https?:\/\//, "")}` : HOST });
|
|
101
|
+
host = hostArg ? `https://${hostArg.replace(/^https?:\/\//, "")}` : null;
|
|
102
|
+
} catch (e) {
|
|
103
|
+
if (!userIdArg) throw e;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!eu) {
|
|
107
|
+
if (!userIdArg) throw new Error("para token de usu\xE1rio de sistema informe --user-id <id da conta do Instagram>");
|
|
108
|
+
host = "https://graph.facebook.com";
|
|
109
|
+
const r = await api(ctx, "GET", userIdArg, { fields: "id,username", access_token: token }, { host });
|
|
110
|
+
eu = { user_id: r.id, id: r.id, username: r.username, account_type: "BUSINESS" };
|
|
111
|
+
}
|
|
112
|
+
let final = token, expiraEm = 60 * 86400;
|
|
113
|
+
if (segredoApp) {
|
|
114
|
+
try {
|
|
115
|
+
const r = await api(ctx, "GET", "access_token", { grant_type: "ig_exchange_token", client_secret: segredoApp, access_token: token }, { semVersao: true });
|
|
116
|
+
final = r.access_token;
|
|
117
|
+
expiraEm = r.expires_in;
|
|
118
|
+
ctx.log?.("\u2713 token trocado por um de longa dura\xE7\xE3o");
|
|
119
|
+
} catch (e) {
|
|
120
|
+
ctx.log?.(`\u26A0 n\xE3o troquei o token (${e.message}); mantive o original`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const salvo = { token: final, userId: String(eu.user_id || eu.id), username: eu.username, tipo: eu.account_type, ...host && /facebook/.test(host) ? { host, expira: null } : { expira: new Date(+agora(ctx) + expiraEm * 1e3).toISOString() }, conectadoEm: agora(ctx).toISOString() };
|
|
124
|
+
guardarToken(ctx, salvo);
|
|
125
|
+
ctx.log?.(`\u2713 Instagram conectado: @${salvo.username} (${salvo.tipo || "tipo desconhecido"}); ${salvo.expira ? "token vale at\xE9 " + salvo.expira.slice(0, 10) : "token de usu\xE1rio de sistema, sem expira\xE7\xE3o"}`);
|
|
126
|
+
if (salvo.tipo && !/BUSINESS|CREATOR|MEDIA_CREATOR/i.test(salvo.tipo)) ctx.log?.("\u26A0 a conta n\xE3o parece profissional (Business/Creator): a API s\xF3 publica em conta profissional");
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
async function status({ ctx }) {
|
|
130
|
+
const t = lerToken(ctx);
|
|
131
|
+
if (!t?.token) return { conectado: false, detalhe: "rode: praeko conta instagram conectar --token \u2026" };
|
|
132
|
+
const dias = diasRestantes(t, agora(ctx));
|
|
133
|
+
return { conectado: dias > 0, detalhe: `@${t.username}, ${t.tipo || "tipo ?"}, ${!t.expira ? "token sem expira\xE7\xE3o (usu\xE1rio de sistema)" : dias > 0 ? `token vale mais ${Math.floor(dias)} dias${dias < 15 ? " (renova sozinho)" : ""}` : "token EXPIRADO"}` };
|
|
134
|
+
}
|
|
135
|
+
function sondar(arq, ctx) {
|
|
136
|
+
if (ctx.sondar) return ctx.sondar(arq);
|
|
137
|
+
const r = spawnSync("ffprobe", ["-v", "error", "-select_streams", "v:0", "-show_entries", "stream=codec_name,width,height,r_frame_rate", "-show_entries", "format=duration", "-of", "json", arq], { encoding: "utf8" });
|
|
138
|
+
if (r.status !== 0) return null;
|
|
139
|
+
const j = JSON.parse(r.stdout), s = j.streams[0], [a, b] = String(s.r_frame_rate).split("/").map(Number);
|
|
140
|
+
return { codec: s.codec_name, largura: s.width, altura: s.height, fps: b ? a / b : a, duracao: +j.format.duration };
|
|
141
|
+
}
|
|
142
|
+
function avisosVideo(arq, ctx = {}) {
|
|
143
|
+
const av = [];
|
|
144
|
+
try {
|
|
145
|
+
try {
|
|
146
|
+
const bytes = statSync(arq).size;
|
|
147
|
+
if (bytes > LIMITES.bytes) av.push(`v\xEDdeo com ${(bytes / 1048576).toFixed(0)} MB (limite do Reel: 300 MB)`);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
const s = sondar(arq, ctx);
|
|
151
|
+
if (!s) return av;
|
|
152
|
+
if (s.duracao < LIMITES.duracaoMin || s.duracao > LIMITES.duracaoMax) av.push(`dura\xE7\xE3o ${s.duracao.toFixed(1)} s fora de 3 s a 15 min`);
|
|
153
|
+
if (s.fps < LIMITES.fpsMin || s.fps > LIMITES.fpsMax) av.push(`${s.fps.toFixed(1)} fps fora de 23 a 60`);
|
|
154
|
+
if (!/^(h264|hevc)$/.test(s.codec)) av.push(`codec ${s.codec} (use H.264 ou HEVC)`);
|
|
155
|
+
const prop = s.largura / s.altura;
|
|
156
|
+
if (prop < 0.5 || prop > 0.6) av.push(`propor\xE7\xE3o ${s.largura}x${s.altura}: 9:16 evita cortes e faixas`);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
av.push(`n\xE3o consegui conferir o v\xEDdeo (${e.message})`);
|
|
159
|
+
}
|
|
160
|
+
return av;
|
|
161
|
+
}
|
|
162
|
+
function avisosTexto(texto) {
|
|
163
|
+
const av = [];
|
|
164
|
+
if (texto.length > LIMITES.legenda) av.push(`legenda com ${texto.length} caracteres (limite ${LIMITES.legenda})`);
|
|
165
|
+
const n = (texto.match(/#[\p{L}\p{N}_]+/gu) || []).length;
|
|
166
|
+
if (n > LIMITES.hashtags) av.push(`${n} hashtags (limite ${LIMITES.hashtags})`);
|
|
167
|
+
return av;
|
|
168
|
+
}
|
|
169
|
+
async function planejar({ item, ctx }) {
|
|
170
|
+
const l = [`Reel na hora marcada; preparo ${antecedenciaMs / 6e4} min antes (v\xEDdeo e capa v\xE3o para a hospedagem p\xFAblica)`];
|
|
171
|
+
l.push(item.capa ? `capa: ${basename(item.capa)} (vira cover_url; PNG \xE9 convertido para JPEG)` : "sem capa: miniatura = quadro 0 do v\xEDdeo");
|
|
172
|
+
const t = lerToken(ctx);
|
|
173
|
+
l.push(t ? `conta @${t.username}; token vale mais ${Math.floor(diasRestantes(t, agora(ctx)))} dias` : "\u26A0 conta n\xE3o conectada (praeko conta instagram conectar)");
|
|
174
|
+
for (const a of [...avisosTexto(item.textos?.texto || ""), ...item.video ? avisosVideo(item.video, ctx) : []]) l.push(`\u26A0 ${a}`);
|
|
175
|
+
return l;
|
|
176
|
+
}
|
|
177
|
+
function capaJpeg(capa, ctx) {
|
|
178
|
+
if (/\.jpe?g$/i.test(capa)) return capa;
|
|
179
|
+
if (ctx.converterCapa) return ctx.converterCapa(capa);
|
|
180
|
+
const saida = join(tmpdir(), `praeko-capa-${basename(capa, extname(capa))}-${Date.now()}.jpg`);
|
|
181
|
+
const r = spawnSync("ffmpeg", ["-v", "error", "-y", "-i", capa, "-q:v", "2", saida], { stdio: "ignore" });
|
|
182
|
+
return r.status === 0 && existsSync(saida) ? saida : capa;
|
|
183
|
+
}
|
|
184
|
+
async function limpar(ctx, prep) {
|
|
185
|
+
for (const chave of prep?.chavesHospedadas || []) {
|
|
186
|
+
try {
|
|
187
|
+
await ctx.removerHospedado(chave);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
ctx.log?.(`\u26A0 n\xE3o removi ${chave} da hospedagem (${e.message})`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function preparar({ item, ctx }) {
|
|
194
|
+
if (item.prep?.containerId && !item.prep.publicado && +agora(ctx) - Date.parse(item.prep.criadoEm) < 23 * 36e5) return item.prep;
|
|
195
|
+
if (item.prep?.containerId && !item.prep.publicado) {
|
|
196
|
+
await limpar(ctx, item.prep);
|
|
197
|
+
item.prep = null;
|
|
198
|
+
}
|
|
199
|
+
const t = await tokenValido(ctx), texto = item.textos?.texto || "";
|
|
200
|
+
const erros = avisosTexto(texto).filter((x) => /caracteres|hashtags/.test(x));
|
|
201
|
+
if (erros.length) throw new Error(erros.join("; "));
|
|
202
|
+
const chaves = [];
|
|
203
|
+
try {
|
|
204
|
+
const v = await ctx.hospedar(item.video, item.slug);
|
|
205
|
+
chaves.push(v.chave);
|
|
206
|
+
const params = { media_type: "REELS", video_url: v.url, caption: texto, share_to_feed: "true", access_token: t.token };
|
|
207
|
+
if (item.capa) {
|
|
208
|
+
const c = await ctx.hospedar(capaJpeg(item.capa, ctx), item.slug);
|
|
209
|
+
chaves.push(c.chave);
|
|
210
|
+
params.cover_url = c.url;
|
|
211
|
+
} else if (item.opcoes?.miniaturaMs != null) params.thumb_offset = item.opcoes.miniaturaMs;
|
|
212
|
+
const r = await api(ctx, "POST", `${t.userId}/media`, params);
|
|
213
|
+
item.prep = { containerId: r.id, criadoEm: agora(ctx).toISOString(), chavesHospedadas: chaves, videoUrl: v.url, capaUrl: params.cover_url || null };
|
|
214
|
+
ctx.log?.(`\u2713 Instagram: cont\xEAiner ${r.id} criado`);
|
|
215
|
+
return item.prep;
|
|
216
|
+
} catch (e) {
|
|
217
|
+
await limpar(ctx, { chavesHospedadas: chaves });
|
|
218
|
+
throw e;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function esperarContainer({ ctx, token, containerId, intervaloMs, maxMs }) {
|
|
222
|
+
const tentativas = Math.max(1, Math.ceil(maxMs / intervaloMs));
|
|
223
|
+
for (let i = 0; i < tentativas; i++) {
|
|
224
|
+
const r = await api(ctx, "GET", containerId, { fields: "status_code,status", access_token: token });
|
|
225
|
+
if (r.status_code === "FINISHED" || r.status_code === "PUBLISHED") return r.status_code;
|
|
226
|
+
if (r.status_code === "ERROR" || r.status_code === "EXPIRED") {
|
|
227
|
+
const e = new Error(`Instagram: cont\xEAiner ${r.status_code}${r.status ? ` (${r.status})` : ""}`);
|
|
228
|
+
e.codigo = r.status_code;
|
|
229
|
+
throw e;
|
|
230
|
+
}
|
|
231
|
+
if (i < tentativas - 1) await pausa(ctx, intervaloMs);
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`Instagram: o v\xEDdeo ainda est\xE1 processando depois de ${Math.round(maxMs / 6e4)} min; a pr\xF3xima tentativa continua`);
|
|
234
|
+
}
|
|
235
|
+
async function publicar({ item, ctx }) {
|
|
236
|
+
if (item.prep?.publicado) return item.prep.resultado;
|
|
237
|
+
const prep = await preparar({ item, ctx });
|
|
238
|
+
const t = await tokenValido(ctx), op = item.opcoes || {};
|
|
239
|
+
try {
|
|
240
|
+
await esperarContainer({ ctx, token: t.token, containerId: prep.containerId, intervaloMs: op.intervaloMs ?? 3e4, maxMs: op.esperaMaxMs ?? 10 * 6e4 });
|
|
241
|
+
} catch (e) {
|
|
242
|
+
if (e.codigo) {
|
|
243
|
+
await limpar(ctx, prep);
|
|
244
|
+
item.prep = null;
|
|
245
|
+
}
|
|
246
|
+
throw e;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const q = await api(ctx, "GET", `${t.userId}/content_publishing_limit`, { fields: "quota_usage,config", access_token: t.token });
|
|
250
|
+
const uso = q.data?.[0]?.quota_usage, total = q.data?.[0]?.config?.quota_total;
|
|
251
|
+
if (uso != null && total != null && uso >= total) throw Object.assign(new Error(`cota de publica\xE7\xE3o do Instagram esgotada (${uso}/${total} em 24 h)`), { cota: true });
|
|
252
|
+
} catch (e) {
|
|
253
|
+
if (e.cota) throw e;
|
|
254
|
+
}
|
|
255
|
+
const pub = await api(ctx, "POST", `${t.userId}/media_publish`, { creation_id: prep.containerId, access_token: t.token });
|
|
256
|
+
const resultado = { id: pub.id, containerId: prep.containerId };
|
|
257
|
+
prep.publicado = pub.id;
|
|
258
|
+
prep.resultado = resultado;
|
|
259
|
+
try {
|
|
260
|
+
const m = await api(ctx, "GET", pub.id, { fields: "permalink", access_token: t.token });
|
|
261
|
+
resultado.url = m.permalink;
|
|
262
|
+
} catch (e) {
|
|
263
|
+
ctx.log?.(`\u26A0 publicado, mas sem o link (${e.message})`);
|
|
264
|
+
}
|
|
265
|
+
if (op.comentar !== false && item.textos?.comentarioFixo) {
|
|
266
|
+
try {
|
|
267
|
+
await api(ctx, "POST", `${pub.id}/comments`, { message: item.textos.comentarioFixo, access_token: t.token });
|
|
268
|
+
resultado.comentario = true;
|
|
269
|
+
} catch (e) {
|
|
270
|
+
ctx.log?.(`\u26A0 publicado, mas o coment\xE1rio falhou (${e.message}); precisa do escopo instagram_business_manage_comments no token (o post saiu normal); comente pelo app`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
await limpar(ctx, prep);
|
|
274
|
+
ctx.log?.(`\u2713 Instagram publicado${resultado.url ? ": " + resultado.url : ""}`);
|
|
275
|
+
return resultado;
|
|
276
|
+
}
|
|
277
|
+
export {
|
|
278
|
+
LIMITES,
|
|
279
|
+
antecedenciaMs,
|
|
280
|
+
avisosTexto,
|
|
281
|
+
avisosVideo,
|
|
282
|
+
conectar,
|
|
283
|
+
diasRestantes,
|
|
284
|
+
esperarContainer,
|
|
285
|
+
lerToken,
|
|
286
|
+
nome,
|
|
287
|
+
planejar,
|
|
288
|
+
preparar,
|
|
289
|
+
publicar,
|
|
290
|
+
rede,
|
|
291
|
+
status,
|
|
292
|
+
tokenValido
|
|
293
|
+
};
|