diff --git a/package-lock.json b/package-lock.json
index 4a435ec2c..1f3a54419 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,12 +23,13 @@
"multer": "^1.4.5-lts.1",
"node-rest-client": "^3.1.1",
"open": "^8.4.0",
+ "piexifjs": "^1.0.6",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0",
"rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3",
- "webp-converter": "^2.3.3",
+ "webp-converter": "2.3.2",
"ws": "^8.13.0"
},
"bin": {
@@ -1581,6 +1582,11 @@
"resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz",
"integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA=="
},
+ "node_modules/piexifjs": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
+ "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
+ },
"node_modules/pixelmatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz",
@@ -2028,14 +2034,6 @@
"node": ">= 0.4.0"
}
},
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -2050,12 +2048,9 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/webp-converter": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/webp-converter/-/webp-converter-2.3.3.tgz",
- "integrity": "sha512-2p4XvPCIQ/CbUztEFA9vdkILVrRTdMtMxFpQTxlnPc3qx14MV5wnpVvK7m6pG70QdeL+Ser0+Tp843ONwh8VbQ==",
- "dependencies": {
- "uuid": "^8.3.2"
- }
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/webp-converter/-/webp-converter-2.3.2.tgz",
+ "integrity": "sha512-9kQ9Q/MPzUV2mye8Tv7vA6vDIPk77rI4AWWm2vSaCyGAEsxqyVZYeVU2MSJY5fLkf6u7G5K343vLxKubOxz16Q=="
},
"node_modules/whatwg-fetch": {
"version": "3.6.2",
@@ -3305,6 +3300,11 @@
"resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz",
"integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA=="
},
+ "piexifjs": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
+ "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
+ },
"pixelmatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz",
@@ -3665,11 +3665,6 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
},
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
- },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -3681,12 +3676,9 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"webp-converter": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/webp-converter/-/webp-converter-2.3.3.tgz",
- "integrity": "sha512-2p4XvPCIQ/CbUztEFA9vdkILVrRTdMtMxFpQTxlnPc3qx14MV5wnpVvK7m6pG70QdeL+Ser0+Tp843ONwh8VbQ==",
- "requires": {
- "uuid": "^8.3.2"
- }
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/webp-converter/-/webp-converter-2.3.2.tgz",
+ "integrity": "sha512-9kQ9Q/MPzUV2mye8Tv7vA6vDIPk77rI4AWWm2vSaCyGAEsxqyVZYeVU2MSJY5fLkf6u7G5K343vLxKubOxz16Q=="
},
"whatwg-fetch": {
"version": "3.6.2",
diff --git a/package.json b/package.json
index 48668408f..9124f0644 100644
--- a/package.json
+++ b/package.json
@@ -15,12 +15,13 @@
"multer": "^1.4.5-lts.1",
"node-rest-client": "^3.1.1",
"open": "^8.4.0",
+ "piexifjs": "^1.0.6",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0",
"rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3",
- "webp-converter": "^2.3.3",
+ "webp-converter": "2.3.2",
"ws": "^8.13.0"
},
"name": "TavernAI",
diff --git a/public/index.html b/public/index.html
index 90ef8816f..e897b3728 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1172,6 +1172,11 @@
+
diff --git a/public/script.js b/public/script.js
index 48da24d84..ce391084e 100644
--- a/public/script.js
+++ b/public/script.js
@@ -182,6 +182,9 @@ let is_mes_reload_avatar = false;
let optionsPopper = Popper.createPopper(document.getElementById('send_form'), document.getElementById('options'), {
placement: 'top-start'
});
+let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), {
+ placement: 'left',
+});
let dialogueResolve = null;
let chat_metadata = {};
@@ -4482,12 +4485,41 @@ $(document).ready(function () {
},
});
});
- $("#export_button").click(function () {
- var link = document.createElement("a");
- link.href = "characters/" + characters[this_chid].avatar;
- link.download = characters[this_chid].avatar;
- document.body.appendChild(link);
- link.click();
+ $("#export_button").click(function (e) {
+ $('#export_format_popup').toggle();
+ exportPopper.update();
+ });
+ $(document).on('click', '.export_format', async function () {
+ const format = $(this).data('format');
+
+ if (!format) {
+ return;
+ }
+
+ const body = { format, avatar_url: characters[this_chid].avatar };
+
+ const response = await fetch('/exportcharacter', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': token,
+ },
+ body: JSON.stringify(body),
+ });
+
+ if (response.ok) {
+ const filename = characters[this_chid].avatar.replace('.png', `.${format}`);
+ const blob = await response.blob();
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(blob);
+ a.setAttribute("download", filename);
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ }
+
+
+ $('#export_format_popup').hide();
});
//**************************CHAT IMPORT EXPORT*************************//
$("#chat_import_button").click(function () {
@@ -4589,6 +4621,10 @@ $(document).ready(function () {
$("html").on('touchstart mousedown', function (e) {
var clickTarget = $(e.target);
+ if ($('#export_format_popup').is(':visible') && clickTarget.closest('#export_button').length == 0 && clickTarget.closest('#export_format_popup').length == 0) {
+ $('#export_format_popup').hide();
+ }
+
const forbiddenTargets = ['#character_cross', '#avatar-and-name-block', '#shadow_popup', '#world_popup'];
for (const id of forbiddenTargets) {
if (clickTarget.closest(id).length > 0) {
@@ -4607,7 +4643,6 @@ $(document).ready(function () {
}
}
-
}
});
diff --git a/public/scripts/extensions/dice/style.css b/public/scripts/extensions/dice/style.css
index 3d7d069e6..268e363b8 100644
--- a/public/scripts/extensions/dice/style.css
+++ b/public/scripts/extensions/dice/style.css
@@ -18,32 +18,3 @@
#roll_dice:hover {
opacity: 1;
}
-
-.list-group {
- display: flex;
- flex-direction: column;
- padding-left: 0;
- margin-top: 0;
- margin-bottom: 3px;
- overflow: hidden;
- background-color: black;
- border: 1px solid #666;
- border-radius: 15px;
- box-shadow: 0 0 5px black;
- text-shadow: 0 0 3px black;
-}
-
-.list-group-item:hover {
- background-color: rgba(255, 255, 255, 0.3);
-}
-
-.list-group-item {
- color: rgba(229, 224, 216, 1);
- position: relative;
- display: block;
- padding: 0.75rem 1.25rem;
- margin-bottom: -1px;
- box-sizing: border-box;
- user-select: none;
- cursor: pointer;
-}
\ No newline at end of file
diff --git a/public/style.css b/public/style.css
index 784ab3e57..0ef7f72b1 100644
--- a/public/style.css
+++ b/public/style.css
@@ -2576,6 +2576,38 @@ a {
text-decoration: none;
}
+#export_format_popup {
+ display: none;
+}
+
+.list-group {
+ display: flex;
+ flex-direction: column;
+ padding-left: 0;
+ margin-top: 0;
+ margin-bottom: 3px;
+ overflow: hidden;
+ background-color: black;
+ border: 1px solid #666;
+ border-radius: 15px;
+ box-shadow: 0 0 5px black;
+ text-shadow: 0 0 3px black;
+}
+
+.list-group-item:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+}
+
+.list-group-item {
+ color: rgba(229, 224, 216, 1);
+ position: relative;
+ display: block;
+ padding: 0.75rem 1.25rem;
+ margin-bottom: -1px;
+ box-sizing: border-box;
+ user-select: none;
+ cursor: pointer;
+}
/* ############################################################# */
/* Right nav panel and nav-toggle */
diff --git a/server.js b/server.js
index 4905ddb28..958ff684c 100644
--- a/server.js
+++ b/server.js
@@ -26,6 +26,7 @@ const ipaddr = require('ipaddr.js');
const json5 = require('json5');
const ExifReader = require('exifreader');
+const exif = require('piexifjs');
const webp = require('webp-converter');
const config = require(path.join(process.cwd(), './config.conf'));
@@ -1341,6 +1342,66 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
}
});
+app.post("/exportcharacter", jsonParser, async function (request, response) {
+ if (!request.body.format || !request.body.avatar_url) {
+ return response.sendStatus(400);
+ }
+
+ let filename = path.join(directories.characters, sanitize(request.body.avatar_url));
+
+ if (!fs.existsSync(filename)) {
+ return response.sendStatus(404);
+ }
+
+ switch (request.body.format) {
+ case 'png':
+ return response.sendFile(filename, { root: __dirname });
+ case 'json': {
+ try {
+ let json = await charaRead(filename);
+ let jsonObject = json5.parse(json);
+ return response.type('json').send(jsonObject)
+ }
+ catch {
+ return response.sendStatus(400);
+ }
+ }
+ case 'webp': {
+ try {
+ let json = await charaRead(filename);
+ let inputWebpPath = `./uploads/${Date.now()}_input.webp`;
+ let outputWebpPath = `./uploads/${Date.now()}_output.webp`;
+ let metadataPath = `./uploads/${Date.now()}_metadata.exif`;
+ let metadata =
+ {
+ "Exif": {
+ [exif.ExifIFD.UserComment]: json,
+ },
+ };
+ const exifString = exif.dump(metadata);
+ fs.writeFileSync(metadataPath, exifString, 'binary');
+
+ await webp.cwebp(filename, inputWebpPath, '-q 95');
+ await webp.webpmux_add(inputWebpPath, outputWebpPath, metadataPath, 'exif');
+
+ response.sendFile(outputWebpPath, { root: __dirname });
+
+ fs.rmSync(inputWebpPath);
+ fs.rmSync(metadataPath);
+
+ return;
+ }
+ catch (err) {
+ console.log(err);
+ return response.sendStatus(400);
+ }
+ }
+ }
+
+ return response.sendStatus(400);
+});
+
+
app.post("/importchat", urlencodedParser, function (request, response) {
//console.log(humanizedISO8601DateTime()+':/importchat begun');
if (!request.body) return response.sendStatus(400);