feat: update userscript to allow dumping of definitions as well

This commit is contained in:
11b 2022-12-21 15:39:42 -03:00
parent ecf2e65e76
commit d638bb5625
3 changed files with 90 additions and 18 deletions

View File

@ -1,6 +1,6 @@
# CharacterAI Dumper Userscript
This userscript allows you to download your saved messages with any bot you've ever talked to, given you can reach their chat history page.
This userscript allows you to download your saved messages with any bot you've ever talked to, given you can reach their chat history page. If you're a bot creator, it also allows you to separately download your bot's definitions.
## How to use
@ -12,8 +12,26 @@ This userscript allows you to download your saved messages with any bot you've e
- After a few seconds, a `Download` link should pop up next to the "Your past conversations with so-and-so" header:
![What the download link looks like](./example-images/02.png)
- Clicking on the link will download a `.json` file containing the bot's basic info (name, description, greeting) and all the interactions you've ever had with it.
- If you're a bot creator, you can also head over into the Character Editor to download a bot's definitions:
![Where to find the definitions download](./example-images/03.png)
**NOTE:** The script attempts to anonymize the dumped data (it scrubs known sensitive fields and attempts to replace any instances of your name within messages), but if you're paranoid, you should open the downloaded JSON and search for your username/email/display name just to make sure.
---
**NOTE 1:** If you've never used the "Save and Start New Chat" feature, you won't have the "View Saved Chats" option shown in the first screenshot.
That's fine, it just means you'll need to manually access the histories page by replacing the `/chat` path with `/histories` in the URL. For example, if you're at:
https://beta.character.ai/chat?char={{BOT_ID_HERE}}
Just rewrite the URL so it reads:
https://beta.character.ai/histories?char={{BOT_ID_HERE}}
And you'll reach the page that should show the `Download` link.
---
**NOTE 2:** The script attempts to anonymize the dumped data (it scrubs known sensitive fields and attempts to replace any instances of your name within messages), but if you're paranoid, you should open the downloaded JSON and search for your username/email/display name just to make sure.
## Troubleshooting

View File

@ -3,28 +3,29 @@
// @namespace Violentmonkey Scripts
// @match https://beta.character.ai/*
// @grant none
// @version 1.2
// @version 1.3
// @author 0x000011b
// @description Allows downloading saved chat messages from CharacterAI.
// @description Allows downloading saved chat messages and character definitions from CharacterAI.
// @downloadURL https://git.fuwafuwa.moe/waifu-collective/toolbox/raw/branch/master/extras/characterai-dumper/characterai-dumper.user.js
// @updateURL https://git.fuwafuwa.moe/waifu-collective/toolbox/raw/branch/master/extras/characterai-dumper/characterai-dumper.user.js
// ==/UserScript==
const log = (firstArg, ...remainingArgs) =>
console.log(`[CharacterAI Dumper v1.2] ${firstArg}`, ...remainingArgs);
console.log(`[CharacterAI Dumper v1.3] ${firstArg}`, ...remainingArgs);
log.error = (firstArg, ...remainingArgs) =>
console.error(`[CharacterAI Dumper v1.2] ${firstArg}`, ...remainingArgs);
console.error(`[CharacterAI Dumper v1.3] ${firstArg}`, ...remainingArgs);
// Endpoints to intercept.
const CHARACTER_INFO_URL = "https://beta.character.ai/chat/character/info/";
const CHARACTER_EXTRA_INFO_URL = "https://beta.character.ai/chat/character/";
const CHARACTER_HISTORIES_URL =
"https://beta.character.ai/chat/character/histories/";
/** Maps a character's identifier to their basic info + chat histories. */
const characterToSavedDataMap = {};
//
// Code to add download link to the page.
//
const addDownloadLinkFor = (dataString, filename) => {
/** Creates the "Download" link on the "View Saved Chats" page. */
const addDownloadLinkInSavedChats = (dataString, filename) => {
// Don't create duplicate links.
if (document.getElementById("injected-chat-dl-link")) {
return;
@ -48,13 +49,41 @@ const addDownloadLinkFor = (dataString, filename) => {
}
};
//
// Logic to remove personal data from the dumps.
//
/** Creates the "Download" link in the "Character Editor" page. */
const addDownloadLinkInCharacterEditor = (
dataString,
filename,
characterName
) => {
if (document.getElementById("injected-character-info-dl-link")) {
return;
}
const suspectedElements = document.querySelectorAll(
"div.p-0.m-1.mb-3.border.rounded.m-1"
);
for (const element of suspectedElements) {
if (!element.textContent.includes(characterName)) {
continue;
}
const dataBlob = new Blob([dataString], { type: "text/plain" });
const downloadLink = document.createElement("a");
downloadLink.id = "injected-character-info-dl-link";
downloadLink.textContent = "Download";
downloadLink.href = URL.createObjectURL(dataBlob);
downloadLink.download = filename;
downloadLink.style = "padding-left: 66px";
element.appendChild(downloadLink);
}
};
/** Escapes a string so it can be used inside a regex. */
const escapeStringForRegExp = (stringToGoIntoTheRegex) => {
return stringToGoIntoTheRegex.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
};
/** Takes in chat histories and anonymizes them. */
const anonymizeHistories = (histories) => {
const namesToReplace = new Set();
@ -137,16 +166,15 @@ const anonymizeHistories = (histories) => {
return histories;
};
//
// Request intercept and data handling logic.
//
/** Configures XHook to intercept the endpoints we care about. */
const configureXHookIntercepts = () => {
xhook.after((_req, res) => {
try {
const endpoint = res.finalUrl;
if (
endpoint !== CHARACTER_INFO_URL &&
endpoint !== CHARACTER_HISTORIES_URL
endpoint !== CHARACTER_HISTORIES_URL &&
endpoint !== CHARACTER_EXTRA_INFO_URL
) {
// We don't care about other endpoints.
return;
@ -157,6 +185,8 @@ const configureXHookIntercepts = () => {
if (res.finalUrl === CHARACTER_INFO_URL) {
characterIdentifier = data.character.name;
data.character.user__username = "[BOT_CREATOR_NAME_REDACTED]";
log(`Got character info for ${characterIdentifier}, caching...`);
if (!characterToSavedDataMap[characterIdentifier]) {
@ -172,6 +202,30 @@ const configureXHookIntercepts = () => {
}
characterToSavedDataMap[characterIdentifier].histories =
anonymizeHistories(data);
} else if (res.finalUrl === CHARACTER_EXTRA_INFO_URL) {
characterIdentifier = data.character.name;
data.user__username = "[BOT_CREATOR_NAME_REDACTED]";
data.character.user__username = "[BOT_CREATOR_NAME_REDACTED]";
log(
`Got definitions for ${characterIdentifier}, creating download link.`
);
log("If it doesn't show up, here's the data:", JSON.stringify(data));
// The character editor returns all the info we want in a single
// request, so we can just create the button and return from this
// function already.
setTimeout(
() =>
addDownloadLinkInCharacterEditor(
JSON.stringify(data),
`${characterIdentifier} (Definitions).json`,
characterIdentifier
),
2000
);
return;
}
const currentCharacter = characterToSavedDataMap[characterIdentifier];
@ -191,7 +245,7 @@ const configureXHookIntercepts = () => {
// so we wait a little while instead. Probably React re-render fuckery.
setTimeout(
() =>
addDownloadLinkFor(
addDownloadLinkInSavedChats(
JSON.stringify(currentCharacter),
`${characterIdentifier}.json`
),

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB