Auto-Cards/utils/ai.dungeon/index.js
Magic e9d2900142
updated workspace
Signed-off-by: Magic <magicoflolis@tuta.io>
2025-05-26 12:56:10 -07:00

418 lines
17 KiB
JavaScript

"use strict";
import path from "path";
import fs from "node:fs";
import Watchpack from "watchpack";
import axios from "axios";
import { minify } from "terser";
const log = (...msg) => {
console.log("[LOG]", ...msg);
};
const err = (...msg) => {
console.error("[ERROR]", ...msg);
};
/**
* @template O
* @param { O } obj
* @returns { string }
*/
const objToStr = (obj) => Object.prototype.toString.call(obj).match(/\[object (.*)\]/)[1];
/**
* Object is typeof `object` / JSON Object
* @template O
* @param { O } obj
*/
const isObj = (obj) => objToStr(obj) === "Object";
/**
* Object is `null` or `undefined`
* @template O
* @param { O } obj
*/
const isNull = (obj) => {
return Object.is(obj, null) || Object.is(obj, undefined);
};
/**
* Object is Blank
* @template O
* @param { O } obj
*/
const isBlank = (obj) => {
return (
(typeof obj === "string" && Object.is(obj.trim(), "")) ||
((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) ||
(Array.isArray(obj) && Object.is(obj.length, 0)) ||
(isObj(obj) && Object.is(Object.keys(obj).length, 0))
);
};
/**
* Object is Empty
* @template O
* @param { O } obj
*/
const isEmpty = (obj) => {
return isNull(obj) || isBlank(obj);
};
const toTime = () => {
return new Intl.DateTimeFormat("default", {
hour: "numeric",
minute: "numeric",
second: "numeric",
fractionalSecondDigits: 3
}).format(new Date());
};
/**
* @param {import('node:fs').PathLike} filePath
* @param {string} encoding
*/
const canAccess = async (filePath, encoding = "utf-8") => {
try {
await fs.promises.access(filePath, fs.promises.constants.R_OK | fs.promises.constants.W_OK);
const data = await fs.promises.readFile(filePath, encoding);
return data.toString(encoding);
} catch (e) {
err(e);
}
};
// /**
// * @returns {Promise<string>}
// */
// const getToken = () => {
// return new Promise((resolve, reject) => {
// if (typeof indexedDB !== "undefined") {
// const dbReq = indexedDB.open("firebaseLocalStorageDb");
// dbReq.onerror = reject;
// dbReq.onsuccess = (event) => {
// const transaction = event.target.result.transaction(["firebaseLocalStorage"], "readwrite");
// const objectStore = transaction.objectStore("firebaseLocalStorage");
// const allKeys = objectStore.getAllKeys();
// allKeys.onerror = reject;
// allKeys.onsuccess = (evt) => {
// const key = evt.target.result.find((r) => r.includes("firebase:authUser:"));
// objectStore.get(key).onsuccess = (evt) => {
// const { value } = evt.target.result;
// resolve(`AID_TOKEN="${value.stsTokenManager.accessToken}"`);
// };
// };
// };
// } else {
// reject(new Error("indexedDB is not defined."))
// }
// });
// };
const AIDRequest = class {
#_;
/** @type {string} */
token;
/**
* @type { { operationName: string; variables: {[key: string]: unknown}; query: string; } }
*/
data = {};
constructor(accessToken) {
this.token = accessToken;
}
get headers() {
return this.#_;
}
/**
* @param {string} data
*/
set headers(data) {
this.#_ = {
authorization: `firebase ${this.token}`,
"content-type": "application/json",
"x-gql-operation-name": data,
"Sec-GPC": "1",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
Priority: "u=4"
};
}
/**
* @param {this["data"]} d
*/
setBody(d) {
d.variables ??= {};
this.data = d;
return this;
}
format() {
const { headers, data } = this;
return {
headers,
data
};
}
async fetch(type, shortId) {
const resp = {
data: {}
};
try {
const template = {
adventure: {
headers: {
"x-gql-operation-name": "GetGameplayAdventure"
},
body: {
operationName: "GetGameplayAdventure",
variables: { shortId, limit: 1000000, desc: true },
query:
"query GetGameplayAdventure($shortId: String, $limit: Int, $offset: Int, $desc: Boolean) {\n adventure(shortId: $shortId) {\n id\n publicId\n shortId\n scenarioId\n instructions\n title\n description\n tags\n nsfw\n isOwner\n userJoined\n gameState\n actionCount\n contentType\n createdAt\n showComments\n commentCount\n allowComments\n voteCount\n userVote\n editedAt\n published\n unlisted\n deletedAt\n saveCount\n isSaved\n user {\n id\n isCurrentUser\n isMember\n profile {\n id\n title\n thumbImageUrl\n __typename\n }\n __typename\n }\n shortCode\n thirdPerson\n imageStyle\n memory\n authorsNote\n image\n actionWindow(limit: $limit, offset: $offset, desc: $desc) {\n id\n imageText\n ...ActionSubscriptionAction\n __typename\n }\n allPlayers {\n ...PlayerSubscriptionPlayer\n __typename\n }\n storyCards {\n id\n ...StoryCard\n __typename\n }\n __typename\n }\n}\n\nfragment ActionSubscriptionAction on Action {\n id\n text\n type\n imageUrl\n shareUrl\n imageText\n adventureId\n decisionId\n undoneAt\n deletedAt\n createdAt\n logId\n __typename\n}\n\nfragment PlayerSubscriptionPlayer on Player {\n id\n userId\n characterName\n isTypingAt\n user {\n id\n isMember\n profile {\n id\n title\n thumbImageUrl\n __typename\n }\n __typename\n }\n createdAt\n deletedAt\n blockedAt\n __typename\n}\n\nfragment StoryCard on StoryCard {\n id\n type\n keys\n value\n title\n useForCharacterCreation\n description\n updatedAt\n deletedAt\n __typename\n}"
}
},
adventureDetails: {
body: {
operationName: "GetAdventureDetails",
variables: { shortId },
query:
"query GetAdventureDetails($shortId: String) {\n adventureState(shortId: $shortId) {\n id\n details\n __typename\n }\n}"
}
},
scenario: {
headers: {
"x-gql-operation-name": "GetScenario"
},
body: {
operationName: "GetScenario",
variables: { shortId },
query:
"query GetScenario($shortId: String) {\n scenario(shortId: $shortId) {\n id\n contentType\n createdAt\n editedAt\n publicId\n shortId\n title\n description\n prompt\n memory\n authorsNote\n image\n isOwner\n published\n unlisted\n allowComments\n showComments\n commentCount\n voteCount\n userVote\n saveCount\n storyCardCount\n isSaved\n tags\n adventuresPlayed\n thirdPerson\n nsfw\n contentRating\n contentRatingLockedAt\n contentRatingLockedMessage\n tags\n type\n details\n parentScenario {\n id\n shortId\n title\n __typename\n }\n user {\n isCurrentUser\n isMember\n profile {\n title\n thumbImageUrl\n __typename\n }\n __typename\n }\n options {\n id\n userId\n shortId\n title\n prompt\n parentScenarioId\n deletedAt\n __typename\n }\n storyCards {\n id\n ...StoryCard\n __typename\n }\n ...CardSearchable\n __typename\n }\n}\n\nfragment CardSearchable on Searchable {\n id\n contentType\n publicId\n shortId\n title\n description\n image\n tags\n userVote\n voteCount\n published\n unlisted\n publishedAt\n createdAt\n isOwner\n editedAt\n deletedAt\n blockedAt\n isSaved\n saveCount\n commentCount\n userId\n contentRating\n user {\n id\n isMember\n profile {\n id\n title\n thumbImageUrl\n __typename\n }\n __typename\n }\n ... on Adventure {\n actionCount\n userJoined\n playPublicId\n unlisted\n playerCount\n __typename\n }\n ... on Scenario {\n adventuresPlayed\n __typename\n }\n __typename\n}\n\nfragment StoryCard on StoryCard {\n id\n type\n keys\n value\n title\n useForCharacterCreation\n description\n updatedAt\n deletedAt\n __typename\n}"
}
},
scenarioScripting: {
operationName: "GetScenarioScripting",
variables: { shortId },
query:
"query GetScenarioScripting($shortId: String) {\n scenario(shortId: $shortId) {\n gameCodeSharedLibrary\n gameCodeOnInput\n gameCodeOnOutput\n gameCodeOnModelContext\n recentScriptLogs\n lastModelContext\n }\n}"
},
aiVersions: {
headers: {
"x-gql-operation-name": "GetAiVersions"
},
body: {
operationName: "GetAiVersions",
variables: {},
query:
"query GetAiVersions {\n aiVisibleVersions {\n success\n message\n aiVisibleVersions {\n id\n type\n versionName\n aiDetails\n aiSettings\n access\n release\n available\n instructions\n engineNameEngine {\n engineName\n available\n availableSettings\n __typename\n }\n __typename\n }\n visibleTextVersions {\n id\n type\n versionName\n aiDetails\n aiSettings\n access\n release\n available\n instructions\n engineNameEngine {\n engineName\n available\n availableSettings\n __typename\n }\n __typename\n }\n visibleImageVersions {\n id\n type\n versionName\n aiDetails\n aiSettings\n access\n release\n available\n instructions\n engineNameEngine {\n engineName\n available\n availableSettings\n __typename\n }\n __typename\n }\n __typename\n }\n}"
}
},
importStoryCards: {
headers: {
"x-gql-operation-name": "ImportStoryCards"
},
body: {
operationName: "ImportStoryCards",
variables: {
input: shortId
},
query:
"mutation ImportStoryCards($input: ImportStoryCardsInput!) { importStoryCards(input: $input) { success message storyCards { keys value type __typename } __typename }}"
}
},
UpdateScenario: {
headers: {
"x-gql-operation-name": "UpdateScenario"
},
body: {
operationName: "UpdateScenario",
variables: {
input: shortId
},
query:
"mutation UpdateScenario($input: ScenarioInput) { updateScenario(input: $input) { scenario { id title description prompt memory authorsNote tags nsfw contentRating contentRatingLockedAt contentRatingLockedMessage published thirdPerson allowComments unlisted image uploadId type details editedAt __typename } message success __typename }}"
}
},
UpdateScenarioScripts: {
headers: {
"x-gql-operation-name": "UpdateScenarioScripts"
},
body: {
operationName: "UpdateScenarioScripts",
variables: shortId,
query:
"mutation UpdateScenarioScripts($shortId: String, $gameCode: JSONObject) { updateScenarioScripts(shortId: $shortId, gameCode: $gameCode) { success message scenario { id gameCodeSharedLibrary gameCodeOnInput gameCodeOnOutput gameCodeOnModelContext __typename } __typename }}"
}
},
UpdateOptionTitle: {
headers: {
"x-gql-operation-name": "UpdateOptionTitle"
},
body: {
operationName: "UpdateOptionTitle",
variables: {
input: shortId
},
query:
"mutation UpdateOptionTitle($input: ScenarioInput) { updateScenario(input: $input) { scenario { id shortId title prompt parentScenarioId deletedAt __typename } message success __typename }}"
}
},
UpdateAdventureState: {
headers: {
"x-gql-operation-name": "UpdateAdventureState"
},
body: {
operationName: "UpdateAdventureState",
variables: {
input: shortId
},
query:
"mutation UpdateAdventureState($input: AdventureStateInput) { updateAdventureState(input: $input) { adventure { id details editedAt __typename } message success __typename }}"
}
},
UpdateAdventurePlot: {
headers: {
"x-gql-operation-name": "UpdateAdventurePlot"
},
body: {
operationName: "UpdateAdventurePlot",
variables: {
input: shortId
},
query:
"mutation UpdateAdventurePlot($input: AdventurePlotInput) { updateAdventurePlot(input: $input) { adventure { id thirdPerson memory authorsNote editedAt __typename } message success __typename }}"
}
}
};
const sel = template[type];
if (!sel) {
return resp;
}
this.headers = sel.headers;
this.setBody(sel.body);
const req = await axios({
method: "post",
url: "/graphql",
baseURL: "https://api.aidungeon.com",
timeout: 1000,
responseType: "json",
...this.format()
});
Object.assign(resp, req);
return resp;
} catch (ex) {
err(ex);
}
return resp;
}
toJSON() {
const { operationName, variables, query } = this.data;
return {
operationName,
variables,
query
};
}
};
const minifyCode = async (code) => {
return (await minify(code, { ecma: 2022 })).code;
};
async function build() {
try {
const { AID_ENV, AID_TOKEN, AID_SHORTID } = process.env;
const isDev = typeof AID_ENV === "string" && AID_ENV === "development";
const r = new AIDRequest(AID_TOKEN);
const buildFiles = async () => {
try {
const files = await Promise.all([
canAccess("./src/context.js").then(async (c) => {
if (!isDev) {
await fs.promises.writeFile("./dist/context.js", c);
}
return { onModelContext: c };
}),
canAccess("./src/input.js").then(async (c) => {
if (!isDev) {
await fs.promises.writeFile("./dist/input.js", c);
}
return { onInput: c };
}),
canAccess("./src/library.js").then(async (c) => {
if (!isDev) {
await fs.promises.writeFile("./dist/library.js", c);
await fs.promises.writeFile("./dist/library.min.js", await minifyCode(c));
}
return { sharedLibrary: c };
}),
canAccess("./src/output.js").then(async (c) => {
if (!isDev) {
await fs.promises.writeFile("./dist/output.js", c);
}
return { onOutput: c };
})
]);
if (!isDev) {
log("Created Files:", {
time: toTime()
});
}
if (typeof AID_SHORTID === "string" && !isEmpty(AID_SHORTID)) {
const gameCode = {};
for (const f of files) {
for (const [k, v] of Object.entries(f)) {
gameCode[k] = v;
}
}
const { data } = await r.fetch("UpdateScenarioScripts", {
gameCode,
shortId: AID_SHORTID
});
if (isEmpty(data.data)) {
err(data);
return;
}
if (data.data.errors && Array.isArray(data.data.errors)) {
throw new Error(data.data.errors[0].message)
}
log("UpdateScenarioScripts:", {
message: data.data.updateScenarioScripts.message,
time: toTime()
});
}
} catch (ex) {
err(ex);
}
};
log(`Node ENV: ${AID_ENV}`);
await buildFiles();
if (isDev) {
const wp = new Watchpack();
let changed = new Set();
wp.watch(["context.js", "input.js", "library.js", "output.js"], ["./src"]);
wp.on("change", (changedFile, mtime) => {
if (mtime === null) {
changed.delete(changedFile);
} else {
changed.add(changedFile);
}
});
wp.on("aggregated", async () => {
// Filter out files that start with a dot from detected changes
// (as they are hidden files or temp files created by an editor).
const changes = Array.from(changed).filter((filePath) => {
return !path.basename(filePath).startsWith(".");
});
changed = new Set();
if (changes.length === 0) {
return;
}
await buildFiles();
});
} else {
process.exit(0);
}
} catch (ex) {
err(ex);
}
}
build();
// export { build };
// export default build;