Transcriptor / analyze.js
CreatorKit
Initial commit: CreatorKit — plataforma para criadores
8661b79
Raw
History Blame Contribute Delete
23.4 kB
/**
* analyze.js — content generation engine for CreatorKit
* All processing is local — no external API needed.
*/
// ── Stop words (PT + EN) ──────────────────────────────────────
const STOP_WORDS = new Set([
// PT
'a','ao','aos','aquela','aquelas','aquele','aqueles','aquilo','as','até','com',
'como','da','das','de','dela','delas','dele','deles','depois','do','dos','e',
'ela','elas','ele','eles','em','entre','era','eram','essa','essas','esse',
'esses','esta','estas','este','estes','eu','foi','for','foram','há','isso',
'isto','já','lhe','lhes','mais','mas','me','mesmo','meu','minha','muito',
'na','nas','nem','no','nos','nós','não','num','numa','o','os','ou','para',
'pela','pelas','pelo','pelos','por','que','qual','quando','quem','são','se',
'sem','ser','seu','seus','si','sua','suas','também','te','tem','ter','teu',
'tua','tuas','tudo','um','uma','uns','umas','vai','vem','você','vocês','à',
'às','é','isso','assim','então','agora','aqui','ali','lá','ainda','logo',
'porque','então','pois','portanto','todavia','porém','entanto','contudo',
'apenas','só','todo','toda','todos','todas','cada','outro','outra','outros',
'outras','tal','tais','tanto','tantos','tanta','tantas','bem','mal','muito',
'pouco','sempre','nunca','jamais','hoje','ontem','amanhã','quando','onde',
// EN
'a','an','the','and','or','but','in','on','at','to','for','of','with',
'as','is','was','are','were','be','been','being','have','has','had',
'do','does','did','will','would','could','should','may','might','shall',
'can','this','that','these','those','i','we','you','he','she','they','it',
'my','our','your','his','her','their','its','me','us','him','them','who',
'what','which','how','when','where','why','all','any','both','each',
'few','more','most','other','some','such','no','not','only','same','so',
'than','too','very','just','about','above','after','again','also','before',
'between','here','into','like','now','out','over','own','then','there',
'through','under','until','up','while','from','if',
]);
// ── Tokenize & extract keywords ───────────────────────────────
function tokenize(text) {
return text
.toLowerCase()
.replace(/[^a-záàãâäéèêëíìîïóòõôöúùûüçñ\s]/gi, ' ')
.split(/\s+/)
.filter(w => w.length > 3 && !STOP_WORDS.has(w));
}
function extractKeywords(text, topN = 20) {
const words = tokenize(text);
const freq = {};
for (const w of words) freq[w] = (freq[w] || 0) + 1;
return Object.entries(freq)
.sort((a, b) => b[1] - a[1])
.slice(0, topN)
.map(([word, count]) => ({ word, count }));
}
// ── Extractive summary ────────────────────────────────────────
function summarize(text, numSentences = 4) {
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
if (sentences.length <= numSentences) return text.trim();
const keywords = new Set(extractKeywords(text, 15).map(k => k.word));
const scored = sentences.map(s => {
const words = tokenize(s);
const score = words.filter(w => keywords.has(w)).length / (words.length || 1);
return { s: s.trim(), score };
});
return scored
.sort((a, b) => b.score - a.score)
.slice(0, numSentences)
.sort((a, b) => sentences.indexOf(a.s) - sentences.indexOf(b.s))
.map(x => x.s)
.join(' ');
}
// ── Hashtag generator ─────────────────────────────────────────
function generateHashtags(text, keywords) {
const tags = keywords
.slice(0, 12)
.map(k => '#' + k.word.replace(/\s+/g, '').normalize('NFD').replace(/[̀-ͯ]/g, ''));
// Detect topics and add contextual tags
const lower = text.toLowerCase();
const contextual = [];
if (/negócio|empresa|empreend|startup|produto|vend/.test(lower)) contextual.push('#empreendedorismo','#negócios','#business');
if (/saúde|treino|exercício|academia|fitness|dieta/.test(lower)) contextual.push('#saúde','#fitness','#bemestar');
if (/tecnologia|tech|software|programação|código|ia|inteligência/.test(lower)) contextual.push('#tecnologia','#tech','#inovação');
if (/motivação|sucesso|crescimento|mindset|foco/.test(lower)) contextual.push('#motivação','#sucesso','#mindset');
if (/receita|cozinha|comida|culinária|alimento/.test(lower)) contextual.push('#gastronomia','#culinária','#foodlover');
if (/moda|roupa|estilo|fashion|look/.test(lower)) contextual.push('#moda','#fashion','#estilo');
if (/viagem|destino|turismo|lugar|cidade/.test(lower)) contextual.push('#viagem','#turismo','#travel');
if (/financ|invest|dinheiro|renda|rico/.test(lower)) contextual.push('#finanças','#investimento','#dinheiro');
if (/relacionamento|amor|família|casal|vida/.test(lower)) contextual.push('#relacionamentos','#vidapessoal');
const all = [...new Set([...tags, ...contextual, '#conteúdo', '#influencer', '#viral'])];
return all.slice(0, 25);
}
// ── Instagram Caption ─────────────────────────────────────────
function generateInstagramCaption(text, keywords, hashtags, lang) {
const summary = summarize(text, 2);
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
const hook = sentences[0]?.trim() || summary.split('.')[0];
const kws = keywords.slice(0, 3).map(k => k.word);
const ctas = [
'💬 Me conta o que você achou nos comentários!',
'👇 Qual foi o ponto que mais te chamou atenção?',
'🔁 Salva esse conteúdo para não perder!',
'📲 Manda pra um amigo que precisa ver isso!',
'✨ Segue para mais conteúdo como esse!',
];
const cta = ctas[Math.floor(Math.random() * ctas.length)];
const topHashtags = hashtags.slice(0, 15).join(' ');
return `${hook}
✨ Neste conteúdo você vai ver:
${kws.map(k => `▸ ${k.charAt(0).toUpperCase() + k.slice(1)}`).join('\n')}
${summary}
${cta}
.
.
.
${topHashtags}`;
}
// ── YouTube Kit ───────────────────────────────────────────────
function generateYouTubeKit(text, keywords, segments) {
const kws = keywords.slice(0, 5).map(k => k.word);
// Title variations
const firstSentence = (text.match(/[^.!?]+[.!?]+/)?.[0] || '').trim().slice(0, 60);
const titles = [
`${firstSentence || kws.slice(0,2).join(' e ')} | Conteúdo Completo`,
`TUDO sobre ${kws[0] || 'esse tema'} que você precisa saber`,
`${kws.slice(0,2).join(', ')} e muito mais — Veja agora!`,
];
// Description
const summary = summarize(text, 3);
const tags = keywords.slice(0, 15).map(k => k.word).join(', ');
// Timestamps from segments
let timestampBlock = '';
if (segments && segments.length > 0) {
const interval = Math.max(1, Math.floor(segments.length / 8));
const picks = segments.filter((_, i) => i % interval === 0).slice(0, 8);
timestampBlock = '\n\n⏱️ CAPÍTULOS:\n' + picks.map(s => `${fmtTime(s.start)} ${s.text.slice(0, 50)}`).join('\n');
}
const description = `📌 ${summary}
${timestampBlock}
🔔 Inscreva-se no canal e ative o sininho para não perder nenhum vídeo!
👍 Se o conteúdo foi útil, deixa o like — ajuda muito!
💬 Tem alguma dúvida? Comenta aqui embaixo!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔗 Minhas redes sociais:
▸ Instagram: @seuinstagram
▸ TikTok: @seutiktok
▸ Twitter: @seutwitter
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#${kws.join(' #')}`;
return { titles, description, tags };
}
// ── Twitter/X Thread ──────────────────────────────────────────
function generateTwitterThread(text, keywords) {
const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()).filter(Boolean);
const summary = summarize(text, 6);
const sumSentences = (summary.match(/[^.!?]+[.!?]+/g) || [summary]).map(s => s.trim()).filter(Boolean);
const tweets = [];
const kws = keywords.slice(0, 3).map(k => k.word);
// Tweet 1: hook
tweets.push(`🧵 ${sentences[0] || kws.join(', ')}\n\nThread completa 👇`);
// Middle tweets: pack sentences into 250 chars each
let current = '';
for (const s of sumSentences) {
if ((current + ' ' + s).length > 250) {
if (current) tweets.push(current.trim());
current = s;
} else {
current = current ? current + ' ' + s : s;
}
}
if (current) tweets.push(current.trim());
// Last tweet: CTA
tweets.push(`📌 Gostou desse conteúdo?\n\n↩️ RT para compartilhar\n❤️ Like se foi útil\n🔔 Siga para mais\n\n#${kws.slice(0,3).join(' #')}`);
return tweets.map((t, i) => `${i + 1}/${tweets.length}\n\n${t}`);
}
// ── LinkedIn Post ─────────────────────────────────────────────
function generateLinkedIn(text, keywords) {
const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim());
const kws = keywords.slice(0, 5);
const summary = summarize(text, 4);
return `💡 ${sentences[0] || 'Reflexão importante'}
${summary}
3 pontos principais sobre ${kws[0]?.word || 'esse tema'}:
${sentences[1]?.trim() || kws[0]?.word || ''}
${sentences[2]?.trim() || kws[1]?.word || ''}
${sentences[3]?.trim() || kws[2]?.word || ''}
O que você pensa sobre isso? Compartilhe nos comentários. 👇
━━━━━━━━━━━━━━━━━━━━━
${kws.slice(0, 5).map(k => `#${k.word}`).join(' ')} #linkedin #networking #conteúdo`;
}
// ── Blog Post ─────────────────────────────────────────────────
function generateBlogPost(text, keywords, title) {
const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()).filter(Boolean);
const kws = keywords.slice(0, 6).map(k => k.word);
const postTitle = title || `${kws[0]?.charAt(0).toUpperCase() + kws[0]?.slice(1) || 'Conteúdo'}: tudo o que você precisa saber`;
// Split into chunks of ~3 sentences per section
const sections = [];
let chunk = [];
for (const s of sentences) {
chunk.push(s);
if (chunk.length === 3) { sections.push(chunk.join(' ')); chunk = []; }
}
if (chunk.length) sections.push(chunk.join(' '));
const sectionTitles = [
'Introdução', 'Por que isso importa', 'Principais pontos',
'Como aplicar na prática', 'Dicas e insights', 'Conclusão',
];
let post = `# ${postTitle}\n\n`;
post += `*Tempo de leitura: ~${Math.ceil(text.split(/\s+/).length / 200)} min*\n\n`;
post += `---\n\n`;
sections.slice(0, 5).forEach((section, i) => {
post += `## ${sectionTitles[i] || `Parte ${i + 1}`}\n\n${section}\n\n`;
});
post += `## Conclusão\n\n${summarize(text, 2)}\n\n`;
post += `---\n\n**Tags:** ${kws.join(', ')}`;
return post;
}
// ── Main analyze function ─────────────────────────────────────
function analyze(text, segments = [], videoTitle = '') {
if (!text || text.trim().length < 10) throw new Error('Texto muito curto para análise.');
const keywords = extractKeywords(text, 20);
const hashtags = generateHashtags(text, keywords);
const summary = summarize(text, 4);
const instagram = generateInstagramCaption(text, keywords, hashtags);
const yt = generateYouTubeKit(text, keywords, segments);
const thread = generateTwitterThread(text, keywords);
const linkedin = generateLinkedIn(text, keywords);
const blog = generateBlogPost(text, keywords, videoTitle);
const wordCount = text.trim().split(/\s+/).length;
const readingTime = Math.ceil(wordCount / 200);
return {
summary,
keywords: keywords.slice(0, 12),
hashtags,
instagram,
youtube: yt,
twitter: thread,
linkedin,
blog,
stats: { wordCount, readingTime, sentenceCount: (text.match(/[.!?]+/g) || []).length },
};
}
function fmtTime(s) {
const m = Math.floor(s / 60), sec = Math.floor(s % 60).toString().padStart(2,'0');
return `${m}:${sec}`;
}
// ══════════════════════════════════════════════════════════════
// STANDALONE CREATOR TOOLS (topic-based, no video needed)
// ══════════════════════════════════════════════════════════════
function pick(arr, n) {
const copy = [...arr];
const out = [];
while (out.length < n && copy.length) {
out.push(copy.splice(Math.floor(Math.random() * copy.length), 1)[0]);
}
return out;
}
const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
// ── 🪝 HOOKS ──────────────────────────────────────────────────
function generateHooks(topic) {
if (!topic || !topic.trim()) throw new Error('Informe um tópico.');
const t = topic.trim().toLowerCase();
const T = cap(t);
const templates = [
`Pare de errar em ${t} — faça assim a partir de hoje.`,
`Ninguém te conta a verdade sobre ${t}...`,
`Eu perdi anos errando em ${t}. Não cometa o mesmo erro.`,
`3 verdades sobre ${t} que mudaram tudo pra mim.`,
`O segredo de ${t} que os experts não compartilham.`,
`Como eu dominei ${t} em apenas 30 dias.`,
`A maioria das pessoas erra em ${t}. Veja como acertar.`,
`${T} não é o que você imagina. Olha só isso.`,
`Faça ISSO antes de tentar qualquer coisa com ${t}.`,
`O maior mito sobre ${t} — desmascarado.`,
`Se você quer resultado com ${t}, assista até o final.`,
`Eu testei tudo sobre ${t} pra você não precisar.`,
`Esse erro em ${t} está te custando caro (e você nem percebe).`,
`${T} em 60 segundos: o guia que você precisava.`,
`Por que 90% das pessoas falham em ${t}?`,
`O que eu faria se tivesse que começar do zero em ${t}.`,
];
return { topic, hooks: pick(templates, 10) };
}
// ── 💡 CONTENT IDEAS ──────────────────────────────────────────
function generateIdeas(niche) {
if (!niche || !niche.trim()) throw new Error('Informe seu nicho.');
const n = niche.trim().toLowerCase();
const N = cap(n);
const ideas = [
{ fmt: 'Reels', icon: '🎬', title: `Top 5 erros que iniciantes cometem em ${n}` },
{ fmt: 'Carrossel', icon: '🖼️', title: `Mitos vs verdades sobre ${n}` },
{ fmt: 'Reels', icon: '🎬', title: `Um dia na vida de quem trabalha com ${n}` },
{ fmt: 'YouTube', icon: '▶️', title: `Tutorial passo a passo de ${n} para iniciantes` },
{ fmt: 'Carrossel', icon: '🖼️', title: `O que eu gostaria de saber antes de começar em ${n}` },
{ fmt: 'Story', icon: '📲', title: `Caixinha de perguntas: tudo sobre ${n}` },
{ fmt: 'Reels', icon: '🎬', title: `Antes e depois: minha evolução em ${n}` },
{ fmt: 'Carrossel', icon: '🖼️', title: `5 ferramentas essenciais para ${n}` },
{ fmt: 'YouTube', icon: '▶️', title: `Reagindo às maiores tendências de ${n}` },
{ fmt: 'Reels', icon: '🎬', title: `Desafio de 30 dias de ${n} — resultados reais` },
{ fmt: 'Carrossel', icon: '🖼️', title: `Guia rápido: ${n} do zero ao avançado` },
{ fmt: 'Reels', icon: '🎬', title: `3 hábitos que transformaram meu ${n}` },
{ fmt: 'Story', icon: '📲', title: `Bastidores: como eu produzo conteúdo de ${n}` },
{ fmt: 'YouTube', icon: '▶️', title: `${N}: vale a pena em 2026? Minha opinião sincera` },
{ fmt: 'Carrossel', icon: '🖼️', title: `Os 7 termos de ${n} que todo mundo deveria saber` },
];
return { niche, ideas: pick(ideas, 9) };
}
// ── ✍️ BIO GENERATOR ──────────────────────────────────────────
function generateBio({ name = '', niche = '', cta = '' }) {
if (!niche.trim()) throw new Error('Informe seu nicho.');
const n = niche.trim();
const link = cta.trim() || '👇 Link abaixo';
const bios = [
`✨ ${name || 'Criador(a)'} | ${cap(n)}\n📍 Conteúdo diário sobre ${n.toLowerCase()}\n💡 Dicas que realmente funcionam\n${link}`,
`${cap(n)} descomplicado 🚀\nAjudo você a evoluir em ${n.toLowerCase()}\n🎯 Novo conteúdo toda semana\n${link}`,
`${name ? name + ' • ' : ''}Apaixonado(a) por ${n.toLowerCase()} 💜\nTransformando conhecimento em resultado\n📲 Me segue pra não perder nada\n${link}`,
`Seu cantinho de ${n.toLowerCase()} 🌟\n✅ Conteúdo prático e direto\n✅ Sem enrolação\n${link}`,
];
return { bios };
}
// ── 📅 CONTENT CALENDAR ───────────────────────────────────────
function generateCalendar({ niche = '', perWeek = 5 }) {
if (!niche.trim()) throw new Error('Informe seu nicho.');
const n = niche.trim().toLowerCase();
const days = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo'];
const blocks = [
{ icon: '🎬', fmt: 'Reels', theme: `Dica rápida de ${n}`, best: '12h ou 19h' },
{ icon: '🖼️', fmt: 'Carrossel', theme: `Passo a passo sobre ${n}`, best: '18h' },
{ icon: '📲', fmt: 'Story', theme: `Bastidores + enquete de ${n}`, best: 'Ao longo do dia' },
{ icon: '🎬', fmt: 'Reels', theme: `Mito vs verdade de ${n}`, best: '20h' },
{ icon: '▶️', fmt: 'YouTube', theme: `Vídeo aprofundado de ${n}`, best: 'Sábado 10h' },
{ icon: '🖼️', fmt: 'Carrossel', theme: `Lista: top ferramentas de ${n}`, best: '17h' },
{ icon: '📲', fmt: 'Story', theme: `Caixinha de perguntas de ${n}`, best: 'Domingo' },
];
const count = Math.min(Math.max(parseInt(perWeek) || 5, 1), 7);
const plan = days.slice(0, count).map((d, i) => ({ day: d, ...blocks[i % blocks.length] }));
return { niche, plan };
}
// ── 📸 CAPTION FROM TOPIC ─────────────────────────────────────
function generateCaptionFromTopic(topic) {
if (!topic || !topic.trim()) throw new Error('Informe um tópico.');
const t = topic.trim();
const keywords = extractKeywords(t + ' ' + t, 10);
const hashtags = generateHashtags(t, keywords);
const ctas = [
'💬 Comenta aqui o que você achou!',
'🔁 Salva esse post para não esquecer!',
'📲 Marca um amigo que precisa ver isso!',
'✨ Segue pra mais conteúdo como esse!',
];
const caption = `${cap(t)} 🚀\n\nVocê sabia disso? Deixa eu te explicar de um jeito simples 👇\n\n▸ Ponto 1 sobre ${t.toLowerCase()}\n▸ Ponto 2 que faz a diferença\n▸ Ponto 3 que ninguém te conta\n\n${pick(ctas, 1)[0]}\n\n.\n.\n.\n${hashtags.slice(0, 15).join(' ')}`;
return { caption, hashtags };
}
// ── 📝 SCRIPT / ROTEIRO ───────────────────────────────────────
function generateScript({ topic = '', format = 'reels' }) {
if (!topic.trim()) throw new Error('Informe o tema do vídeo.');
const t = topic.trim().toLowerCase();
const T = cap(t);
const hooks = [
`Pare tudo: se você se interessa por ${t}, precisa ver isso.`,
`Ninguém te conta a verdade sobre ${t}...`,
`Eu errei MUITO em ${t} até descobrir isso.`,
`Esse erro em ${t} está te custando caro.`,
`${T} em 30 segundos — presta atenção.`,
];
const ctas = [
`Salva esse vídeo pra não esquecer e segue pra mais conteúdo sobre ${t}! 🚀`,
`Comenta aqui embaixo a sua maior dúvida sobre ${t}. Eu respondo todo mundo! 💬`,
`Marca aquele amigo que precisa ver isso e me segue pra mais dicas! 📲`,
];
const isYT = format === 'youtube';
const sections = [
{ label: '🎣 Gancho (0–3s)', tip: 'Fale isso olhando pra câmera, com energia.', text: pick(hooks, 1)[0] },
{ label: '🎬 Contexto (3–10s)', tip: 'Mostre que você entende a dor de quem assiste.', text: `Se você sente que ${t} é complicado ou já tentou e não deu certo, relaxa — eu vou simplificar tudo pra você agora.` },
{ label: '📍 Ponto 1', tip: 'Primeira ideia prática.', text: `O primeiro passo em ${t} é entender o fundamento. A maioria pula essa parte e é por isso que trava.` },
{ label: '📍 Ponto 2', tip: 'Segunda ideia, aprofunde.', text: `Depois disso, foque em consistência. Pequenas ações repetidas valem mais do que grandes esforços isolados.` },
{ label: '📍 Ponto 3', tip: 'Terceira ideia, a mais forte.', text: `E o pulo do gato: ${t} funciona muito melhor quando você acompanha os resultados e ajusta no caminho.` },
{ label: '🔥 Virada', tip: 'O insight que prende até o fim.', text: `Mas o que quase ninguém te conta é que ${t} não é sobre fazer mais — é sobre fazer o certo, de forma constante.` },
{ label: '📢 Chamada pra ação', tip: 'Diga exatamente o que fazer agora.', text: pick(ctas, 1)[0] },
];
if (isYT) {
sections.splice(2, 0, { label: '👋 Intro do canal (10–20s)', tip: 'Só no YouTube — apresente-se rápido.', text: `Se é a primeira vez aqui no canal, seja muito bem-vindo! Aqui a gente fala sobre ${t} de um jeito direto e sem enrolação. Bora?` });
}
const full = sections.map(s => `${s.label}\n${s.text}`).join('\n\n');
const wordCount = full.split(/\s+/).length;
const estSeconds = Math.round(wordCount / 2.5); // ~150 wpm falado
return { topic, format, sections, full, estSeconds };
}
// ── #️⃣ HASHTAGS FROM TOPIC ────────────────────────────────────
function generateHashtagsFromTopic(topic) {
if (!topic || !topic.trim()) throw new Error('Informe um tópico.');
const keywords = extractKeywords(topic + ' ' + topic, 12);
const hashtags = generateHashtags(topic, keywords);
return { hashtags };
}
// ── DISPATCHER ────────────────────────────────────────────────
function generateTools(type, params = {}) {
switch (type) {
case 'hooks': return generateHooks(params.topic);
case 'ideas': return generateIdeas(params.niche);
case 'bio': return generateBio(params);
case 'calendar': return generateCalendar(params);
case 'caption': return generateCaptionFromTopic(params.topic);
case 'script': return generateScript(params);
case 'hashtags': return generateHashtagsFromTopic(params.topic);
default: throw new Error('Ferramenta desconhecida: ' + type);
}
}
module.exports = { analyze, generateTools };