import { language } from "src/lang" import { alertConfirm, alertError, alertModuleSelect, alertNormal, alertStore } from "../alert" import { getCurrentCharacter, getCurrentChat, getDatabase, setCurrentCharacter, setDatabase, type customscript, type loreBook, type triggerscript } from "../storage/database.svelte" import { AppendableBuffer, downloadFile, isNodeServer, isTauri, readImage, saveAsset } from "../globalApi.svelte" import { selectSingleFile, sleep } from "../util" import { v4 } from "uuid" import { convertExternalLorebook } from "./lorebook.svelte" import { decodeRPack, encodeRPack } from "../rpack/rpack_bg" import { convertImage } from "../parser.svelte" import { Capacitor } from "@capacitor/core" import { HideIconStore, moduleBackgroundEmbedding, ReloadGUIPointer } from "../stores.svelte" import {get} from "svelte/store" export interface RisuModule{ name: string description: string lorebook?: loreBook[] regex?: customscript[] cjs?: string trigger?: triggerscript[] id: string lowLevelAccess?: boolean hideIcon?: boolean backgroundEmbedding?:string assets?:[string,string,string][] namespace?:string customModuleToggle?:string } export async function exportModule(module:RisuModule, arg:{ alertEnd?:boolean saveData?:boolean } = {}){ const alertEnd = arg.alertEnd ?? true const saveData = arg.saveData ?? true const apb = new AppendableBuffer() const writeLength = (len:number) => { const lenbuf = Buffer.alloc(4) lenbuf.writeUInt32LE(len, 0) apb.append(lenbuf) } const writeByte = (byte:number) => { //byte is 0-255 const buf = Buffer.alloc(1) buf.writeUInt8(byte, 0) apb.append(buf) } const assets = module.assets ?? [] module = safeStructuredClone(module) module.assets ??= [] module.assets = module.assets.map((asset) => { return [asset[0], '', asset[2]] as [string,string,string] }) const mainbuf = await encodeRPack(Buffer.from(JSON.stringify({ module: module, type: 'risuModule' }, null, 2), 'utf-8')) writeByte(111) //magic number writeByte(0) //version writeLength(mainbuf.length) apb.append(mainbuf) for(let i=0;i { let pos = 0 const readLength = () => { const len = buf.readUInt32LE(pos) pos += 4 return len } const readByte = () => { const byte = buf.readUInt8(pos) pos += 1 return byte } const readData = (len:number) => { const data = buf.subarray(pos, pos + len) pos += len return data } if(readByte() !== 111){ console.error("Invalid magic number") alertError(language.errors.noData) return } if(readByte() !== 0){ //Version check console.error("Invalid version") alertError(language.errors.noData) return } const mainLen = readLength() const mainData = readData(mainLen) const main:{ type:'risuModule' module:RisuModule } = JSON.parse(Buffer.from(await decodeRPack(mainData)).toString()) if(main.type !== 'risuModule'){ console.error("Invalid module type") alertError(language.errors.noData) return } let module = main.module let i = 0 while(true){ const mark = readByte() if(mark === 0){ break } if(mark !== 1){ alertError(language.errors.noData) return } const len = readLength() const data = readData(len) module.assets[i][1] = await saveAsset(Buffer.from(await decodeRPack(data))) alertStore.set({ type: 'wait', msg: `Loading... (Adding Assets ${i} / ${module.assets.length})` }) if(!isTauri && !Capacitor.isNativePlatform() &&!isNodeServer){ await sleep(100) } i++ } alertStore.set({ type: 'none', msg: '' }) module.id = v4() return module } export async function importModule(){ const f = await selectSingleFile(['json', 'lorebook', 'risum']) if(!f){ return } let fileData = f.data const db = getDatabase() if(f.name.endsWith('.risum')){ try { const buf = Buffer.from(fileData) const module = await readModule(buf) db.modules.push(module) setDatabase(db) return } catch (error) { console.error(error) alertError(language.errors.noData) } } try { const importData = JSON.parse(Buffer.from(fileData).toString()) if(importData.type === 'risuModule'){ if( (!importData.name) || (!importData.id) ){ alertError(language.errors.noData) return } importData.id = v4() if(importData.lowLevelAccess){ const conf = await alertConfirm(language.lowLevelAccessConfirm) if(!conf){ return false } } db.modules.push(importData) setDatabase(db) return } if(importData.type === 'risu' && importData.data){ const lores:loreBook[] = importData.data const importModule = { name: importData.name || 'Imported Lorebook', description: importData.description || 'Converted from risu lorebook', lorebook: lores, id: v4() } db.modules.push(importModule) setDatabase(db) return } if(importData.entries){ const lores:loreBook[] = convertExternalLorebook(importData.entries) const importModule = { name: importData.name || 'Imported Lorebook', description: importData.description || 'Converted from external lorebook', lorebook: lores, id: v4() } db.modules.push(importModule) setDatabase(db) return } if(importData.type === 'regex' && importData.data){ const regexs:customscript[] = importData.data const importModule = { name: importData.name || 'Imported Regex', description: importData.description || 'Converted from risu regex', regex: regexs, id: v4() } db.modules.push(importModule) setDatabase(db) return } } catch (error) { alertNormal(language.errors.noData) } } function getModuleById(id:string){ const db = getDatabase() for(let i=0;i m.id === ids[i] || (m.namespace === ids[i] && m.namespace)) if(module){ modules.push(module) } } modules = deduplicateModuleById(modules) return modules } function deduplicateModuleById(modules:RisuModule[]){ let ids:string[] = [] let newModules:RisuModule[] = [] for(let i=0;i s.trim()) ids = ids.concat(intList) } const idsJoined = ids.join('-') if(lastModules === idsJoined){ return lastModuleData } let modules:RisuModule[] = getModuleByIds(ids) lastModules = idsJoined lastModuleData = modules return modules } export function getModuleLorebooks() { const modules = getModules() let lorebooks: loreBook[] = [] for (const module of modules) { if(!module){ continue } if (module.lorebook) { lorebooks = lorebooks.concat(module.lorebook) } } return lorebooks } export function getModuleAssets() { const modules = getModules() let assets: [string,string,string][] = [] for (const module of modules) { if(!module){ continue } if (module.assets) { assets = assets.concat(module.assets) } } return assets } export function getModuleTriggers() { const modules = getModules() let triggers: triggerscript[] = [] for (const module of modules) { if(!module){ continue } if (module.trigger) { triggers = triggers.concat(module.trigger.map((t) => { t.lowLevelAccess = module.lowLevelAccess return t })) } } return triggers } export function getModuleRegexScripts() { const modules = getModules() let customscripts: customscript[] = [] for (const module of modules) { if(!module){ continue } if (module.regex) { customscripts = customscripts.concat(module.regex) } } return customscripts } export function getModuleToggles() { const modules = getModules() let costomModuleToggles: string = '' for (const module of modules) { if(!module){ continue } if (module.customModuleToggle) { costomModuleToggles += '\n' + module.customModuleToggle + '\n' } } return costomModuleToggles } export async function applyModule() { const sel = await alertModuleSelect() if (!sel) { return } const module = safeStructuredClone(getModuleById(sel)) if (!module) { return } const currentChar = getCurrentCharacter() if (!currentChar) { return } if(currentChar.type === 'group'){ return } if (module.lorebook) { for (const lore of module.lorebook) { currentChar.globalLore.push(lore) } } if (module.regex) { for (const regex of module.regex) { currentChar.customscript.push(regex) } } if (module.trigger) { for (const trigger of module.trigger) { currentChar.triggerscript.push(trigger) } } setCurrentCharacter(currentChar) alertNormal(language.successApplyModule) } let lastModuleIds:string = '' export function moduleUpdate(){ const m = getModules() const ids = m.map((m) => m.id).join('-') let moduleHideIcon = false let backgroundEmbedding = '' m.forEach((module) => { if(!module){ return } if(module.hideIcon){ moduleHideIcon = true } if(module.backgroundEmbedding){ backgroundEmbedding += '\n' + module.backgroundEmbedding + '\n' } }) if(backgroundEmbedding){ moduleBackgroundEmbedding.set(backgroundEmbedding) } HideIconStore.set(getCurrentCharacter()?.hideChatIcon || moduleHideIcon) if(lastModuleIds !== ids){ ReloadGUIPointer.set(get(ReloadGUIPointer) + 1) lastModuleIds = ids } } export function refreshModules(){ lastModules = '' lastModuleData = [] }