@lenml/char-card-reader
Version:
SillyTavern character card info reader
156 lines (145 loc) • 4.41 kB
text/typescript
export function toBase64(
input: Blob | Buffer | ArrayBuffer | Uint8Array
): Promise<string> {
if (typeof Buffer !== "undefined" && Buffer.isBuffer(input)) {
// Node.js Buffer
return Promise.resolve(input.toString("base64"));
}
if (typeof Blob !== "undefined" && input instanceof Blob) {
// Browser Blob
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
const base64 = result.split(",")[1]; // data:*/*;base64,...
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(input);
});
}
// ArrayBuffer or Uint8Array
let uint8: Uint8Array;
if (input instanceof ArrayBuffer) {
uint8 = new Uint8Array(input);
} else if (input instanceof Uint8Array) {
uint8 = input;
} else {
throw new TypeError("Unsupported input type");
}
if (typeof Buffer !== "undefined") {
// Node.js environment
return Promise.resolve(Buffer.from(uint8).toString("base64"));
} else {
// Browser: use btoa
let binary = "";
for (let i = 0; i < uint8.length; i++) {
binary += String.fromCharCode(uint8[i]);
}
return Promise.resolve(btoa(binary));
}
}
export function isValidImageUrl(url: any) {
if (typeof url !== "string") {
return false;
}
// http 或者 data:
if (
url.startsWith("http://") ||
url.startsWith("https://") ||
url.startsWith("data:")
) {
return true;
}
return false;
}
export const deepClone: <T>(x: T) => T = globalThis.structuredClone
? globalThis.structuredClone
: <T>(x: T): T => JSON.parse(JSON.stringify(x));
export class Base64 {
static encode(input: string): string {
if (typeof window !== "undefined" && typeof window.btoa === "function") {
return window.btoa(unescape(encodeURIComponent(input)));
} else if (typeof Buffer !== "undefined") {
return Buffer.from(input, "utf-8").toString("base64");
} else {
throw new Error("Base64.encode: Environment not supported.");
}
}
static decode(base64: string): string {
if (typeof window !== "undefined" && typeof window.atob === "function") {
return decodeURIComponent(escape(window.atob(base64)));
} else if (typeof Buffer !== "undefined") {
return Buffer.from(base64, "base64").toString("utf-8");
} else {
throw new Error("Base64.decode: Environment not supported.");
}
}
}
function isDeepEqual(a: any, b: any) {
if (a === b) {
return true;
}
if (
a === null ||
b === null ||
typeof a !== "object" ||
typeof b !== "object"
) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!isDeepEqual(a[i], b[i])) {
return false;
}
}
return true;
}
const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) {
return false;
}
for (const key of keys) {
if (!isDeepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
function isObject(x: any): x is object {
return typeof x === "object" && x !== null;
}
function isNoNone(x: any): x is any {
return x !== undefined && x !== null;
}
/**
* Recursively merge objects into a single object.
*
* If all values for a key are objects, merge them recursively.
* If any value for a key is not an object, take the last non-null value.
* @param {...any[]} objects objects to merge
* @returns merged object
*/
export function mergeObjects(...objects: any[]): any {
const keys = new Set(objects.flatMap(Object.keys));
return Object.fromEntries(
Array.from(keys).map((key) => {
const vals = objects.map((x) => x[key]);
if (vals.some(Array.isArray)) {
// NOTE: 直接用最后一个非空,数组没有合并逻辑
return [key, [...vals].reverse().filter(isNoNone).filter(Boolean)[0]];
}
if (vals.some(isObject)) {
return [key, mergeObjects(...vals.filter(isObject))];
}
return [key, [...vals].reverse().filter(isNoNone)[0]];
})
);
}
export function uniq<T>(arr: T[]): T[] {
return Array.from(new Set(arr));
}