1 Commits

Author SHA1 Message Date
SoniaNvm
3bc20ea40d attempt at metadata grabbing 2026-02-02 02:27:09 -08:00
4 changed files with 876 additions and 873 deletions

View File

@@ -2,4 +2,4 @@
# Disable Discord community banner (optional) # Disable Discord community banner (optional)
# Uncomment the line below to hide the Discord banner # Uncomment the line below to hide the Discord banner
# NEXT_PUBLIC_DISABLE_DISCORD_BANNER=true NEXT_PUBLIC_DISABLE_DISCORD_BANNER=true

5
.gitignore vendored
View File

@@ -1,4 +1,9 @@
.idea/
bun.lock
.env
node_modules node_modules
.next .next
.env.local .env.local
.env*.local .env*.local
.env

View File

@@ -2,6 +2,5 @@ services:
web: web:
build: . build: .
image: sucker image: sucker
restart: unless-stopped
ports: ports:
- "3000:3000" - "3000:3000"

View File

@@ -89,6 +89,9 @@ export default function Home() {
"https://sucker.severian.dev/api/proxy" "https://sucker.severian.dev/api/proxy"
); );
const [pageSource, setPageSource] = useState("");
const [metadataJson, setMetadataJson] = useState<any>(null);
const fetchCards = async () => { const fetchCards = async () => {
try { try {
setIsRefreshing(true); setIsRefreshing(true);
@@ -233,6 +236,15 @@ export default function Home() {
const arrayBuffer = await pngBlob.arrayBuffer(); const arrayBuffer = await pngBlob.arrayBuffer();
// Fallback if metadataJson hasn't been fetched for this card
const safeMetadata = metadataJson || {};
const creator = (safeMetadata.creator_name || (card.data as any).creator || "Unknown") + (safeMetadata.creator_verified ? " ✅" : "");
// Tag Parsing
const normalTags = (safeMetadata.tags || (card.data as any).tags || []).map((t: any) => t.name);
const customTags = safeMetadata.custom_tags || [];
const allTagsArray = [...normalTags, ...customTags];
// Use initial version for PNG embedding, or current version if no initial version available // Use initial version for PNG embedding, or current version if no initial version available
const chosen = card.initialVersion || card.data; const chosen = card.initialVersion || card.data;
const pngData = { const pngData = {
@@ -244,13 +256,13 @@ export default function Home() {
personality: chosen.personality, personality: chosen.personality,
mes_example: chosen.mes_example, mes_example: chosen.mes_example,
scenario: chosen.scenario, scenario: chosen.scenario,
creator: (chosen as any).creator || "", creator: creator,
creator_notes: (chosen as any).creator_notes || "", creator_notes: safeMetadata.description || (chosen as any).creator_notes || "",
system_prompt: (chosen as any).system_prompt || "", system_prompt: (chosen as any).system_prompt || "",
post_history_instructions: post_history_instructions:
(chosen as any).post_history_instructions || "", (chosen as any).post_history_instructions || "",
tags: (chosen as any).tags || [], tags: allTagsArray,
character_version: (chosen as any).character_version || "1", character_version: safeMetadata.name || (chosen as any).character_version || "1",
extensions: (chosen as any).extensions || {}, extensions: (chosen as any).extensions || {},
}, },
spec: card.spec || "chara_card_v2", spec: card.spec || "chara_card_v2",
@@ -295,39 +307,9 @@ export default function Home() {
const isCharacterUrl = /janitorai\.com\/characters\//.test(characterUrl); const isCharacterUrl = /janitorai\.com\/characters\//.test(characterUrl);
if (isCharacterUrl) { if (isCharacterUrl) {
// Extract character ID and open metadata page, then show second input // Open html source, then show second input
const match = characterUrl.match(/characters\/([\w-]+)/); window.open(`view-source:${characterUrl}`, "_blank");
if (match && match[1]) {
const characterId = match[1].split("_")[0];
window.open(
`https://janitorai.com/hampter/characters/${characterId}`,
"_blank"
);
setIsMetadataOpen(true); setIsMetadataOpen(true);
}
return;
}
// Check if the input is a direct image link (webp filename or full image URL)
const isImagePath = /\.(webp|png|jpg|jpeg|gif)(\?.*)?$/i.test(characterUrl);
const isFullImageUrl = (characterUrl.startsWith("http://") || characterUrl.startsWith("https://")) && isImagePath;
const isWebpFilename = /^[\w-]+\.(webp|png|jpg|jpeg|gif)$/i.test(characterUrl);
if (isFullImageUrl || isWebpFilename) {
// Directly set the avatar URL without opening metadata
if (selectedCardIndex === null) return;
const avatarUrl = isFullImageUrl
? characterUrl
: `https://ella.janitorai.com/bot-avatars/${characterUrl}`;
const updatedCards = [...cards];
updatedCards[selectedCardIndex] = {
...updatedCards[selectedCardIndex],
avatarUrl,
};
setCards(updatedCards);
setDialogOpen(false);
return; return;
} }
}; };
@@ -336,7 +318,42 @@ export default function Home() {
if (selectedCardIndex === null) return; if (selectedCardIndex === null) return;
try { try {
const avatarUrl = `https://ella.janitorai.com/bot-avatars/${avatarPath}`; const storeKey = "Sk--a:a-a--characterStore";
// Define the anchor points we are looking for
const prefix = 'window.mbxM.push(JSON.parse("';
const suffix = '"));';
// Locate the script content
const startIndex = pageSource.indexOf(prefix);
if (startIndex === -1) {
throw new Error("Could not find character data in the page source.");
}
// Move index to the start of the actual JSON string
const jsonStartIndex = startIndex + prefix.length;
// Find the end of the statement
const jsonEndIndex = pageSource.indexOf(suffix, jsonStartIndex);
if (jsonEndIndex === -1) {
throw new Error("Could not find closing tag for JSON content.");
}
// Extract the escaped string
const escapedJsonString = pageSource.substring(jsonStartIndex, jsonEndIndex);
// Wrap it in quotes so it gets treated as a string containing JSON
const rawJsonString = JSON.parse(`"${escapedJsonString}"`);
console.log(rawJsonString);
// Parse the actual JSON data into an object
const data = JSON.parse(rawJsonString);
// Return the specific character data
const char = data[storeKey].character;
setMetadataJson(char);
const avatarUrl = `https://ella.janitorai.com/bot-avatars/${char.avatar}`;
const updatedCards = [...cards]; const updatedCards = [...cards];
updatedCards[selectedCardIndex] = { updatedCards[selectedCardIndex] = {
...updatedCards[selectedCardIndex], ...updatedCards[selectedCardIndex],
@@ -742,7 +759,7 @@ export default function Home() {
onClick={() => handleOpenDialog(index)} onClick={() => handleOpenDialog(index)}
variant="outline" variant="outline"
> >
Fetch Avatar (required for PNG) Fetch Metadata
</Button> </Button>
) : ( ) : (
<Button <Button
@@ -750,7 +767,7 @@ export default function Home() {
variant="default" variant="default"
disabled={!card.avatarUrl} disabled={!card.avatarUrl}
> >
Download PNG Download Complete Card
</Button> </Button>
)} )}
</div> </div>
@@ -993,43 +1010,25 @@ export default function Home() {
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{isMetadataOpen ? "Enter Avatar Path" : "Fetch Avatar"} {isMetadataOpen ? "Enter Page Source Code" : "Fetch Metadata"}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
{isMetadataOpen {isMetadataOpen ? "Paste the entire source code here." : "Enter a character URL (janitorai.com/characters/...) to open page source."}
? "Look for the avatar field in the opened tab and paste the value here."
: "Enter a character URL (janitorai.com/characters/...) to open metadata, or paste an image filename (id.webp) or full image URL directly."}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{isMetadataOpen ? ( {isMetadataOpen ? (
<div className="space-y-4"> <div className="space-y-4">
<Input <Input placeholder="<!DOCTYPE html><html..." value={pageSource} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPageSource(e.target.value)} />
placeholder="id.webp"
value={avatarPath}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setAvatarPath(e.target.value)
}
/>
<Button onClick={handleFetchAvatar} className="w-full"> <Button onClick={handleFetchAvatar} className="w-full">
Fetch Avatar Fetch Metadata
</Button> </Button>
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
<Input <Input placeholder="URL" value={characterUrl} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setCharacterUrl(e.target.value)} />
placeholder="URL or id.webp"
value={characterUrl}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setCharacterUrl(e.target.value)
}
/>
<p className="text-sm text-muted-foreground">
For character URLs, a new tab will open with metadata. For image
filenames or full image URLs, the avatar will be set directly.
</p>
<Button onClick={handleOpenMetadata} className="w-full"> <Button onClick={handleOpenMetadata} className="w-full">
Fetch Avatar Fetch Metadata
</Button> </Button>
</div> </div>
)} )}