Extra – Gerador de Frases em Japonês
Utilize nossa ferramenta abaixo para gerar frases em japonês com significados, perfeito para adicionar ao Anki. (Pode levar uns 30 segundos para gerar as 5 frases). [mwai-form-container id=”hyrvuvcch” theme=”ChatGPT”] ) {
window.audioAtual = window.cacheVoicevox[texto];
window.audioAtual.currentTime = 0;
let playPromise = window.audioAtual.play();
if (playPromise !== undefined) {
playPromise.catch(e => {
if (e.name !== ‘AbortError’) usarTTSNativo(texto);
});
}
return;
}
// Se chegou aqui, é a PRIMEIRA vez. Mostra a ampulheta!
if (btnElement) btnElement.innerHTML = “⏳”;
const speakerId = Math.floor(Math.random() * 34) + 1;
const urlVoicevox = `https://api.tts.quest/v3/voicevox/synthesis?text=${encodeURIComponent(texto)}&speaker=${speakerId}`;
try {
const response = await fetch(urlVoicevox);
if (!response.ok) throw new Error(“Erro na API”);
const data = await response.json();
if (data.success && data.audioStatusUrl && data.mp3DownloadUrl) {
// ESPERA O SERVIDOR TERMINAR (Polling de 0.5s)
let isReady = false;
let tentativas = 0;
while (!isReady && tentativas < 20) { // Tenta por 10 segundos no máx
const statusRes = await fetch(data.audioStatusUrl);
const statusData = await statusRes.json();
if (statusData.isAudioReady) {
isReady = true;
} else {
await new Promise(r => setTimeout(r, 500));
tentativas++;
}
}
if (!isReady) throw new Error(“Servidor sobrecarregado”);
// Agora sabemos que o áudio realmente existe!
const novoAudio = new Audio(data.mp3DownloadUrl);
novoAudio.playbackRate = 0.9;
// Salva o áudio perfeito no cache
window.cacheVoicevox[texto] = novoAudio;
window.audioAtual = novoAudio;
let playPromise = window.audioAtual.play();
if (playPromise !== undefined) {
playPromise.catch(e => {
if (e.name !== ‘AbortError’) usarTTSNativo(texto);
});
}
// Áudio começou a tocar, restaura o botão para Play
if (btnElement) btnElement.innerHTML = “▷”;
} else {
throw new Error(“Resposta inválida da API”);
}
} catch (error) {
console.warn(“Erro no Voicevox, fallback para Nativo:”, error);
usarTTSNativo(texto);
}
}
document.addEventListener(“mouseup”, function () {
const selecao = window.getSelection().toString();
const textoLimpo = selecao.trim();
if (textoLimpo) {
let temJap = false;
let temRomaji = false;
if (/[\u3040-\u30ff\u4e00-\u9faf]/.test(textoLimpo)) {
temJap = true;
}
if (/[a-zA-ZáàâãéèêíïóôõöúçñÁÀÂÃÉÈÊÍÏÓÔÕÖÚÇÑ]/.test(textoLimpo)) {
temRomaji = true;
}
if (temJap) {
if (temRomaji === false) {
falarJapones(textoLimpo, false); // Seleção usa apenas TTS Nativo
}
}
}
});
const extrairTextoLimpo = (elemento) => {
const clone = elemento.cloneNode(true);
clone.querySelectorAll(‘rt, rp, button, .playbt, .writebt’).forEach(el => el.remove());
return clone.textContent.trim();
};
document.querySelectorAll(“.wp-block-preformatted, .frase pre”).forEach(pre => {
let temPlay = false;
let temCopiar = false;
let temEscrita = false;
pre.querySelectorAll(‘button’).forEach(btn => {
const html = btn.innerHTML;
if (html.includes(“▷”)) temPlay = true;
if (html.includes(“✄”)) temCopiar = true;
if (html.includes(“✎”)) temEscrita = true;
});
if (temPlay === false) {
const btnPlay = document.createElement(“button”);
btnPlay.className = “playbt”;
btnPlay.innerHTML = “▷”;
btnPlay.addEventListener(“click”, function () {
falarJapones(extrairTextoLimpo(pre), true, btnPlay);
});
pre.appendChild(btnPlay);
}
if (temCopiar === false) {
const btnCopiar = document.createElement(“button”);
btnCopiar.className = “playbt”;
btnCopiar.innerHTML = “✄”;
btnCopiar.addEventListener(“click”, function () {
navigator.clipboard.writeText(extrairTextoLimpo(pre));
});
pre.appendChild(btnCopiar);
}
if (temEscrita === false) {
const btnEscrita = document.createElement(“button”);
btnEscrita.className = “playbt writebt”;
btnEscrita.innerHTML = “✎”;
btnEscrita.addEventListener(“click”, function () {
if (typeof jQuery === ‘undefined’) {
alert(“jQuery não encontrado. Verifique se foi carregado.”);
return;
}
document.querySelectorAll(“.draw”).forEach(el => el.remove());
document.querySelectorAll(“.sample-btn”).forEach(el => el.remove());
const drawDiv = document.createElement(“div”);
drawDiv.id = “draw”;
drawDiv.className = “draw”;
pre.parentNode.insertBefore(drawDiv, pre.nextSibling);
if (typeof jQuery(“#draw”).dmak === ‘function’) {
jQuery(“#draw”).dmak(extrairTextoLimpo(pre), {
width: 80, height: 80,
stroke: { order: { visible: true } },
step: 0.02
});
} else {
alert(“Função dmak não encontrada. Verifique o jquery.dmak.js.”);
}
});
pre.appendChild(btnEscrita);
}
});
document.querySelectorAll(“div.dynamic-entry-content ul”).forEach(ul => {
let temCopiar = false;
ul.querySelectorAll(‘button’).forEach(btn => {
if (btn.innerHTML.includes(“✄”)) temCopiar = true;
});
if (temCopiar === false) {
const btnCopiar = document.createElement(“button”);
btnCopiar.className = “playbt”;
btnCopiar.innerHTML = “✄”;
btnCopiar.addEventListener(“click”, function () {
navigator.clipboard.writeText(extrairTextoLimpo(ul));
});
ul.appendChild(btnCopiar);
}
});
const btnAnki = document.getElementById(‘btn-anki-csv’);
if (btnAnki) {
btnAnki.addEventListener(‘click’, () => {
const hasJP = s => {
let str = s;
if (!str) str = ”;
return /[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u3400-\u4DBF\u4E00-\u9FFF]/.test(str);
};
const stripBR = s => {
let str = s;
if (!str) str = ”;
return str.replace(/
/gi, ”); }; const normHTML = s => stripBR(s).replace(/\t/g, ‘ ‘).replace(/\r?\n/g, ”); const csvField = s => { let text = normHTML(s); return `”${text.replace(/”/g, ‘””‘)}”`; }; const timestamp = () => { const d = new Date(); const p = n => String(n).padStart(2, ‘0’); return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`; }; const rmButtons = root => root.querySelectorAll(‘button, .playbt, .writebt’).forEach(el => el.remove()); const unwrap = el => { const f = document.createDocumentFragment(); while (el.firstChild) f.appendChild(el.firstChild); el.replaceWith(f); }; const trimLeadingBRs = root => { while (root.firstElementChild) { if (root.firstElementChild.tagName === ‘BR’) { root.firstElementChild.remove(); } else { break; } } }; function sanitize(root, config) { let allowRuby = false; if (config) { if (config.allowRuby) allowRuby = true; } root.querySelectorAll(‘script, style, iframe, svg, mark’).forEach(el => el.remove()); rmButtons(root); if (!allowRuby) { root.querySelectorAll(‘rt, rp’).forEach(el => el.remove()); root.querySelectorAll(‘rb’).forEach(unwrap); root.querySelectorAll(‘ruby’).forEach(unwrap); } const allowedTags = [‘UL’, ‘OL’, ‘LI’, ‘P’, ‘A’, ‘STRONG’, ‘EM’, ‘B’, ‘I’, ‘U’, ‘S’, ‘SMALL’, ‘CODE’, ‘KBD’, ‘SAMP’, ‘SUB’, ‘SUP’, ‘SPAN’]; if (allowRuby) { allowedTags.push(‘RUBY’); allowedTags.push(‘RB’); allowedTags.push(‘RT’); allowedTags.push(‘RP’); } root.querySelectorAll(‘pre’).forEach(unwrap); const stack = [root]; while (stack.length) { const n = stack.pop(); if (!n) continue; if (n.nodeType === 1) { if (n !== root) { let tagPermitida = false; allowedTags.forEach(t => { if (n.tagName === t) tagPermitida = true; }); if (!tagPermitida) unwrap(n); } } for (let i = n.childNodes.length – 1; i >= 0; i–) stack.push(n.childNodes[i]); } } function frontFromPre(pre) { const c = pre.cloneNode(true); trimLeadingBRs(c); sanitize(c, { allowRuby: false }); return c.innerHTML.trim(); } function preWithFuriganaIfAny(pre) { if (!/
/gi, ”); }; const normHTML = s => stripBR(s).replace(/\t/g, ‘ ‘).replace(/\r?\n/g, ”); const csvField = s => { let text = normHTML(s); return `”${text.replace(/”/g, ‘””‘)}”`; }; const timestamp = () => { const d = new Date(); const p = n => String(n).padStart(2, ‘0’); return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`; }; const rmButtons = root => root.querySelectorAll(‘button, .playbt, .writebt’).forEach(el => el.remove()); const unwrap = el => { const f = document.createDocumentFragment(); while (el.firstChild) f.appendChild(el.firstChild); el.replaceWith(f); }; const trimLeadingBRs = root => { while (root.firstElementChild) { if (root.firstElementChild.tagName === ‘BR’) { root.firstElementChild.remove(); } else { break; } } }; function sanitize(root, config) { let allowRuby = false; if (config) { if (config.allowRuby) allowRuby = true; } root.querySelectorAll(‘script, style, iframe, svg, mark’).forEach(el => el.remove()); rmButtons(root); if (!allowRuby) { root.querySelectorAll(‘rt, rp’).forEach(el => el.remove()); root.querySelectorAll(‘rb’).forEach(unwrap); root.querySelectorAll(‘ruby’).forEach(unwrap); } const allowedTags = [‘UL’, ‘OL’, ‘LI’, ‘P’, ‘A’, ‘STRONG’, ‘EM’, ‘B’, ‘I’, ‘U’, ‘S’, ‘SMALL’, ‘CODE’, ‘KBD’, ‘SAMP’, ‘SUB’, ‘SUP’, ‘SPAN’]; if (allowRuby) { allowedTags.push(‘RUBY’); allowedTags.push(‘RB’); allowedTags.push(‘RT’); allowedTags.push(‘RP’); } root.querySelectorAll(‘pre’).forEach(unwrap); const stack = [root]; while (stack.length) { const n = stack.pop(); if (!n) continue; if (n.nodeType === 1) { if (n !== root) { let tagPermitida = false; allowedTags.forEach(t => { if (n.tagName === t) tagPermitida = true; }); if (!tagPermitida) unwrap(n); } } for (let i = n.childNodes.length – 1; i >= 0; i–) stack.push(n.childNodes[i]); } } function frontFromPre(pre) { const c = pre.cloneNode(true); trimLeadingBRs(c); sanitize(c, { allowRuby: false }); return c.innerHTML.trim(); } function preWithFuriganaIfAny(pre) { if (!/
