All checks were successful
Build & Deploy / deploy (push) Successful in 19s
Fixe 18er-Batches sprengen bei langen/mehrseitigen Texten das InputText-Limit der Scriptalizer-API (32000 Zeichen) -> 'Input length limit 32000 exceeded'. Jetzt werden Batches nach kumulativer Zeichenlaenge (max 28000, Sicherheitsabstand) gebildet, die Anzahl 18 bleibt als zusaetzlicher Deckel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
251 lines
7.4 KiB
JavaScript
251 lines
7.4 KiB
JavaScript
const https = require("https");
|
||
const { parseStringPromise } = require("xml2js");
|
||
const config = require("../config");
|
||
|
||
// API Request Counter (resets daily)
|
||
const stats = {
|
||
requestsToday: 0,
|
||
textsToday: 0,
|
||
lastResetDate: new Date().toDateString(),
|
||
};
|
||
|
||
/**
|
||
* Prüft ob der Zähler zurückgesetzt werden muss (neuer Tag)
|
||
*/
|
||
function checkAndResetDailyStats() {
|
||
const today = new Date().toDateString();
|
||
if (stats.lastResetDate !== today) {
|
||
console.log(`[Scriptalizer] 📅 New day detected, resetting counters`);
|
||
console.log(`[Scriptalizer] 📊 Yesterday's stats: ${stats.requestsToday} API calls, ${stats.textsToday} texts processed`);
|
||
stats.requestsToday = 0;
|
||
stats.textsToday = 0;
|
||
stats.lastResetDate = today;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Gibt die aktuellen Stats zurück
|
||
*/
|
||
function getStats() {
|
||
checkAndResetDailyStats();
|
||
return { ...stats };
|
||
}
|
||
|
||
/**
|
||
* Ruft die Scriptalizer API auf
|
||
* @param {string} inputText - Text zum Scriptalisieren
|
||
* @param {string} fontName - Font-Name (z.B. 'PremiumUltra79')
|
||
* @param {number} errFrequency - Fehlerfrequenz
|
||
* @returns {Promise<{status: string, outputText: string}>}
|
||
*/
|
||
async function callScriptalizer(inputText, fontName, errFrequency = 10) {
|
||
// Prüfe ob neuer Tag
|
||
checkAndResetDailyStats();
|
||
|
||
return new Promise((resolve, reject) => {
|
||
if (!config.scriptalizer.licenseKey) {
|
||
reject(new Error("Scriptalizer License Key fehlt in Konfiguration"));
|
||
return;
|
||
}
|
||
|
||
// Use x-www-form-urlencoded format
|
||
const params = new URLSearchParams();
|
||
params.append("LicenseKey", config.scriptalizer.licenseKey);
|
||
params.append("FontName", fontName);
|
||
params.append("ErrFrequency", String(errFrequency));
|
||
params.append("InputText", inputText);
|
||
|
||
const body = params.toString();
|
||
|
||
// Check input size (48KB limit)
|
||
if (Buffer.byteLength(body) > config.scriptalizer.maxInputSize) {
|
||
reject(
|
||
new Error(
|
||
`Input size exceeds 48KB limit (${Buffer.byteLength(body)} bytes)`
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
|
||
const options = {
|
||
hostname: "www.scriptalizer.co.uk",
|
||
port: 443,
|
||
path: "/QuantumScriptalize.asmx/Scriptalize",
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||
"Content-Length": Buffer.byteLength(body),
|
||
},
|
||
timeout: 30000, // 30 second timeout
|
||
};
|
||
|
||
const req = https.request(options, (res) => {
|
||
let data = "";
|
||
|
||
res.on("data", (chunk) => {
|
||
data += chunk;
|
||
});
|
||
|
||
res.on("end", async () => {
|
||
try {
|
||
const result = await parseStringPromise(data, {
|
||
explicitArray: false,
|
||
});
|
||
const response = result.ScriptalizerResponse;
|
||
|
||
if (!response || response.Status !== "OK") {
|
||
reject(new Error(response?.Status || "UNKNOWN_STATUS"));
|
||
return;
|
||
}
|
||
|
||
// Zähler erhöhen bei erfolgreichem Request
|
||
stats.requestsToday++;
|
||
console.log(`[Scriptalizer] 📊 API Request #${stats.requestsToday} today completed`);
|
||
|
||
resolve({ status: "OK", outputText: response.OutputText });
|
||
} catch (err) {
|
||
reject(
|
||
new Error(`Failed to parse Scriptalizer response: ${err.message}`)
|
||
);
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on("error", (err) => {
|
||
reject(new Error(`Scriptalizer request failed: ${err.message}`));
|
||
});
|
||
|
||
req.on("timeout", () => {
|
||
req.destroy();
|
||
reject(new Error("Scriptalizer request timed out"));
|
||
});
|
||
|
||
req.write(body);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Scriptalisiert mehrere Texte in 25er Batches
|
||
* @param {string[]} texts - Array von Texten
|
||
* @param {string} font - Font-Key (tilda, alva, ellie)
|
||
* @param {number} errFrequency - Fehlerfrequenz
|
||
* @returns {Promise<string[]>} - Array von scriptalisierten Texten
|
||
*/
|
||
async function scriptalizeBatch(texts, font = "tilda", errFrequency = 10) {
|
||
if (!Array.isArray(texts) || texts.length === 0) {
|
||
return [];
|
||
}
|
||
|
||
const fontName =
|
||
config.scriptalizer.fontMap[font.toLowerCase()] ||
|
||
config.scriptalizer.fontMap.tilda;
|
||
const separator = config.scriptalizer.separator;
|
||
const BATCH_SIZE = 18; // Zusätzlicher Deckel: max. 18 Texte pro Call (unabhängig von der Länge).
|
||
// Primäre Grenze ist die ZEICHENLÄNGE: die Scriptalizer-API begrenzt den
|
||
// InputText auf 32000 Zeichen pro Call ("Input length limit 32000 exceeded").
|
||
// Fixe 18er-Batches sprengen das bei langen (mehrseitigen) Texten – daher nach
|
||
// kumulativer Länge batchen, mit Sicherheitsabstand.
|
||
const MAX_BATCH_CHARS = 28000;
|
||
const sepLen = separator.length;
|
||
|
||
// Batches nach Länge (und max. Anzahl) bilden.
|
||
const batches = [];
|
||
{
|
||
let cur = [];
|
||
let curLen = 0;
|
||
for (const t of texts) {
|
||
const tLen = String(t || "").length;
|
||
const projected = curLen + (cur.length ? sepLen : 0) + tLen;
|
||
if (cur.length && (projected > MAX_BATCH_CHARS || cur.length >= BATCH_SIZE)) {
|
||
batches.push(cur);
|
||
cur = [];
|
||
curLen = 0;
|
||
}
|
||
cur.push(t);
|
||
curLen += (cur.length > 1 ? sepLen : 0) + tLen;
|
||
}
|
||
if (cur.length) batches.push(cur);
|
||
}
|
||
|
||
const totalBatches = batches.length;
|
||
console.log(
|
||
`[Scriptalizer] Processing ${texts.length} texts in ${totalBatches} batch(es) (max ${BATCH_SIZE} texts / ${MAX_BATCH_CHARS} chars each)`
|
||
);
|
||
|
||
const allScriptalized = [];
|
||
|
||
for (let b = 0; b < batches.length; b += 1) {
|
||
const batch = batches[b];
|
||
const batchNumber = b + 1;
|
||
const combinedText = batch.join(separator);
|
||
|
||
console.log(
|
||
`[Scriptalizer] Processing batch ${batchNumber}/${totalBatches} (${batch.length} texts, ${combinedText.length} chars)`
|
||
);
|
||
|
||
try {
|
||
const result = await callScriptalizer(
|
||
combinedText,
|
||
fontName,
|
||
errFrequency
|
||
);
|
||
|
||
// Trenne Ergebnis wieder
|
||
const scriptalized = result.outputText.split(separator);
|
||
|
||
if (scriptalized.length !== batch.length) {
|
||
console.warn(
|
||
`[Scriptalizer] Batch ${batchNumber}: returned ${scriptalized.length} parts, expected ${batch.length}`
|
||
);
|
||
}
|
||
|
||
allScriptalized.push(...scriptalized);
|
||
console.log(
|
||
`[Scriptalizer] Batch ${batchNumber}/${totalBatches} completed successfully`
|
||
);
|
||
} catch (err) {
|
||
console.error(`[Scriptalizer] Batch ${batchNumber} failed:`, err.message);
|
||
throw err;
|
||
}
|
||
}
|
||
|
||
// Textzähler erhöhen
|
||
stats.textsToday += allScriptalized.length;
|
||
|
||
console.log(
|
||
`[Scriptalizer] All batches completed. Total scriptalized: ${allScriptalized.length}`
|
||
);
|
||
console.log(
|
||
`[Scriptalizer] 📊 Daily stats: ${stats.requestsToday} API calls, ${stats.textsToday} texts processed`
|
||
);
|
||
return allScriptalized;
|
||
}
|
||
|
||
/**
|
||
* Scriptalisiert einen einzelnen Text
|
||
* @param {string} text - Text zum Scriptalisieren
|
||
* @param {string} font - Font-Key (tilda, alva, ellie)
|
||
* @param {number} errFrequency - Fehlerfrequenz
|
||
* @returns {Promise<string>} - Scriptalisierter Text
|
||
*/
|
||
async function scriptalizeSingle(text, font = "tilda", errFrequency = 10) {
|
||
const fontName =
|
||
config.scriptalizer.fontMap[font.toLowerCase()] ||
|
||
config.scriptalizer.fontMap.tilda;
|
||
|
||
try {
|
||
const result = await callScriptalizer(text, fontName, errFrequency);
|
||
return result.outputText;
|
||
} catch (err) {
|
||
console.error("Scriptalizer single call failed:", err.message);
|
||
throw err;
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
scriptalizeBatch,
|
||
scriptalizeSingle,
|
||
getStats,
|
||
};
|