UNPKG

hv-monsterdb-userscript

Version:
1,161 lines (1,138 loc) 97.6 kB
// ==UserScript== // @name HentaiVerse Monster Database UserScript // @description M-M-M-MONSTER DATABASE! // @namespace https://hentaiverse.org // @run-at document-idle // @match *://*.hentaiverse.org/* // @exclude http*://hentaiverse.org/pages/showequip.php?* // @exclude *hentaiverse.org/equip/* // @version 2.0.0 // @author Sukka <https://skk.moe> // @grant unsafeWindow // @grant GM.getValue // @grant GM.setValue // @grant GM.deleteValue // ==/UserScript== // ============= vvv SETTINGS vvv ============= const SETTINGS = { /** * Debug * * true - enable verbose output in console, and access to debug method * false - disable verbose output in console (default) */ debug: false, /** * Scan Expire Date * * Monsters that haven't been scanned in this number of days will be considered expired. * In the Isekai, monsters will only expire once per season. */ scanExpireDays: 45, /** * Scan Highlight Color * * Highlight expired monsters (haven't been scanned in "scanExpireDays"). * Set to "false" (without quote) will disable the expired highlight feature. * * (In order to be compatible with Monsterbation's "monsterKeyword", only monster letters part will be highlighted) */ scanHighlightColor: 'coral', /** * Monster Info Box * * Monster Database Script provides a draggable float box during battle. If you don't * like the ui, you can disable it here. */ showMonsterInfoBox: true, /** * Compact Monster Info Box * * true - only show trainer, PL, monster class in the float box, mitigation data will be hidden * false - show all mitigation data along with trainer, PL, monster class in the float box (default) */ compactMonsterInfoBox: false, /** * Highlight Monster * * Similar to Monsterbation's "monsterKeyword" feature, highlight monsters where the name, id, class, attack matches. * (In order to be compatible with Monsterbation's "monsterKeyword", only monster letters part will be highlighted) * * The configuration accepts an object. The key of the object accepts the color, supports any valid CSS color. * The value of the object accepts a RegExp (same syntax as Monsterbation 1.3.2.1) or a function that returns boolean value. */ highlightMonster: { /* This matches monsters whose trainer is Noni */ // '#66ccff': /"trainer":"Noni"/, /** * This matches monsters * whose name (or whose trainer name) contains Meiling, * OR whose PL is 2250, * OR whose monster id is 70699, * OR whose monster class is Undead AND attack type is Crushing */ // 'red': /(Meiling|"plvl":2250|"monsterId":70699|Undead.*Crushing)/ /** * If you are an advanced player who can write javascript, I promise you will love this * * You can find the type definition of monsterInfo here: * https://suka.js.org/hv-monsterdb-userscript/interfaces/hvmonsterdatabase.monsterinfo.html */ // 'rgb(28, 46, 69)': (monsterInfo) => { // if (monsterInfo.monsterName.includes('Meiling')) return true; // if (monsterInfo.plvl > 1700) return true; // if (monsterInfo.monsterClass !== 'Giant') return true; // if (monsterInfo.attack === 'Piercing' || monsterInfo.attack === 'Crushing') return true; // return false // } }, /** * Dark Mode * * Made by @raraha (https://forums.e-hentai.org/index.php?showuser=4071895) * Enable Dark theme for the Monster Info Box */ darkMode: false }; // ============= ^^^ SETTINGS ^^^ ============= /* * The code below is generated by TypeScript Compiler (http://npm.im/typescript) and Rollup Bundler (https://rollupjs.org/guide/en/) * If you want to make some modifications, it is recommeneded to build your own script from the source code. The source code is * released on GitHub under MIT License: https://github.com/SukkaW/hv-monsterdb-userscript */ // -+-+-+ DO NOT EDIT THE CODE BELOW THE LINE BY HAND +-+-+- this.unsafeWindow = this.unsafeWindow || {}; this.unsafeWindow.HVMonsterDB = (function (exports) { 'use strict'; const DOM_REF_FIELD = "__m_dom_ref"; const OLD_VNODE_FIELD = "__m_old_vnode"; const NODE_OBJECT_POOL_FIELD = "__m_node_object_pool"; const XLINK_NS = "http://www.w3.org/1999/xlink"; const XML_NS = "http://www.w3.org/XML/1998/namespace"; const COLON_CHAR = 58; const X_CHAR = 120; var Flags = /* @__PURE__ */ ((Flags2) => { Flags2[Flags2["IGNORE_NODE"] = 0] = "IGNORE_NODE"; Flags2[Flags2["REPLACE_NODE"] = 1] = "REPLACE_NODE"; Flags2[Flags2["NO_CHILDREN"] = 2] = "NO_CHILDREN"; Flags2[Flags2["ONLY_TEXT_CHILDREN"] = 3] = "ONLY_TEXT_CHILDREN"; Flags2[Flags2["ONLY_KEYED_CHILDREN"] = 4] = "ONLY_KEYED_CHILDREN"; Flags2[Flags2["ANY_CHILDREN"] = 5] = "ANY_CHILDREN"; return Flags2; })(Flags || {}); var EffectTypes = /* @__PURE__ */ ((EffectTypes2) => { EffectTypes2[EffectTypes2["CREATE"] = 0] = "CREATE"; EffectTypes2[EffectTypes2["REMOVE"] = 1] = "REMOVE"; EffectTypes2[EffectTypes2["REPLACE"] = 2] = "REPLACE"; EffectTypes2[EffectTypes2["UPDATE"] = 3] = "UPDATE"; EffectTypes2[EffectTypes2["SET_PROP"] = 4] = "SET_PROP"; EffectTypes2[EffectTypes2["REMOVE_PROP"] = 5] = "REMOVE_PROP"; return EffectTypes2; })(EffectTypes || {}); var DeltaTypes = /* @__PURE__ */ ((DeltaTypes2) => { DeltaTypes2[DeltaTypes2["INSERT"] = 0] = "INSERT"; DeltaTypes2[DeltaTypes2["UPDATE"] = 1] = "UPDATE"; DeltaTypes2[DeltaTypes2["DELETE"] = 2] = "DELETE"; return DeltaTypes2; })(DeltaTypes || {}); const svg = (vnode) => { if (!vnode.props) vnode.props = {}; ns(vnode.tag, vnode.props, vnode.children); return vnode; }; const ns = (tag, props, children) => { if (props.className) { props.class = props.className; delete props.className; } props.ns = "http://www.w3.org/2000/svg"; if (children && tag !== "foreignObject") { for (const child of children) { if (typeof child !== "string" && child.props) ns(child.tag, child.props, child.children); } } }; const className = (classObject) => Object.keys(classObject).filter((className2) => classObject[className2]).join(" "); const style = (styleObject) => Object.entries(styleObject).map((style2) => style2.join(":")).join(";"); const kebab = (camelCaseObject) => { const kebabCaseObject = {}; for (const key in camelCaseObject) { kebabCaseObject[key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()] = camelCaseObject[key]; } return kebabCaseObject; }; const m = (tag, props, children, flag, delta) => { let key = void 0; if (props?.key) { key = props.key; delete props.key; } const vnode = { tag, props, children, key, flag, delta }; return vnode.tag?.toLowerCase() === "svg" ? svg(vnode) : vnode; }; const normalize = (jsxVNode) => { if (Array.isArray(jsxVNode)) { const normalizedChildren = []; for (let i = 0; i < jsxVNode.length; i++) { normalizedChildren.push(normalize(jsxVNode[i])); } return normalizedChildren; } else if (typeof jsxVNode === "string" || typeof jsxVNode === "number" || typeof jsxVNode === "boolean") { return String(jsxVNode); } else { return jsxVNode; } }; const h = (tag, props, ...children) => { if (typeof tag === "function") return tag(props); let flag = Flags.NO_CHILDREN; let delta; const normalizedChildren = []; if (props) { const rawDelta = props.delta; if (rawDelta && rawDelta.length) { delta = rawDelta; delete props.delta; } } if (children) { const keysInChildren = /* @__PURE__ */ new Set(); let hasVElementChildren = false; flag = Flags.ANY_CHILDREN; if (children.every((child) => typeof child === "string")) { flag = Flags.ONLY_TEXT_CHILDREN; } let childrenLength = 0; for (let i = 0; i < children.length; ++i) { if (children[i] !== void 0 && children[i] !== null && children[i] !== false && children[i] !== "") { const unwrappedChild = normalize(children[i]); const subChildren = Array.isArray(unwrappedChild) ? (childrenLength += unwrappedChild.length, unwrappedChild) : (childrenLength++, [unwrappedChild]); for (let i2 = 0; i2 < subChildren.length; i2++) { if (subChildren[i2] || subChildren[i2] === "") { normalizedChildren.push(subChildren[i2]); if (typeof subChildren[i2] === "object") { hasVElementChildren = true; if (typeof subChildren[i2].key === "string" && subChildren[i2].key !== "") { keysInChildren.add(subChildren[i2].key); } } } } } } if (keysInChildren.size === childrenLength) { flag = Flags.ONLY_KEYED_CHILDREN; } if (!hasVElementChildren) { flag = Flags.ONLY_TEXT_CHILDREN; } } if (props) { if (typeof props.flag === "number") { flag = props.flag; delete props.flag; } if (typeof props.className === "object") { props.className = className(props.className); } if (typeof props.style === "object") { const rawStyle = props.style; const normalizedStyle = Object.keys(rawStyle).some((key) => /[-A-Z]/gim.test(key)) ? kebab(rawStyle) : rawStyle; props.style = style(normalizedStyle); } } const vnode = m(tag, props, normalizedChildren, flag, delta); return tag === "svg" ? svg(vnode) : vnode; }; const jsx = (tag, props, key) => { if (typeof tag === "function") return tag(props, key); let children = []; if (props) { if (props.children) { children = Array.isArray(props.children) ? props.children : [props.children]; delete props.children; } if (key) props.key = key; } return h(tag, props, ...children); }; function isIsekai() { return window.location.pathname.includes('isekai'); } function isFightingInBattle() { return Boolean(document.getElementById('textlog')); } function getUTCDate() { return new Date().toISOString().split('T')[0]; } const MonsterDatabaseCompatibleDateCache = new Map(); const padNumber = (num)=>num.toString().padStart(2, '0') ; function getMonsterDatabaseCompatibleDate(timestamp) { if (timestamp && MonsterDatabaseCompatibleDateCache.has(timestamp)) return MonsterDatabaseCompatibleDateCache.get(timestamp); const date = timestamp ? new Date(timestamp) : new Date(); const year = date.getUTCFullYear(); const month = date.getUTCMonth() + 1; const day = date.getUTCDate(); const result = `${year}-${padNumber(month)}-${padNumber(day)}`; if (timestamp) { MonsterDatabaseCompatibleDateCache.set(timestamp, result); } return result; } function showPopup(msgHtml, color = '#000', title = 'HentaiVerse Monster Datase UserScript') { const popupEl = document.body.appendChild(document.createElement('div')); popupEl.style.cssText = 'position:fixed;top:0;left:0;width:1236px;height:702px;padding:3px 100% 100% 3px;background-color:rgba(0,0,0,.3);z-index:1001;display:flex;justify-content:center;align-items:center'; const popupMsgEl = popupEl.appendChild(document.createElement('div')); popupMsgEl.style.cssText = 'min-width:400px;min-height:80px;max-width:100%;max-height:100%;padding:10px;background-color:#fff;border:1px solid #333;cursor:pointer;display:flex;flex-direction:column;justify-content:center;font-size:10pt'; const titleEl = popupMsgEl.appendChild(document.createElement('h3')); titleEl.textContent = title; titleEl.style.marginTop = '0'; const contentEl = popupMsgEl.appendChild(document.createElement('div')); contentEl.style.color = color; contentEl.innerHTML = msgHtml; const closePopupKeyboardEventHandler = (e)=>{ if (e instanceof KeyboardEvent) { if (e.key === 'Enter' || e.key === ' ' || e.key === 'Escape') { popupEl.remove(); document.removeEventListener('keydown', closePopupKeyboardEventHandler); } } }; popupEl.addEventListener('click', ()=>{ popupEl.remove(); }); document.addEventListener('keydown', closePopupKeyboardEventHandler); } var EncodedMonsterDatabase; (function(EncodedMonsterDatabase1) { (function(EMonsterClass) { EMonsterClass[EMonsterClass["Arthropod"] = 0] = "Arthropod"; EMonsterClass[EMonsterClass["Avion"] = 1] = "Avion"; EMonsterClass[EMonsterClass["Beast"] = 2] = "Beast"; EMonsterClass[EMonsterClass["Celestial"] = 3] = "Celestial"; EMonsterClass[EMonsterClass["Daimon"] = 4] = "Daimon"; EMonsterClass[EMonsterClass["Dragonkin"] = 5] = "Dragonkin"; EMonsterClass[EMonsterClass["Elemental"] = 6] = "Elemental"; EMonsterClass[EMonsterClass["Giant"] = 7] = "Giant"; EMonsterClass[EMonsterClass["Humanoid"] = 8] = "Humanoid"; EMonsterClass[EMonsterClass["Mechanoid"] = 9] = "Mechanoid"; EMonsterClass[EMonsterClass["Reptilian"] = 10] = "Reptilian"; EMonsterClass[EMonsterClass["Sprite"] = 11] = "Sprite"; EMonsterClass[EMonsterClass["Undead"] = 12] = "Undead"; EMonsterClass[EMonsterClass["Rare"] = 13] = "Rare"; EMonsterClass[EMonsterClass["Legendary"] = 14] = "Legendary"; EMonsterClass[EMonsterClass["Ultimate"] = 15] = "Ultimate"; EMonsterClass[EMonsterClass["Common"] = 16] = "Common"; })(EncodedMonsterDatabase1.EMonsterClass || (EncodedMonsterDatabase1.EMonsterClass = {})); (function(EMonsterAttack) { EMonsterAttack[EMonsterAttack["Piercing"] = 0] = "Piercing"; EMonsterAttack[EMonsterAttack["Crushing"] = 1] = "Crushing"; EMonsterAttack[EMonsterAttack["Slashing"] = 2] = "Slashing"; EMonsterAttack[EMonsterAttack["Fire"] = 3] = "Fire"; EMonsterAttack[EMonsterAttack["Cold"] = 4] = "Cold"; EMonsterAttack[EMonsterAttack["Wind"] = 5] = "Wind"; EMonsterAttack[EMonsterAttack["Elec"] = 6] = "Elec"; EMonsterAttack[EMonsterAttack["Holy"] = 7] = "Holy"; EMonsterAttack[EMonsterAttack["Dark"] = 8] = "Dark"; EMonsterAttack[EMonsterAttack["Void"] = 9] = "Void"; })(EncodedMonsterDatabase1.EMonsterAttack || (EncodedMonsterDatabase1.EMonsterAttack = {})); (function(EMonsterInfo) { EMonsterInfo[EMonsterInfo["monsterName"] = 0] = "monsterName"; EMonsterInfo[EMonsterInfo["monsterId"] = 1] = "monsterId"; EMonsterInfo[EMonsterInfo["monsterClass"] = 2] = "monsterClass"; EMonsterInfo[EMonsterInfo["plvl"] = 3] = "plvl"; EMonsterInfo[EMonsterInfo["attack"] = 4] = "attack"; EMonsterInfo[EMonsterInfo["trainer"] = 5] = "trainer"; EMonsterInfo[EMonsterInfo["piercing"] = 6] = "piercing"; EMonsterInfo[EMonsterInfo["crushing"] = 7] = "crushing"; EMonsterInfo[EMonsterInfo["slashing"] = 8] = "slashing"; EMonsterInfo[EMonsterInfo["cold"] = 9] = "cold"; EMonsterInfo[EMonsterInfo["wind"] = 10] = "wind"; EMonsterInfo[EMonsterInfo["elec"] = 11] = "elec"; EMonsterInfo[EMonsterInfo["fire"] = 12] = "fire"; EMonsterInfo[EMonsterInfo["dark"] = 13] = "dark"; EMonsterInfo[EMonsterInfo["holy"] = 14] = "holy"; EMonsterInfo[EMonsterInfo["lastUpdate"] = 15] = "lastUpdate"; })(EncodedMonsterDatabase1.EMonsterInfo || (EncodedMonsterDatabase1.EMonsterInfo = {})); })(EncodedMonsterDatabase || (EncodedMonsterDatabase = {})); function convertMonsterInfoToEncodedMonsterInfo(monster) { return { [EncodedMonsterDatabase.EMonsterInfo.monsterName]: monster.monsterName, [EncodedMonsterDatabase.EMonsterInfo.monsterClass]: EncodedMonsterDatabase.EMonsterClass[monster.monsterClass], [EncodedMonsterDatabase.EMonsterInfo.plvl]: monster.plvl, [EncodedMonsterDatabase.EMonsterInfo.attack]: EncodedMonsterDatabase.EMonsterAttack[monster.attack], [EncodedMonsterDatabase.EMonsterInfo.trainer]: monster.trainer, [EncodedMonsterDatabase.EMonsterInfo.piercing]: monster.piercing, [EncodedMonsterDatabase.EMonsterInfo.crushing]: monster.crushing, [EncodedMonsterDatabase.EMonsterInfo.slashing]: monster.slashing, [EncodedMonsterDatabase.EMonsterInfo.cold]: monster.cold, [EncodedMonsterDatabase.EMonsterInfo.wind]: monster.wind, [EncodedMonsterDatabase.EMonsterInfo.elec]: monster.elec, [EncodedMonsterDatabase.EMonsterInfo.fire]: monster.fire, [EncodedMonsterDatabase.EMonsterInfo.dark]: monster.dark, [EncodedMonsterDatabase.EMonsterInfo.holy]: monster.holy, [EncodedMonsterDatabase.EMonsterInfo.lastUpdate]: monster.lastUpdate ? new Date(monster.lastUpdate).getTime() : new Date('1970-1-1').getTime() }; } function convertEncodedMonsterInfoToMonsterInfo(monsterId, simpleMonster) { return { monsterId, monsterName: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.monsterName], monsterClass: EncodedMonsterDatabase.EMonsterClass[simpleMonster[EncodedMonsterDatabase.EMonsterInfo.monsterClass]], plvl: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.plvl], attack: EncodedMonsterDatabase.EMonsterAttack[simpleMonster[EncodedMonsterDatabase.EMonsterInfo.attack]], trainer: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.trainer], piercing: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.piercing], crushing: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.crushing], slashing: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.slashing], cold: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.cold], wind: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.wind], elec: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.elec], fire: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.fire], dark: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.dark], holy: simpleMonster[EncodedMonsterDatabase.EMonsterInfo.holy], lastUpdate: getMonsterDatabaseCompatibleDate(simpleMonster[EncodedMonsterDatabase.EMonsterInfo.lastUpdate]) }; } function getStoredValue(key) { return GM.getValue(key); } function setStoredValue(key, value) { return GM.setValue(key, value); } function removeStoredValue(key) { return GM.deleteValue(key); } class IDBKV { static promisifyRequest(request) { return new Promise((resolve, reject)=>{ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - file size hacks // eslint-disable-next-line no-multi-assign request.oncomplete = request.onsuccess = ()=>resolve(request.result) ; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - file size hacks // eslint-disable-next-line no-multi-assign request.onabort = request.onerror = ()=>reject(request.error) ; }); } get(key) { return this.performDatabaseOperation('readonly', (store)=>{ return IDBKV.promisifyRequest(store.get(key)); }); } set(key, value) { return this.performDatabaseOperation('readwrite', (store)=>{ store.put(value, key); return IDBKV.promisifyRequest(store.transaction); }); } setMany(entries) { return this.performDatabaseOperation('readwrite', (store)=>{ entries.forEach((entry)=>store.put(entry[1], entry[0]) ); return IDBKV.promisifyRequest(store.transaction); }); } getMany(keys) { return this.performDatabaseOperation('readonly', (store)=>Promise.all(keys.map((key)=>IDBKV.promisifyRequest(store.get(key)) )) ); } /** Update a value. This lets you see the old value and update it as an atomic operation. */ update(key, updater) { return this.performDatabaseOperation('readwrite', // Need to create the promise manually. // If I try to chain promises, the transaction closes in browsers // that use a promise polyfill (IE10/11). (store)=>new Promise((resolve, reject)=>{ store.get(key).onsuccess = function() { try { const newValue = updater(this.result); if (newValue !== this.result) { store.put(updater(this.result), key); resolve(IDBKV.promisifyRequest(store.transaction)); } else { resolve(); } } catch (err) { reject(err); } }; }) ); } del(key) { return this.performDatabaseOperation('readwrite', (store)=>{ store.delete(key); return IDBKV.promisifyRequest(store.transaction); }); } delMany(keys) { return this.performDatabaseOperation('readwrite', (store)=>{ keys.forEach((key)=>store.delete(key) ); return IDBKV.promisifyRequest(store.transaction); }); } async clear() { return this.performDatabaseOperation('readwrite', (store)=>{ store.clear(); return IDBKV.promisifyRequest(store.transaction); }); } keys() { const items = []; return this.eachCursor((cursor)=>items.push(cursor.key) ).then(()=>items ); } values() { const items = []; return this.eachCursor((cursor)=>items.push(cursor.value) ).then(()=>items ); } entries() { const items = []; return this.eachCursor((cursor)=>items.push([ cursor.key, cursor.value ]) ).then(()=>items ); } initializeOpenDatabasePromise() { if (this.databasePromise === null) { const promise = new Promise((resolve, reject)=>{ const request = self.indexedDB.open(this.dbName, this.dbVersion); request.onsuccess = ()=>{ const database = request.result; database.onclose = ()=>{ this.databasePromise = null; }; database.onversionchange = ()=>{ database.close(); this.databasePromise = null; }; resolve(database); }; request.onerror = ()=>reject(request.error) ; request.onupgradeneeded = ()=>{ try { // Whatever the KV instance is opened, always create all objectStore we needed OBJECT_STORES.forEach((storeName)=>request.result.createObjectStore(storeName) ); } catch (e) { reject(e); } }; }); this.databasePromise = promise; return promise; } } async performDatabaseOperation(txMode, callback) { if (!this.databasePromise) { this.initializeOpenDatabasePromise(); } const db = await this.databasePromise; const store = db.transaction(this.storeName, txMode).objectStore(this.storeName); return callback(store); } eachCursor(callback) { return this.performDatabaseOperation('readonly', (store)=>{ store.openCursor().onsuccess = function() { if (!this.result) return; callback(this.result); this.result.continue(); }; return IDBKV.promisifyRequest(store.transaction); }); } constructor(dbName, storeName, dbVersion){ this.databasePromise = null; this.dbName = dbName; this.storeName = storeName; this.dbVersion = dbVersion; this.initializeOpenDatabasePromise(); } } const DBNAME = 'hv-monster-database-script'; const OBJECT_STORES = [ 'MONSTER_NAME_ID_MAP', 'databaseV2', 'databaseIsekaiV2' ]; /** The position of monster info box */ let MONSTER_INFO_BOX_POSITION = { x: 10, y: 10 }; // eslint-disable-next-line @typescript-eslint/naming-convention class MONSTER_NAME_ID_MAP { static async get(monsterName) { if (MONSTER_NAME_ID_MAP.cache.has(monsterName)) return MONSTER_NAME_ID_MAP.cache.get(monsterName); const monsterId = await MONSTER_NAME_ID_MAP.store.get(monsterName); if (monsterId) { MONSTER_NAME_ID_MAP.cache.set(monsterName, monsterId); } return monsterId; } static async updateMany(entries) { if (entries.length > 0) { return MONSTER_NAME_ID_MAP.store.performDatabaseOperation('readwrite', (store)=>{ entries.forEach((entry)=>{ if (entry) { const [monsterName, newMonsterId] = entry; if (this.cache.get(monsterName) !== newMonsterId) { this.cache.set(monsterName, newMonsterId); store.get(monsterName).onsuccess = function() { if (this.result !== newMonsterId) { store.put(newMonsterId, monsterName); } }; } } }); return IDBKV.promisifyRequest(store.transaction); }); } } } MONSTER_NAME_ID_MAP.cache = new Map(); MONSTER_NAME_ID_MAP.store = new IDBKV(DBNAME, 'MONSTER_NAME_ID_MAP'); class LocalMonsterDatabase { async get(monsterId) { if (this.cache.has(monsterId)) return this.cache.get(monsterId); const encodedMonsterInfo = await this.store.get(monsterId); if (encodedMonsterInfo) { this.cache.set(monsterId, encodedMonsterInfo); return encodedMonsterInfo; } } getAll() { return this.store.performDatabaseOperation('readonly', (store)=>{ return Promise.all([ IDBKV.promisifyRequest(store.getAllKeys()), IDBKV.promisifyRequest(store.getAll()) ]).then(([keys, values])=>keys.map((key, i)=>[ key, values[i] ] ) ); }); } getMany(monsterIds) { if (monsterIds.map((id)=>id && this.cache.has(id) ).length === monsterIds.length) { return Promise.resolve(monsterIds.map((id)=>id ? this.cache.get(id) : undefined )); } return this.store.performDatabaseOperation('readonly', (store)=>{ const resultPromises = []; monsterIds.forEach((id)=>{ if (id) { if (this.cache.has(id)) { resultPromises.push(this.cache.get(id)); } else { resultPromises.push(IDBKV.promisifyRequest(store.get(id))); } } else { resultPromises.push(undefined); } }); return Promise.all(resultPromises); }); } set(monsterId, monsterInfo) { this.cache.set(monsterId, monsterInfo); return this.store.set(monsterId, monsterInfo); } updateMany(entries) { if (entries.length > 0) { this.cache.clear(); return this.store.performDatabaseOperation('readwrite', (store)=>{ entries.forEach((entry)=>{ if (entry) { const [monsterId, newMonsterInfo] = entry; store.get(monsterId).onsuccess = function() { if (!LocalMonsterDatabase.monsterInfoIsEquial(this.result, newMonsterInfo)) { store.put(newMonsterInfo, monsterId); } }; } }); return IDBKV.promisifyRequest(store.transaction); }); } return Promise.resolve(); } static monsterInfoIsEquial(monster1, monster2) { if (!monster1) return false; if ([ EncodedMonsterDatabase.EMonsterInfo.monsterName, EncodedMonsterDatabase.EMonsterInfo.monsterClass, EncodedMonsterDatabase.EMonsterInfo.plvl, EncodedMonsterDatabase.EMonsterInfo.attack, EncodedMonsterDatabase.EMonsterInfo.trainer, EncodedMonsterDatabase.EMonsterInfo.piercing, EncodedMonsterDatabase.EMonsterInfo.crushing, EncodedMonsterDatabase.EMonsterInfo.slashing, EncodedMonsterDatabase.EMonsterInfo.cold, EncodedMonsterDatabase.EMonsterInfo.wind, EncodedMonsterDatabase.EMonsterInfo.elec, EncodedMonsterDatabase.EMonsterInfo.fire, EncodedMonsterDatabase.EMonsterInfo.dark, EncodedMonsterDatabase.EMonsterInfo.holy, EncodedMonsterDatabase.EMonsterInfo.lastUpdate ].every((k)=>monster1[k] === monster2[k] )) { return true; } return false; } constructor(storeName){ this.cache = new Map(); this.store = new IDBKV(DBNAME, storeName); } } const LOCAL_MONSTER_DATABASE_PERSISTENT = new LocalMonsterDatabase('databaseV2'); const LOCAL_MONSTER_DATABASE_ISEKAI = new LocalMonsterDatabase('databaseIsekaiV2'); const LOCAL_MONSTER_DATABASE = isIsekai() ? LOCAL_MONSTER_DATABASE_ISEKAI : LOCAL_MONSTER_DATABASE_PERSISTENT; /** * According to MDN: * > (Map) performs better in scenarios involving frequent additions and removals of key-value pairs. * * And in modern V8 javascript engine, Map is about 40% faster than Object literal. * * However, as Map can not be serialized, it can not be stored using either localStorage or GM.setValue, * so it is required to convert Map to Object literal before storing, and convert it back after retrieving. */ function storeTmpValue() { return setStoredValue('monsterInfoBoxPosition', MONSTER_INFO_BOX_POSITION); } async function retrieveTmpValue() { MONSTER_INFO_BOX_POSITION = await getStoredValue('monsterInfoBoxPosition') || { x: 10, y: 10 }; } const rMatchMonsterId = /MID=(\d+) \((.+)\)/; const rMatchScan = /Scanning (.+?)\.\.\..+?Monster Class.+>([A-Z][a-z]+)(?:, Power Level (\d+)<|<).+?Monster Trainer:<\/strong><\/td><td>([^<>]*)<.+?<\/strong><\/td><td>([A-Za-z]+)<.+?Fire:.+?>([+-])(\d+)%<.+?Cold:.+?>([+-])(\d+)%<.+?Elec:.+?>([+-])(\d+)%<.+?Wind:.+?>([+-])(\d+)%<.+?Holy:.+?>([+-])(\d+)%<.+?Dark:.+?>([+-])(\d+)%<.+?Crushing:.+?>([+-])(\d+)%<.+?Slashing:.+?>([+-])(\d+)%<.+?Piercing:.+?>([+-])(\d+)%/; function parseMonsterNameAndId(singleLogText) { const matches = singleLogText.match(rMatchMonsterId); if (matches) { const monsterId = Number(matches[1]); const monsterName = matches[2]; if (!Number.isNaN(monsterId)) { return { monsterId, monsterName }; } } return null; } const isPositiveOrNegative = (modifier)=>modifier === '+' ? 1 : -1 ; async function parseScanResult(singleLogHtml) { if (singleLogHtml.includes('Scanning')) { const matches = singleLogHtml.match(rMatchScan); if (matches) { const monsterName = matches[1]; const monsterId = await MONSTER_NAME_ID_MAP.get(monsterName); const lastUpdate = getMonsterDatabaseCompatibleDate(); // System Monster has no Power Level results in undefined // Treat it as PL 0 instead const plvl = Number(matches[3] ?? 0); const fire = Number(matches[7]) * isPositiveOrNegative(matches[6]); const cold = Number(matches[9]) * isPositiveOrNegative(matches[8]); const elec = Number(matches[11]) * isPositiveOrNegative(matches[10]); const wind = Number(matches[13]) * isPositiveOrNegative(matches[12]); const holy = Number(matches[15]) * isPositiveOrNegative(matches[14]); const dark = Number(matches[17]) * isPositiveOrNegative(matches[16]); const crushing = Number(matches[19]) * isPositiveOrNegative(matches[18]); const slashing = Number(matches[21]) * isPositiveOrNegative(matches[20]); const piercing = Number(matches[23]) * isPositiveOrNegative(matches[22]); if (![ plvl, fire, cold, elec, wind, holy, dark, crushing, slashing, piercing ].some(Number.isNaN) && monsterId) { return { monsterName, monsterId, monsterClass: matches[2], plvl, trainer: matches[4], attack: matches[5], fire, cold, elec, wind, holy, dark, crushing, slashing, piercing, lastUpdate }; } } } } function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z = ".style-module_monsterdb_info__Rp7EW{opacity:1;position:absolute;width:175px;z-index:3}.style-module_monsterdb_dark__tgfWT{color:#dddad6}.style-module_compact__1rkaK{width:75px}.style-module_drag__T988-{opacity:.6}.style-module_drag__T988- .style-module_header__EOkm8,.style-module_drag__T988- .style-module_table__A3lw7{border-style:dashed}.style-module_header__EOkm8{background-color:#e5e2d5;border:2px solid #5c0d11;cursor:move;font-weight:600;height:20px;line-height:20px;margin-bottom:-2px;text-align:center;user-select:none;visibility:hidden}.style-module_monsterdb_dark__tgfWT .style-module_header__EOkm8{background-color:#16171d;border-color:#c41c26}.style-module_monsterdb_info__Rp7EW:hover .style-module_header__EOkm8{visibility:visible}.style-module_table_container__rgU-7{font-family:Consolas,Monaco,SFMono-Regular,Andale Mono,Liberation Mono,Ubuntu Mono,Menlo,monospace;font-weight:700;height:56px;margin:2px auto}.style-module_monsterdb_dark__tgfWT .style-module_table_container__rgU-7{background-color:#1f2129}.style-module_notify__nbf7p{color:red;font-size:20px;line-height:56px}.style-module_table__A3lw7{background-color:#eeece1;border:2px ridge #5c0d12;border-collapse:collapse;border-spacing:0;font-size:12px;height:100%;letter-spacing:-1px;line-height:1;width:100%}.style-module_monsterdb_dark__tgfWT .style-module_table__A3lw7{border-color:#c41c26}.style-module_table__A3lw7 td{border:1px solid #5c0d11;padding:0 1px}.style-module_table__A3lw7 td:last-child{max-width:70px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:70px}.style-module_fire__7TDZO{color:#ce3600}.style-module_cold__cEVYS{color:#1b78d4}.style-module_elec__Ck96M{color:#be900f}.style-module_wind__rnPOC{color:#16a084}.style-module_holy__n1BUU{color:#a19f02}.style-module_dark__LG-Fr{color:#814399}.style-module_hidden__SPpn9{display:none}"; var styles = {"monsterdb_info":"style-module_monsterdb_info__Rp7EW","monsterdb_dark":"style-module_monsterdb_dark__tgfWT","compact":"style-module_compact__1rkaK","drag":"style-module_drag__T988-","header":"style-module_header__EOkm8","table":"style-module_table__A3lw7","table_container":"style-module_table_container__rgU-7","notify":"style-module_notify__nbf7p","fire":"style-module_fire__7TDZO","cold":"style-module_cold__cEVYS","elec":"style-module_elec__Ck96M","wind":"style-module_wind__rnPOC","holy":"style-module_holy__n1BUU","dark":"style-module_dark__LG-Fr","hidden":"style-module_hidden__SPpn9"}; styleInject(css_248z); const createElement = (vnode, attachField = true) => { if (vnode?.data) { if (vnode?.el) return vnode.el; else return createElement(vnode?.resolve()); } if (vnode === void 0 || vnode === null) return document.createComment(""); if (typeof vnode === "string") return document.createTextNode(vnode); const velement = vnode; const el = velement.props?.ns ? document.createElementNS(velement.props?.ns, velement.tag) : document.createElement(velement.tag); if (velement.props) { for (const propName in velement.props) { const propValue = velement.props[propName]; if (propName.startsWith("on")) { const eventPropName = propName.slice(2).toLowerCase(); el.addEventListener(eventPropName, propValue); } else if (propName.charCodeAt(0) === X_CHAR) { if (propName.charCodeAt(3) === COLON_CHAR) { el.setAttributeNS(XML_NS, propName, String(propValue)); } else if (propName.charCodeAt(5) === COLON_CHAR) { el.setAttributeNS(XLINK_NS, propName, String(propValue)); } } else if (el[propName] !== void 0 && !(el instanceof SVGElement)) { el[propName] = propValue; } else { el.setAttribute(propName, String(propValue)); } } } if (velement.children) { if (velement.flag === Flags.ONLY_TEXT_CHILDREN) { el.textContent = Array.isArray(velement.children) ? velement.children?.join("") : velement.children; } else { for (let i = 0; i < velement.children.length; ++i) { el.appendChild(createElement(velement.children[i], false)); } } } if (attachField) el[OLD_VNODE_FIELD] = vnode; return el; }; const useChildren = (drivers = []) => (el, newVNode, oldVNode, commit = (work) => work(), effects = [], driver) => { const getData = (element) => ({ el: element, newVNode, oldVNode, effects, commit, driver }); const finish = (element) => { const data = getData(element); for (let i = 0; i < drivers.length; ++i) { commit(() => { drivers[i](el, newVNode, oldVNode, commit, effects, driver); }, data); } return data; }; const oldVNodeChildren = oldVNode?.children ?? []; const newVNodeChildren = newVNode.children; const delta = newVNode.delta; const diff = (el2, newVNode2, oldVNode2) => driver(el2, newVNode2, oldVNode2, commit, effects).effects; if (delta) { for (let i = 0; i < delta.length; ++i) { const [deltaType, deltaPosition] = delta[i]; const child = el.childNodes.item(deltaPosition); if (deltaType === DeltaTypes.INSERT) { effects.push({ type: EffectTypes.CREATE, flush: () => el.insertBefore(createElement(newVNodeChildren[deltaPosition], false), child) }); } if (deltaType === DeltaTypes.UPDATE) { commit(() => { effects = diff(child, newVNodeChildren[deltaPosition], oldVNodeChildren[deltaPosition]); }, getData(child)); } if (deltaType === DeltaTypes.DELETE) { effects.push({ type: EffectTypes.REMOVE, flush: () => el.removeChild(child) }); } } return finish(el); } if (!newVNodeChildren || newVNode.flag === Flags.NO_CHILDREN) { if (!oldVNodeChildren) return finish(el); effects.push({ type: EffectTypes.REMOVE, flush: () => el.textContent = "" }); return finish(el); } if (!oldVNodeChildren || oldVNodeChildren?.length === 0) { for (let i = 0; i < newVNodeChildren.length; ++i) { effects.push({ type: EffectTypes.CREATE, flush: () => el.appendChild(createElement(newVNodeChildren[i], false)) }); } return finish(el); } if (newVNode.flag === Flags.ONLY_KEYED_CHILDREN) { if (!el[NODE_OBJECT_POOL_FIELD]) el[NODE_OBJECT_POOL_FIELD] = {}; let oldHead = 0; let newHead = 0; let oldTail = oldVNodeChildren.length - 1; let newTail = newVNodeChildren.length - 1; while (oldHead <= oldTail && newHead <= newTail) { const oldTailVNode = oldVNodeChildren[oldTail]; const newTailVNode = newVNodeChildren[newTail]; const oldHeadVNode = oldVNodeChildren[oldHead]; const newHeadVNode = newVNodeChildren[newHead]; if (oldTailVNode.key === newTailVNode.key) { oldTail--; newTail--; } else if (oldHeadVNode.key === newHeadVNode.key) { oldHead++; newHead++; } else if (oldHeadVNode.key === newTailVNode.key) { const node = el.childNodes.item(oldHead++); const tail = newTail--; effects.push({ type: EffectTypes.CREATE, flush: () => el.insertBefore(node, el.childNodes.item(tail).nextSibling) }); } else if (oldTailVNode.key === newHeadVNode.key) { const node = el.childNodes.item(oldTail--); const head = newHead++; effects.push({ type: EffectTypes.CREATE, flush: () => el.insertBefore(node, el.childNodes.item(head)) }); } else break; } if (oldHead > oldTail) { while (newHead <= newTail) { const head = newHead++; effects.push({ type: EffectTypes.CREATE, flush: () => el.insertBefore(el[NODE_OBJECT_POOL_FIELD][newVNodeChildren[head].key] ?? createElement(newVNodeChildren[head], false), el.childNodes.item(head)) }); } } else if (newHead > newTail) { while (oldHead <= oldTail) { const head = oldHead++; const node = el.childNodes.item(head); el[NODE_OBJECT_POOL_FIELD][oldVNodeChildren[head].key] = node; effects.push({ type: EffectTypes.REMOVE, flush: () => el.removeChild(node) }); } } else { const oldKeyMap = {}; for (; oldHead <= oldTail; ) { oldKeyMap[oldVNodeChildren[oldHead].key] = oldHead++; } while (newHead <= newTail) { const head = newHead++; const newVNodeChild = newVNodeChildren[head]; const oldVNodePosition = oldKeyMap[newVNodeChild.key]; if (oldVNodePosition !== void 0) { const node = el.childNodes.item(oldVNodePosition); effects.push({ type: EffectTypes.CREATE, flush: () => el.insertBefore(node, el.childNodes.item(head)) }); delete oldKeyMap[newVNodeChild.key]; } else { effects.push({ type: EffectTypes.CREATE, flush: () => el.insertBefore(el[NODE_OBJECT_POOL_FIELD][newVNodeChild.key] ?? createElement(newVNodeChild, false), el.childNodes.item(head)) }); } } for (const oldVNodeKey in oldKeyMap) { const node = el.childNodes.item(oldKeyMap[oldVNodeKey]); el[NODE_OBJECT_POOL_FIELD][oldVNodeKey] = node; effects.push({ type: EffectTypes.REMOVE, flush: () => el.removeChild(node) }); } } return finish(el); } if (newVNode.flag === Flags.ONLY_TEXT_CHILDREN) { const oldString = Array.isArray(oldVNode?.children) ? oldVNode?.children.join("") : oldVNode?.children; const newString = Array.isArray(newVNode?.children) ? newVNode?.children.join("") : newVNode?.children; if (oldString !== newString) { effects.push({ type: EffectTypes.REPLACE, flush: () => el.textContent = newString }); } return finish(el); } if (newVNode.flag === void 0 || newVNode.flag === Flags.ANY_CHILDREN) { if (oldVNodeChildren && newVNodeChildren) { const commonLength = Math.min(oldVNodeChildren.length, newVNodeChildren.length); for (let i = commonLength - 1; i >= 0; --i) { commit(() => { effects = diff(el.childNodes.item(i), newVNodeChildren[i], oldVNodeChildren[i]); }, getData(el)); } if (newVNodeChildren.length > oldVNodeChildren.length) { for (let i = commonLength; i < newVNodeChildren.length; ++i) { const node = createElement(newVNodeChildren[i], false); effects.push({ type: EffectTypes.CREATE, flush: () => el.appendChild(node) }); } } else if (newVNodeChildren.length < oldVNodeChildren.length) { for (let i = oldVNodeChildren.length - 1; i >= commonLength; --i) { effects.push({ type: EffectTypes.REMOVE, flush: () => el.removeChild(el.childNodes.item(i)) }); } } } else if (newVNodeChildren) { for (let i = 0; i < newVNodeChildren.length; ++i) { const node = createElement(newVNodeChildren[i], false); effects.push({ type: EffectTypes.CREATE, flush: () => el.appendChild(node) }); } } return finish(el); } return finish(el); }; const useNode = (drivers) => { const nodeDriver = (el, newVNode, oldVNode, commit = (work) => work(), effects = []) => { const finish = (element) => { if (!oldVNode) { effects.push({ type: EffectTypes.SET_PROP, flush: () => element[OLD_VNODE_FIELD] = newVNode }); } return { el: element, newVNode, oldVNode, effects }; }; if (newVNode?.flag === Flags.IGNORE_NODE || oldVNode?.flag === Flags.IGNORE_NODE) { return finish(el); } if (newVNode === void 0 || newVNode === null) { effects.push({ type: EffectTypes.REMOVE, flush: () => el.remove() }); return finish(el); } else { let prevVNode = oldVNode ?? el[OLD_VNODE_FIELD]; const hasString = typeof prevVNode === "string" || typeof newVNode === "string"; if (hasString && prevVNode !== newVNode) { const newEl = createElement(newVNode, false); effects.push({ type: EffectTypes.REPLACE, flush: () => el.replaceWith(newEl) }); return finish(newEl); } if (!hasString) { const prevVEntity = prevVNode; const newVEntity = newVNode; if (newVEntity.ignore) return finish(el); if (prevVEntity?.data) prevVNode = prevVEntity.resolve(); if (newVEntity?.data) newVNode = newVEntity.resolve(); const oldVElement = prevVNode; const newVElement = newVNode; if (newVElement.flag === Flags.REPLACE_NODE || oldVElement.flag === Flags.REPLACE_NODE) { const newEl = createElement(newVNode); el.replaceWith(newEl); return finish(el); } if (oldVElement?.key === void 0 && newVElement?.key === void 0 || oldVElement?.key !== newVElement?.key) { if (oldVElement?.tag !== newVElement?.tag || el instanceof Text) { const newEl = createElement(newVElement, false); effects.push({ type: EffectTypes.REPLACE, flush: () => el.replaceWith(newEl) }); return finish(newEl); } for (let i = 0; i < drivers.length; ++i) { commit(() => { drivers[i](el, newVElement, oldVElement, commit, effects, nodeDriver); }, { el, newVNode, oldVNode,