import DOMPurify from 'isomorphic-dompurify';
import showdown from 'showdown';
import { Marked } from 'marked';
import { DataBase, type Database, type Message, type character, type customscript, type groupChat } from './storage/database';
import { getFileSrc } from './storage/globalApi';
import { processScriptFull } from './process/scripts';
import { get } from 'svelte/store';
import css from '@adobe/css-tools'
import { selectedCharID } from './stores';
import { calcString } from './process/infunctions';
import { findCharacterbyId } from './util';
import { getInlayImage } from './image';
import { autoMarkNew } from './plugins/automark';
const mconverted = new Marked({
gfm: true,
breaks: true,
silent: true,
tokenizer: {
}
})
mconverted.use({
tokenizer: {
del(src) {
const cap = /^~~+(?=\S)([\s\S]*?\S)~~+/.exec(src);
if (cap) {
return {
type: 'del',
raw: cap[0],
text: cap[2],
tokens: []
};
}
return false;
}
}
});
DOMPurify.addHook("uponSanitizeElement", (node: HTMLElement, data) => {
if (data.tagName === "iframe") {
const src = node.getAttribute("src") || "";
if (!src.startsWith("https://www.youtube.com/embed/")) {
return node.parentNode.removeChild(node);
}
}
});
DOMPurify.addHook("uponSanitizeAttribute", (node, data) => {
switch(data.attrName){
case 'style':{
data.attrValue = data.attrValue.replace(/(absolute)|(z-index)|(fixed)/g, '')
break
}
case 'class':{
if(data.attrValue){
data.attrValue = data.attrValue.split(' ').map((v) => {
return "x-risu-" + v
}).join(' ')
}
break
}
case 'href':{
if(data.attrValue.startsWith('http://') || data.attrValue.startsWith('https://')){
node.setAttribute('target', '_blank')
break
}
data.attrValue = ''
break
}
}
})
const assetRegex = /{{(raw|img|video|audio|bg|emotion)::(.+?)}}/g
async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|character, mode:'normal'|'back', mode2:'unset'|'pre'|'post' = 'unset'){
const db = get(DataBase)
const assetWidthString = (db.assetWidth && db.assetWidth !== -1 || db.assetWidth === 0) ? `max-width:${db.assetWidth}rem;` : ''
if(char.additionalAssets){
let assetPaths:{[key:string]:string} = {}
let emoPaths:{[key:string]:string} = {}
for(const asset of char.additionalAssets){
const assetPath = await getFileSrc(asset[1])
assetPaths[asset[0].toLocaleLowerCase()] = assetPath
}
if(char.emotionImages){
for(const emo of char.emotionImages){
const emoPath = await getFileSrc(emo[1])
emoPaths[emo[0].toLocaleLowerCase()] = emoPath
}
}
data = data.replaceAll(assetRegex, (full:string, type:string, name:string) => {
name = name.toLocaleLowerCase()
if(type === 'emotion'){
console.log(emoPaths, name)
const path = emoPaths[name]
if(!path){
return ''
}
return ``
}
const path = assetPaths[name]
if(!path){
return ''
}
switch(type){
case 'raw':
return path
case 'img':
return `
`
case 'video':
return ``
case 'audio':
return ``
case 'bg':
if(mode === 'back'){
return `
`
}
case 'equal':{
return (arra[1] === arra[2]) ? '1' : '0'
}
case 'not_equal':{
return (arra[1] !== arra[2]) ? '1' : '0'
}
case 'greater':{
return (Number(arra[1]) > Number(arra[2])) ? '1' : '0'
}
case 'less':{
return (Number(arra[1]) < Number(arra[2])) ? '1' : '0'
}
case 'greater_equal':{
return (Number(arra[1]) >= Number(arra[2])) ? '1' : '0'
}
case 'less_equal':{
return (Number(arra[1]) <= Number(arra[2])) ? '1' : '0'
}
case 'and':{
return (Number(arra[1]) && Number(arra[2])) ? '1' : '0'
}
case 'or':{
return (Number(arra[1]) || Number(arra[2])) ? '1' : '0'
}
case 'not':{
return (Number(arra[1]) === 0) ? '1' : '0'
}
}
}
if(p1.startsWith('random')){
if(p1.startsWith('random::')){
const randomIndex = Math.floor(Math.random() * (arra.length - 1)) + 1
if(matcherArg.tokenizeAccurate){
return arra[0]
}
return arra[randomIndex]
}
else{
const arr = p1.split(/\:|\,/g)
const randomIndex = Math.floor(Math.random() * (arr.length - 1)) + 1
if(matcherArg.tokenizeAccurate){
return arra[0]
}
return arr[randomIndex]
}
}
if(p1.startsWith('roll')){
const arr = p1.split(/\:|\ /g)
let ina = arr.at(-1)
if(ina.startsWith('d')){
ina = ina.substring(1)
}
const maxRoll = parseInt(ina)
if(isNaN(maxRoll)){
return 'NaN'
}
return (Math.floor(Math.random() * maxRoll) + 1).toString()
}
return null
}
const smMatcher = (p1:string,matcherArg:matcherArg) => {
const lowerCased = p1.toLocaleLowerCase()
const db = matcherArg.db
const chara = matcherArg.chara
switch(lowerCased){
case 'char':
case 'bot':{
if(matcherArg.consistantChar){
return 'botname'
}
let selectedChar = get(selectedCharID)
let currentChar = db.characters[selectedChar]
if(currentChar && currentChar.type !== 'group'){
return currentChar.name
}
if(chara){
if(typeof(chara) === 'string'){
return chara
}
else{
return chara.name
}
}
return currentChar.name
}
case 'user':{
if(matcherArg.consistantChar){
return 'username'
}
return db.username
}
}
}
const blockMatcher = (p1:string,matcherArg:matcherArg) => {
const bn = p1.indexOf('\n')
if(bn === -1){
return null
}
const logic = p1.substring(0, bn)
const content = p1.substring(bn + 1)
const statement = logic.split(" ", 2)
switch(statement[0]){
case 'if':{
if(["","0","-1"].includes(statement[1])){
return ''
}
return content.trim()
}
}
return null
}
export function risuChatParser(da:string, arg:{
chatID?:number
db?:Database
chara?:string|character|groupChat
rmVar?:boolean,
var?:{[key:string]:string}
tokenizeAccurate?:boolean
consistantChar?:boolean
visualize?:boolean
} = {}):string{
const chatID = arg.chatID ?? -1
const db = arg.db ?? get(DataBase)
const aChara = arg.chara
const visualize = arg.visualize ?? false
let chara:character|string = null
if(aChara){
if(typeof(aChara) !== 'string' && aChara.type === 'group'){
const gc = findCharacterbyId(aChara.chats[aChara.chatPage].message.at(-1).saying ?? '')
if(gc.name !== 'Unknown Character'){
chara = gc
}
}
else{
chara = aChara
}
}
if(arg.tokenizeAccurate){
const db = arg.db ?? get(DataBase)
const selchar = chara ?? db.characters[get(selectedCharID)]
if(!selchar){
chara = 'bot'
}
}
let pointer = 0;
let nested:string[] = [""]
let pf = performance.now()
let v = new Uint8Array(512)
let pureMode = false
let commentMode = false
let commentLatest:string[] = [""]
let commentV = new Uint8Array(512)
let thinkingMode = false
const matcherObj = {
chatID: chatID,
chara: chara,
rmVar: arg.rmVar ?? false,
db: db,
var: arg.var ?? null,
tokenizeAccurate: arg.tokenizeAccurate ?? false
}
while(pointer < da.length){
switch(da[pointer]){
case '{':{
if(da[pointer + 1] !== '{' && da[pointer + 1] !== '#'){
nested[0] += da[pointer]
break
}
pointer++
nested.unshift('')
v[nested.length] = 1
break
}
case '<':{
nested.unshift('')
v[nested.length] = 2
break
}
case '#':{
if(da[pointer + 1] !== '}' || nested.length === 1 || v[nested.length] !== 1){
nested[0] += da[pointer]
break
}
pointer++
const dat = nested.shift()
const mc = blockMatcher(dat, matcherObj)
nested[0] += mc ?? `{#${dat}#}`
break
}
case '}':{
if(da[pointer + 1] !== '}' || nested.length === 1 || v[nested.length] !== 1){
nested[0] += da[pointer]
break
}
pointer++
const dat = nested.shift()
const mc = (pureMode) ? null :matcher(dat, matcherObj)
nested[0] += mc ?? `{{${dat}}}`
break
}
case '>':{
if(nested.length === 1 || v[nested.length] !== 2){
break
}
const dat = nested.shift()
switch(dat){
case 'Pure':{
pureMode = true
break
}
case '/Pure':{
pureMode = false
break
}
case 'Comment':{
if(!commentMode){
thinkingMode = false
commentMode = true
commentLatest = nested.map((f) => f)
if(commentLatest[0].endsWith('\n')){
commentLatest[0] = commentLatest[0].substring(0, commentLatest[0].length - 1)
}
commentV = new Uint8Array(v)
}
break
}
case '/Comment':{
if(commentMode){
nested = commentLatest
v = commentV
commentMode = false
}
break
}
case 'Thoughts':{
if(!visualize){
nested[0] += `<${dat}>`
break
}
if(!commentMode){
thinkingMode = true
commentMode = true
commentLatest = nested.map((f) => f)
if(commentLatest[0].endsWith('\n')){
commentLatest[0] = commentLatest[0].substring(0, commentLatest[0].length - 1)
}
commentV = new Uint8Array(v)
}
break
}
case '/Thoughts':{
if(!visualize){
nested[0] += `<${dat}>`
break
}
if(commentMode){
nested = commentLatest
v = commentV
commentMode = false
}
break
}
default:{
const mc = (pureMode) ? null : smMatcher(dat, matcherObj)
nested[0] += mc ?? `<${dat}>`
break
}
}
break
}
default:{
nested[0] += da[pointer]
break
}
}
pointer++
}
if(commentMode){
nested = commentLatest
v = commentV
if(thinkingMode){
nested[0] += `