Feature/Perchance AI Character Chat import (#4211)
* Adding ZLib and Jimp imports. * Adding checks to see if UUID or URL are from Perchance. * Adding conversion for Perchance cards and avatar. * Adding label and example for Perchance character import. * Adding localization of Perchance import option. * Lint dangling comma fix. * Simplifying one liner arrow function. * Checking .gz at the end of Perchance url. * Refactoring. * Handling Base64 avatars. * Fixing issue with UUID and refactoring. * Adding char name to Perchance UUID example. * Undoing unwanted variable name change of avatarBuffer to defaultAvatarBuffer * Adding null check. * Minor adjustments: renaming variable and organizing imports. * Simple refactoring and reducing level of console messages. * Add character source for perchance * Add null check * Use slug for source --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
363192dd39
commit
34118bbdc2
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "للمضيفين المسموح بهم)",
|
||||
"char_import_8": "شخصية RisuRealm (رابط مباشر)",
|
||||
"char_import_9": "شخصية Soulkyn (رابط مباشر)",
|
||||
"char_import_10": "شخصية Perchance (رابط مباشر أو UUID + .gz)",
|
||||
"Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.",
|
||||
"Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.",
|
||||
"Export for character": "تصدير للشخصية",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "für erlaubte Hosts)",
|
||||
"char_import_8": "RisuRealm-Charakter (Direktlink)",
|
||||
"char_import_9": "Soulkyn-Charakter (Direktlink)",
|
||||
"char_import_10": "Perchance-Charakter (Direktlink oder UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.",
|
||||
"Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.",
|
||||
"Export for character": "Export für Zeichen",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "para hosts permitidos)",
|
||||
"char_import_8": "Personaje RisuRealm (Enlace directo)",
|
||||
"char_import_9": "Personaje Soulkyn (Enlace directo)",
|
||||
"char_import_10": "Personaje Perchance (enlace directo o UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Admite la importación de múltiples caracteres.",
|
||||
"Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.",
|
||||
"Export for character": "Exportar para personaje",
|
||||
|
||||
@@ -1302,6 +1302,7 @@
|
||||
"char_import_7": "pour les hôtes autorisés)",
|
||||
"char_import_8": "Personnage de RisuRealm (lien direct)",
|
||||
"char_import_9": "Personnage de Soulkyn (lien direct)",
|
||||
"char_import_10": "Personnage de Perchance (lien direct ou UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.",
|
||||
"Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.",
|
||||
"Export for character": "Exportation pour le personnage",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "fyrir leyfilega gestgjafa)",
|
||||
"char_import_8": "RisuRealm karakter (beinn hlekkur)",
|
||||
"char_import_9": "Soulkyn karakter (beinn hlekkur)",
|
||||
"char_import_10": "Perchance karakter (beinn hlekkur eða UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.",
|
||||
"Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.",
|
||||
"Export for character": "Flytja út fyrir persónu",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "per gli host consentiti)",
|
||||
"char_import_8": "Personaggio RisuRealm (collegamento diretto)",
|
||||
"char_import_9": "Personaggio Soulkyn (collegamento diretto)",
|
||||
"char_import_10": "Perchance Personaggio (collegamento diretto o UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Supporta l'importazione di più caratteri.",
|
||||
"Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.",
|
||||
"Export for character": "Esporta per carattere",
|
||||
|
||||
@@ -1382,6 +1382,7 @@
|
||||
"char_import_7": "許可されたホストの場合)",
|
||||
"char_import_8": "RisuRealm キャラクター (直接リンク)",
|
||||
"char_import_9": "Soulkyn キャラクター (直接リンク)",
|
||||
"char_import_10": "Perchance キャラクター (直接リンクまたは UUID + .gz)",
|
||||
"Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。",
|
||||
"Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。",
|
||||
"Export for character": "キャラクターのエクスポート",
|
||||
|
||||
@@ -1399,6 +1399,7 @@
|
||||
"char_import_7": "허용된 호스트의 경우)",
|
||||
"char_import_8": "RisuRealm 캐릭터 (직접링크)",
|
||||
"char_import_9": "Soulkyn 캐릭터 (직접링크)",
|
||||
"char_import_10": "Perchance 캐릭터 (직접 링크 또는 UUID + .gz)",
|
||||
"Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.",
|
||||
"Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.",
|
||||
"Export for character": "캐릭터 내보내기",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "voor toegestane hosts)",
|
||||
"char_import_8": "RisuRealm-personage (directe link)",
|
||||
"char_import_9": "Soulkyn-personage (directe link)",
|
||||
"char_import_10": "Perchance-personage (directe link of UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.",
|
||||
"Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.",
|
||||
"Export for character": "Exporteren voor karakter",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "para hosts permitidos)",
|
||||
"char_import_8": "Personagem RisuRealm (link direto)",
|
||||
"char_import_9": "Personagem Soulkyn (link direto)",
|
||||
"char_import_10": "Personagem Perchance (Link Direto ou UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Suporta importação de vários caracteres.",
|
||||
"Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.",
|
||||
"Export for character": "Exportar para personagem",
|
||||
|
||||
@@ -966,6 +966,7 @@
|
||||
"char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в",
|
||||
"char_import_7": ")",
|
||||
"char_import_9": "Персонаж с Soulkyn (прямая ссылка)",
|
||||
"char_import_10": "Персонаж с Perchance (прямая ссылка или UUID + .gz)",
|
||||
"Grammar String": "Грамматика",
|
||||
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
|
||||
"Account": "Аккаунт",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "для дозволених хостів)",
|
||||
"char_import_8": "Персонаж RisuRealm (пряме посилання)",
|
||||
"char_import_9": "Персонаж Soulkyn (пряме посилання)",
|
||||
"char_import_10": "Персонаж Perchance (пряме посилання або UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Підтримується імпорт кількох символів.",
|
||||
"Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.",
|
||||
"Export for character": "Експорт для персонажа",
|
||||
|
||||
@@ -1380,6 +1380,7 @@
|
||||
"char_import_7": "đối với các máy chủ được phép)",
|
||||
"char_import_8": "RisuRealm (URL trực tiếp)",
|
||||
"char_import_9": "Soulkyn (URL trực tiếp)",
|
||||
"char_import_10": "Perchance (Nhập URL trực tiếp hoặc UUID + .gz)",
|
||||
"Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.",
|
||||
"Write each URL or ID into a new line.": "Viết mỗi URL hoặc ID vào một dòng mới.",
|
||||
"Export for character": "Xuất cho nhân vật",
|
||||
|
||||
@@ -1894,6 +1894,7 @@
|
||||
"char_import_7": ")",
|
||||
"char_import_8": "RisuRealm 角色(直链)",
|
||||
"char_import_9": "Soulkyn 角色(直链)",
|
||||
"char_import_10": "Perchance 角色(直链或UUID + .gz)",
|
||||
"Supports importing multiple characters.": "支持导入多个角色。",
|
||||
"Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。",
|
||||
"Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装",
|
||||
|
||||
@@ -1385,6 +1385,7 @@
|
||||
"char_import_7": "對於允許的主機)",
|
||||
"char_import_8": "RisuRealm 角色(直接連結)",
|
||||
"char_import_9": "Soulkyn 角色(直接連結)",
|
||||
"char_import_10": "Perchance 角色(直接連結或 ID + .gz)",
|
||||
"Supports importing multiple characters.": "支援匯入多個字元。",
|
||||
"Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。",
|
||||
"Export for character": "匯出字元",
|
||||
|
||||
@@ -1836,6 +1836,12 @@ function getCharacterSource(chId = this_chid) {
|
||||
return `https://realm.risuai.net/character/${realmId}`;
|
||||
}
|
||||
|
||||
const perchanceSlug = characters[chId]?.data?.extensions?.perchance_data?.slug;
|
||||
|
||||
if (perchanceSlug) {
|
||||
return `https://perchance.org/ai-character-chat?data=${perchanceSlug}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<li><span data-i18n="char_import_6">Direct PNG Link (refer to</span> <code>config.yaml</code><span data-i18n="char_import_7"> for allowed hosts)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://files.catbox.moe/notarealfile.png</tt></li>
|
||||
<li><span data-i18n="char_import_8">RisuRealm Character (Direct Link)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://realm.risuai.net/character/3ca54c71-6efe-46a2-b9d0-4f62df23d712</tt></li>
|
||||
<li><span data-i18n="char_import_9">Soulkyn Character (Direct Link)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://soulkyn.com/l/en-US/@haruka-509al</tt></li>
|
||||
<li><span data-i18n="char_import_10">Perchance Character (Direct Link or UUID + .gz)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://perchance.org/ai-character-chat?data=Loreena~cb3a1b531477378db7cad0148ba62d71.gz</tt><br><span data-i18n="char_import_example">Example:</span> <tt>Loreena~cb3a1b531477378db7cad0148ba62d71.gz</tt></li>
|
||||
</ul>
|
||||
</div>
|
||||
<small>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import zlib from 'node:zlib';
|
||||
import { Buffer } from 'node:buffer';
|
||||
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
||||
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
||||
|
||||
import { getConfigValue, color, setPermissionsSync } from '../util.js';
|
||||
import { getConfigValue, color, setPermissionsSync, isValidUrl } from '../util.js';
|
||||
import { write } from '../character-card-parser.js';
|
||||
import { serverDirectory } from '../server-directory.js';
|
||||
import { Jimp, JimpMime } from '../jimp.js';
|
||||
import { DEFAULT_AVATAR_PATH } from '../constants.js';
|
||||
|
||||
const contentDirectory = path.join(serverDirectory, 'default/content');
|
||||
@@ -818,6 +820,193 @@ async function downloadSoulkynCharacter(slug) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** * Check if the given string is a valid Perchance UUID.
|
||||
* @param {string} uuid UUID string to check
|
||||
* @returns {boolean} True if the UUID is valid, false otherwise
|
||||
*/
|
||||
function isPerchanceUUID(uuid) {
|
||||
if (!uuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//example: Personality_Advisor~6903e991c90fd1dba52c036d917e99c6.gz
|
||||
//charactername~uuid.gz
|
||||
|
||||
const uuidRegex = /^\w+~[a-f0-9]{32}\.gz$/;
|
||||
return uuidRegex.test(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Perchance URL to extract the character slug.
|
||||
* @param {string} url Perchance character URL
|
||||
* @returns {string} Slug of the character
|
||||
*/
|
||||
function parsePerchanceSlug(url) {
|
||||
// Example: https://perchance.org/ai-character-chat?data=Personality_Advisor~6903e991c90fd1dba52c036d917e99c6.gz
|
||||
// or: Personality_Advisor~6903e991c90fd1dba52c036d917e99c6.gz
|
||||
return url?.split('~')[1] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Download Perchance character card
|
||||
* @param {string} slug Slug of the character
|
||||
* @returns {Promise<{buffer: Buffer, fileName: string, fileType: string} | null>}
|
||||
*/
|
||||
async function downloadPerchanceCharacter(slug) {
|
||||
// example of slug
|
||||
// 6903e991c90fd1dba52c036d917e99c6.gz
|
||||
const perchanceBaseURL = 'https://user.uploads.dev/file';
|
||||
|
||||
try {
|
||||
const charURL = `${perchanceBaseURL}/${slug}`;
|
||||
console.log('Downloading Perchance character from URL:', charURL);
|
||||
const result = await fetch(charURL, {
|
||||
headers: { 'Content-Type': 'application/json', 'User-Agent': USER_AGENT },
|
||||
});
|
||||
|
||||
//decompress gzipped content
|
||||
if (result.ok) {
|
||||
const perchanceChar = await extractPerchanceCharacterFromGz(result);
|
||||
|
||||
const avatarUrl = perchanceChar.avatar?.url;
|
||||
|
||||
//check if avatarURL is a base64 of any image type
|
||||
const isAvatarBase64 = avatarUrl && avatarUrl.startsWith('data:image/');
|
||||
|
||||
const charData = {
|
||||
name: perchanceChar.name || 'Unnamed Perchance Character',
|
||||
first_mes: '',
|
||||
tags: [],
|
||||
description: perchanceChar.roleInstruction || '',
|
||||
creator: perchanceChar.metaTitle || '',
|
||||
creator_notes: perchanceChar.metaDescription || '',
|
||||
alternate_greetings: [],
|
||||
character_version: '',
|
||||
mes_example: '',
|
||||
post_history_instructions: '',
|
||||
system_prompt: '',
|
||||
scenario: '',
|
||||
personality: perchanceChar.reminderMessage || '',
|
||||
extensions: {
|
||||
perchance_data: {
|
||||
slug: slug,
|
||||
char_url: charURL,
|
||||
uuid: perchanceChar.uuid || null,
|
||||
avatar_url: isAvatarBase64 ? null : (avatarUrl || null),
|
||||
folder_path: perchanceChar.folderPath || null,
|
||||
folder_name: perchanceChar.folderName || null,
|
||||
custom_data: perchanceChar.customData || {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const avatarBuffer = await fetchPerchanceAvatar(avatarUrl, isAvatarBase64);
|
||||
|
||||
// Character card
|
||||
const buffer = write(avatarBuffer, JSON.stringify({
|
||||
'spec': 'chara_card_v2',
|
||||
'spec_version': '2.0',
|
||||
'data': charData,
|
||||
}));
|
||||
|
||||
const fileName = `${charData.name}.png`;
|
||||
const fileType = 'image/png';
|
||||
|
||||
return { buffer, fileName, fileType };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error downloading character:', error);
|
||||
throw error;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** * Extracts Perchance character data from a gzipped response.
|
||||
* @param {import('node-fetch').Response} result Fetch response containing gzipped character data
|
||||
* @returns {Promise<Object>} Parsed Perchance character data
|
||||
* @throws {Error} If the character data is invalid or missing required fields
|
||||
*/
|
||||
async function extractPerchanceCharacterFromGz(result) {
|
||||
const compressedBuffer = Buffer.from(await result.arrayBuffer());
|
||||
const decompressedBuffer = zlib.gunzipSync(compressedBuffer);
|
||||
|
||||
// inside the gz file, there is a file of the same name without extensions, but it is a json file
|
||||
|
||||
if (!decompressedBuffer || decompressedBuffer.length === 0) {
|
||||
console.error('Perchance character data is empty or invalid');
|
||||
throw new Error('Failed to download character: Invalid Perchance character data');
|
||||
}
|
||||
|
||||
// Parse the decompressed JSON
|
||||
const perchanceCharData = JSON.parse(decompressedBuffer.toString());
|
||||
|
||||
if (!perchanceCharData?.addCharacter) {
|
||||
console.error('Perchance character data is missing addCharacter field', perchanceCharData);
|
||||
throw new Error('Failed to download character: Invalid Perchance character data');
|
||||
}
|
||||
|
||||
return perchanceCharData.addCharacter;
|
||||
}
|
||||
|
||||
/** * Fetches the avatar from Perchance URL or uses a default avatar if not available.
|
||||
* @param {string} avatarUrl URL of the avatar
|
||||
* @param {boolean} isAvatarBase64 Flag indicating if the avatar URL is a base64 string
|
||||
* @returns {Promise<Buffer>} Buffer containing the avatar image
|
||||
*/
|
||||
async function fetchPerchanceAvatar(avatarUrl, isAvatarBase64) {
|
||||
const defaultAvatarPath = path.join(serverDirectory, DEFAULT_AVATAR_PATH);
|
||||
const defaultAvatarBuffer = fs.readFileSync(defaultAvatarPath);
|
||||
|
||||
if (!avatarUrl || (!isAvatarBase64 && !isValidUrl(avatarUrl))) {
|
||||
console.warn('Perchance character does not have an avatar, it is not base64, or it is an invalid url, using default avatar');
|
||||
return defaultAvatarBuffer;
|
||||
}
|
||||
|
||||
if (isAvatarBase64) {
|
||||
// check if avatarUrl is a png
|
||||
const isPng = avatarUrl.startsWith('data:image/png;base64,');
|
||||
const base64 = avatarUrl.split(',')[1];
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
|
||||
if (isPng) {
|
||||
return buffer;
|
||||
} else {
|
||||
// use jimp to convert the base64 to PNG if it's not PNG
|
||||
console.trace('Perchance character avatar is not PNG, converting to PNG...');
|
||||
return await Jimp.read(buffer).then(image => image.getBuffer(JimpMime.png));
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch avatar from URL
|
||||
console.log('Fetching Perchance avatar from URL:', avatarUrl);
|
||||
const avatarResponse = await fetch(avatarUrl, { headers: { 'User-Agent': USER_AGENT } });
|
||||
|
||||
if (avatarResponse.ok) {
|
||||
const avatarContentType = avatarResponse.headers.get('content-type');
|
||||
const avatarBuffer = Buffer.from(await avatarResponse.arrayBuffer());
|
||||
|
||||
if (avatarContentType === 'image/png') {
|
||||
return avatarBuffer;
|
||||
} else {
|
||||
console.trace(`Perchance character avatar is not PNG: ${avatarContentType}. Converting to PNG...`);
|
||||
|
||||
// use jimp to convert the image to PNG if it's not PNG
|
||||
return await Jimp.read(avatarBuffer)
|
||||
.then(image => image.getBuffer(JimpMime.png));
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Failed to fetch Perchance avatar:', avatarResponse.statusText);
|
||||
const isPerchanceOrgFileUploader = avatarUrl.includes('https://user-uploads.perchance.org');
|
||||
|
||||
if (isPerchanceOrgFileUploader) {
|
||||
console.warn('Files from https://user-uploads.perchance.org are sometimes blocked by CloudFlare, try reuploading it in https://perchance.org/upload to get the new link from https://user-uploads.dev instead.');
|
||||
}
|
||||
|
||||
console.warn('You can also download the avatar manually and assign it to the character:', avatarUrl);
|
||||
return defaultAvatarBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} url
|
||||
* @returns {String | null } UUID of the character
|
||||
@@ -874,6 +1063,7 @@ router.post('/importURL', async (request, response) => {
|
||||
const isAICharacterCardsContent = host.includes('aicharactercards.com');
|
||||
const isRisu = host.includes('realm.risuai.net');
|
||||
const isSoulkyn = host.includes('soulkyn.com');
|
||||
const isPerchance = host.includes('perchance.org');
|
||||
const isGeneric = isHostWhitelisted(host);
|
||||
|
||||
if (isPygmalionContent) {
|
||||
@@ -929,6 +1119,13 @@ router.post('/importURL', async (request, response) => {
|
||||
}
|
||||
type = 'character';
|
||||
result = await downloadSoulkynCharacter(soulkynSlug);
|
||||
} else if (isPerchance) {
|
||||
const perchanceSlug = parsePerchanceSlug(url);
|
||||
if (!perchanceSlug) {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
type = 'character';
|
||||
result = await downloadPerchanceCharacter(perchanceSlug);
|
||||
} else if (isGeneric) {
|
||||
console.info('Downloading from generic url:', url);
|
||||
type = 'character';
|
||||
@@ -964,6 +1161,7 @@ router.post('/importUUID', async (request, response) => {
|
||||
const isJannny = uuid.includes('_character');
|
||||
const isPygmalion = (!isJannny && uuid.length == 36);
|
||||
const isAICC = uuid.startsWith('AICC/');
|
||||
const isPerchance = isPerchanceUUID(uuid);
|
||||
const uuidType = uuid.includes('lorebook') ? 'lorebook' : 'character';
|
||||
|
||||
if (isPygmalion) {
|
||||
@@ -976,6 +1174,10 @@ router.post('/importUUID', async (request, response) => {
|
||||
const [, author, card] = uuid.split('/');
|
||||
console.info('Downloading AICC character:', `${author}/${card}`);
|
||||
result = await downloadAICCCharacter(`${author}/${card}`);
|
||||
} else if (isPerchance) {
|
||||
console.info('Downloading Perchance character:', uuid);
|
||||
const parsedUuid = parsePerchanceSlug(uuid);
|
||||
result = await downloadPerchanceCharacter(parsedUuid);
|
||||
} else {
|
||||
if (uuidType === 'character') {
|
||||
console.info('Downloading chub character:', uuid);
|
||||
@@ -990,6 +1192,10 @@ router.post('/importUUID', async (request, response) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Failed to download content');
|
||||
}
|
||||
|
||||
if (result.fileType) response.set('Content-Type', result.fileType);
|
||||
response.set('Content-Disposition', `attachment; filename="${result.fileName}"`);
|
||||
response.set('X-Custom-Content-Type', uuidType);
|
||||
|
||||
Reference in New Issue
Block a user