Rename MediaUtils and add CharAi Dumper
This commit is contained in:
parent
1860d1564e
commit
475c5193d7
3 changed files with 152 additions and 34 deletions
|
|
@ -3,6 +3,8 @@
|
||||||
Various Userscripts
|
Various Userscripts
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
| Site | Link |
|
| Site | Link |
|
||||||
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Jellyfin QuickDelete | [](https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/jellyfin-quick-delete.user.js) |
|
| Jellyfin QuickDelete | [](https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/jellyfin-quick-delete.user.js) |
|
||||||
|
| Character.ai History Dumper | [](https://git.m3.fyi/Marsn3/userscripts/~raw/main/src/charai-history-dumper.user.js) |
|
||||||
|
|
|
||||||
118
src/charai-history-dumper.user.js
Normal file
118
src/charai-history-dumper.user.js
Normal 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");
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name QuickDelete
|
// @name Jellyfin MediaUtils
|
||||||
// @description Quickly delete or refresh items in Jellyfin
|
// @description Utilities for Jellyfin to quickly delete, update or compare media
|
||||||
// @namespace https://git.m3.fyi/Marsn3/userscripts
|
// @namespace https://git.m3.fyi/Marsn3/userscripts
|
||||||
// @version 0.4
|
// @version 0.5
|
||||||
// @author Marsn3
|
// @author Marsn3
|
||||||
// @match https://media.m3.fyi/*
|
// @match YOUR-JELLYFIN-URL
|
||||||
// @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
|
|
||||||
// @grant none
|
// @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
|
// @run-at document-end
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
|
|
@ -14,19 +16,19 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Define Constants
|
// Define Constants
|
||||||
const username = "mars";
|
const PASSWORD = "PASSWORD";
|
||||||
const password = "absw3712mcS";
|
const USERID = "USERID";
|
||||||
const userid = "e55a6ca076a14cb5a6ca9cfaa75498c1";
|
const BASEURL = window.origin;
|
||||||
const baseURL = window.origin;
|
const RE = /\[(.*)\]/;
|
||||||
const re = /\[(.*)\]/;
|
const HEADERS = {
|
||||||
var apiKey = "38346c399d57454da3bdbf47ba716765";
|
"X-Emby-Authorization": `MediaBrowser Client="Jellyfin MediaUtils", Device="Browser", DeviceId="", Version="1.0.0", Token="${apiKey}"`,
|
||||||
|
};
|
||||||
|
var apiKey = "APIKEY";
|
||||||
|
|
||||||
// Authorize user
|
// Authorize user
|
||||||
fetch(`${baseURL}/Users/${userid}/Authenticate/?pw=${password}`, {
|
fetch(`${BASEURL}/Users/${USERID}/Authenticate/?pw=${PASSWORD}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: HEADERS,
|
||||||
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Browser", DeviceId="", Version="1.0.0", Token="${apiKey}"`,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => (apiKey = data.AccessToken)); // Store AccessToken
|
.then((data) => (apiKey = data.AccessToken)); // Store AccessToken
|
||||||
|
|
@ -53,9 +55,7 @@
|
||||||
// Send refresh request
|
// Send refresh request
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: HEADERS,
|
||||||
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Chrome", DeviceId="test", Version="10.8.9", Token="${apiKey}"`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// Replace empty image with rotating spinner
|
// Replace empty image with rotating spinner
|
||||||
this.parentElement.firstChild.firstChild.classList.remove("audiotrack");
|
this.parentElement.firstChild.firstChild.classList.remove("audiotrack");
|
||||||
|
|
@ -75,8 +75,9 @@
|
||||||
}`;
|
}`;
|
||||||
// Add style element to document head
|
// Add style element to document head
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
this.parentElement.style.backgroundColor = "rgba(131, 165, 152, 0.3)"
|
this.parentElement.style.backgroundColor = "rgba(131, 165, 152, 0.3)";
|
||||||
this.parentElement.firstChild.style.backgroundColor = "rgba(69, 133, 136, 0.5)"
|
this.parentElement.firstChild.style.backgroundColor =
|
||||||
|
"rgba(69, 133, 136, 0.5)";
|
||||||
};
|
};
|
||||||
//Return complete element
|
//Return complete element
|
||||||
return el;
|
return el;
|
||||||
|
|
@ -95,23 +96,22 @@
|
||||||
|
|
||||||
// Bind get function
|
// Bind get function
|
||||||
el.onclick = 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}`);
|
console.log(`Fetching ${url}`);
|
||||||
|
|
||||||
// Send deletion request
|
// Send deletion request
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: HEADERS,
|
||||||
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Chrome", DeviceId="test", Version="10.8.9", Token="${apiKey}"`,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) =>
|
.then((data) =>
|
||||||
// Display bitrate
|
// Display bitrate
|
||||||
|
|
||||||
alert(
|
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
|
// Send deletion request
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: HEADERS,
|
||||||
"X-Emby-Authorization": `MediaBrowser Client="QuickDelete", Device="Chrome", DeviceId="test", Version="10.8.9", Token="${apiKey}"`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove parent to provide feedback and prevent double deletion
|
// Remove parent to provide feedback and prevent double deletion
|
||||||
|
|
@ -158,8 +156,8 @@
|
||||||
let curr = collection[i];
|
let curr = collection[i];
|
||||||
if (curr.firstChild.firstChild !== null) {
|
if (curr.firstChild.firstChild !== null) {
|
||||||
if (curr.firstChild.firstChild.classList.contains("audiotrack")) {
|
if (curr.firstChild.firstChild.classList.contains("audiotrack")) {
|
||||||
curr.style.backgroundColor = "rgba(251, 73, 52, 0.7)"
|
curr.style.backgroundColor = "rgba(251, 73, 52, 0.7)";
|
||||||
curr.firstChild.style.backgroundColor = "#cc2412"
|
curr.firstChild.style.backgroundColor = "#cc2412";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue