Fix import of chars and lorebook by chub URL Closes #4011

c3376da3939e9c55555515a17f0e50642e978655

Cohee <18619528+Cohee1207@users.noreply.github.com>

1 files changed, +53 -20Ignore whitespace
src/endpoints/content-manager.js+53 -20
@@ -17,6 +17,7 @@ const contentIndexPath = path.join(contentDirectory, 'index.json');
17const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json');17const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json');
1818
19const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []);19const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []);
20const USER_AGENT = 'SillyTavern';
2021
21/**22/**
22 * @typedef {Object} ContentItem23 * @typedef {Object} ContentItem
@@ -323,48 +324,80 @@ function getContentLog(contentLogPath) {
323}324}
324325
325async function downloadChubLorebook(id) {326async function downloadChubLorebook(id) {
326 const result = await fetch('https://api.chub.ai/api/lorebooks/download', {327 const [lorebooks, creatorName, projectName] = id.split('/');
327 method: 'POST',328 const result = await fetch(`https://api.chub.ai/api/${lorebooks}/${creatorName}/${projectName}`, {
328 headers: { 'Content-Type': 'application/json' },329 method: 'GET',
329 body: JSON.stringify({330 headers: { 'Accept': 'application/json', 'User-Agent': USER_AGENT },
330 'fullPath': id,
331 'format': 'SILLYTAVERN',
332 }),
333 });331 });
334332
335 if (!result.ok) {333 if (!result.ok) {
336 const text = await result.text();334 const text = await result.text();
337 console.error('Chub returned error', result.statusText, text);335 console.error('Chub returned error', result.statusText, text);
336 throw new Error('Failed to fetch lorebook metadata');
337 }
338
339 /** @type {any} */
340 const metadata = await result.json();
341 const projectId = metadata.node?.id;
342
343 if (!projectId) {
344 throw new Error('Project ID not found in lorebook metadata');
345 }
346
347 const downloadUrl = `https://api.chub.ai/api/v4/projects/${projectId}/repository/files/raw%252Fsillytavern_raw.json/raw`;
348 const downloadResult = await fetch(downloadUrl, {
349 method: 'GET',
350 headers: { 'Accept': 'application/json', 'User-Agent': USER_AGENT },
351 });
352
353 if (!downloadResult.ok) {
354 const text = await downloadResult.text();
355 console.error('Chub returned error', downloadResult.statusText, text);
338 throw new Error('Failed to download lorebook');356 throw new Error('Failed to download lorebook');
339 }357 }
340358
341 const name = id.split('/').pop();359 const name = projectName;
342 const buffer = Buffer.from(await result.arrayBuffer());360 const buffer = Buffer.from(await downloadResult.arrayBuffer());
343 const fileName = `${sanitize(name)}.json`;361 const fileName = `${sanitize(name)}.json`;
344 const fileType = result.headers.get('content-type');362 const fileType = downloadResult.headers.get('content-type');
345363
346 return { buffer, fileName, fileType };364 return { buffer, fileName, fileType };
347}365}
348366
349async function downloadChubCharacter(id) {367async function downloadChubCharacter(id) {
350 const result = await fetch('https://api.chub.ai/api/characters/download', {368 const [creatorName, projectName] = id.split('/');
351 method: 'POST',369 const result = await fetch(`https://api.chub.ai/api/characters/${creatorName}/${projectName}`, {
352 headers: { 'Content-Type': 'application/json' },370 method: 'GET',
353 body: JSON.stringify({371 headers: { 'Accept': 'application/json', 'User-Agent': USER_AGENT },
354 'format': 'tavern',
355 'fullPath': id,
356 }),
357 });372 });
358373
359 if (!result.ok) {374 if (!result.ok) {
360 const text = await result.text();375 const text = await result.text();
361 console.error('Chub returned error', result.statusText, text);376 console.error('Chub returned error', result.statusText, text);
377 throw new Error('Failed to fetch character metadata');
378 }
379
380 /** @type {any} */
381 const metadata = await result.json();
382 const downloadUrl = metadata.node?.max_res_url;
383
384 if (!downloadUrl) {
385 throw new Error('Download URL not found in character metadata');
386 }
387
388 const downloadResult = await fetch(downloadUrl);
389
390 if (!downloadResult.ok) {
391 const text = await downloadResult.text();
392 console.error('Chub returned error', downloadResult.statusText, text);
362 throw new Error('Failed to download character');393 throw new Error('Failed to download character');
363 }394 }
364395
365 const buffer = Buffer.from(await result.arrayBuffer());396 const buffer = Buffer.from(await downloadResult.arrayBuffer());
366 const fileName = result.headers.get('content-disposition')?.split('filename=')[1] || `${sanitize(id)}.png`;397 const fileName =
367 const fileType = result.headers.get('content-type');398 downloadResult.headers.get('content-disposition')?.split('filename=')[1]?.replace(/["']/g, '') ||
399 `${sanitize(projectName)}.png`;
400 const fileType = downloadResult.headers.get('content-type');
368401
369 return { buffer, fileName, fileType };402 return { buffer, fileName, fileType };
370}403}