| 323 | 324 | } |
| 324 | 325 | |
| 325 | 326 | async 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 | }); |
| 334 | 332 | |
| 335 | 333 | if (!result.ok) { |
| 336 | 334 | const text = await result.text(); |
| 337 | 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 | 356 | throw new Error('Failed to download lorebook'); |
| 339 | 357 | } |
| 340 | 358 | |
| 341 | 359 | const name = id.split('/').pop()projectName; |
| 342 | 360 | const buffer = Buffer.from(await resultdownloadResult.arrayBuffer()); |
| 343 | 361 | const fileName = `${sanitize(name)}.json`; |
| 344 | 362 | const fileType = resultdownloadResult.headers.get('content-type'); |
| 345 | 363 | |
| 346 | 364 | return { buffer, fileName, fileType }; |
| 347 | 365 | } |
| 348 | 366 | |
| 349 | 367 | async 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 | }); |
| 358 | 373 | |
| 359 | 374 | if (!result.ok) { |
| 360 | 375 | const text = await result.text(); |
| 361 | 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 | 393 | throw new Error('Failed to download character'); |
| 363 | 394 | } |
| 364 | 395 | |
| 365 | 396 | const buffer = Buffer.from(await resultdownloadResult.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'); |
| 368 | 401 | |
| 369 | 402 | return { buffer, fileName, fileType }; |
| 370 | 403 | } |