instruction
\n${data.title || ''}
\n${data.msg}
\n \n `\n\n theme.register(wrap)\n lang.register(wrap)\n\n const btn = wrap.querySelector('.btn') as HTMLButtonElement\n\n if (btn) {\n wrap.addEventListener('click', (ev) => {\n ev.stopPropagation()\n })\n\n btn.addEventListener('click', () => {\n this.remove(wrap)\n })\n\n window.addEventListener(EVT.list.closeCenterPanel, () => {\n this.remove(wrap)\n })\n }\n\n document.body.append(wrap)\n btn.focus()\n\n bg.useBG(wrap)\n }\n\n private remove(el: HTMLDivElement) {\n el && el.parentNode && el.parentNode.removeChild(el)\n }\n}\n\nconst msgBox = new MsgBox()\nexport { msgBox }\n","import { WorkThumbnail } from './WorkThumbnail'\nimport { pageType } from './PageType'\nimport { Tools } from './Tools'\nimport { Config } from './Config'\n\n// 查找小说作品的缩略图,当鼠标进入、移出时等动作触发时执行回调函数\nclass NovelThumbnail extends WorkThumbnail {\n constructor() {\n super()\n\n if (Config.mobile) {\n // 移动端的作品选择器就这一个\n this.selectors = ['.works-item-novel']\n } else {\n this.selectors = [\n 'li[size=\"1\"]>div',\n 'section li>div',\n 'nav>div>div',\n 'div.gtm-novel-work-recommend-link',\n 'section ul>div',\n 'section ul>li',\n 'div._ranking-item',\n 'div[size=\"496\"]',\n 'li',\n ]\n }\n\n this.findThumbnail(document.body)\n this.createObserver(document.body)\n }\n\n protected readonly selectors: string[] = []\n\n protected findThumbnail(parent: HTMLElement) {\n if (!parent.querySelectorAll) {\n return\n }\n // 遍历所有的选择器,为找到的元素绑定事件\n // 注意:有时候一个节点里会含有多种尺寸的缩略图,为了全部查找到它们,必须遍历所有的选择器。\n // 如果在查找到某个选择器之后,不再查找剩余的选择器,就可能会遗漏一部分缩略图。\n // 但是,这有可能会导致事件的重复绑定,所以下载器添加了 dataset.mouseover 标记以减少重复绑定\n for (const selector of this.selectors) {\n // 处理桌面端特殊情况中使用的选择器\n if (!Config.mobile) {\n // 在用户主页只使用指定的选择器,避免其他选择器导致顶部“精选”的小说作品被重复绑定事件\n if (\n pageType.type === pageType.list.UserHome &&\n selector !== 'section ul>li' &&\n selector !== 'li[size=\"1\"]>div'\n ) {\n continue\n }\n\n // 在小说排行榜里只使用 div._ranking-item\n if (\n pageType.type === pageType.list.NovelRanking &&\n selector !== 'div._ranking-item'\n ) {\n continue\n }\n\n // 在小说系列页面里只使用 section ul>li\n if (\n pageType.type === pageType.list.NovelSeries &&\n selector !== 'section ul>li'\n ) {\n continue\n }\n\n // div.gtm-novel-work-recommend-link 只能在小说页面里使用\n if (\n selector === 'div.gtm-novel-work-recommend-link' &&\n pageType.type !== pageType.list.Novel\n ) {\n continue\n }\n }\n\n let elements: HTMLElement[] | NodeListOf${r18HTML} ${aiHTML}
\n${workData.body.title}
\n${workData.body.description}
\n \n${workData.body.width} x ${workData.body.height}
\n${bmkHTML.join('')}
\n${new Date(\n workData.body.uploadDate\n ).toLocaleString()}
\n \n与
,以对应 EPUB 文本惯例\n static replaceEPUBTextWithP(str: string) {\n return (\n '' +\n str\n .replaceAll(/&/g, '&')\n .replaceAll(//g, '
')\n .replaceAll(/
/g, '
')\n .replaceAll(/\\n/g, '
')\n .replaceAll('
', '
') +\n '
'\n )\n }\n\n // 小说标题里有些符号需要和正文进行不同的处理\n // 标题里的 & 符号必须去掉或将其转换为普通字符\n // 至于换行标记,不知道标题里有没有,如果有的话也需要将其转换成普通符号\n static replaceEPUBTitle(str: string) {\n return str\n .replace(/&/g, ' and ')\n .replace(/与
, 以对应EPUB文本惯例\n content = Tools.replaceEPUBTextWithP(content)\n\n const userName = Tools.replaceEPUBText(\n Utils.replaceUnsafeStr(data.userName)\n )\n const title = Tools.replaceEPUBTitle(Utils.replaceUnsafeStr(data.title))\n const novelURL = `https://www.pixiv.net/novel/show.php?id=${data.id}`\n\n // 开始生成 EPUB 文件\n await downloadInterval.wait()\n\n const cover = await fetch(data.coverUrl).then((response) => {\n if (response.ok) return response.arrayBuffer()\n throw 'Network response was not ok.'\n })\n\n const date = DateFormat.format(data.createDate, settings.dateFormat)\n\n const jepub = new jEpub()\n jepub.init({\n i18n: lang.type,\n // 对 EPUB 左侧的一些文字进行本地化\n i18n_config: {\n code: lang.type,\n cover: 'Cover',\n toc: lang.transl('_目录'),\n info: lang.transl('_Information'),\n note: 'Notes',\n },\n title: title,\n author: userName,\n publisher: novelURL,\n // description 的内容会被添加到 book.opf 的与
, 以对应EPUB文本惯例\n description:\n `${date}
` + Tools.replaceEPUBTextWithP(data.description),\n })\n\n jepub.uuid(novelURL)\n jepub.date(new Date(data.createDate))\n jepub.cover(cover)\n\n // 添加小说里的图片\n const imageList = await downloadNovelEmbeddedImage.getImageList(\n data.id,\n content,\n data.embeddedImages\n )\n\n let current = 1\n const total = imageList.length\n const novelID = data.id\n for (const image of imageList) {\n log.log(\n lang.transl('_正在下载小说中的插画', `${current} / ${total}`),\n 1,\n false,\n 'downloadNovelImage' + novelID\n )\n current++\n\n const imageID = image.flag_id_part\n if (image.url === '') {\n content = content.replaceAll(image.flag, `image ${imageID} not found`)\n continue\n }\n\n // 加载图片\n await downloadInterval.wait()\n\n let illustration: Blob | undefined = undefined\n try {\n illustration = await fetch(image.url).then((response) => {\n if (response.ok) {\n return response.blob()\n }\n })\n } catch (error) {\n // 如果请求失败,不做处理,因为我懒……\n // 而且不排除有什么异常情况会导致重试也依然会失败,贸然重试可能会死循环\n console.log(error)\n }\n // 如果图片获取失败,不重试,并将其替换为提示\n if (illustration === undefined) {\n content = content.replaceAll(image.flag, `fetch ${image.url} failed`)\n continue\n }\n jepub.image(illustration, imageID)\n\n // 将小说正文里的图片标记替换为真实的的图片路径,以在 EPUB 里显示\n // [uploadedimage:17995414]\n // \n // 小说页面的文件是 OEBPS/page-0.html\n // 小说里的图片保存在 OEBPS/assets 文件夹里(封面图除外,它直接保存在 OEBPS/cover-image.jpg)\n // 注意:img src 的 assets 前面不要添加相对位置的符号: ./\n // 也就是说不能是 src=\"./assets/17995414.png\"\n // 因为某些在线阅读器(https://epub-reader.online/)会读取图片内容,生成 blob URL,然后替换原 src 里的值。\n // 当 src 前面有 ./ 的时候,blob URL 会跟在 ./ 后面,导致图片路径错误,无法显示\n const ext = Utils.getURLExt(image.url)\n // 在图片前后添加换行,因为有时图片和文字挨在一起,或者多张图片挨在一起。\n // 不添加换行的话,在某些阅读器里这些内容会并排,影响阅读体验\n const imgTag = `与
, 以对应EPUB文本惯例\n let content = Tools.replaceEPUBTextWithP(novelData.content)\n const novelID = novelData.id\n // 添加小说里的图片\n const imageList = await downloadNovelEmbeddedImage.getImageList(\n novelID,\n content,\n novelData.embeddedImages\n )\n\n let current = 1\n const total = imageList.length\n for (const image of imageList) {\n log.log(\n lang.transl('_正在下载小说中的插画', `${current} / ${total}`),\n 1,\n false,\n 'downloadNovelImage' + novelID\n )\n current++\n\n const imageID = image.flag_id_part\n if (image.url === '') {\n content = content.replaceAll(\n image.flag,\n `image ${imageID} not found`\n )\n continue\n }\n\n // 加载图片\n let illustration: Blob | undefined = undefined\n try {\n illustration = await fetch(image.url).then((response) => {\n if (response.ok) {\n return response.blob()\n }\n })\n } catch (error) {\n console.log(error)\n }\n // 如果图片获取失败,不重试,并将其替换为提示\n if (illustration === undefined) {\n content = content.replaceAll(\n image.flag,\n `fetch ${image.url} failed`\n )\n continue\n }\n jepub.image(illustration, imageID)\n\n // 将小说正文里的图片标记替换为真实的的图片路径,以在 EPUB 里显示\n const ext = Utils.getURLExt(image.url)\n const imgTag = `${defaultName}: ${fullNameHtml}
`\n resultArr.push(nowResult)\n } else {\n // 小说作品不显示原文件名(因为没有此数据)\n const nowResult = `${fullNameHtml}
`\n resultArr.push(nowResult)\n }\n }\n\n result = resultArr.join('')\n } else {\n // 不生成 html 标签,只生成纯文本,保存为 txt 文件\n for (let i = 0; i < length; i++) {\n const data = store.result[i]\n const fullName = fileName.createFileName(data)\n\n if (data.type !== 3) {\n // 图片作品,在文件名前面显示文件 url 里的文件名\n let defaultName = data.original.replace(/.*\\//, '')\n resultArr.push(`${defaultName}: ${fullName}`)\n } else {\n // 小说作品不显示原文件名(因为没有此数据)\n resultArr.push(fullName)\n }\n }\n\n result = resultArr.join('\\n')\n }\n\n EVT.fire('output', {\n content: result,\n title: '_预览文件名',\n })\n }\n}\n\nnew PreviewFileName()\n","import { store } from '../store/Store'\nimport { EVT } from '../EVT'\nimport { lang } from '../Lang'\nimport { settings } from '../setting/Settings'\nimport { toast } from '../Toast'\nimport { Config } from '../Config'\n\n// 显示 url\nclass ShowURLs {\n constructor() {\n this.bindEvents()\n }\n\n private bindEvents() {\n window.addEventListener(EVT.list.showURLs, () => {\n this.showURLs()\n })\n }\n\n private showURLs() {\n const urls: string[] = []\n const size = settings.imageSize\n for (const data of store.result) {\n // 只输出图片文件的 url\n // 小说文件没有固定的 url 所以不输出\n if (data.type !== 3) {\n urls.push(data[size])\n }\n }\n\n if (store.result.length === 0 || urls.length === 0) {\n return toast.error(lang.transl('_没有可用的抓取结果'))\n }\n\n let result = ''\n if (store.result.length < Config.outputMax) {\n result = urls.join('instruction
\n${data.title || ''}
\n${data.msg}
\n \n `\n\n theme.register(wrap)\n lang.register(wrap)\n\n const btn = wrap.querySelector('.btn') as HTMLButtonElement\n\n if (btn) {\n wrap.addEventListener('click', (ev) => {\n ev.stopPropagation()\n })\n\n btn.addEventListener('click', () => {\n this.remove(wrap)\n })\n\n window.addEventListener(EVT.list.closeCenterPanel, () => {\n this.remove(wrap)\n })\n }\n\n document.body.append(wrap)\n btn.focus()\n\n bg.useBG(wrap)\n }\n\n private remove(el: HTMLDivElement) {\n el && el.parentNode && el.parentNode.removeChild(el)\n }\n}\n\nconst msgBox = new MsgBox()\nexport { msgBox }\n","import { WorkThumbnail } from './WorkThumbnail'\nimport { pageType } from './PageType'\nimport { Tools } from './Tools'\nimport { Config } from './Config'\n\n// 查找小说作品的缩略图,当鼠标进入、移出时等动作触发时执行回调函数\nclass NovelThumbnail extends WorkThumbnail {\n constructor() {\n super()\n\n if (Config.mobile) {\n // 移动端的作品选择器就这一个\n this.selectors = ['.works-item-novel']\n } else {\n this.selectors = [\n 'li[size=\"1\"]>div',\n 'section li>div',\n 'nav>div>div',\n 'div.gtm-novel-work-recommend-link',\n 'section ul>div',\n 'section ul>li',\n 'div._ranking-item',\n 'div[size=\"496\"]',\n 'li',\n ]\n }\n\n this.findThumbnail(document.body)\n this.createObserver(document.body)\n }\n\n protected readonly selectors: string[] = []\n\n protected findThumbnail(parent: HTMLElement) {\n if (!parent.querySelectorAll) {\n return\n }\n // 遍历所有的选择器,为找到的元素绑定事件\n // 注意:有时候一个节点里会含有多种尺寸的缩略图,为了全部查找到它们,必须遍历所有的选择器。\n // 如果在查找到某个选择器之后,不再查找剩余的选择器,就可能会遗漏一部分缩略图。\n // 但是,这有可能会导致事件的重复绑定,所以下载器添加了 dataset.mouseover 标记以减少重复绑定\n for (const selector of this.selectors) {\n // 处理桌面端特殊情况中使用的选择器\n if (!Config.mobile) {\n // 在用户主页只使用指定的选择器,避免其他选择器导致顶部“精选”的小说作品被重复绑定事件\n if (\n pageType.type === pageType.list.UserHome &&\n selector !== 'section ul>li' &&\n selector !== 'li[size=\"1\"]>div'\n ) {\n continue\n }\n\n // 在小说排行榜里只使用 div._ranking-item\n if (\n pageType.type === pageType.list.NovelRanking &&\n selector !== 'div._ranking-item'\n ) {\n continue\n }\n\n // 在小说系列页面里只使用 section ul>li\n if (\n pageType.type === pageType.list.NovelSeries &&\n selector !== 'section ul>li'\n ) {\n continue\n }\n\n // div.gtm-novel-work-recommend-link 只能在小说页面里使用\n if (\n selector === 'div.gtm-novel-work-recommend-link' &&\n pageType.type !== pageType.list.Novel\n ) {\n continue\n }\n }\n\n let elements: HTMLElement[] | NodeListOf${r18HTML} ${aiHTML}
\n${workData.body.title}
\n${workData.body.description}
\n \n${workData.body.width} x ${workData.body.height}
\n${bmkHTML.join('')}
\n${new Date(\n workData.body.uploadDate\n ).toLocaleString()}
\n \n与
,以对应 EPUB 文本惯例\n static replaceEPUBTextWithP(str: string) {\n return (\n '' +\n str\n .replaceAll(/&/g, '&')\n .replaceAll(//g, '
')\n .replaceAll(/
/g, '
')\n .replaceAll(/\\n/g, '
')\n .replaceAll('
', '
') +\n '
'\n )\n }\n\n // 小说标题里有些符号需要和正文进行不同的处理\n // 标题里的 & 符号必须去掉或将其转换为普通字符\n // 至于换行标记,不知道标题里有没有,如果有的话也需要将其转换成普通符号\n static replaceEPUBTitle(str: string) {\n return str\n .replace(/&/g, ' and ')\n .replace(/