diff --git a/ComicRead-AdGuard.user.js b/ComicRead-AdGuard.user.js index 5142933c..6d68812c 100644 --- a/ComicRead-AdGuard.user.js +++ b/ComicRead-AdGuard.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ComicRead // @namespace ComicRead -// @version 9.3.0 +// @version 9.3.1 // @description 为漫画站增加双页阅读、翻译等优化体验的增强功能。百合会(记录阅读历史、自动签到等)、百合会新站、动漫之家(解锁隐藏漫画)、E-Hentai(关联 nhentai、快捷收藏、标签染色、识别广告页等)、nhentai(彻底屏蔽漫画、无限滚动)、Yurifans(自动签到)、拷贝漫画(copymanga)(显示最后阅读记录)、PonpomuYuri、明日方舟泰拉记事社、禁漫天堂、漫画柜(manhuagui)、漫画DB(manhuadb)、动漫屋(dm5)、绅士漫画(wnacg)、mangabz、komiic、无限动漫、新新漫画、hitomi、Anchira、kemono、nekohouse、welovemanga // @description:en Add enhanced features to the comic site for optimized experience, including dual-page reading and translation. E-Hentai (Associate nhentai, Quick favorite, Colorize tags, Detect advertise page, etc.) | nhentai (Totally block comics, Auto page turning) | hitomi | Anchira | kemono | nekohouse | welovemanga. // @description:ru Добавляет расширенные функции для удобства на сайт, такие как двухстраничный режим и перевод. @@ -4398,7 +4398,7 @@ const handleWheel = e => { if ((e.ctrlKey || e.altKey) && store.option.scrollMode.enabled && store.zoom.scale === 100) { e.preventDefault(); if (store.option.scrollMode.fitToWidth) return; - return zoomScrollModeImg(isWheelDown ? -0.1 : 0.1); + return zoomScrollModeImg(isWheelDown ? -0.05 : 0.05); } if (e.ctrlKey || e.altKey || store.zoom.scale !== 100) { e.preventDefault(); @@ -4632,7 +4632,7 @@ const checkImgSize = (i, e) => { }; /** 图片加载完毕的回调 */ -const handleImgLoaded = (i, e) => () => { +const handleImgLoaded = (i, e) => async () => { setState(state => { const img = state.imgList[i]; if (!img) return; @@ -4642,6 +4642,7 @@ const handleImgLoaded = (i, e) => () => { }); setLoadLock(false); loadingImgMap.delete(i); + await e.decode(); }; /** 图片加载出错的次数 */ @@ -4737,8 +4738,8 @@ const updateImgLoadType = singleThreaded(() => { loadRangeImg(preloadNum().back) || // 再加载前面几页 loadRangeImg(-preloadNum().front) || - // 根据图片总数和设置决定是否要继续加载其余图片 - !store.option.alwaysLoadAllImg && store.imgList.length > 60 || + // 根据设置决定是否要继续加载其余图片 + !store.option.alwaysLoadAllImg || // 加载当前页后面的图片 loadRangeImg(Number.POSITIVE_INFINITY, 5) || // 加载当前页前面的图片 @@ -4911,9 +4912,8 @@ const EmptyTip = () => { var _tmpl$$B = /*#__PURE__*/web.template(\`\`), _tmpl$2$a = /*#__PURE__*/web.template(\`
\`), - _tmpl$3$4 = /*#__PURE__*/web.template(\`\`), - _tmpl$4$1 = /*#__PURE__*/web.template(\`\`); -const RenderComicImg = img => { + _tmpl$3$4 = /*#__PURE__*/web.template(\`\`); +const ComicImg = img => { const showState = () => imgShowState().get(img.index); const src = () => { if (img.loadType === 'wait') return ''; @@ -5022,47 +5022,6 @@ const RenderComicImg = img => { })]; }; -// 用于防止图片缓存被浏览器回收 -const SaveComicImg = img => (() => { - var _el$4 = _tmpl$3$4(); - web.insert(_el$4, web.createComponent(solidJs.Show, { - get when() { - return img.loadType === 'loaded'; - }, - get children() { - return (() => { - var _el$5 = _tmpl$4$1(); - web.effect(() => web.setAttribute(_el$5, "src", img.src)); - return _el$5; - })(); - } - })); - web.effect(_p$ => { - var _v$9 = modules_c21c94f2$1.img, - _v$10 = \`\${img.index}\`; - _v$9 !== _p$.e && web.className(_el$4, _p$.e = _v$9); - _v$10 !== _p$.t && web.setAttribute(_el$4, "id", _p$.t = _v$10); - return _p$; - }, { - e: undefined, - t: undefined - }); - return _el$4; -})(); - -/** 漫画图片 */ -const ComicImg = img => web.createComponent(solidJs.Show, { - get when() { - return renderImgList().has(img.index); - }, - get children() { - return RenderComicImg(img); - }, - get fallback() { - return SaveComicImg(img); - } -}); - // 目前即使是不显示的图片也必须挂载上,否则解析好的图片会被浏览器垃圾回收掉, // 导致在 ehentai 上无法正常加载图片。但这样会在图片过多时造成性能问题, // 虽然也尝试了将解析好的 Image 对象存储起来挂上引用和另外放到一个避免渲染的 dom 下, @@ -7390,10 +7349,8 @@ const useFab = async initProps => { }; var _tmpl$$1 = /*#__PURE__*/web.template(\`

🥳 ComicRead 已更新到 v\`), - _tmpl$2 = /*#__PURE__*/web.template(\`

新增\`), - _tmpl$3 = /*#__PURE__*/web.template(\`
  • 增加 ehentai 标签染色功能

  • 增加 ehentai 快捷评分功能\`), - _tmpl$4 = /*#__PURE__*/web.template(\`

    修复\`), - _tmpl$5 = /*#__PURE__*/web.template(\`
    • 修复 ehentai 无法正常加载图片的 bug

    • 修复在 safari 上图片加载完毕依然不显示的 bug\`); + _tmpl$2 = /*#__PURE__*/web.template(\`

      修复\`), + _tmpl$3 = /*#__PURE__*/web.template(\`
      • 修复出现多余功能按钮的 bug

      • 修复预加载页数未正确生效的 bug\`); const migrationOption = async (name, editFn) => { try { const option = await GM.getValue(name); @@ -7463,7 +7420,7 @@ const handleVersionUpdate = async () => { _el$.firstChild; web.insert(_el$, () => GM.info.script.version, null); return _el$; - })(), _tmpl$2(), _tmpl$3(), _tmpl$4(), _tmpl$5()], { + })(), _tmpl$2(), _tmpl$3()], { id: 'Version Tip', type: 'custom', duration: Number.POSITIVE_INFINITY, @@ -7486,6 +7443,17 @@ const getHotkeys = async () => ({ ...(await GM.getValue('Hotkeys', {})) }); +/** 清理多余的配置项 */ +const clear = (options, defaultOptions) => { + let isClear = false; + for (const key of Object.keys(options)) { + if (Reflect.has(defaultOptions, key)) continue; + Reflect.deleteProperty(options, key); + isClear = true; + } + return isClear; +}; + /** * 对修改站点配置的相关方法的封装 * @param name 站点名 @@ -7493,6 +7461,8 @@ const getHotkeys = async () => ({ */ const useSiteOptions = async (name, defaultOptions = {}) => { const _defaultOptions = { + option: undefined, + defaultOption: undefined, autoShow: true, hiddenFAB: false, ...defaultOptions @@ -7500,7 +7470,7 @@ const useSiteOptions = async (name, defaultOptions = {}) => { const saveOptions = await GM.getValue(name); const options = store$2.createMutable(assign(_defaultOptions, saveOptions)); const setOptions = async newValue => { - Object.assign(options, newValue); + if (newValue) Object.assign(options, newValue); // 只保存和默认设置不同的部分 return GM.setValue(name, difference(options, _defaultOptions)); }; @@ -7508,6 +7478,8 @@ const useSiteOptions = async (name, defaultOptions = {}) => { const isStored = saveOptions !== undefined; // 如果当前站点没有存储配置,就补充上去 if (!isStored) await GM.setValue(name, {}); + // 否则检查是否有多余的配置 + else if (clear(options, _defaultOptions)) await setOptions(); return { /** 站点配置 */ options, @@ -9655,8 +9627,8 @@ const updateTagColor = async () => { } css += ` /* 禁用 eh 的变色效果 */ - #taglist a { color: #DDDDDD !important; position: relative; } - #taglist a:hover { color: #EEEEEE !important; } + #taglist a { color: var(--tag) !important; position: relative; } + #taglist a:hover { color: var(--tag-hover) !important; } #taglist a::after { content: ""; @@ -9667,30 +9639,41 @@ const updateTagColor = async () => { height: 2px; bottom: -7px; } - .tup { --color: #00E639; } - .tdn { --color: #FF3333; } + .tup { --color: var(--tup) } + .tdn { --color: var(--tdn) } #taglist a[style="color: blue;"] { --color: blue; } `; await GM.setValue('ehTagColorizeCss', css); return css; }; +const getTagColorizeCss = async () => { + let colorizeCss = await GM.getValue('ehTagColorizeCss'); + colorizeCss ||= await updateTagColor(); + return colorizeCss; +}; /** 标签染色 */ const colorizeTag = async pageType => { switch (pageType) { case 'gallery': { - let colorizeCss = await GM.getValue('ehTagColorizeCss'); - colorizeCss ||= await updateTagColor(); - return GM_addStyle(colorizeCss); + let css = location.origin === 'https://exhentai.org' ? '--tag: #DDDDDD; --tag-hover: #EEEEEE; --tup: #00E639; --tdn: #FF3333;' : '--tag: #5C0D11; --tag-hover: #8F4701; --tup: green; --tdn: red;'; + css = `#taglist { ${css} }\n\n${await getTagColorizeCss()}`; + return GM_addStyle(css); } case 'mytags': { // 进入时更新 updateTagColor(); + let oldCss = await getTagColorizeCss(); + /** 不断循环直至获取到新数据 */ + const waitUpdate = main.singleThreaded(async () => { + const newCss = await getTagColorizeCss(); + if (newCss === oldCss) setTimeout(waitUpdate, 1000);else oldCss = newCss; + }); // 点击保存按钮时更新 - document.addEventListener('click', e => e.target.tagName === 'BUTTON' && e.target.id.startsWith('tagsave_') && updateTagColor()); + document.addEventListener('click', e => e.target.tagName === 'BUTTON' && e.target.id.startsWith('tagsave_') && waitUpdate()); } // 除了在 mytags 里更新外,还可以在列表页检查高亮的标签和脚本存储的标签颜色数据是否对应, @@ -10422,8 +10405,7 @@ const main = require('main'); // #[禁漫天堂](https://18comic.vip) case 'jmcomic.me': - case '18comic-erdtree.xyz': - case '18-comicstellar.xyz': + case '18comic-erdtree.cc': case '18comic.org': case '18comic.vip': { diff --git a/ComicRead.user.js b/ComicRead.user.js index 5bfe8958..bd4490ce 100644 --- a/ComicRead.user.js +++ b/ComicRead.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ComicRead // @namespace ComicRead -// @version 9.3.0 +// @version 9.3.1 // @description 为漫画站增加双页阅读、翻译等优化体验的增强功能。百合会(记录阅读历史、自动签到等)、百合会新站、动漫之家(解锁隐藏漫画)、E-Hentai(关联 nhentai、快捷收藏、标签染色、识别广告页等)、nhentai(彻底屏蔽漫画、无限滚动)、Yurifans(自动签到)、拷贝漫画(copymanga)(显示最后阅读记录)、PonpomuYuri、明日方舟泰拉记事社、禁漫天堂、漫画柜(manhuagui)、漫画DB(manhuadb)、动漫屋(dm5)、绅士漫画(wnacg)、mangabz、komiic、无限动漫、新新漫画、hitomi、Anchira、kemono、nekohouse、welovemanga // @description:en Add enhanced features to the comic site for optimized experience, including dual-page reading and translation. E-Hentai (Associate nhentai, Quick favorite, Colorize tags, Detect advertise page, etc.) | nhentai (Totally block comics, Auto page turning) | hitomi | Anchira | kemono | nekohouse | welovemanga. // @description:ru Добавляет расширенные функции для удобства на сайт, такие как двухстраничный режим и перевод. @@ -4332,7 +4332,7 @@ const handleWheel = e => { if ((e.ctrlKey || e.altKey) && store.option.scrollMode.enabled && store.zoom.scale === 100) { e.preventDefault(); if (store.option.scrollMode.fitToWidth) return; - return zoomScrollModeImg(isWheelDown ? -0.1 : 0.1); + return zoomScrollModeImg(isWheelDown ? -0.05 : 0.05); } if (e.ctrlKey || e.altKey || store.zoom.scale !== 100) { e.preventDefault(); @@ -4566,7 +4566,7 @@ const checkImgSize = (i, e) => { }; /** 图片加载完毕的回调 */ -const handleImgLoaded = (i, e) => () => { +const handleImgLoaded = (i, e) => async () => { setState(state => { const img = state.imgList[i]; if (!img) return; @@ -4576,6 +4576,7 @@ const handleImgLoaded = (i, e) => () => { }); setLoadLock(false); loadingImgMap.delete(i); + await e.decode(); }; /** 图片加载出错的次数 */ @@ -4671,8 +4672,8 @@ const updateImgLoadType = singleThreaded(() => { loadRangeImg(preloadNum().back) || // 再加载前面几页 loadRangeImg(-preloadNum().front) || - // 根据图片总数和设置决定是否要继续加载其余图片 - !store.option.alwaysLoadAllImg && store.imgList.length > 60 || + // 根据设置决定是否要继续加载其余图片 + !store.option.alwaysLoadAllImg || // 加载当前页后面的图片 loadRangeImg(Number.POSITIVE_INFINITY, 5) || // 加载当前页前面的图片 @@ -4845,9 +4846,8 @@ const EmptyTip = () => { var _tmpl$$B = /*#__PURE__*/web.template(\`\`), _tmpl$2$a = /*#__PURE__*/web.template(\`

        \`), - _tmpl$3$4 = /*#__PURE__*/web.template(\`\`), - _tmpl$4$1 = /*#__PURE__*/web.template(\`\`); -const RenderComicImg = img => { + _tmpl$3$4 = /*#__PURE__*/web.template(\`\`); +const ComicImg = img => { const showState = () => imgShowState().get(img.index); const src = () => { if (img.loadType === 'wait') return ''; @@ -4956,47 +4956,6 @@ const RenderComicImg = img => { })]; }; -// 用于防止图片缓存被浏览器回收 -const SaveComicImg = img => (() => { - var _el$4 = _tmpl$3$4(); - web.insert(_el$4, web.createComponent(solidJs.Show, { - get when() { - return img.loadType === 'loaded'; - }, - get children() { - return (() => { - var _el$5 = _tmpl$4$1(); - web.effect(() => web.setAttribute(_el$5, "src", img.src)); - return _el$5; - })(); - } - })); - web.effect(_p$ => { - var _v$9 = modules_c21c94f2$1.img, - _v$10 = \`\${img.index}\`; - _v$9 !== _p$.e && web.className(_el$4, _p$.e = _v$9); - _v$10 !== _p$.t && web.setAttribute(_el$4, "id", _p$.t = _v$10); - return _p$; - }, { - e: undefined, - t: undefined - }); - return _el$4; -})(); - -/** 漫画图片 */ -const ComicImg = img => web.createComponent(solidJs.Show, { - get when() { - return renderImgList().has(img.index); - }, - get children() { - return RenderComicImg(img); - }, - get fallback() { - return SaveComicImg(img); - } -}); - // 目前即使是不显示的图片也必须挂载上,否则解析好的图片会被浏览器垃圾回收掉, // 导致在 ehentai 上无法正常加载图片。但这样会在图片过多时造成性能问题, // 虽然也尝试了将解析好的 Image 对象存储起来挂上引用和另外放到一个避免渲染的 dom 下, @@ -7324,10 +7283,8 @@ const useFab = async initProps => { }; var _tmpl$$1 = /*#__PURE__*/web.template(\`

        🥳 ComicRead 已更新到 v\`), - _tmpl$2 = /*#__PURE__*/web.template(\`

        新增\`), - _tmpl$3 = /*#__PURE__*/web.template(\`
        • 增加 ehentai 标签染色功能

        • 增加 ehentai 快捷评分功能\`), - _tmpl$4 = /*#__PURE__*/web.template(\`

          修复\`), - _tmpl$5 = /*#__PURE__*/web.template(\`
          • 修复 ehentai 无法正常加载图片的 bug

          • 修复在 safari 上图片加载完毕依然不显示的 bug\`); + _tmpl$2 = /*#__PURE__*/web.template(\`

            修复\`), + _tmpl$3 = /*#__PURE__*/web.template(\`
            • 修复出现多余功能按钮的 bug

            • 修复预加载页数未正确生效的 bug\`); const migrationOption = async (name, editFn) => { try { const option = await GM.getValue(name); @@ -7397,7 +7354,7 @@ const handleVersionUpdate = async () => { _el$.firstChild; web.insert(_el$, () => GM.info.script.version, null); return _el$; - })(), _tmpl$2(), _tmpl$3(), _tmpl$4(), _tmpl$5()], { + })(), _tmpl$2(), _tmpl$3()], { id: 'Version Tip', type: 'custom', duration: Number.POSITIVE_INFINITY, @@ -7420,6 +7377,17 @@ const getHotkeys = async () => ({ ...(await GM.getValue('Hotkeys', {})) }); +/** 清理多余的配置项 */ +const clear = (options, defaultOptions) => { + let isClear = false; + for (const key of Object.keys(options)) { + if (Reflect.has(defaultOptions, key)) continue; + Reflect.deleteProperty(options, key); + isClear = true; + } + return isClear; +}; + /** * 对修改站点配置的相关方法的封装 * @param name 站点名 @@ -7427,6 +7395,8 @@ const getHotkeys = async () => ({ */ const useSiteOptions = async (name, defaultOptions = {}) => { const _defaultOptions = { + option: undefined, + defaultOption: undefined, autoShow: true, hiddenFAB: false, ...defaultOptions @@ -7434,7 +7404,7 @@ const useSiteOptions = async (name, defaultOptions = {}) => { const saveOptions = await GM.getValue(name); const options = store$2.createMutable(assign(_defaultOptions, saveOptions)); const setOptions = async newValue => { - Object.assign(options, newValue); + if (newValue) Object.assign(options, newValue); // 只保存和默认设置不同的部分 return GM.setValue(name, difference(options, _defaultOptions)); }; @@ -7442,6 +7412,8 @@ const useSiteOptions = async (name, defaultOptions = {}) => { const isStored = saveOptions !== undefined; // 如果当前站点没有存储配置,就补充上去 if (!isStored) await GM.setValue(name, {}); + // 否则检查是否有多余的配置 + else if (clear(options, _defaultOptions)) await setOptions(); return { /** 站点配置 */ options, @@ -9589,8 +9561,8 @@ const updateTagColor = async () => { } css += ` /* 禁用 eh 的变色效果 */ - #taglist a { color: #DDDDDD !important; position: relative; } - #taglist a:hover { color: #EEEEEE !important; } + #taglist a { color: var(--tag) !important; position: relative; } + #taglist a:hover { color: var(--tag-hover) !important; } #taglist a::after { content: ""; @@ -9601,30 +9573,41 @@ const updateTagColor = async () => { height: 2px; bottom: -7px; } - .tup { --color: #00E639; } - .tdn { --color: #FF3333; } + .tup { --color: var(--tup) } + .tdn { --color: var(--tdn) } #taglist a[style="color: blue;"] { --color: blue; } `; await GM.setValue('ehTagColorizeCss', css); return css; }; +const getTagColorizeCss = async () => { + let colorizeCss = await GM.getValue('ehTagColorizeCss'); + colorizeCss ||= await updateTagColor(); + return colorizeCss; +}; /** 标签染色 */ const colorizeTag = async pageType => { switch (pageType) { case 'gallery': { - let colorizeCss = await GM.getValue('ehTagColorizeCss'); - colorizeCss ||= await updateTagColor(); - return GM_addStyle(colorizeCss); + let css = location.origin === 'https://exhentai.org' ? '--tag: #DDDDDD; --tag-hover: #EEEEEE; --tup: #00E639; --tdn: #FF3333;' : '--tag: #5C0D11; --tag-hover: #8F4701; --tup: green; --tdn: red;'; + css = `#taglist { ${css} }\n\n${await getTagColorizeCss()}`; + return GM_addStyle(css); } case 'mytags': { // 进入时更新 updateTagColor(); + let oldCss = await getTagColorizeCss(); + /** 不断循环直至获取到新数据 */ + const waitUpdate = main.singleThreaded(async () => { + const newCss = await getTagColorizeCss(); + if (newCss === oldCss) setTimeout(waitUpdate, 1000);else oldCss = newCss; + }); // 点击保存按钮时更新 - document.addEventListener('click', e => e.target.tagName === 'BUTTON' && e.target.id.startsWith('tagsave_') && updateTagColor()); + document.addEventListener('click', e => e.target.tagName === 'BUTTON' && e.target.id.startsWith('tagsave_') && waitUpdate()); } // 除了在 mytags 里更新外,还可以在列表页检查高亮的标签和脚本存储的标签颜色数据是否对应, @@ -10356,8 +10339,7 @@ const main = require('main'); // #[禁漫天堂](https://18comic.vip) case 'jmcomic.me': - case '18comic-erdtree.xyz': - case '18-comicstellar.xyz': + case '18comic-erdtree.cc': case '18comic.org': case '18comic.vip': { diff --git a/docs/.other/CHANGELOG.md b/docs/.other/CHANGELOG.md index 76c21d19..7ee651d0 100644 --- a/docs/.other/CHANGELOG.md +++ b/docs/.other/CHANGELOG.md @@ -1,5 +1,13 @@ +## [9.3.1](https://github.com/hymbz/ComicReadScript/compare/v9.3.0...v9.3.1) (2024-07-12) + + +### Bug Fixes + +* :bug: 修复出现多余功能按钮的 bug ([27a1ba4](https://github.com/hymbz/ComicReadScript/commit/27a1ba486d10d4cc01f41255d4d40b484b86a79d)), closes [#172](https://github.com/hymbz/ComicReadScript/issues/172) +* :bug: 修复预加载页数未正确生效的 bug ([c9f4d8e](https://github.com/hymbz/ComicReadScript/commit/c9f4d8e9372f7b80514d94c574f9bda993df6068)) + ## [9.3.0](https://github.com/hymbz/ComicReadScript/compare/v9.2.0...v9.3.0) (2024-07-07) diff --git a/docs/.other/LatestChange.md b/docs/.other/LatestChange.md index 1f67b5ca..8ddba0de 100644 --- a/docs/.other/LatestChange.md +++ b/docs/.other/LatestChange.md @@ -1,13 +1,7 @@ -## [9.3.0](https://github.com/hymbz/ComicReadScript/compare/v9.2.0...v9.3.0) (2024-07-07) - - -### Features - -* :sparkles: 增加 ehentai 标签染色功能 ([95ed709](https://github.com/hymbz/ComicReadScript/commit/95ed709bbf5d3f2198e102f9f9aaf29e8cca2e8f)) -* :sparkles: 增加 ehentai 快捷评分功能 ([55889d2](https://github.com/hymbz/ComicReadScript/commit/55889d286d5886be5e0ba33cde5c9662fa12b6fc)) +## [9.3.1](https://github.com/hymbz/ComicReadScript/compare/v9.3.0...v9.3.1) (2024-07-12) ### Bug Fixes -* :bug: 修复 ehentai 无法正常加载图片的 bug ([f843e84](https://github.com/hymbz/ComicReadScript/commit/f843e842d6905f9eb80bbff18d3010ec7adca735)) -* :bug: 修复在 safari 上图片加载完毕依然不显示的 bug ([510c524](https://github.com/hymbz/ComicReadScript/commit/510c5241706d438f111c65afd4861b4cc5b420fe)), closes [/sleazyfork.org/zh-CN/scripts/374903/discussions/249178#comment-512383](https://github.com/hymbz//sleazyfork.org/zh-CN/scripts/374903/discussions/249178/issues/comment-512383) +* :bug: 修复出现多余功能按钮的 bug ([27a1ba4](https://github.com/hymbz/ComicReadScript/commit/27a1ba486d10d4cc01f41255d4d40b484b86a79d)), closes [#172](https://github.com/hymbz/ComicReadScript/issues/172) +* :bug: 修复预加载页数未正确生效的 bug ([c9f4d8e](https://github.com/hymbz/ComicReadScript/commit/c9f4d8e9372f7b80514d94c574f9bda993df6068)) diff --git a/package.json b/package.json index 4f3d3784..294dc333 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ComicRead", - "version": "9.3.0", + "version": "9.3.1", "description": "", "author": "hymbz", "license": "AGPL-3.0-or-later",