Rename MediaUtils and add CharAi Dumper

This commit is contained in:
Mars Niermann 2023-04-08 05:30:32 +02:00
parent 1860d1564e
commit 475c5193d7
No known key found for this signature in database
GPG key ID: B2D0FC62A74FC971
3 changed files with 152 additions and 34 deletions

View file

@ -3,6 +3,8 @@
Various Userscripts
### Installation
| Site | Link |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Jellyfin QuickDelete | [![Install this Userscript](https://img.shields.io/badge/Install-Userscript-282828.svg)](https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/jellyfin-quick-delete.user.js) |
| Site | Link |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Jellyfin QuickDelete | [![Install this Userscript](https://img.shields.io/badge/Install-Userscript-282828.svg)](https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/jellyfin-quick-delete.user.js) |
| Character.ai History Dumper | [![Install this Userscript](https://img.shields.io/badge/Install-Userscript-282828.svg)](https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/charai-history-dumper.user.js) |

View file

@ -0,0 +1,118 @@
// ==UserScript==
// @name CharacterAI History Dumper
// @namespace https://git.m3.fyi/Marsn3/userscripts
// @match https://beta.character.ai/*
// @grant none
// @version 1.0
// @author Marsn3
// @description Allows downloading saved chat messages from CharacterAI.
// @downloadURL https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/charai-history-dumper.user
// @updateURL https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/charai-history-dumper.user
// ==/UserScript==
const log = (firstArg, ...remainingArgs) =>
console.log(
`[CharacterAI History Dumper v1.0] ${firstArg}`,
...remainingArgs
);
log.error = (firstArg, ...remainingArgs) =>
console.error(
`[CharacterAI History Dumper v1.0] ${firstArg}`,
...remainingArgs
);
// Endpoints to intercept.
const CHARACTER_MESSAGES_URL =
"https://beta.character.ai/chat/history/external/msgs/?history";
/** Maps a character's identifier to their basic info + chat histories. */
const characterToSavedDataMap = {};
/** 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;
}
// We want to add a link next to the "your past conversations with XXX" text.
const element = document.getElementsByClassName("postButtonCaption")[0];
const dataBlob = new Blob([dataString], { type: "text/plain" });
const downloadLink = document.createElement("a");
downloadLink.id = "injected-chat-dl-link";
downloadLink.textContent = "Download";
downloadLink.href = URL.createObjectURL(dataBlob);
downloadLink.download = filename;
downloadLink.style = "padding-left: 8px";
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. */
/** Configures XHook to intercept the endpoints we care about. */
const configureXHookIntercepts = () => {
xhook.after((_req, res) => {
try {
const endpoint = res.finalUrl;
if (endpoint !== undefined) {
if (endpoint.split("=")[0] !== CHARACTER_MESSAGES_URL) {
// We don't care about other endpoints.
return;
} else {
console.log("Found URL");
}
const data = JSON.parse(res.data);
let characterIdentifier;
characterIdentifier = data.messages[0].src__name.trim();
// We have all the downloadable data for this character, and we're on the
// correct page. Create the download link.
log(
`Got all the data for ${characterIdentifier}, creating download link.`
);
//log("If it doesn't show up, here's the data:", JSON.stringify(data));
// For some reason, the link doesn't get added if we call this right now,
// so we wait a little while instead. Probably React re-render fuckery.
addDownloadLinkInSavedChats(
JSON.stringify(data),
`${characterIdentifier}.json`
);
}
} catch (err) {
log.error("ERROR:", err);
}
});
};
// This is where XHook (lib for intercepting XHR/AJAX calls) gets injected into
// the document, and once it gets properly parsed it'll call out to the setup
// function.
//
// Copy-pasted and slightly adapted from: https://stackoverflow.com/a/8578840
log("Injecting XHook to intercept XHR/AJAX calls.");
(function (document, elementTagName, elementTagId) {
var js,
fjs = document.getElementsByTagName(elementTagName)[0];
if (document.getElementById(elementTagId)) {
return;
}
js = document.createElement(elementTagName);
js.id = elementTagId;
js.onload = function () {
log("Done! Configuring intercepts.");
configureXHookIntercepts();
};
// Link to hosted version taken from the official repo:
// https://github.com/jpillora/xhook
js.src = "https://jpillora.com/xhook/dist/xhook.min.js";
fjs.parentNode.insertBefore(js, fjs);
})(document, "script", "xhook");

View file

@ -1,12 +1,14 @@
// ==UserScript==
// @name QuickDelete
// @description Quickly delete or refresh items in Jellyfin
// @name Jellyfin MediaUtils
// @description Utilities for Jellyfin to quickly delete, update or compare media
// @namespace https://git.m3.fyi/Marsn3/userscripts
// @version 0.4
// @version 0.5
// @author Marsn3
// @match https://media.m3.fyi/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @match YOUR-JELLYFIN-URL
// @grant none
// @downloadURL https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/jellyfin-mediautils.user
// @updateURL https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/jellyfin-mediautils.user
// @run-at document-end
// ==/UserScript==
@ -14,19 +16,19 @@
"use strict";
// Define Constants
const username = "mars";
const password = "absw3712mcS";
const userid = "e55a6ca076a14cb5a6ca9cfaa75498c1";
const baseURL = window.origin;
const re = /\[(.*)\]/;
var apiKey = "38346c399d57454da3bdbf47ba716765";
const PASSWORD = "PASSWORD";
const USERID = "USERID";
const BASEURL = window.origin;
const RE = /\[(.*)\]/;
const HEADERS = {
"X-Emby-Authorization": `MediaBrowser Client="Jellyfin MediaUtils", Device="Browser", DeviceId="", Version="1.0.0", Token="${apiKey}"`,
};
var apiKey = "APIKEY";
// Authorize user
fetch(`${baseURL}/Users/${userid}/Authenticate/?pw=${password}`, {
fetch(`${BASEURL}/Users/${USERID}/Authenticate/?pw=${PASSWORD}`, {
method: "POST",
headers: {
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Browser", DeviceId="", Version="1.0.0", Token="${apiKey}"`,
},
headers: HEADERS,
})
.then((response) => response.json())
.then((data) => (apiKey = data.AccessToken)); // Store AccessToken
@ -53,9 +55,7 @@
// Send refresh request
fetch(url, {
method: "POST",
headers: {
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Chrome", DeviceId="test", Version="10.8.9", Token="${apiKey}"`,
},
headers: HEADERS,
});
// Replace empty image with rotating spinner
this.parentElement.firstChild.firstChild.classList.remove("audiotrack");
@ -75,8 +75,9 @@
}`;
// Add style element to document head
document.head.appendChild(style);
this.parentElement.style.backgroundColor = "rgba(131, 165, 152, 0.3)"
this.parentElement.firstChild.style.backgroundColor = "rgba(69, 133, 136, 0.5)"
this.parentElement.style.backgroundColor = "rgba(131, 165, 152, 0.3)";
this.parentElement.firstChild.style.backgroundColor =
"rgba(69, 133, 136, 0.5)";
};
//Return complete element
return el;
@ -95,23 +96,22 @@
// Bind get function
el.onclick = function () {
let url = `${window.origin}/Users/${userid}/Items/${this.parentElement.dataset.id}`;
let url = `${window.origin}/Users/${USERID}/Items/${this.parentElement.dataset.id}`;
console.log(`Fetching ${url}`);
// Send deletion request
fetch(url, {
method: "GET",
headers: {
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Chrome", DeviceId="test", Version="10.8.9", Token="${apiKey}"`,
},
headers: HEADERS,
})
.then((response) => response.json())
.then((data) =>
// Display bitrate
alert(
`${data.MediaStreams[0].BitRate.toString().substring(0, 4)}kbps | ${data.MediaStreams[0].BitRate.toString().length} | ${re.exec(data.Path)[1]
}`
`${data.MediaStreams[0].BitRate.toString().substring(0, 4)}kbps | ${
data.MediaStreams[0].BitRate.toString().length
} | ${RE.exec(data.Path)[1]}`
)
);
};
@ -138,9 +138,7 @@
// Send deletion request
fetch(url, {
method: "DELETE",
headers: {
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Chrome", DeviceId="test", Version="10.8.9", Token="${apiKey}"`,
},
headers: HEADERS,
});
// Remove parent to provide feedback and prevent double deletion
@ -158,8 +156,8 @@
let curr = collection[i];
if (curr.firstChild.firstChild !== null) {
if (curr.firstChild.firstChild.classList.contains("audiotrack")) {
curr.style.backgroundColor = "rgba(251, 73, 52, 0.7)"
curr.firstChild.style.backgroundColor = "#cc2412"
curr.style.backgroundColor = "rgba(251, 73, 52, 0.7)";
curr.firstChild.style.backgroundColor = "#cc2412";
}
}