hv-monsterdb-userscript
Version:
M-M-M-MONSTER DATABASE!
1,161 lines (1,138 loc) • 97.6 kB
JavaScript
// ==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,