diff --git a/.gitignore b/.gitignore index 15a5eb4..51325f1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,8 @@ pnpm-debug.log* lerna-debug.log* node_modules -dist +# 开发版本直接作为仓库内容发布。大更新再发release +# dist dist-ssr *.local @@ -22,5 +23,3 @@ dist-ssr *.njsproj *.sln *.sw? - -node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..8041288 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# 番茄小说助手 (fanqie-assistant) + +一个用于 [番茄小说网页版](https://fanqienovel.com) 的用户脚本(Userscript):去广告、去推广、解锁章节、优化阅读体验。 + +> **项目处于开发初期。** 功能仅覆盖基础阅读,接口和目录结构都可能随时调整。欢迎试用和反馈,但请做好随时会有破坏性变更的准备。 + +## 已实现 + +第一阶段「基础阅读」已经可用: + +- **正文替换**:阅读页拉取完整正文并渲染,替换网页原有内容容器,同时允许复制文本。 +- **解锁章节**:网页端被屏蔽的章节可以正常阅读。 +- **漫画阅读**:图片章节支持懒加载,加密图片滚动到视口时自动处理。 +- **字体反混淆**:还原网页自绘字体渲染的混淆文本,动态插入的内容同样生效。 +- **屏蔽埋点上报**:丢弃发往字节、百度统计域名的埋点请求。 +- **章节信息补全**:补上卷名、精确到秒的更新时间,并修正页面标题。 +- **用户信息展示**:已登录时在头像菜单里显示已读本数和累计阅读时长。 + +## 开发计划 + +计划不分先后,也可能会开发以下列表以外的功能: + +1. 短剧网页端播放 +2. 更完善的用户详情界面(自己的和他人的) +3. 段评、章评、书评 +4. 阅读数据上报(与安卓端同步阅读进度与时长) +5. APP 端带推荐的排行榜 +6. 分类筛选 +7. 解锁书架中因下架或版权原因无法在网页阅读、但 APP 端仍可阅读的书籍 +8. 更详细的书籍信息(评分、在读人数等) +9. 推书页面 +10. 听书 +如果您有更多好的想法,欢迎提 PR 或 Issue。 + +## 使用 + +需要一个用户脚本管理器,推荐 [Tampermonkey](https://www.tampermonkey.net/)。 + +安装构建产物 `fanqie-assistant.user.js` 即可。 + +## 开发 + +```bash +npm install +npm run dev # 启动开发服务器 +npm run build # 类型检查 + 打包 +npm run preview # 预览构建产物 +``` + +### 调试 + +`npm run dev` 后,vite-plugin-monkey 会输出一个安装地址,在脚本管理器里安装这个开发版即可。它指向本地开发服务器,改完代码刷新页面就能看到效果,不需要每次重新打包安装。 + +脚本运行时机是 `document-start`,日志都打在浏览器控制台,按 `fqa` 或钩子 id(如 `readerHook_load`)过滤比较方便。 + +调试时注意关掉正式版脚本,两个版本同时启用会重复注入。 + +### 编译 + +```bash +npm run build +``` + +产物是单文件 `dist/fanqie-assistant.user.js`。`build` 会先跑 `tsc` 做类型检查,类型报错会中断打包。 + +## 项目结构 + +``` +src/ +├── main.ts 入口,安装导航钩子并按顺序初始化各模块 +├── config.ts 设备配置与全局常量 +├── types.ts 书籍、卷、章节等数据模型 +├── cssInject.ts 样式注入 +├── fontDecrypt.ts 自绘字体文本还原 +├── localStorage.ts 存储读写封装 +├── api/ 接口层(用户、书籍、目录、正文) +├── hooks/ 页面钩子(阅读页、用户菜单、请求拦截) +├── utils/ 请求封装与加解密工具 +└── assets/ 样式与图标 +``` + +新增页面功能时,在 `hooks/` 下建一个模块,导出 `HookConfig[]` 并在 `hooks/index.ts` 里注册。每个钩子声明自己关心的事件和一个 `filter`,命中才执行,互不影响。 + +### UI 技术选型 + +目前的界面改动都是直接操作 DOM,因为改的量还很小,插几个节点就够了。Vue 已经引入并配好构建(`@vitejs/plugin-vue`),等做到短剧播放、用户详情、评论这类大型界面时会用 Vue 写,但现在还没到那一步。 + +## 关于账号安全 + +正文与书籍详情走 `*.jxbhmy.com`,这是番茄小说旗下**红烛小说**的接口,与番茄数据基本通用,风控更低且不需要请求签名。这是第一方接口,不是第三方服务。 + +脚本**没有申请 `GM_cookie` 权限**。`sessionid` 是 HttpOnly 的,脚本读不到、也没打算读。所有需要登录凭据的请求都用页面原生 `fetch` 发送的同源请求完成,由浏览器自动带上 Cookie,凭据不经过脚本,也不会被发往任何其他地方。 +如果后续同源接口无法满足脚本需求,会考虑使用 `GM_cookie` 权限。但我们确保您的凭据不会被发送到番茄小说及其相关平台以外的任何第三方。 + +代码全部开源,欢迎审查。 + +## 许可 + +[GPL-3.0](LICENSE) + +## 免责声明 + +本项目仅供学习和技术研究使用。请遵守番茄小说的用户协议与相关法律法规,不要用于商业用途或内容再分发。 +本项目永久开源免费,未经授权不得用于售卖。 +本项目不保证任何功能的稳定性、准确性和安全性,使用时请自行承担风险。 \ No newline at end of file diff --git a/dist/fanqie-assistant.user.js b/dist/fanqie-assistant.user.js new file mode 100644 index 0000000..5b675ec --- /dev/null +++ b/dist/fanqie-assistant.user.js @@ -0,0 +1,5464 @@ +// ==UserScript== +// @name 番茄小说助手 +// @namespace https://github.com/naiyQAQ/fanqie-assistant +// @version 0.0.2 +// @author naiyQAQ +// @description 番茄小说助手,去广告、去推广、解锁章节、优化体验。 +// @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiAgICA8cGF0aA0KICAgICAgICBkPSJNMjMuMzExNSAxSDYuNjg4NTNDMy41NDY4NCAxIDEgMy41NDY4NCAxIDYuNjg4NTNWMjMuMzExNUMxIDI2LjQ1MzIgMy41NDY4NCAyOSA2LjY4ODUzIDI5SDIzLjMxMTVDMjYuNDUzMiAyOSAyOSAyNi40NTMyIDI5IDIzLjMxMTVWNi42ODg1M0MyOSAzLjU0Njg0IDI2LjQ1MzIgMSAyMy4zMTE1IDFaIg0KICAgICAgICBmaWxsPSJ3aGl0ZSI+PC9wYXRoPg0KICAgIDxwYXRoDQogICAgICAgIGQ9Ik0yMy4zMTE1IDAuNzVINi42ODg1M0MzLjQwODc3IDAuNzUgMC43NSAzLjQwODc3IDAuNzUgNi42ODg1M1YyMy4zMTE1QzAuNzUgMjYuNTkxMiAzLjQwODc3IDI5LjI1IDYuNjg4NTMgMjkuMjVIMjMuMzExNUMyNi41OTEyIDI5LjI1IDI5LjI1IDI2LjU5MTIgMjkuMjUgMjMuMzExNVY2LjY4ODUzQzI5LjI1IDMuNDA4NzcgMjYuNTkxMiAwLjc1IDIzLjMxMTUgMC43NVoiDQogICAgICAgIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjA4IiBzdHJva2Utd2lkdGg9IjAuNSI+PC9wYXRoPg0KICAgIDxtYXNrIGlkPSJtYXNrMF80NzBfNDgzNjQiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHg9IjEiIHk9IjEiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCI+DQogICAgICAgIDxwYXRoDQogICAgICAgICAgICBkPSJNMjMuMzExNSAxSDYuNjg4NTNDMy41NDY4NCAxIDEgMy41NDY4NCAxIDYuNjg4NTNWMjMuMzExNUMxIDI2LjQ1MzIgMy41NDY4NCAyOSA2LjY4ODUzIDI5SDIzLjMxMTVDMjYuNDUzMiAyOSAyOSAyNi40NTMyIDI5IDIzLjMxMTVWNi42ODg1M0MyOSAzLjU0Njg0IDI2LjQ1MzIgMSAyMy4zMTE1IDFaIg0KICAgICAgICAgICAgZmlsbD0id2hpdGUiPjwvcGF0aD4NCiAgICA8L21hc2s+DQogICAgPGcgbWFzaz0idXJsKCNtYXNrMF80NzBfNDgzNjQpIj4NCiAgICAgICAgPHBhdGgNCiAgICAgICAgICAgIGQ9Ik0yMy4zMTE1IDFINi42ODg1M0MzLjU0Njg0IDEgMSAzLjU0Njg0IDEgNi42ODg1M1YyMy4zMTE1QzEgMjYuNDUzMiAzLjU0Njg0IDI5IDYuNjg4NTMgMjlIMjMuMzExNUMyNi40NTMyIDI5IDI5IDI2LjQ1MzIgMjkgMjMuMzExNVY2LjY4ODUzQzI5IDMuNTQ2ODQgMjYuNDUzMiAxIDIzLjMxMTUgMVoiDQogICAgICAgICAgICBmaWxsPSJ3aGl0ZSI+PC9wYXRoPg0KICAgICAgICA8cGF0aA0KICAgICAgICAgICAgZD0iTTE1LjAwMDggNDguNjY0MkMyNS40MDE3IDQ4LjY2NDIgMzMuODMzNCA0MC4yMzI2IDMzLjgzMzQgMjkuODMxNkMzMy44MzM0IDE5LjQzMDcgMjUuNDAxNyAxMC45OTkgMTUuMDAwOCAxMC45OTlDNC41OTk4NSAxMC45OTkgLTMuODMxNzkgMTkuNDMwNyAtMy44MzE3OSAyOS44MzE2Qy0zLjgzMTc5IDQwLjIzMjYgNC41OTk4NSA0OC42NjQyIDE1LjAwMDggNDguNjY0MloiDQogICAgICAgICAgICBmaWxsPSJ1cmwoI3BhaW50MF9yYWRpYWxfNDcwXzQ4MzY0KSI+PC9wYXRoPg0KICAgICAgICA8cGF0aCBkPSJNMjMuMjY4OCAxVjcuMjEyOTRMMjAuNjY2MyA1LjcxNDM3TDE4LjA2NzQgNy4yMTI5NFYxSDIzLjI2ODhaIiBmaWxsPSIjRkY1RjAwIj48L3BhdGg+DQogICAgICAgIDxwYXRoDQogICAgICAgICAgICBkPSJNMTUuMTM0MyAxOC44ODFDMTUuMTM0MyAxOC44ODEgMTYuMTAxNCAxNy41NTEzIDE2LjEwMTQgMTYuNDA2NUMxNi4xMDE0IDE1LjI2MTcgMTUuNjY3NiAxNC43MzczIDE1LjEzNDMgMTQuNzM3M0MxNC42MDEgMTQuNzM3MyAxNC4xNjczIDE1LjI2MzUgMTQuMTY3MyAxNi40MDY1QzE0LjE2NzMgMTcuNTQ5NiAxNS4xMzQzIDE4Ljg4MSAxNS4xMzQzIDE4Ljg4MVoiDQogICAgICAgICAgICBmaWxsPSJ3aGl0ZSI+PC9wYXRoPg0KICAgICAgICA8cGF0aA0KICAgICAgICAgICAgZD0iTTcuNjI3MjQgMjIuNjU4NUM4Ljc3MjA1IDIyLjY1ODUgMTAuMTAxNyAyMy42MjU2IDEwLjEwMTcgMjMuNjI1NkMxMC4xMDE3IDIzLjYyNTYgOC43NzAyNyAyNC41OTI2IDcuNjI3MjQgMjQuNTkyNkM2LjQ4NDIgMjQuNTkyNiA1Ljk1ODAxIDI0LjE1ODkgNS45NTgwMSAyMy42MjU2QzUuOTU4MDEgMjMuMDkyMyA2LjQ4MjQyIDIyLjY1ODUgNy42MjcyNCAyMi42NTg1WiINCiAgICAgICAgICAgIGZpbGw9IndoaXRlIj48L3BhdGg+DQogICAgICAgIDxwYXRoDQogICAgICAgICAgICBkPSJNMjIuNjM5NiAyNC41OTI2QzIxLjQ5NDggMjQuNTkyNiAyMC4xNjUxIDIzLjYyNTYgMjAuMTY1MSAyMy42MjU2QzIwLjE2NTEgMjMuNjI1NiAyMS40OTY2IDIyLjY1ODUgMjIuNjM5NiAyMi42NTg1QzIzLjc4MjYgMjIuNjU4NSAyNC4zMDg4IDIzLjA5MjMgMjQuMzA4OCAyMy42MjU2QzI0LjMwODggMjQuMTU4OSAyMy43ODQ0IDI0LjU5MjYgMjIuNjM5NiAyNC41OTI2WiINCiAgICAgICAgICAgIGZpbGw9IndoaXRlIj48L3BhdGg+DQogICAgICAgIDxwYXRoDQogICAgICAgICAgICBkPSJNMTAuNDU1NSAxOC4zMTM5QzExLjI2NDMgMTkuMTIyNyAxMS41MjIxIDIwLjc0NzUgMTEuNTIyMSAyMC43NDc1QzExLjUyMjEgMjAuNzQ3NSA5Ljg5NzMyIDIwLjQ4OTcgOS4wODg0OCAxOS42ODA5QzguMjc5NjQgMTguODcyMSA4LjIxMzg3IDE4LjE5NDggOC41OTI1MSAxNy44MTYxQzguOTcxMTUgMTcuNDM3NSA5LjY0NjY2IDE3LjUwMzMgMTAuNDU3MyAxOC4zMTIxTDEwLjQ1NTUgMTguMzEzOVoiDQogICAgICAgICAgICBmaWxsPSJ3aGl0ZSI+PC9wYXRoPg0KICAgICAgICA8cGF0aA0KICAgICAgICAgICAgZD0iTTE4Ljc0NjUgMjAuNzQ3NkMxOC43NDY1IDIwLjc0NzYgMTkuMDA0MyAxOS4xMjI4IDE5LjgxMzEgMTguMzE0TDE5LjgxMTMgMTguMzEyMkMyMC42MjIgMTcuNTAzMyAyMS4yOTkzIDE3LjQzOTMgMjEuNjc2MSAxNy44MTYyQzIyLjA1NDggMTguMTk0OSAyMS45ODkgMTguODcyMSAyMS4xODAyIDE5LjY4MUMyMC4zNzEzIDIwLjQ4OTggMTguNzQ2NSAyMC43NDc2IDE4Ljc0NjUgMjAuNzQ3NloiDQogICAgICAgICAgICBmaWxsPSJ3aGl0ZSI+PC9wYXRoPg0KICAgIDwvZz4NCiAgICA8ZGVmcz4NCiAgICAgICAgPHJhZGlhbEdyYWRpZW50IGlkPSJwYWludDBfcmFkaWFsXzQ3MF80ODM2NCIgY3g9IjAiIGN5PSIwIiByPSIxIg0KICAgICAgICAgICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiDQogICAgICAgICAgICBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKDE1LjAwMDggMjkuODMxNikgc2NhbGUoMTguODMyNikiPg0KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iI0NDMDUwMCI+PC9zdG9wPg0KICAgICAgICAgICAgPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkY1RjAwIj48L3N0b3A+DQogICAgICAgIDwvcmFkaWFsR3JhZGllbnQ+DQogICAgPC9kZWZzPg0KPC9zdmc+ +// @match *://*.fanqienovel.com/* +// @connect fanqienovel.com +// @connect api5-sinfonlinec.jxbhmy.com +// @connect api3-sinfonlinec.jxbhmy.com +// @connect reading.snssdk.com +// @grant GM_addStyle +// @grant GM_deleteValue +// @grant GM_getValue +// @grant GM_setValue +// @grant GM_xmlhttpRequest +// @grant unsafeWindow +// @run-at document-start +// ==/UserScript== + +(function () { + 'use strict'; + + var __getOwnPropNames = Object.getOwnPropertyNames; + var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + }; + var require_main_001 = __commonJS({ + "main-BS5h560h.js"(exports, module) { + const shared_key = new Uint8Array([172, 37, 198, 125, 221, 143, 56, 193, 179, 122, 35, 72, 130, 142, 34, 46]).buffer; + const fetch = unsafeWindow.fetch; + unsafeWindow.XMLHttpRequest; + const defaultConfig = { + install_id: "2187355326270644", + device_id: "2187355326004404", + device_type: "P30" + }; + const _config = { + currentConfig: defaultConfig + }; + const scriptcss = "/* 移除章节锁定图标 */\r\n.muyeicon-lock {\r\n display: none;\r\n}\r\n/* 移除APP推广图标 */\r\n.muye-to-fanqie {\r\n display: none!important;\r\n}\r\n.reader-toolbar-item-download {\r\n display: none!important;\r\n}\r\n.download-btn {\r\n display: none!important;\r\n}\r\n.download-icon {\r\n display: none!important;\r\n}\r\n\r\n.fqa-hide {\r\n display: none!important;\r\n}\r\n/* 404 */\r\n.no-content {\r\n display: none!important;\r\n}\r\n\r\n.fqa-comic-img {\r\n width: 100%!important;\r\n height: 100%!important;\r\n max-width: 100%!important;\r\n max-height: 100%!important;\r\n padding-top: 0!important;\r\n padding-bottom: 0!important;\r\n margin-top: 0!important;\r\n margin-bottom: 0!important;\r\n}\r\n\r\n.fqa-comic-reader {\r\n line-height: 0!important;\r\n}\r\n\r\n.fqa-menu-item,\r\n.arco-menu-item {\r\n width: 100%!important;\r\n}"; + async function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + function cloneElement(element) { + return element.cloneNode(true); + } + async function inject() { + while (!document.body) { + console.log("Waiting for body..."); + await sleep(500); + } + GM_addStyle(scriptcss); + console.log("CSS injected successfully!"); + } + const enTag = ".font-DNMrHsV173Pd4pgy"; + const code_ed = 58715; + const code_st = 58344; + const mapping = [ + "D", + "在", + "主", + "特", + "家", + "军", + "然", + "表", + "场", + "4", + "要", + "只", + "v", + "和", + "?", + "6", + "别", + "还", + "g", + "现", + "儿", + "岁", + "?", + "?", + "此", + "象", + "月", + "3", + "出", + "战", + "工", + "相", + "o", + "男", + "直", + "失", + "世", + "F", + "都", + "平", + "文", + "什", + "V", + "O", + "将", + "真", + "T", + "那", + "当", + "?", + "会", + "立", + "些", + "u", + "是", + "十", + "张", + "学", + "气", + "大", + "爱", + "两", + "命", + "全", + "后", + "东", + "性", + "通", + "被", + "1", + "它", + "乐", + "接", + "而", + "感", + "车", + "山", + "公", + "了", + "常", + "以", + "何", + "可", + "话", + "先", + "p", + "i", + "叫", + "轻", + "M", + "士", + "w", + "着", + "变", + "尔", + "快", + "l", + "个", + "说", + "少", + "色", + "里", + "安", + "花", + "远", + "7", + "难", + "师", + "放", + "t", + "报", + "认", + "面", + "道", + "S", + "?", + "克", + "地", + "度", + "I", + "好", + "机", + "U", + "民", + "写", + "把", + "万", + "同", + "水", + "新", + "没", + "书", + "电", + "吃", + "像", + "斯", + "5", + "为", + "y", + "白", + "几", + "日", + "教", + "看", + "但", + "第", + "加", + "候", + "作", + "上", + "拉", + "住", + "有", + "法", + "r", + "事", + "应", + "位", + "利", + "你", + "声", + "身", + "国", + "问", + "马", + "女", + "他", + "Y", + "比", + "父", + "x", + "A", + "H", + "N", + "s", + "X", + "边", + "美", + "对", + "所", + "金", + "活", + "回", + "意", + "到", + "z", + "从", + "j", + "知", + "又", + "内", + "因", + "点", + "Q", + "三", + "定", + "8", + "R", + "b", + "正", + "或", + "夫", + "向", + "德", + "听", + "更", + "?", + "得", + "告", + "并", + "本", + "q", + "过", + "记", + "L", + "让", + "打", + "f", + "人", + "就", + "者", + "去", + "原", + "满", + "体", + "做", + "经", + "K", + "走", + "如", + "孩", + "c", + "G", + "给", + "使", + "物", + "?", + "最", + "笑", + "部", + "?", + "员", + "等", + "受", + "k", + "行", + "一", + "条", + "果", + "动", + "光", + "门", + "头", + "见", + "往", + "自", + "解", + "成", + "处", + "天", + "能", + "于", + "名", + "其", + "发", + "总", + "母", + "的", + "死", + "手", + "入", + "路", + "进", + "心", + "来", + "h", + "时", + "力", + "多", + "开", + "已", + "许", + "d", + "至", + "由", + "很", + "界", + "n", + "小", + "与", + "Z", + "想", + "代", + "么", + "分", + "生", + "口", + "再", + "妈", + "望", + "次", + "西", + "风", + "种", + "带", + "J", + "?", + "实", + "情", + "才", + "这", + "?", + "E", + "我", + "神", + "格", + "长", + "觉", + "间", + "年", + "眼", + "无", + "不", + "亲", + "关", + "结", + "0", + "友", + "信", + "下", + "却", + "重", + "己", + "老", + "2", + "音", + "字", + "m", + "呢", + "明", + "之", + "前", + "高", + "P", + "B", + "目", + "太", + "e", + "9", + "起", + "稜", + "她", + "也", + "W", + "用", + "方", + "子", + "英", + "每", + "理", + "便", + "四", + "数", + "期", + "中", + "C", + "外", + "样", + "a", + "海", + "们", + "任" + ]; + function decryptText(text) { + let result = ""; + let changed = false; + for (const char of text) { + const codePoint = char.codePointAt(0); + if (typeof codePoint !== "number") { + return text; + } + if (codePoint < code_st || codePoint > code_ed) { + result += char; + continue; + } + const mapped = mapping[codePoint - code_st]; + if (mapped && mapped !== "?") { + result += mapped; + changed = true; + } else { + result += char; + } + } + return changed ? result : text; + } + function decryptElement(element) { + const walker = document.createTreeWalker( + element, + NodeFilter.SHOW_TEXT + ); + let textNode; + while (textNode = walker.nextNode()) { + const oldText = textNode.nodeValue; + if (!oldText) { + continue; + } + const newText = decryptText(oldText); + if (newText !== oldText) { + textNode.nodeValue = newText; + } + } + } + function decryptPage(root) { + if (root instanceof Element && root.matches(enTag)) { + decryptElement(root); + } + root.querySelectorAll(enTag).forEach(decryptElement); + } + function initFontDecrypt() { + const observer = new MutationObserver((mutations) => { + var _a, _b; + for (const mutation of mutations) { + if (mutation.type === "characterData") { + const encryptedElement = (_a = mutation.target.parentElement) == null ? void 0 : _a.closest( + enTag + ); + if (encryptedElement) { + decryptElement(encryptedElement); + } + continue; + } + for (const node of mutation.addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + decryptPage(node); + continue; + } + if (node.nodeType === Node.TEXT_NODE) { + const encryptedElement = (_b = node.parentElement) == null ? void 0 : _b.closest( + enTag + ); + if (encryptedElement) { + decryptElement(encryptedElement); + } + } + } + } + }); + observer.observe(document, { + subtree: true, + childList: true, + characterData: true + }); + decryptPage(document); + } + const supportedMethods = /* @__PURE__ */ new Set([ + "GET", + "HEAD", + "POST", + "PUT", + "DELETE" + ]); + function apiFetch(url, options = {}) { + return new Promise((resolve, reject) => { + const { signal } = options; + if (signal == null ? void 0 : signal.aborted) { + reject(signal.reason ?? new DOMException("The operation was aborted", "AbortError")); + return; + } + const headers = normalizeHeaders(options.headers); + const data = normalizeBody(options.body); + const method = options.method ?? (data ? "POST" : "GET"); + if (!supportedMethods.has(method)) { + reject(new TypeError(`Unsupported request method: ${method}`)); + return; + } + let request; + const abort = () => request == null ? void 0 : request.abort(); + function cleanup() { + signal == null ? void 0 : signal.removeEventListener("abort", abort); + } + request = GM_xmlhttpRequest({ + url, + method, + headers, + data, + anonymous: options.credentials === "omit", + redirect: options.redirect === "error" ? "error" : "follow", + onload(response) { + cleanup(); + resolve(Object.assign(response, { + json() { + return JSON.parse(this.responseText); + } + })); + }, + onerror(response) { + cleanup(); + reject(createRequestError("Network request failed", response)); + }, + ontimeout() { + cleanup(); + reject(createRequestError("Network request timed out", { + status: 0, + statusText: "Timeout", + url + })); + }, + onabort() { + cleanup(); + reject( + (signal == null ? void 0 : signal.reason) ?? new DOMException("The operation was aborted", "AbortError") + ); + } + }); + signal == null ? void 0 : signal.addEventListener("abort", abort, { once: true }); + }); + } + function normalizeHeaders(headers) { + if (!headers) { + return void 0; + } + return Object.fromEntries(new Headers(headers).entries()); + } + function normalizeBody(body) { + if (body == null) { + return void 0; + } + if (body instanceof URLSearchParams) { + return body.toString(); + } + if (typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || body instanceof FormData) { + return body; + } + if (ArrayBuffer.isView(body)) { + return body.buffer.slice( + body.byteOffset, + body.byteOffset + body.byteLength + ); + } + throw new TypeError( + "GM_xmlhttpRequest does not support ReadableStream request bodies" + ); + } + function createRequestError(message, response) { + const error = new TypeError(message); + Object.defineProperty(error, "response", { + configurable: true, + enumerable: false, + value: response + }); + return error; + } + unsafeWindow.localStorage; + function write(key, value) { + GM_setValue(key, JSON.stringify(value)); + } + function read(key) { + return JSON.parse(GM_getValue(key) || "null"); + } + function del(key) { + GM_deleteValue(key); + } + function getCookie(key) { + var _a; + return ((_a = document.cookie.split(";").find((c) => c.trim().startsWith(key + "="))) == null ? void 0 : _a.split("=")[1]) || null; + } + const baseUrl = "https://api5-sinfonlinec.jxbhmy.com/redcandle/search"; + const baseQuery = { + aid: "1967", + app_name: "novelapp" + }; + async function get$3(path, cookie, query, headers, optionsOverride) { + let url = baseUrl + path; + getCookie("sessionid"); + const h = { + Cookie: "", + ...headers + }; + const deviceQuery = { + device_id: _config.currentConfig.device_id, + iid: _config.currentConfig.install_id + }; + const finalQuery = { + ...baseQuery, + ...deviceQuery, + ...query + }; + url += "?" + new URLSearchParams(finalQuery).toString(); + const options = { + method: "GET", + headers: h, + ...optionsOverride + }; + console.log("---start--- GET ", url, options); + const res = await apiFetch(url, options); + console.log("---complete--- GET ", url, res); + return res; + } + async function post(path, cookie, query, body, headers, optionsOverride) { + let url = baseUrl + path; + getCookie("sessionid"); + const h = { + Cookie: "", + ...headers + }; + const deviceQuery = { + device_id: _config.currentConfig.device_id, + iid: _config.currentConfig.install_id + }; + const finalQuery = { + ...baseQuery, + ...deviceQuery, + ...query + }; + url += "?" + new URLSearchParams(finalQuery).toString(); + const options = { + method: "POST", + headers: h, + body, + ...optionsOverride + }; + console.log("---start--- POST ", url, options); + const res = await apiFetch(url, options); + console.log("---complete--- POST ", url, res); + return res; + } + function getCrypto() { + const c = globalThis.crypto ?? unsafeWindow.crypto; + if (!(c == null ? void 0 : c.subtle)) { + throw new Error("Crypto API不可用,请检查浏览器版本是否支持该API"); + } + return c; + } + function getSubtle() { + return getCrypto().subtle; + } + function b64decode(b64) { + const binaryString = atob(b64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; + } + function b64encode(buffer) { + const bytes = new Uint8Array(buffer); + const chunkSize = 32768; + const chunks = []; + for (let i = 0; i < bytes.length; i += chunkSize) { + chunks.push( + String.fromCharCode(...bytes.subarray(i, i + chunkSize)) + ); + } + return btoa(chunks.join("")); + } + function unhex(hex) { + if (hex.length % 2 !== 0) { + throw new Error("Invalid hex string"); + } + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + const byte = parseInt(hex.slice(i, i + 2), 16); + if (Number.isNaN(byte)) { + throw new Error("Invalid hex string"); + } + bytes[i / 2] = byte; + } + return bytes.buffer; + } + function randomString(length) { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + const array = new Uint8Array(length); + getCrypto().getRandomValues(array); + for (let i = 0; i < length; i++) { + result += chars.charAt(array[i] % chars.length); + } + return result; + } + async function decryptChapter(encrypted, config = defaultConfig) { + var _a; + if (!encrypted) { + throw new Error("Invalid encrypted chapter"); + } + const buf = b64decode(encrypted); + const iv = buf.slice(0, 16); + const data = buf.slice(16); + const key = (_a = config.key_info) == null ? void 0 : _a.key; + if (!key) { + throw new Error("Missing decrypt key"); + } + const subtle = getSubtle(); + const cryptoKey = await subtle.importKey( + "raw", + key, + { name: "AES-CBC" }, + false, + ["decrypt"] + ); + return subtle.decrypt( + { name: "AES-CBC", iv }, + cryptoKey, + data + ).then((decrypted) => { + const decoder = new TextDecoder(); + const plain = decoder.decode(decrypted); + if (plain.trim().startsWith("<")) { + return plain; + } + try { + return JSON.parse(plain); + } catch (e) { + console.warn("Invalid chapter content: ", plain, e); + return void 0; + } + }); + } + function reverseHex(value) { + const be = BigInt(value).toString(16).padStart(32, "0"); + let result = ""; + for (let i = be.length; i > 0; i -= 2) result += be.slice(i - 2, i); + return result; + } + async function encryptKeyinfoBody(config) { + const deviceId = config.device_id; + const iv = new TextEncoder().encode(randomString(16)); + const data = new Uint8Array(unhex(reverseHex(deviceId))).slice(0, 8); + console.log(data); + const subtle = getSubtle(); + const k = await subtle.importKey( + "raw", + shared_key, + { name: "AES-CBC" }, + false, + ["encrypt"] + ); + const encrypted = await subtle.encrypt( + { name: "AES-CBC", iv }, + k, + data + ); + const final = new Uint8Array(iv.length + encrypted.byteLength); + console.log(final); + final.set(iv, 0); + final.set(new Uint8Array(encrypted), iv.length); + return JSON.stringify({ + content: b64encode(final.buffer) + }); + } + async function decryptKeyinfoResponse(encrypted) { + const buf = b64decode(encrypted); + const iv = buf.slice(0, 16); + const data = buf.slice(16); + const subtle = getSubtle(); + const k = await subtle.importKey( + "raw", + shared_key, + { name: "AES-CBC" }, + false, + ["decrypt"] + ); + return subtle.decrypt( + { name: "AES-CBC", iv }, + k, + data + ); + } + async function decryptComicImage(image, key) { + const subtle = getSubtle(); + const cryptoKey = await subtle.importKey( + "raw", + unhex(key), + { name: "AES-GCM" }, + false, + ["decrypt"] + ); + const iv = image.slice(0, 12); + const data = image.slice(12); + return await subtle.decrypt( + { name: "AES-GCM", iv }, + cryptoKey, + data + ); + } + async function refreshKeyinfo() { + var _a, _b, _c; + const b = await encryptKeyinfoBody(_config.currentConfig); + const res = await post("/reader/crypt/registerkey/v1", false, {}, b); + console.log(res); + const j = res.json(); + console.log("response: ", j); + const ek = (_a = j == null ? void 0 : j.data) == null ? void 0 : _a.key; + if (!ek) { + throw new Error("Failed to get key info"); + } + const key = await decryptKeyinfoResponse(ek); + const keyinfo = { + key, + keyver: (_b = j == null ? void 0 : j.data) == null ? void 0 : _b.keyver + }; + console.log("Refreshed key info:", keyinfo); + _config.currentConfig.key_info = keyinfo; + write("keyinfo", { + key: b64encode(key), + keyver: (_c = j == null ? void 0 : j.data) == null ? void 0 : _c.keyver + }); + } + async function ensureKeyinfo(expectedKeyVersion) { + const keyinfo = _config.currentConfig.key_info; + const cachedKeyInfo = read("keyinfo"); + console.log("cached key info: ", cachedKeyInfo); + if (cachedKeyInfo) { + const cki = { + key: b64decode(cachedKeyInfo.key), + keyver: cachedKeyInfo.keyver + }; + if (typeof expectedKeyVersion === "undefined" || cki.keyver === expectedKeyVersion) { + _config.currentConfig.key_info = cki; + return; + } + } + if (!keyinfo) { + return await refreshKeyinfo(); + } + if ((keyinfo == null ? void 0 : keyinfo.keyver) !== expectedKeyVersion) { + return await refreshKeyinfo(); + } + } + async function getChapter(itemId, _retry) { + var _a, _b; + if (typeof _retry === "undefined") _retry = 0; + if (_retry > 5) { + throw new Error(`Failed to get chapter: ${itemId}`); + } + if (!_config.currentConfig.key_info) { + await ensureKeyinfo(); + } + const res = await get$3(`/reader/full/v1`, false, { item_id: itemId }); + const j = (_a = res.json()) == null ? void 0 : _a.data; + if (!j) { + console.warn("Failed to get chapter: ", itemId, ", response: ", j); + return await getChapter(itemId, _retry + 1); + } + if ((j == null ? void 0 : j.content) === "Invalid" || (j == null ? void 0 : j.key_version) !== ((_b = _config.currentConfig.key_info) == null ? void 0 : _b.keyver)) { + console.warn("Key reg expired, regster again and retrying..."); + await ensureKeyinfo(parseInt(j == null ? void 0 : j.key_version)); + return await getChapter(itemId, _retry + 1); + } + j.content = await decryptChapter(j == null ? void 0 : j.content, _config.currentConfig); + return j; + } + //! moment.js + //! version : 2.30.1 + //! authors : Tim Wood, Iskren Chernev, Moment.js contributors + //! license : MIT + //! momentjs.com + var hookCallback; + function hooks$1() { + return hookCallback.apply(null, arguments); + } + function setHookCallback(callback) { + hookCallback = callback; + } + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === "[object Array]"; + } + function isObject(input) { + return input != null && Object.prototype.toString.call(input) === "[object Object]"; + } + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return Object.getOwnPropertyNames(obj).length === 0; + } else { + var k; + for (k in obj) { + if (hasOwnProp(obj, k)) { + return false; + } + } + return true; + } + } + function isUndefined(input) { + return input === void 0; + } + function isNumber(input) { + return typeof input === "number" || Object.prototype.toString.call(input) === "[object Number]"; + } + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === "[object Date]"; + } + function map(arr, fn) { + var res = [], i, arrLen = arr.length; + for (i = 0; i < arrLen; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + if (hasOwnProp(b, "toString")) { + a.toString = b.toString; + } + if (hasOwnProp(b, "valueOf")) { + a.valueOf = b.valueOf; + } + return a; + } + function createUTC(input, format2, locale2, strict) { + return createLocalOrUTC(input, format2, locale2, strict, true).utc(); + } + function defaultParsingFlags() { + return { + empty: false, + unusedTokens: [], + unusedInput: [], + overflow: -2, + charsLeftOver: 0, + nullInput: false, + invalidEra: null, + invalidMonth: null, + invalidFormat: false, + userInvalidated: false, + iso: false, + parsedDateParts: [], + era: null, + meridiem: null, + rfc2822: false, + weekdayMismatch: false + }; + } + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function(fun) { + var t = Object(this), len = t.length >>> 0, i; + for (i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + return false; + }; + } + function isValid(m) { + var flags = null, parsedParts = false, isNowValid = m._d && !isNaN(m._d.getTime()); + if (isNowValid) { + flags = getParsingFlags(m); + parsedParts = some.call(flags.parsedDateParts, function(i) { + return i != null; + }); + isNowValid = flags.overflow < 0 && !flags.empty && !flags.invalidEra && !flags.invalidMonth && !flags.invalidWeekday && !flags.weekdayMismatch && !flags.nullInput && !flags.invalidFormat && !flags.userInvalidated && (!flags.meridiem || flags.meridiem && parsedParts); + if (m._strict) { + isNowValid = isNowValid && flags.charsLeftOver === 0 && flags.unusedTokens.length === 0 && flags.bigHour === void 0; + } + } + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } else { + return isNowValid; + } + return m._isValid; + } + function createInvalid(flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } else { + getParsingFlags(m).userInvalidated = true; + } + return m; + } + var momentProperties = hooks$1.momentProperties = [], updateInProgress = false; + function copyConfig(to2, from2) { + var i, prop, val, momentPropertiesLen = momentProperties.length; + if (!isUndefined(from2._isAMomentObject)) { + to2._isAMomentObject = from2._isAMomentObject; + } + if (!isUndefined(from2._i)) { + to2._i = from2._i; + } + if (!isUndefined(from2._f)) { + to2._f = from2._f; + } + if (!isUndefined(from2._l)) { + to2._l = from2._l; + } + if (!isUndefined(from2._strict)) { + to2._strict = from2._strict; + } + if (!isUndefined(from2._tzm)) { + to2._tzm = from2._tzm; + } + if (!isUndefined(from2._isUTC)) { + to2._isUTC = from2._isUTC; + } + if (!isUndefined(from2._offset)) { + to2._offset = from2._offset; + } + if (!isUndefined(from2._pf)) { + to2._pf = getParsingFlags(from2); + } + if (!isUndefined(from2._locale)) { + to2._locale = from2._locale; + } + if (momentPropertiesLen > 0) { + for (i = 0; i < momentPropertiesLen; i++) { + prop = momentProperties[i]; + val = from2[prop]; + if (!isUndefined(val)) { + to2[prop] = val; + } + } + } + return to2; + } + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = /* @__PURE__ */ new Date(NaN); + } + if (updateInProgress === false) { + updateInProgress = true; + hooks$1.updateOffset(this); + updateInProgress = false; + } + } + function isMoment(obj) { + return obj instanceof Moment || obj != null && obj._isAMomentObject != null; + } + function warn(msg) { + if (hooks$1.suppressDeprecationWarnings === false && typeof console !== "undefined" && console.warn) { + console.warn("Deprecation warning: " + msg); + } + } + function deprecate(msg, fn) { + var firstTime = true; + return extend(function() { + if (hooks$1.deprecationHandler != null) { + hooks$1.deprecationHandler(null, msg); + } + if (firstTime) { + var args = [], arg, i, key, argLen = arguments.length; + for (i = 0; i < argLen; i++) { + arg = ""; + if (typeof arguments[i] === "object") { + arg += "\n[" + i + "] "; + for (key in arguments[0]) { + if (hasOwnProp(arguments[0], key)) { + arg += key + ": " + arguments[0][key] + ", "; + } + } + arg = arg.slice(0, -2); + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn( + msg + "\nArguments: " + Array.prototype.slice.call(args).join("") + "\n" + new Error().stack + ); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + var deprecations = {}; + function deprecateSimple(name2, msg) { + if (hooks$1.deprecationHandler != null) { + hooks$1.deprecationHandler(name2, msg); + } + if (!deprecations[name2]) { + warn(msg); + deprecations[name2] = true; + } + } + hooks$1.suppressDeprecationWarnings = false; + hooks$1.deprecationHandler = null; + function isFunction(input) { + return typeof Function !== "undefined" && input instanceof Function || Object.prototype.toString.call(input) === "[object Function]"; + } + function set(config) { + var prop, i; + for (i in config) { + if (hasOwnProp(config, i)) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this["_" + i] = prop; + } + } + } + this._config = config; + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + "|" + /\d{1,2}/.source + ); + } + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && !hasOwnProp(childConfig, prop) && isObject(parentConfig[prop])) { + res[prop] = extend({}, res[prop]); + } + } + return res; + } + function Locale(config) { + if (config != null) { + this.set(config); + } + } + var keys; + if (Object.keys) { + keys = Object.keys; + } else { + keys = function(obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + var defaultCalendar = { + sameDay: "[Today at] LT", + nextDay: "[Tomorrow at] LT", + nextWeek: "dddd [at] LT", + lastDay: "[Yesterday at] LT", + lastWeek: "[Last] dddd [at] LT", + sameElse: "L" + }; + function calendar(key, mom, now2) { + var output = this._calendar[key] || this._calendar["sameElse"]; + return isFunction(output) ? output.call(mom, now2) : output; + } + function zeroFill(number, targetLength, forceSign) { + var absNumber = "" + Math.abs(number), zerosToFill = targetLength - absNumber.length, sign2 = number >= 0; + return (sign2 ? forceSign ? "+" : "" : "-") + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; + } + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, formatFunctions = {}, formatTokenFunctions = {}; + function addFormatToken(token2, padded, ordinal2, callback) { + var func = callback; + if (typeof callback === "string") { + func = function() { + return this[callback](); + }; + } + if (token2) { + formatTokenFunctions[token2] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function() { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal2) { + formatTokenFunctions[ordinal2] = function() { + return this.localeData().ordinal( + func.apply(this, arguments), + token2 + ); + }; + } + } + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + function makeFormatFunction(format2) { + var array = format2.match(formattingTokens), i, length; + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + return function(mom) { + var output = "", i2; + for (i2 = 0; i2 < length; i2++) { + output += isFunction(array[i2]) ? array[i2].call(mom, format2) : array[i2]; + } + return output; + }; + } + function formatMoment(m, format2) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + format2 = expandFormat(format2, m.localeData()); + formatFunctions[format2] = formatFunctions[format2] || makeFormatFunction(format2); + return formatFunctions[format2](m); + } + function expandFormat(format2, locale2) { + var i = 5; + function replaceLongDateFormatTokens(input) { + return locale2.longDateFormat(input) || input; + } + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format2)) { + format2 = format2.replace( + localFormattingTokens, + replaceLongDateFormatTokens + ); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + return format2; + } + var defaultLongDateFormat = { + LTS: "h:mm:ss A", + LT: "h:mm A", + L: "MM/DD/YYYY", + LL: "MMMM D, YYYY", + LLL: "MMMM D, YYYY h:mm A", + LLLL: "dddd, MMMM D, YYYY h:mm A" + }; + function longDateFormat(key) { + var format2 = this._longDateFormat[key], formatUpper = this._longDateFormat[key.toUpperCase()]; + if (format2 || !formatUpper) { + return format2; + } + this._longDateFormat[key] = formatUpper.match(formattingTokens).map(function(tok) { + if (tok === "MMMM" || tok === "MM" || tok === "DD" || tok === "dddd") { + return tok.slice(1); + } + return tok; + }).join(""); + return this._longDateFormat[key]; + } + var defaultInvalidDate = "Invalid date"; + function invalidDate() { + return this._invalidDate; + } + var defaultOrdinal = "%d", defaultDayOfMonthOrdinalParse = /\d{1,2}/; + function ordinal(number) { + return this._ordinal.replace("%d", number); + } + var defaultRelativeTime = { + future: "in %s", + past: "%s ago", + s: "a few seconds", + ss: "%d seconds", + m: "a minute", + mm: "%d minutes", + h: "an hour", + hh: "%d hours", + d: "a day", + dd: "%d days", + w: "a week", + ww: "%d weeks", + M: "a month", + MM: "%d months", + y: "a year", + yy: "%d years" + }; + function relativeTime(number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return isFunction(output) ? output(number, withoutSuffix, string, isFuture) : output.replace(/%d/i, number); + } + function pastFuture(diff2, output) { + var format2 = this._relativeTime[diff2 > 0 ? "future" : "past"]; + return isFunction(format2) ? format2(output) : format2.replace(/%s/i, output); + } + var aliases = { + D: "date", + dates: "date", + date: "date", + d: "day", + days: "day", + day: "day", + e: "weekday", + weekdays: "weekday", + weekday: "weekday", + E: "isoWeekday", + isoweekdays: "isoWeekday", + isoweekday: "isoWeekday", + DDD: "dayOfYear", + dayofyears: "dayOfYear", + dayofyear: "dayOfYear", + h: "hour", + hours: "hour", + hour: "hour", + ms: "millisecond", + milliseconds: "millisecond", + millisecond: "millisecond", + m: "minute", + minutes: "minute", + minute: "minute", + M: "month", + months: "month", + month: "month", + Q: "quarter", + quarters: "quarter", + quarter: "quarter", + s: "second", + seconds: "second", + second: "second", + gg: "weekYear", + weekyears: "weekYear", + weekyear: "weekYear", + GG: "isoWeekYear", + isoweekyears: "isoWeekYear", + isoweekyear: "isoWeekYear", + w: "week", + weeks: "week", + week: "week", + W: "isoWeek", + isoweeks: "isoWeek", + isoweek: "isoWeek", + y: "year", + years: "year", + year: "year" + }; + function normalizeUnits(units) { + return typeof units === "string" ? aliases[units] || aliases[units.toLowerCase()] : void 0; + } + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, normalizedProp, prop; + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + return normalizedInput; + } + var priorities = { + date: 9, + day: 11, + weekday: 11, + isoWeekday: 11, + dayOfYear: 4, + hour: 13, + millisecond: 16, + minute: 14, + month: 8, + quarter: 7, + second: 15, + weekYear: 1, + isoWeekYear: 1, + week: 5, + isoWeek: 5, + year: 1 + }; + function getPrioritizedUnits(unitsObj) { + var units = [], u; + for (u in unitsObj) { + if (hasOwnProp(unitsObj, u)) { + units.push({ unit: u, priority: priorities[u] }); + } + } + units.sort(function(a, b) { + return a.priority - b.priority; + }); + return units; + } + var match1 = /\d/, match2 = /\d\d/, match3 = /\d{3}/, match4 = /\d{4}/, match6 = /[+-]?\d{6}/, match1to2 = /\d\d?/, match3to4 = /\d\d\d\d?/, match5to6 = /\d\d\d\d\d\d?/, match1to3 = /\d{1,3}/, match1to4 = /\d{1,4}/, match1to6 = /[+-]?\d{1,6}/, matchUnsigned = /\d+/, matchSigned = /[+-]?\d+/, matchOffset = /Z|[+-]\d\d:?\d\d/gi, matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, match1to2NoLeadingZero = /^[1-9]\d?/, match1to2HasZero = /^([1-9]\d|\d)/, regexes; + regexes = {}; + function addRegexToken(token2, regex, strictRegex) { + regexes[token2] = isFunction(regex) ? regex : function(isStrict, localeData2) { + return isStrict && strictRegex ? strictRegex : regex; + }; + } + function getParseRegexForToken(token2, config) { + if (!hasOwnProp(regexes, token2)) { + return new RegExp(unescapeFormat(token2)); + } + return regexes[token2](config._strict, config._locale); + } + function unescapeFormat(s) { + return regexEscape( + s.replace("\\", "").replace( + /\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, + function(matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + } + ) + ); + } + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); + } + function absFloor(number) { + if (number < 0) { + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, value = 0; + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + return value; + } + var tokens = {}; + function addParseToken(token2, callback) { + var i, func = callback, tokenLen; + if (typeof token2 === "string") { + token2 = [token2]; + } + if (isNumber(callback)) { + func = function(input, array) { + array[callback] = toInt(input); + }; + } + tokenLen = token2.length; + for (i = 0; i < tokenLen; i++) { + tokens[token2[i]] = func; + } + } + function addWeekParseToken(token2, callback) { + addParseToken(token2, function(input, array, config, token3) { + config._w = config._w || {}; + callback(input, config._w, config, token3); + }); + } + function addTimeToArrayFromToken(token2, input, config) { + if (input != null && hasOwnProp(tokens, token2)) { + tokens[token2](input, config._a, config, token2); + } + } + function isLeapYear(year) { + return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; + } + var YEAR = 0, MONTH = 1, DATE = 2, HOUR = 3, MINUTE = 4, SECOND = 5, MILLISECOND = 6, WEEK = 7, WEEKDAY = 8; + addFormatToken("Y", 0, 0, function() { + var y = this.year(); + return y <= 9999 ? zeroFill(y, 4) : "+" + y; + }); + addFormatToken(0, ["YY", 2], 0, function() { + return this.year() % 100; + }); + addFormatToken(0, ["YYYY", 4], 0, "year"); + addFormatToken(0, ["YYYYY", 5], 0, "year"); + addFormatToken(0, ["YYYYYY", 6, true], 0, "year"); + addRegexToken("Y", matchSigned); + addRegexToken("YY", match1to2, match2); + addRegexToken("YYYY", match1to4, match4); + addRegexToken("YYYYY", match1to6, match6); + addRegexToken("YYYYYY", match1to6, match6); + addParseToken(["YYYYY", "YYYYYY"], YEAR); + addParseToken("YYYY", function(input, array) { + array[YEAR] = input.length === 2 ? hooks$1.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken("YY", function(input, array) { + array[YEAR] = hooks$1.parseTwoDigitYear(input); + }); + addParseToken("Y", function(input, array) { + array[YEAR] = parseInt(input, 10); + }); + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + hooks$1.parseTwoDigitYear = function(input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2e3); + }; + var getSetYear = makeGetSet("FullYear", true); + function getIsLeapYear() { + return isLeapYear(this.year()); + } + function makeGetSet(unit, keepTime) { + return function(value) { + if (value != null) { + set$1(this, unit, value); + hooks$1.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; + } + function get(mom, unit) { + if (!mom.isValid()) { + return NaN; + } + var d = mom._d, isUTC = mom._isUTC; + switch (unit) { + case "Milliseconds": + return isUTC ? d.getUTCMilliseconds() : d.getMilliseconds(); + case "Seconds": + return isUTC ? d.getUTCSeconds() : d.getSeconds(); + case "Minutes": + return isUTC ? d.getUTCMinutes() : d.getMinutes(); + case "Hours": + return isUTC ? d.getUTCHours() : d.getHours(); + case "Date": + return isUTC ? d.getUTCDate() : d.getDate(); + case "Day": + return isUTC ? d.getUTCDay() : d.getDay(); + case "Month": + return isUTC ? d.getUTCMonth() : d.getMonth(); + case "FullYear": + return isUTC ? d.getUTCFullYear() : d.getFullYear(); + default: + return NaN; + } + } + function set$1(mom, unit, value) { + var d, isUTC, year, month, date; + if (!mom.isValid() || isNaN(value)) { + return; + } + d = mom._d; + isUTC = mom._isUTC; + switch (unit) { + case "Milliseconds": + return void (isUTC ? d.setUTCMilliseconds(value) : d.setMilliseconds(value)); + case "Seconds": + return void (isUTC ? d.setUTCSeconds(value) : d.setSeconds(value)); + case "Minutes": + return void (isUTC ? d.setUTCMinutes(value) : d.setMinutes(value)); + case "Hours": + return void (isUTC ? d.setUTCHours(value) : d.setHours(value)); + case "Date": + return void (isUTC ? d.setUTCDate(value) : d.setDate(value)); + // case 'Day': // Not real + // return void (isUTC ? d.setUTCDay(value) : d.setDay(value)); + // case 'Month': // Not used because we need to pass two variables + // return void (isUTC ? d.setUTCMonth(value) : d.setMonth(value)); + case "FullYear": + break; + // See below ... + default: + return; + } + year = value; + month = mom.month(); + date = mom.date(); + date = date === 29 && month === 1 && !isLeapYear(year) ? 28 : date; + void (isUTC ? d.setUTCFullYear(year, month, date) : d.setFullYear(year, month, date)); + } + function stringGet(units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + function stringSet(units, value) { + if (typeof units === "object") { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units), i, prioritizedLen = prioritized.length; + for (i = 0; i < prioritizedLen; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + function mod(n, x) { + return (n % x + x) % x; + } + var indexOf; + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function(o) { + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 ? isLeapYear(year) ? 29 : 28 : 31 - modMonth % 7 % 2; + } + addFormatToken("M", ["MM", 2], "Mo", function() { + return this.month() + 1; + }); + addFormatToken("MMM", 0, 0, function(format2) { + return this.localeData().monthsShort(this, format2); + }); + addFormatToken("MMMM", 0, 0, function(format2) { + return this.localeData().months(this, format2); + }); + addRegexToken("M", match1to2, match1to2NoLeadingZero); + addRegexToken("MM", match1to2, match2); + addRegexToken("MMM", function(isStrict, locale2) { + return locale2.monthsShortRegex(isStrict); + }); + addRegexToken("MMMM", function(isStrict, locale2) { + return locale2.monthsRegex(isStrict); + }); + addParseToken(["M", "MM"], function(input, array) { + array[MONTH] = toInt(input) - 1; + }); + addParseToken(["MMM", "MMMM"], function(input, array, config, token2) { + var month = config._locale.monthsParse(input, token2, config._strict); + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + var defaultLocaleMonths = "January_February_March_April_May_June_July_August_September_October_November_December".split( + "_" + ), defaultLocaleMonthsShort = "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/, defaultMonthsShortRegex = matchWord, defaultMonthsRegex = matchWord; + function localeMonths(m, format2) { + if (!m) { + return isArray(this._months) ? this._months : this._months["standalone"]; + } + return isArray(this._months) ? this._months[m.month()] : this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format2) ? "format" : "standalone"][m.month()]; + } + function localeMonthsShort(m, format2) { + if (!m) { + return isArray(this._monthsShort) ? this._monthsShort : this._monthsShort["standalone"]; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : this._monthsShort[MONTHS_IN_FORMAT.test(format2) ? "format" : "standalone"][m.month()]; + } + function handleStrictParse(monthName, format2, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2e3, i]); + this._shortMonthsParse[i] = this.monthsShort( + mom, + "" + ).toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, "").toLocaleLowerCase(); + } + } + if (strict) { + if (format2 === "MMM") { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format2 === "MMM") { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + function localeMonthsParse(monthName, format2, strict) { + var i, mom, regex; + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format2, strict); + } + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + for (i = 0; i < 12; i++) { + mom = createUTC([2e3, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp( + "^" + this.months(mom, "").replace(".", "") + "$", + "i" + ); + this._shortMonthsParse[i] = new RegExp( + "^" + this.monthsShort(mom, "").replace(".", "") + "$", + "i" + ); + } + if (!strict && !this._monthsParse[i]) { + regex = "^" + this.months(mom, "") + "|^" + this.monthsShort(mom, ""); + this._monthsParse[i] = new RegExp(regex.replace(".", ""), "i"); + } + if (strict && format2 === "MMMM" && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format2 === "MMM" && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + function setMonth(mom, value) { + if (!mom.isValid()) { + return mom; + } + if (typeof value === "string") { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + if (!isNumber(value)) { + return mom; + } + } + } + var month = value, date = mom.date(); + date = date < 29 ? date : Math.min(date, daysInMonth(mom.year(), month)); + void (mom._isUTC ? mom._d.setUTCMonth(month, date) : mom._d.setMonth(month, date)); + return mom; + } + function getSetMonth(value) { + if (value != null) { + setMonth(this, value); + hooks$1.updateOffset(this, true); + return this; + } else { + return get(this, "Month"); + } + } + function getDaysInMonth() { + return daysInMonth(this.year(), this.month()); + } + function monthsShortRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, "_monthsRegex")) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, "_monthsShortRegex")) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? this._monthsShortStrictRegex : this._monthsShortRegex; + } + } + function monthsRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, "_monthsRegex")) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, "_monthsRegex")) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? this._monthsStrictRegex : this._monthsRegex; + } + } + function computeMonthsParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + var shortPieces = [], longPieces = [], mixedPieces = [], i, mom, shortP, longP; + for (i = 0; i < 12; i++) { + mom = createUTC([2e3, i]); + shortP = regexEscape(this.monthsShort(mom, "")); + longP = regexEscape(this.months(mom, "")); + shortPieces.push(shortP); + longPieces.push(longP); + mixedPieces.push(longP); + mixedPieces.push(shortP); + } + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + this._monthsRegex = new RegExp("^(" + mixedPieces.join("|") + ")", "i"); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp( + "^(" + longPieces.join("|") + ")", + "i" + ); + this._monthsShortStrictRegex = new RegExp( + "^(" + shortPieces.join("|") + ")", + "i" + ); + } + function createDate(y, m, d, h, M, s, ms) { + var date; + if (y < 100 && y >= 0) { + date = new Date(y + 400, m, d, h, M, s, ms); + if (isFinite(date.getFullYear())) { + date.setFullYear(y); + } + } else { + date = new Date(y, m, d, h, M, s, ms); + } + return date; + } + function createUTCDate(y) { + var date, args; + if (y < 100 && y >= 0) { + args = Array.prototype.slice.call(arguments); + args[0] = y + 400; + date = new Date(Date.UTC.apply(null, args)); + if (isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + } else { + date = new Date(Date.UTC.apply(null, arguments)); + } + return date; + } + function firstWeekOffset(year, dow, doy) { + var fwd = 7 + dow - doy, fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + return -fwdlw + fwd - 1; + } + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, weekOffset = firstWeekOffset(year, dow, doy), dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, resYear, resDayOfYear; + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + return { + year: resYear, + dayOfYear: resDayOfYear + }; + } + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, resWeek, resYear; + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + return { + week: resWeek, + year: resYear + }; + } + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + addFormatToken("w", ["ww", 2], "wo", "week"); + addFormatToken("W", ["WW", 2], "Wo", "isoWeek"); + addRegexToken("w", match1to2, match1to2NoLeadingZero); + addRegexToken("ww", match1to2, match2); + addRegexToken("W", match1to2, match1to2NoLeadingZero); + addRegexToken("WW", match1to2, match2); + addWeekParseToken( + ["w", "ww", "W", "WW"], + function(input, week, config, token2) { + week[token2.substr(0, 1)] = toInt(input); + } + ); + function localeWeek(mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + var defaultLocaleWeek = { + dow: 0, + // Sunday is the first day of the week. + doy: 6 + // The week that contains Jan 6th is the first week of the year. + }; + function localeFirstDayOfWeek() { + return this._week.dow; + } + function localeFirstDayOfYear() { + return this._week.doy; + } + function getSetWeek(input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, "d"); + } + function getSetISOWeek(input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, "d"); + } + addFormatToken("d", 0, "do", "day"); + addFormatToken("dd", 0, 0, function(format2) { + return this.localeData().weekdaysMin(this, format2); + }); + addFormatToken("ddd", 0, 0, function(format2) { + return this.localeData().weekdaysShort(this, format2); + }); + addFormatToken("dddd", 0, 0, function(format2) { + return this.localeData().weekdays(this, format2); + }); + addFormatToken("e", 0, 0, "weekday"); + addFormatToken("E", 0, 0, "isoWeekday"); + addRegexToken("d", match1to2); + addRegexToken("e", match1to2); + addRegexToken("E", match1to2); + addRegexToken("dd", function(isStrict, locale2) { + return locale2.weekdaysMinRegex(isStrict); + }); + addRegexToken("ddd", function(isStrict, locale2) { + return locale2.weekdaysShortRegex(isStrict); + }); + addRegexToken("dddd", function(isStrict, locale2) { + return locale2.weekdaysRegex(isStrict); + }); + addWeekParseToken(["dd", "ddd", "dddd"], function(input, week, config, token2) { + var weekday = config._locale.weekdaysParse(input, token2, config._strict); + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + addWeekParseToken(["d", "e", "E"], function(input, week, config, token2) { + week[token2] = toInt(input); + }); + function parseWeekday(input, locale2) { + if (typeof input !== "string") { + return input; + } + if (!isNaN(input)) { + return parseInt(input, 10); + } + input = locale2.weekdaysParse(input); + if (typeof input === "number") { + return input; + } + return null; + } + function parseIsoWeekday(input, locale2) { + if (typeof input === "string") { + return locale2.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + function shiftWeekdays(ws, n) { + return ws.slice(n, 7).concat(ws.slice(0, n)); + } + var defaultLocaleWeekdays = "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), defaultLocaleWeekdaysShort = "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), defaultLocaleWeekdaysMin = "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), defaultWeekdaysRegex = matchWord, defaultWeekdaysShortRegex = matchWord, defaultWeekdaysMinRegex = matchWord; + function localeWeekdays(m, format2) { + var weekdays = isArray(this._weekdays) ? this._weekdays : this._weekdays[m && m !== true && this._weekdays.isFormat.test(format2) ? "format" : "standalone"]; + return m === true ? shiftWeekdays(weekdays, this._week.dow) : m ? weekdays[m.day()] : weekdays; + } + function localeWeekdaysShort(m) { + return m === true ? shiftWeekdays(this._weekdaysShort, this._week.dow) : m ? this._weekdaysShort[m.day()] : this._weekdaysShort; + } + function localeWeekdaysMin(m) { + return m === true ? shiftWeekdays(this._weekdaysMin, this._week.dow) : m ? this._weekdaysMin[m.day()] : this._weekdaysMin; + } + function handleStrictParse$1(weekdayName, format2, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + for (i = 0; i < 7; ++i) { + mom = createUTC([2e3, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin( + mom, + "" + ).toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort( + mom, + "" + ).toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, "").toLocaleLowerCase(); + } + } + if (strict) { + if (format2 === "dddd") { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format2 === "ddd") { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format2 === "dddd") { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format2 === "ddd") { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + function localeWeekdaysParse(weekdayName, format2, strict) { + var i, mom, regex; + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format2, strict); + } + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + for (i = 0; i < 7; i++) { + mom = createUTC([2e3, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp( + "^" + this.weekdays(mom, "").replace(".", "\\.?") + "$", + "i" + ); + this._shortWeekdaysParse[i] = new RegExp( + "^" + this.weekdaysShort(mom, "").replace(".", "\\.?") + "$", + "i" + ); + this._minWeekdaysParse[i] = new RegExp( + "^" + this.weekdaysMin(mom, "").replace(".", "\\.?") + "$", + "i" + ); + } + if (!this._weekdaysParse[i]) { + regex = "^" + this.weekdays(mom, "") + "|^" + this.weekdaysShort(mom, "") + "|^" + this.weekdaysMin(mom, ""); + this._weekdaysParse[i] = new RegExp(regex.replace(".", ""), "i"); + } + if (strict && format2 === "dddd" && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format2 === "ddd" && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format2 === "dd" && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + function getSetDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = get(this, "Day"); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, "d"); + } else { + return day; + } + } + function getSetLocaleDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, "d"); + } + function getSetISODayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + function weekdaysRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, "_weekdaysRegex")) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, "_weekdaysRegex")) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? this._weekdaysStrictRegex : this._weekdaysRegex; + } + } + function weekdaysShortRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, "_weekdaysRegex")) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, "_weekdaysShortRegex")) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } + } + function weekdaysMinRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, "_weekdaysRegex")) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, "_weekdaysMinRegex")) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } + } + function computeWeekdaysParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + mom = createUTC([2e3, 1]).day(i); + minp = regexEscape(this.weekdaysMin(mom, "")); + shortp = regexEscape(this.weekdaysShort(mom, "")); + longp = regexEscape(this.weekdays(mom, "")); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + this._weekdaysRegex = new RegExp("^(" + mixedPieces.join("|") + ")", "i"); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + this._weekdaysStrictRegex = new RegExp( + "^(" + longPieces.join("|") + ")", + "i" + ); + this._weekdaysShortStrictRegex = new RegExp( + "^(" + shortPieces.join("|") + ")", + "i" + ); + this._weekdaysMinStrictRegex = new RegExp( + "^(" + minPieces.join("|") + ")", + "i" + ); + } + function hFormat() { + return this.hours() % 12 || 12; + } + function kFormat() { + return this.hours() || 24; + } + addFormatToken("H", ["HH", 2], 0, "hour"); + addFormatToken("h", ["hh", 2], 0, hFormat); + addFormatToken("k", ["kk", 2], 0, kFormat); + addFormatToken("hmm", 0, 0, function() { + return "" + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + addFormatToken("hmmss", 0, 0, function() { + return "" + hFormat.apply(this) + zeroFill(this.minutes(), 2) + zeroFill(this.seconds(), 2); + }); + addFormatToken("Hmm", 0, 0, function() { + return "" + this.hours() + zeroFill(this.minutes(), 2); + }); + addFormatToken("Hmmss", 0, 0, function() { + return "" + this.hours() + zeroFill(this.minutes(), 2) + zeroFill(this.seconds(), 2); + }); + function meridiem(token2, lowercase) { + addFormatToken(token2, 0, 0, function() { + return this.localeData().meridiem( + this.hours(), + this.minutes(), + lowercase + ); + }); + } + meridiem("a", true); + meridiem("A", false); + function matchMeridiem(isStrict, locale2) { + return locale2._meridiemParse; + } + addRegexToken("a", matchMeridiem); + addRegexToken("A", matchMeridiem); + addRegexToken("H", match1to2, match1to2HasZero); + addRegexToken("h", match1to2, match1to2NoLeadingZero); + addRegexToken("k", match1to2, match1to2NoLeadingZero); + addRegexToken("HH", match1to2, match2); + addRegexToken("hh", match1to2, match2); + addRegexToken("kk", match1to2, match2); + addRegexToken("hmm", match3to4); + addRegexToken("hmmss", match5to6); + addRegexToken("Hmm", match3to4); + addRegexToken("Hmmss", match5to6); + addParseToken(["H", "HH"], HOUR); + addParseToken(["k", "kk"], function(input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(["a", "A"], function(input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(["h", "hh"], function(input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken("hmm", function(input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken("hmmss", function(input, array, config) { + var pos1 = input.length - 4, pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken("Hmm", function(input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken("Hmmss", function(input, array, config) { + var pos1 = input.length - 4, pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + function localeIsPM(input) { + return (input + "").toLowerCase().charAt(0) === "p"; + } + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i, getSetHour = makeGetSet("Hours", true); + function localeMeridiem(hours2, minutes2, isLower) { + if (hours2 > 11) { + return isLower ? "pm" : "PM"; + } else { + return isLower ? "am" : "AM"; + } + } + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + week: defaultLocaleWeek, + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + meridiemParse: defaultLocaleMeridiemParse + }; + var locales = {}, localeFamilies = {}, globalLocale; + function commonPrefix(arr1, arr2) { + var i, minl = Math.min(arr1.length, arr2.length); + for (i = 0; i < minl; i += 1) { + if (arr1[i] !== arr2[i]) { + return i; + } + } + return minl; + } + function normalizeLocale(key) { + return key ? key.toLowerCase().replace("_", "-") : key; + } + function chooseLocale(names) { + var i = 0, j, next, locale2, split; + while (i < names.length) { + split = normalizeLocale(names[i]).split("-"); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split("-") : null; + while (j > 0) { + locale2 = loadLocale(split.slice(0, j).join("-")); + if (locale2) { + return locale2; + } + if (next && next.length >= j && commonPrefix(split, next) >= j - 1) { + break; + } + j--; + } + i++; + } + return globalLocale; + } + function isLocaleNameSane(name2) { + return !!(name2 && name2.match("^[^/\\\\]*$")); + } + function loadLocale(name2) { + var oldLocale = null, aliasedRequire; + if (locales[name2] === void 0 && typeof module !== "undefined" && module && module.exports && isLocaleNameSane(name2)) { + try { + oldLocale = globalLocale._abbr; + aliasedRequire = require; + aliasedRequire("./locale/" + name2); + getSetGlobalLocale(oldLocale); + } catch (e) { + locales[name2] = null; + } + } + return locales[name2]; + } + function getSetGlobalLocale(key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } else { + data = defineLocale(key, values); + } + if (data) { + globalLocale = data; + } else { + if (typeof console !== "undefined" && console.warn) { + console.warn( + "Locale " + key + " not found. Did you forget to load it?" + ); + } + } + } + return globalLocale._abbr; + } + function defineLocale(name2, config) { + if (config !== null) { + var locale2, parentConfig = baseConfig; + config.abbr = name2; + if (locales[name2] != null) { + deprecateSimple( + "defineLocaleOverride", + "use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info." + ); + parentConfig = locales[name2]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + locale2 = loadLocale(config.parentLocale); + if (locale2 != null) { + parentConfig = locale2._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name2, + config + }); + return null; + } + } + } + locales[name2] = new Locale(mergeConfigs(parentConfig, config)); + if (localeFamilies[name2]) { + localeFamilies[name2].forEach(function(x) { + defineLocale(x.name, x.config); + }); + } + getSetGlobalLocale(name2); + return locales[name2]; + } else { + delete locales[name2]; + return null; + } + } + function updateLocale(name2, config) { + if (config != null) { + var locale2, tmpLocale, parentConfig = baseConfig; + if (locales[name2] != null && locales[name2].parentLocale != null) { + locales[name2].set(mergeConfigs(locales[name2]._config, config)); + } else { + tmpLocale = loadLocale(name2); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + if (tmpLocale == null) { + config.abbr = name2; + } + locale2 = new Locale(config); + locale2.parentLocale = locales[name2]; + locales[name2] = locale2; + } + getSetGlobalLocale(name2); + } else { + if (locales[name2] != null) { + if (locales[name2].parentLocale != null) { + locales[name2] = locales[name2].parentLocale; + if (name2 === getSetGlobalLocale()) { + getSetGlobalLocale(name2); + } + } else if (locales[name2] != null) { + delete locales[name2]; + } + } + } + return locales[name2]; + } + function getLocale(key) { + var locale2; + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + if (!key) { + return globalLocale; + } + if (!isArray(key)) { + locale2 = loadLocale(key); + if (locale2) { + return locale2; + } + key = [key]; + } + return chooseLocale(key); + } + function listLocales() { + return keys(locales); + } + function checkOverflow(m) { + var overflow, a = m._a; + if (a && getParsingFlags(m).overflow === -2) { + overflow = a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : a[HOUR] < 0 || a[HOUR] > 24 || a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0) ? HOUR : a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : -1; + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + getParsingFlags(m).overflow = overflow; + } + return m; + } + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, tzRegex = /Z|[+-]\d\d(?::?\d\d)?/, isoDates = [ + ["YYYYYY-MM-DD", /[+-]\d{6}-\d\d-\d\d/], + ["YYYY-MM-DD", /\d{4}-\d\d-\d\d/], + ["GGGG-[W]WW-E", /\d{4}-W\d\d-\d/], + ["GGGG-[W]WW", /\d{4}-W\d\d/, false], + ["YYYY-DDD", /\d{4}-\d{3}/], + ["YYYY-MM", /\d{4}-\d\d/, false], + ["YYYYYYMMDD", /[+-]\d{10}/], + ["YYYYMMDD", /\d{8}/], + ["GGGG[W]WWE", /\d{4}W\d{3}/], + ["GGGG[W]WW", /\d{4}W\d{2}/, false], + ["YYYYDDD", /\d{7}/], + ["YYYYMM", /\d{6}/, false], + ["YYYY", /\d{4}/, false] + ], isoTimes = [ + ["HH:mm:ss.SSSS", /\d\d:\d\d:\d\d\.\d+/], + ["HH:mm:ss,SSSS", /\d\d:\d\d:\d\d,\d+/], + ["HH:mm:ss", /\d\d:\d\d:\d\d/], + ["HH:mm", /\d\d:\d\d/], + ["HHmmss.SSSS", /\d\d\d\d\d\d\.\d+/], + ["HHmmss,SSSS", /\d\d\d\d\d\d,\d+/], + ["HHmmss", /\d\d\d\d\d\d/], + ["HHmm", /\d\d\d\d/], + ["HH", /\d\d/] + ], aspNetJsonRegex = /^\/?Date\((-?\d+)/i, rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60 + }; + function configFromISO(config) { + var i, l, string = config._i, match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), allowTime, dateFormat, timeFormat, tzFormat, isoDatesLen = isoDates.length, isoTimesLen = isoTimes.length; + if (match) { + getParsingFlags(config).iso = true; + for (i = 0, l = isoDatesLen; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimesLen; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + timeFormat = (match[2] || " ") + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = "Z"; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || "") + (tzFormat || ""); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10) + ]; + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } + return result; + } + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2e3 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; + } + function preprocessRFC2822(s) { + return s.replace(/\([^()]*\)|[\n\t]/g, " ").replace(/(\s\s+)/g, " ").replace(/^\s\s*/, "").replace(/\s\s*$/, ""); + } + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), weekdayActual = new Date( + parsedInput[0], + parsedInput[1], + parsedInput[2] + ).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; + } + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + return 0; + } else { + var hm = parseInt(numOffset, 10), m = hm % 100, h = (hm - m) / 100; + return h * 60 + m; + } + } + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)), parsedArray; + if (match) { + parsedArray = extractFromRFC2822Strings( + match[4], + match[3], + match[2], + match[5], + match[6], + match[7] + ); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } + } + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + if (matched !== null) { + config._d = /* @__PURE__ */ new Date(+matched[1]); + return; + } + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + if (config._strict) { + config._isValid = false; + } else { + hooks$1.createFromInputFallback(config); + } + } + hooks$1.createFromInputFallback = deprecate( + "value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.", + function(config) { + config._d = /* @__PURE__ */ new Date(config._i + (config._useUTC ? " UTC" : "")); + } + ); + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + function currentDateArray(config) { + var nowValue = new Date(hooks$1.now()); + if (config._useUTC) { + return [ + nowValue.getUTCFullYear(), + nowValue.getUTCMonth(), + nowValue.getUTCDate() + ]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + function configFromArray(config) { + var i, date, input = [], currentDate, expectedWeekday, yearToUse; + if (config._d) { + return; + } + currentDate = currentDateArray(config); + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { + getParsingFlags(config)._overflowDayOfYear = true; + } + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + for (; i < 7; i++) { + config._a[i] = input[i] = config._a[i] == null ? i === 2 ? 1 : 0 : config._a[i]; + } + if (config._a[HOUR] === 24 && config._a[MINUTE] === 0 && config._a[SECOND] === 0 && config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + config._d = (config._useUTC ? createUTCDate : createDate).apply( + null, + input + ); + expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay(); + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + if (config._nextDay) { + config._a[HOUR] = 24; + } + if (config._w && typeof config._w.d !== "undefined" && config._w.d !== expectedWeekday) { + getParsingFlags(config).weekdayMismatch = true; + } + } + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek; + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + weekYear = defaults( + w.GG, + config._a[YEAR], + weekOfYear(createLocal(), 1, 4).year + ); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + curWeek = weekOfYear(createLocal(), dow, doy); + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + week = defaults(w.w, curWeek.week); + if (w.d != null) { + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + hooks$1.ISO_8601 = function() { + }; + hooks$1.RFC_2822 = function() { + }; + function configFromStringAndFormat(config) { + if (config._f === hooks$1.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks$1.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + var string = "" + config._i, i, parsedInput, tokens2, token2, skipped, stringLength = string.length, totalParsedInputLength = 0, era, tokenLen; + tokens2 = expandFormat(config._f, config._locale).match(formattingTokens) || []; + tokenLen = tokens2.length; + for (i = 0; i < tokenLen; i++) { + token2 = tokens2[i]; + parsedInput = (string.match(getParseRegexForToken(token2, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice( + string.indexOf(parsedInput) + parsedInput.length + ); + totalParsedInputLength += parsedInput.length; + } + if (formatTokenFunctions[token2]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } else { + getParsingFlags(config).unusedTokens.push(token2); + } + addTimeToArrayFromToken(token2, parsedInput, config); + } else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token2); + } + } + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + if (config._a[HOUR] <= 12 && getParsingFlags(config).bigHour === true && config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = void 0; + } + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + config._a[HOUR] = meridiemFixWrap( + config._locale, + config._a[HOUR], + config._meridiem + ); + era = getParsingFlags(config).era; + if (era !== null) { + config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]); + } + configFromArray(config); + checkOverflow(config); + } + function meridiemFixWrap(locale2, hour, meridiem2) { + var isPm; + if (meridiem2 == null) { + return hour; + } + if (locale2.meridiemHour != null) { + return locale2.meridiemHour(hour, meridiem2); + } else if (locale2.isPM != null) { + isPm = locale2.isPM(meridiem2); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + return hour; + } + } + function configFromStringAndArray(config) { + var tempConfig, bestMoment, scoreToBeat, i, currentScore, validFormatFound, bestFormatIsValid = false, configfLen = config._f.length; + if (configfLen === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = /* @__PURE__ */ new Date(NaN); + return; + } + for (i = 0; i < configfLen; i++) { + currentScore = 0; + validFormatFound = false; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + if (isValid(tempConfig)) { + validFormatFound = true; + } + currentScore += getParsingFlags(tempConfig).charsLeftOver; + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + getParsingFlags(tempConfig).score = currentScore; + if (!bestFormatIsValid) { + if (scoreToBeat == null || currentScore < scoreToBeat || validFormatFound) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + if (validFormatFound) { + bestFormatIsValid = true; + } + } + } else { + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + } + extend(config, bestMoment || tempConfig); + } + function configFromObject(config) { + if (config._d) { + return; + } + var i = normalizeObjectUnits(config._i), dayOrDate = i.day === void 0 ? i.date : i.day; + config._a = map( + [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond], + function(obj) { + return obj && parseInt(obj, 10); + } + ); + configFromArray(config); + } + function createFromConfig(config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + res.add(1, "d"); + res._nextDay = void 0; + } + return res; + } + function prepareConfig(config) { + var input = config._i, format2 = config._f; + config._locale = config._locale || getLocale(config._l); + if (input === null || format2 === void 0 && input === "") { + return createInvalid({ nullInput: true }); + } + if (typeof input === "string") { + config._i = input = config._locale.preparse(input); + } + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format2)) { + configFromStringAndArray(config); + } else if (format2) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + if (!isValid(config)) { + config._d = null; + } + return config; + } + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks$1.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === "string") { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function(obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + config._d = new Date(input); + } else { + hooks$1.createFromInputFallback(config); + } + } + function createLocalOrUTC(input, format2, locale2, strict, isUTC) { + var c = {}; + if (format2 === true || format2 === false) { + strict = format2; + format2 = void 0; + } + if (locale2 === true || locale2 === false) { + strict = locale2; + locale2 = void 0; + } + if (isObject(input) && isObjectEmpty(input) || isArray(input) && input.length === 0) { + input = void 0; + } + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale2; + c._i = input; + c._f = format2; + c._strict = strict; + return createFromConfig(c); + } + function createLocal(input, format2, locale2, strict) { + return createLocalOrUTC(input, format2, locale2, strict, false); + } + var prototypeMin = deprecate( + "moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/", + function() { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ), prototypeMax = deprecate( + "moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/", + function() { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + function min() { + var args = [].slice.call(arguments, 0); + return pickBy("isBefore", args); + } + function max() { + var args = [].slice.call(arguments, 0); + return pickBy("isAfter", args); + } + var now = function() { + return Date.now ? Date.now() : +/* @__PURE__ */ new Date(); + }; + var ordering = [ + "year", + "quarter", + "month", + "week", + "day", + "hour", + "minute", + "second", + "millisecond" + ]; + function isDurationValid(m) { + var key, unitHasDecimal = false, i, orderLen = ordering.length; + for (key in m) { + if (hasOwnProp(m, key) && !(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) { + return false; + } + } + for (i = 0; i < orderLen; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + return true; + } + function isValid$1() { + return this._isValid; + } + function createInvalid$1() { + return createDuration(NaN); + } + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), years2 = normalizedInput.year || 0, quarters = normalizedInput.quarter || 0, months2 = normalizedInput.month || 0, weeks2 = normalizedInput.week || normalizedInput.isoWeek || 0, days2 = normalizedInput.day || 0, hours2 = normalizedInput.hour || 0, minutes2 = normalizedInput.minute || 0, seconds2 = normalizedInput.second || 0, milliseconds2 = normalizedInput.millisecond || 0; + this._isValid = isDurationValid(normalizedInput); + this._milliseconds = +milliseconds2 + seconds2 * 1e3 + // 1000 + minutes2 * 6e4 + // 1000 * 60 + hours2 * 1e3 * 60 * 60; + this._days = +days2 + weeks2 * 7; + this._months = +months2 + quarters * 3 + years2 * 12; + this._data = {}; + this._locale = getLocale(); + this._bubble(); + } + function isDuration(obj) { + return obj instanceof Duration; + } + function absRound(number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), lengthDiff = Math.abs(array1.length - array2.length), diffs = 0, i; + for (i = 0; i < len; i++) { + if (toInt(array1[i]) !== toInt(array2[i])) { + diffs++; + } + } + return diffs + lengthDiff; + } + function offset(token2, separator) { + addFormatToken(token2, 0, 0, function() { + var offset2 = this.utcOffset(), sign2 = "+"; + if (offset2 < 0) { + offset2 = -offset2; + sign2 = "-"; + } + return sign2 + zeroFill(~~(offset2 / 60), 2) + separator + zeroFill(~~offset2 % 60, 2); + }); + } + offset("Z", ":"); + offset("ZZ", ""); + addRegexToken("Z", matchShortOffset); + addRegexToken("ZZ", matchShortOffset); + addParseToken(["Z", "ZZ"], function(input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + var chunkOffset = /([\+\-]|\d\d)/gi; + function offsetFromString(matcher, string) { + var matches = (string || "").match(matcher), chunk, parts, minutes2; + if (matches === null) { + return null; + } + chunk = matches[matches.length - 1] || []; + parts = (chunk + "").match(chunkOffset) || ["-", 0, 0]; + minutes2 = +(parts[1] * 60) + toInt(parts[2]); + return minutes2 === 0 ? 0 : parts[0] === "+" ? minutes2 : -minutes2; + } + function cloneWithOffset(input, model) { + var res, diff2; + if (model._isUTC) { + res = model.clone(); + diff2 = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + res._d.setTime(res._d.valueOf() + diff2); + hooks$1.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + } + function getDateOffset(m) { + return -Math.round(m._d.getTimezoneOffset()); + } + hooks$1.updateOffset = function() { + }; + function getSetOffset(input, keepLocalTime, keepMinutes) { + var offset2 = this._offset || 0, localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === "string") { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, "m"); + } + if (offset2 !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract( + this, + createDuration(input - offset2, "m"), + 1, + false + ); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks$1.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset2 : getDateOffset(this); + } + } + function getSetZone(input, keepLocalTime) { + if (input != null) { + if (typeof input !== "string") { + input = -input; + } + this.utcOffset(input, keepLocalTime); + return this; + } else { + return -this.utcOffset(); + } + } + function setOffsetToUTC(keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + function setOffsetToLocal(keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + if (keepLocalTime) { + this.subtract(getDateOffset(this), "m"); + } + } + return this; + } + function setOffsetToParsedOffset() { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === "string") { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } else { + this.utcOffset(0, true); + } + } + return this; + } + function hasAlignedHourOffset(input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + return (this.utcOffset() - input) % 60 === 0; + } + function isDaylightSavingTime() { + return this.utcOffset() > this.clone().month(0).utcOffset() || this.utcOffset() > this.clone().month(5).utcOffset(); + } + function isDaylightSavingTimeShifted() { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + var c = {}, other; + copyConfig(c, this); + c = prepareConfig(c); + if (c._a) { + other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + return this._isDSTShifted; + } + function isLocal() { + return this.isValid() ? !this._isUTC : false; + } + function isUtcOffset() { + return this.isValid() ? this._isUTC : false; + } + function isUtc() { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/, isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + function createDuration(input, key) { + var duration = input, match = null, sign2, ret, diffRes; + if (isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (isNumber(input) || !isNaN(+input)) { + duration = {}; + if (key) { + duration[key] = +input; + } else { + duration.milliseconds = +input; + } + } else if (match = aspNetRegex.exec(input)) { + sign2 = match[1] === "-" ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign2, + h: toInt(match[HOUR]) * sign2, + m: toInt(match[MINUTE]) * sign2, + s: toInt(match[SECOND]) * sign2, + ms: toInt(absRound(match[MILLISECOND] * 1e3)) * sign2 + // the millisecond decimal point is included in the match + }; + } else if (match = isoRegex.exec(input)) { + sign2 = match[1] === "-" ? -1 : 1; + duration = { + y: parseIso(match[2], sign2), + M: parseIso(match[3], sign2), + w: parseIso(match[4], sign2), + d: parseIso(match[5], sign2), + h: parseIso(match[6], sign2), + m: parseIso(match[7], sign2), + s: parseIso(match[8], sign2) + }; + } else if (duration == null) { + duration = {}; + } else if (typeof duration === "object" && ("from" in duration || "to" in duration)) { + diffRes = momentsDifference( + createLocal(duration.from), + createLocal(duration.to) + ); + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + ret = new Duration(duration); + if (isDuration(input) && hasOwnProp(input, "_locale")) { + ret._locale = input._locale; + } + if (isDuration(input) && hasOwnProp(input, "_isValid")) { + ret._isValid = input._isValid; + } + return ret; + } + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + function parseIso(inp, sign2) { + var res = inp && parseFloat(inp.replace(",", ".")); + return (isNaN(res) ? 0 : res) * sign2; + } + function positiveMomentsDifference(base, other) { + var res = {}; + res.months = other.month() - base.month() + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, "M").isAfter(other)) { + --res.months; + } + res.milliseconds = +other - +base.clone().add(res.months, "M"); + return res; + } + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return { milliseconds: 0, months: 0 }; + } + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + return res; + } + function createAdder(direction, name2) { + return function(val, period) { + var dur, tmp; + if (period !== null && !isNaN(+period)) { + deprecateSimple( + name2, + "moment()." + name2 + "(period, number) is deprecated. Please use moment()." + name2 + "(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info." + ); + tmp = val; + val = period; + period = tmp; + } + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + function addSubtract(mom, duration, isAdding, updateOffset) { + var milliseconds2 = duration._milliseconds, days2 = absRound(duration._days), months2 = absRound(duration._months); + if (!mom.isValid()) { + return; + } + updateOffset = updateOffset == null ? true : updateOffset; + if (months2) { + setMonth(mom, get(mom, "Month") + months2 * isAdding); + } + if (days2) { + set$1(mom, "Date", get(mom, "Date") + days2 * isAdding); + } + if (milliseconds2) { + mom._d.setTime(mom._d.valueOf() + milliseconds2 * isAdding); + } + if (updateOffset) { + hooks$1.updateOffset(mom, days2 || months2); + } + } + var add = createAdder(1, "add"), subtract = createAdder(-1, "subtract"); + function isString(input) { + return typeof input === "string" || input instanceof String; + } + function isMomentInput(input) { + return isMoment(input) || isDate(input) || isString(input) || isNumber(input) || isNumberOrStringArray(input) || isMomentInputObject(input) || input === null || input === void 0; + } + function isMomentInputObject(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), propertyTest = false, properties = [ + "years", + "year", + "y", + "months", + "month", + "M", + "days", + "day", + "d", + "dates", + "date", + "D", + "hours", + "hour", + "h", + "minutes", + "minute", + "m", + "seconds", + "second", + "s", + "milliseconds", + "millisecond", + "ms" + ], i, property, propertyLen = properties.length; + for (i = 0; i < propertyLen; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + return objectTest && propertyTest; + } + function isNumberOrStringArray(input) { + var arrayTest = isArray(input), dataTypeTest = false; + if (arrayTest) { + dataTypeTest = input.filter(function(item) { + return !isNumber(item) && isString(input); + }).length === 0; + } + return arrayTest && dataTypeTest; + } + function isCalendarSpec(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), propertyTest = false, properties = [ + "sameDay", + "nextDay", + "lastDay", + "nextWeek", + "lastWeek", + "sameElse" + ], i, property; + for (i = 0; i < properties.length; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + return objectTest && propertyTest; + } + function getCalendarFormat(myMoment, now2) { + var diff2 = myMoment.diff(now2, "days", true); + return diff2 < -6 ? "sameElse" : diff2 < -1 ? "lastWeek" : diff2 < 0 ? "lastDay" : diff2 < 1 ? "sameDay" : diff2 < 2 ? "nextDay" : diff2 < 7 ? "nextWeek" : "sameElse"; + } + function calendar$1(time, formats) { + if (arguments.length === 1) { + if (!arguments[0]) { + time = void 0; + formats = void 0; + } else if (isMomentInput(arguments[0])) { + time = arguments[0]; + formats = void 0; + } else if (isCalendarSpec(arguments[0])) { + formats = arguments[0]; + time = void 0; + } + } + var now2 = time || createLocal(), sod = cloneWithOffset(now2, this).startOf("day"), format2 = hooks$1.calendarFormat(this, sod) || "sameElse", output = formats && (isFunction(formats[format2]) ? formats[format2].call(this, now2) : formats[format2]); + return this.format( + output || this.localeData().calendar(format2, this, createLocal(now2)) + ); + } + function clone() { + return new Moment(this); + } + function isAfter(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || "millisecond"; + if (units === "millisecond") { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + function isBefore(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || "millisecond"; + if (units === "millisecond") { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + function isBetween(from2, to2, units, inclusivity) { + var localFrom = isMoment(from2) ? from2 : createLocal(from2), localTo = isMoment(to2) ? to2 : createLocal(to2); + if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { + return false; + } + inclusivity = inclusivity || "()"; + return (inclusivity[0] === "(" ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) && (inclusivity[1] === ")" ? this.isBefore(localTo, units) : !this.isAfter(localTo, units)); + } + function isSame(input, units) { + var localInput = isMoment(input) ? input : createLocal(input), inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || "millisecond"; + if (units === "millisecond") { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } + function isSameOrAfter(input, units) { + return this.isSame(input, units) || this.isAfter(input, units); + } + function isSameOrBefore(input, units) { + return this.isSame(input, units) || this.isBefore(input, units); + } + function diff(input, units, asFloat) { + var that, zoneDelta, output; + if (!this.isValid()) { + return NaN; + } + that = cloneWithOffset(input, this); + if (!that.isValid()) { + return NaN; + } + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + units = normalizeUnits(units); + switch (units) { + case "year": + output = monthDiff(this, that) / 12; + break; + case "month": + output = monthDiff(this, that); + break; + case "quarter": + output = monthDiff(this, that) / 3; + break; + case "second": + output = (this - that) / 1e3; + break; + // 1000 + case "minute": + output = (this - that) / 6e4; + break; + // 1000 * 60 + case "hour": + output = (this - that) / 36e5; + break; + // 1000 * 60 * 60 + case "day": + output = (this - that - zoneDelta) / 864e5; + break; + // 1000 * 60 * 60 * 24, negate dst + case "week": + output = (this - that - zoneDelta) / 6048e5; + break; + // 1000 * 60 * 60 * 24 * 7, negate dst + default: + output = this - that; + } + return asFloat ? output : absFloor(output); + } + function monthDiff(a, b) { + if (a.date() < b.date()) { + return -monthDiff(b, a); + } + var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()), anchor = a.clone().add(wholeMonthDiff, "months"), anchor2, adjust; + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, "months"); + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, "months"); + adjust = (b - anchor) / (anchor2 - anchor); + } + return -(wholeMonthDiff + adjust) || 0; + } + hooks$1.defaultFormat = "YYYY-MM-DDTHH:mm:ssZ"; + hooks$1.defaultFormatUtc = "YYYY-MM-DDTHH:mm:ss[Z]"; + function toString() { + return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + } + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true, m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment( + m, + utc ? "YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]" : "YYYYYY-MM-DD[T]HH:mm:ss.SSSZ" + ); + } + if (isFunction(Date.prototype.toISOString)) { + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this.valueOf() + this.utcOffset() * 60 * 1e3).toISOString().replace("Z", formatMoment(m, "Z")); + } + } + return formatMoment( + m, + utc ? "YYYY-MM-DD[T]HH:mm:ss.SSS[Z]" : "YYYY-MM-DD[T]HH:mm:ss.SSSZ" + ); + } + function inspect() { + if (!this.isValid()) { + return "moment.invalid(/* " + this._i + " */)"; + } + var func = "moment", zone = "", prefix, year, datetime, suffix; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? "moment.utc" : "moment.parseZone"; + zone = "Z"; + } + prefix = "[" + func + '("]'; + year = 0 <= this.year() && this.year() <= 9999 ? "YYYY" : "YYYYYY"; + datetime = "-MM-DD[T]HH:mm:ss.SSS"; + suffix = zone + '[")]'; + return this.format(prefix + year + datetime + suffix); + } + function format(inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks$1.defaultFormatUtc : hooks$1.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + function from(time, withoutSuffix) { + if (this.isValid() && (isMoment(time) && time.isValid() || createLocal(time).isValid())) { + return createDuration({ to: this, from: time }).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + function fromNow(withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } + function to(time, withoutSuffix) { + if (this.isValid() && (isMoment(time) && time.isValid() || createLocal(time).isValid())) { + return createDuration({ from: this, to: time }).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + function toNow(withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } + function locale(key) { + var newLocaleData; + if (key === void 0) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + var lang = deprecate( + "moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.", + function(key) { + if (key === void 0) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + function localeData() { + return this._locale; + } + var MS_PER_SECOND = 1e3, MS_PER_MINUTE = 60 * MS_PER_SECOND, MS_PER_HOUR = 60 * MS_PER_MINUTE, MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; + function mod$1(dividend, divisor) { + return (dividend % divisor + divisor) % divisor; + } + function localStartOfDate(y, m, d) { + if (y < 100 && y >= 0) { + return new Date(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return new Date(y, m, d).valueOf(); + } + } + function utcStartOfDate(y, m, d) { + if (y < 100 && y >= 0) { + return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return Date.UTC(y, m, d); + } + } + function startOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === void 0 || units === "millisecond" || !this.isValid()) { + return this; + } + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + switch (units) { + case "year": + time = startOfDate(this.year(), 0, 1); + break; + case "quarter": + time = startOfDate( + this.year(), + this.month() - this.month() % 3, + 1 + ); + break; + case "month": + time = startOfDate(this.year(), this.month(), 1); + break; + case "week": + time = startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + ); + break; + case "isoWeek": + time = startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + ); + break; + case "day": + case "date": + time = startOfDate(this.year(), this.month(), this.date()); + break; + case "hour": + time = this._d.valueOf(); + time -= mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ); + break; + case "minute": + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_MINUTE); + break; + case "second": + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_SECOND); + break; + } + this._d.setTime(time); + hooks$1.updateOffset(this, true); + return this; + } + function endOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === void 0 || units === "millisecond" || !this.isValid()) { + return this; + } + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + switch (units) { + case "year": + time = startOfDate(this.year() + 1, 0, 1) - 1; + break; + case "quarter": + time = startOfDate( + this.year(), + this.month() - this.month() % 3 + 3, + 1 + ) - 1; + break; + case "month": + time = startOfDate(this.year(), this.month() + 1, 1) - 1; + break; + case "week": + time = startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + 7 + ) - 1; + break; + case "isoWeek": + time = startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + 7 + ) - 1; + break; + case "day": + case "date": + time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; + break; + case "hour": + time = this._d.valueOf(); + time += MS_PER_HOUR - mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ) - 1; + break; + case "minute": + time = this._d.valueOf(); + time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; + break; + case "second": + time = this._d.valueOf(); + time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; + break; + } + this._d.setTime(time); + hooks$1.updateOffset(this, true); + return this; + } + function valueOf() { + return this._d.valueOf() - (this._offset || 0) * 6e4; + } + function unix() { + return Math.floor(this.valueOf() / 1e3); + } + function toDate() { + return new Date(this.valueOf()); + } + function toArray() { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hour(), + m.minute(), + m.second(), + m.millisecond() + ]; + } + function toObject() { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; + } + function toJSON() { + return this.isValid() ? this.toISOString() : null; + } + function isValid$2() { + return isValid(this); + } + function parsingFlags() { + return extend({}, getParsingFlags(this)); + } + function invalidAt() { + return getParsingFlags(this).overflow; + } + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; + } + addFormatToken("N", 0, 0, "eraAbbr"); + addFormatToken("NN", 0, 0, "eraAbbr"); + addFormatToken("NNN", 0, 0, "eraAbbr"); + addFormatToken("NNNN", 0, 0, "eraName"); + addFormatToken("NNNNN", 0, 0, "eraNarrow"); + addFormatToken("y", ["y", 1], "yo", "eraYear"); + addFormatToken("y", ["yy", 2], 0, "eraYear"); + addFormatToken("y", ["yyy", 3], 0, "eraYear"); + addFormatToken("y", ["yyyy", 4], 0, "eraYear"); + addRegexToken("N", matchEraAbbr); + addRegexToken("NN", matchEraAbbr); + addRegexToken("NNN", matchEraAbbr); + addRegexToken("NNNN", matchEraName); + addRegexToken("NNNNN", matchEraNarrow); + addParseToken( + ["N", "NN", "NNN", "NNNN", "NNNNN"], + function(input, array, config, token2) { + var era = config._locale.erasParse(input, token2, config._strict); + if (era) { + getParsingFlags(config).era = era; + } else { + getParsingFlags(config).invalidEra = input; + } + } + ); + addRegexToken("y", matchUnsigned); + addRegexToken("yy", matchUnsigned); + addRegexToken("yyy", matchUnsigned); + addRegexToken("yyyy", matchUnsigned); + addRegexToken("yo", matchEraYearOrdinal); + addParseToken(["y", "yy", "yyy", "yyyy"], YEAR); + addParseToken(["yo"], function(input, array, config, token2) { + var match; + if (config._locale._eraYearOrdinalRegex) { + match = input.match(config._locale._eraYearOrdinalRegex); + } + if (config._locale.eraYearOrdinalParse) { + array[YEAR] = config._locale.eraYearOrdinalParse(input, match); + } else { + array[YEAR] = parseInt(input, 10); + } + }); + function localeEras(m, format2) { + var i, l, date, eras = this._eras || getLocale("en")._eras; + for (i = 0, l = eras.length; i < l; ++i) { + switch (typeof eras[i].since) { + case "string": + date = hooks$1(eras[i].since).startOf("day"); + eras[i].since = date.valueOf(); + break; + } + switch (typeof eras[i].until) { + case "undefined": + eras[i].until = Infinity; + break; + case "string": + date = hooks$1(eras[i].until).startOf("day").valueOf(); + eras[i].until = date.valueOf(); + break; + } + } + return eras; + } + function localeErasParse(eraName, format2, strict) { + var i, l, eras = this.eras(), name2, abbr, narrow; + eraName = eraName.toUpperCase(); + for (i = 0, l = eras.length; i < l; ++i) { + name2 = eras[i].name.toUpperCase(); + abbr = eras[i].abbr.toUpperCase(); + narrow = eras[i].narrow.toUpperCase(); + if (strict) { + switch (format2) { + case "N": + case "NN": + case "NNN": + if (abbr === eraName) { + return eras[i]; + } + break; + case "NNNN": + if (name2 === eraName) { + return eras[i]; + } + break; + case "NNNNN": + if (narrow === eraName) { + return eras[i]; + } + break; + } + } else if ([name2, abbr, narrow].indexOf(eraName) >= 0) { + return eras[i]; + } + } + } + function localeErasConvertYear(era, year) { + var dir = era.since <= era.until ? 1 : -1; + if (year === void 0) { + return hooks$1(era.since).year(); + } else { + return hooks$1(era.since).year() + (year - era.offset) * dir; + } + } + function getEraName() { + var i, l, val, eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + val = this.clone().startOf("day").valueOf(); + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].name; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].name; + } + } + return ""; + } + function getEraNarrow() { + var i, l, val, eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + val = this.clone().startOf("day").valueOf(); + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].narrow; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].narrow; + } + } + return ""; + } + function getEraAbbr() { + var i, l, val, eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + val = this.clone().startOf("day").valueOf(); + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].abbr; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].abbr; + } + } + return ""; + } + function getEraYear() { + var i, l, dir, val, eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + dir = eras[i].since <= eras[i].until ? 1 : -1; + val = this.clone().startOf("day").valueOf(); + if (eras[i].since <= val && val <= eras[i].until || eras[i].until <= val && val <= eras[i].since) { + return (this.year() - hooks$1(eras[i].since).year()) * dir + eras[i].offset; + } + } + return this.year(); + } + function erasNameRegex(isStrict) { + if (!hasOwnProp(this, "_erasNameRegex")) { + computeErasParse.call(this); + } + return isStrict ? this._erasNameRegex : this._erasRegex; + } + function erasAbbrRegex(isStrict) { + if (!hasOwnProp(this, "_erasAbbrRegex")) { + computeErasParse.call(this); + } + return isStrict ? this._erasAbbrRegex : this._erasRegex; + } + function erasNarrowRegex(isStrict) { + if (!hasOwnProp(this, "_erasNarrowRegex")) { + computeErasParse.call(this); + } + return isStrict ? this._erasNarrowRegex : this._erasRegex; + } + function matchEraAbbr(isStrict, locale2) { + return locale2.erasAbbrRegex(isStrict); + } + function matchEraName(isStrict, locale2) { + return locale2.erasNameRegex(isStrict); + } + function matchEraNarrow(isStrict, locale2) { + return locale2.erasNarrowRegex(isStrict); + } + function matchEraYearOrdinal(isStrict, locale2) { + return locale2._eraYearOrdinalRegex || matchUnsigned; + } + function computeErasParse() { + var abbrPieces = [], namePieces = [], narrowPieces = [], mixedPieces = [], i, l, erasName, erasAbbr, erasNarrow, eras = this.eras(); + for (i = 0, l = eras.length; i < l; ++i) { + erasName = regexEscape(eras[i].name); + erasAbbr = regexEscape(eras[i].abbr); + erasNarrow = regexEscape(eras[i].narrow); + namePieces.push(erasName); + abbrPieces.push(erasAbbr); + narrowPieces.push(erasNarrow); + mixedPieces.push(erasName); + mixedPieces.push(erasAbbr); + mixedPieces.push(erasNarrow); + } + this._erasRegex = new RegExp("^(" + mixedPieces.join("|") + ")", "i"); + this._erasNameRegex = new RegExp("^(" + namePieces.join("|") + ")", "i"); + this._erasAbbrRegex = new RegExp("^(" + abbrPieces.join("|") + ")", "i"); + this._erasNarrowRegex = new RegExp( + "^(" + narrowPieces.join("|") + ")", + "i" + ); + } + addFormatToken(0, ["gg", 2], 0, function() { + return this.weekYear() % 100; + }); + addFormatToken(0, ["GG", 2], 0, function() { + return this.isoWeekYear() % 100; + }); + function addWeekYearFormatToken(token2, getter) { + addFormatToken(0, [token2, token2.length], 0, getter); + } + addWeekYearFormatToken("gggg", "weekYear"); + addWeekYearFormatToken("ggggg", "weekYear"); + addWeekYearFormatToken("GGGG", "isoWeekYear"); + addWeekYearFormatToken("GGGGG", "isoWeekYear"); + addRegexToken("G", matchSigned); + addRegexToken("g", matchSigned); + addRegexToken("GG", match1to2, match2); + addRegexToken("gg", match1to2, match2); + addRegexToken("GGGG", match1to4, match4); + addRegexToken("gggg", match1to4, match4); + addRegexToken("GGGGG", match1to6, match6); + addRegexToken("ggggg", match1to6, match6); + addWeekParseToken( + ["gggg", "ggggg", "GGGG", "GGGGG"], + function(input, week, config, token2) { + week[token2.substr(0, 2)] = toInt(input); + } + ); + addWeekParseToken(["gg", "GG"], function(input, week, config, token2) { + week[token2] = hooks$1.parseTwoDigitYear(input); + }); + function getSetWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.week(), + this.weekday() + this.localeData()._week.dow, + this.localeData()._week.dow, + this.localeData()._week.doy + ); + } + function getSetISOWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.isoWeek(), + this.isoWeekday(), + 1, + 4 + ); + } + function getISOWeeksInYear() { + return weeksInYear(this.year(), 1, 4); + } + function getISOWeeksInISOWeekYear() { + return weeksInYear(this.isoWeekYear(), 1, 4); + } + function getWeeksInYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + function getWeeksInWeekYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy); + } + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + addFormatToken("Q", 0, "Qo", "quarter"); + addRegexToken("Q", match1); + addParseToken("Q", function(input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + function getSetQuarter(input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } + addFormatToken("D", ["DD", 2], "Do", "date"); + addRegexToken("D", match1to2, match1to2NoLeadingZero); + addRegexToken("DD", match1to2, match2); + addRegexToken("Do", function(isStrict, locale2) { + return isStrict ? locale2._dayOfMonthOrdinalParse || locale2._ordinalParse : locale2._dayOfMonthOrdinalParseLenient; + }); + addParseToken(["D", "DD"], DATE); + addParseToken("Do", function(input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); + var getSetDayOfMonth = makeGetSet("Date", true); + addFormatToken("DDD", ["DDDD", 3], "DDDo", "dayOfYear"); + addRegexToken("DDD", match1to3); + addRegexToken("DDDD", match3); + addParseToken(["DDD", "DDDD"], function(input, array, config) { + config._dayOfYear = toInt(input); + }); + function getSetDayOfYear(input) { + var dayOfYear = Math.round( + (this.clone().startOf("day") - this.clone().startOf("year")) / 864e5 + ) + 1; + return input == null ? dayOfYear : this.add(input - dayOfYear, "d"); + } + addFormatToken("m", ["mm", 2], 0, "minute"); + addRegexToken("m", match1to2, match1to2HasZero); + addRegexToken("mm", match1to2, match2); + addParseToken(["m", "mm"], MINUTE); + var getSetMinute = makeGetSet("Minutes", false); + addFormatToken("s", ["ss", 2], 0, "second"); + addRegexToken("s", match1to2, match1to2HasZero); + addRegexToken("ss", match1to2, match2); + addParseToken(["s", "ss"], SECOND); + var getSetSecond = makeGetSet("Seconds", false); + addFormatToken("S", 0, 0, function() { + return ~~(this.millisecond() / 100); + }); + addFormatToken(0, ["SS", 2], 0, function() { + return ~~(this.millisecond() / 10); + }); + addFormatToken(0, ["SSS", 3], 0, "millisecond"); + addFormatToken(0, ["SSSS", 4], 0, function() { + return this.millisecond() * 10; + }); + addFormatToken(0, ["SSSSS", 5], 0, function() { + return this.millisecond() * 100; + }); + addFormatToken(0, ["SSSSSS", 6], 0, function() { + return this.millisecond() * 1e3; + }); + addFormatToken(0, ["SSSSSSS", 7], 0, function() { + return this.millisecond() * 1e4; + }); + addFormatToken(0, ["SSSSSSSS", 8], 0, function() { + return this.millisecond() * 1e5; + }); + addFormatToken(0, ["SSSSSSSSS", 9], 0, function() { + return this.millisecond() * 1e6; + }); + addRegexToken("S", match1to3, match1); + addRegexToken("SS", match1to3, match2); + addRegexToken("SSS", match1to3, match3); + var token, getSetMillisecond; + for (token = "SSSS"; token.length <= 9; token += "S") { + addRegexToken(token, matchUnsigned); + } + function parseMs(input, array) { + array[MILLISECOND] = toInt(("0." + input) * 1e3); + } + for (token = "S"; token.length <= 9; token += "S") { + addParseToken(token, parseMs); + } + getSetMillisecond = makeGetSet("Milliseconds", false); + addFormatToken("z", 0, 0, "zoneAbbr"); + addFormatToken("zz", 0, 0, "zoneName"); + function getZoneAbbr() { + return this._isUTC ? "UTC" : ""; + } + function getZoneName() { + return this._isUTC ? "Coordinated Universal Time" : ""; + } + var proto = Moment.prototype; + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + if (typeof Symbol !== "undefined" && Symbol.for != null) { + proto[Symbol.for("nodejs.util.inspect.custom")] = function() { + return "Moment<" + this.format() + ">"; + }; + } + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + proto.eraName = getEraName; + proto.eraNarrow = getEraNarrow; + proto.eraAbbr = getEraAbbr; + proto.eraYear = getEraYear; + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + proto.quarter = proto.quarters = getSetQuarter; + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.weeksInWeekYear = getWeeksInWeekYear; + proto.isoWeeksInYear = getISOWeeksInYear; + proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear; + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + proto.hour = proto.hours = getSetHour; + proto.minute = proto.minutes = getSetMinute; + proto.second = proto.seconds = getSetSecond; + proto.millisecond = proto.milliseconds = getSetMillisecond; + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + proto.dates = deprecate( + "dates accessor is deprecated. Use date instead.", + getSetDayOfMonth + ); + proto.months = deprecate( + "months accessor is deprecated. Use month instead", + getSetMonth + ); + proto.years = deprecate( + "years accessor is deprecated. Use year instead", + getSetYear + ); + proto.zone = deprecate( + "moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/", + getSetZone + ); + proto.isDSTShifted = deprecate( + "isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information", + isDaylightSavingTimeShifted + ); + function createUnix(input) { + return createLocal(input * 1e3); + } + function createInZone() { + return createLocal.apply(null, arguments).parseZone(); + } + function preParsePostFormat(string) { + return string; + } + var proto$1 = Locale.prototype; + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + proto$1.eras = localeEras; + proto$1.erasParse = localeErasParse; + proto$1.erasConvertYear = localeErasConvertYear; + proto$1.erasAbbrRegex = erasAbbrRegex; + proto$1.erasNameRegex = erasNameRegex; + proto$1.erasNarrowRegex = erasNarrowRegex; + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + function get$1(format2, index, field, setter) { + var locale2 = getLocale(), utc = createUTC().set(setter, index); + return locale2[field](utc, format2); + } + function listMonthsImpl(format2, index, field) { + if (isNumber(format2)) { + index = format2; + format2 = void 0; + } + format2 = format2 || ""; + if (index != null) { + return get$1(format2, index, field, "month"); + } + var i, out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format2, i, field, "month"); + } + return out; + } + function listWeekdaysImpl(localeSorted, format2, index, field) { + if (typeof localeSorted === "boolean") { + if (isNumber(format2)) { + index = format2; + format2 = void 0; + } + format2 = format2 || ""; + } else { + format2 = localeSorted; + index = format2; + localeSorted = false; + if (isNumber(format2)) { + index = format2; + format2 = void 0; + } + format2 = format2 || ""; + } + var locale2 = getLocale(), shift = localeSorted ? locale2._week.dow : 0, i, out = []; + if (index != null) { + return get$1(format2, (index + shift) % 7, field, "day"); + } + for (i = 0; i < 7; i++) { + out[i] = get$1(format2, (i + shift) % 7, field, "day"); + } + return out; + } + function listMonths(format2, index) { + return listMonthsImpl(format2, index, "months"); + } + function listMonthsShort(format2, index) { + return listMonthsImpl(format2, index, "monthsShort"); + } + function listWeekdays(localeSorted, format2, index) { + return listWeekdaysImpl(localeSorted, format2, index, "weekdays"); + } + function listWeekdaysShort(localeSorted, format2, index) { + return listWeekdaysImpl(localeSorted, format2, index, "weekdaysShort"); + } + function listWeekdaysMin(localeSorted, format2, index) { + return listWeekdaysImpl(localeSorted, format2, index, "weekdaysMin"); + } + getSetGlobalLocale("en", { + eras: [ + { + since: "0001-01-01", + until: Infinity, + offset: 1, + name: "Anno Domini", + narrow: "AD", + abbr: "AD" + }, + { + since: "0000-12-31", + until: -Infinity, + offset: 1, + name: "Before Christ", + narrow: "BC", + abbr: "BC" + } + ], + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal: function(number) { + var b = number % 10, output = toInt(number % 100 / 10) === 1 ? "th" : b === 1 ? "st" : b === 2 ? "nd" : b === 3 ? "rd" : "th"; + return number + output; + } + }); + hooks$1.lang = deprecate( + "moment.lang is deprecated. Use moment.locale instead.", + getSetGlobalLocale + ); + hooks$1.langData = deprecate( + "moment.langData is deprecated. Use moment.localeData instead.", + getLocale + ); + var mathAbs = Math.abs; + function abs() { + var data = this._data; + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + return this; + } + function addSubtract$1(duration, input, value, direction) { + var other = createDuration(input, value); + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + return duration._bubble(); + } + function add$1(input, value) { + return addSubtract$1(this, input, value, 1); + } + function subtract$1(input, value) { + return addSubtract$1(this, input, value, -1); + } + function absCeil(number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + function bubble() { + var milliseconds2 = this._milliseconds, days2 = this._days, months2 = this._months, data = this._data, seconds2, minutes2, hours2, years2, monthsFromDays; + if (!(milliseconds2 >= 0 && days2 >= 0 && months2 >= 0 || milliseconds2 <= 0 && days2 <= 0 && months2 <= 0)) { + milliseconds2 += absCeil(monthsToDays(months2) + days2) * 864e5; + days2 = 0; + months2 = 0; + } + data.milliseconds = milliseconds2 % 1e3; + seconds2 = absFloor(milliseconds2 / 1e3); + data.seconds = seconds2 % 60; + minutes2 = absFloor(seconds2 / 60); + data.minutes = minutes2 % 60; + hours2 = absFloor(minutes2 / 60); + data.hours = hours2 % 24; + days2 += absFloor(hours2 / 24); + monthsFromDays = absFloor(daysToMonths(days2)); + months2 += monthsFromDays; + days2 -= absCeil(monthsToDays(monthsFromDays)); + years2 = absFloor(months2 / 12); + months2 %= 12; + data.days = days2; + data.months = months2; + data.years = years2; + return this; + } + function daysToMonths(days2) { + return days2 * 4800 / 146097; + } + function monthsToDays(months2) { + return months2 * 146097 / 4800; + } + function as(units) { + if (!this.isValid()) { + return NaN; + } + var days2, months2, milliseconds2 = this._milliseconds; + units = normalizeUnits(units); + if (units === "month" || units === "quarter" || units === "year") { + days2 = this._days + milliseconds2 / 864e5; + months2 = this._months + daysToMonths(days2); + switch (units) { + case "month": + return months2; + case "quarter": + return months2 / 3; + case "year": + return months2 / 12; + } + } else { + days2 = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case "week": + return days2 / 7 + milliseconds2 / 6048e5; + case "day": + return days2 + milliseconds2 / 864e5; + case "hour": + return days2 * 24 + milliseconds2 / 36e5; + case "minute": + return days2 * 1440 + milliseconds2 / 6e4; + case "second": + return days2 * 86400 + milliseconds2 / 1e3; + // Math.floor prevents floating point math errors here + case "millisecond": + return Math.floor(days2 * 864e5) + milliseconds2; + default: + throw new Error("Unknown unit " + units); + } + } + } + function makeAs(alias) { + return function() { + return this.as(alias); + }; + } + var asMilliseconds = makeAs("ms"), asSeconds = makeAs("s"), asMinutes = makeAs("m"), asHours = makeAs("h"), asDays = makeAs("d"), asWeeks = makeAs("w"), asMonths = makeAs("M"), asQuarters = makeAs("Q"), asYears = makeAs("y"), valueOf$1 = asMilliseconds; + function clone$1() { + return createDuration(this); + } + function get$2(units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + "s"]() : NaN; + } + function makeGetter(name2) { + return function() { + return this.isValid() ? this._data[name2] : NaN; + }; + } + var milliseconds = makeGetter("milliseconds"), seconds = makeGetter("seconds"), minutes = makeGetter("minutes"), hours = makeGetter("hours"), days = makeGetter("days"), months = makeGetter("months"), years = makeGetter("years"); + function weeks() { + return absFloor(this.days() / 7); + } + var round = Math.round, thresholds = { + ss: 44, + // a few seconds to seconds + s: 45, + // seconds to minute + m: 45, + // minutes to hour + h: 22, + // hours to day + d: 26, + // days to month/week + w: null, + // weeks to month + M: 11 + // months to year + }; + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale2) { + return locale2.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + function relativeTime$1(posNegDuration, withoutSuffix, thresholds2, locale2) { + var duration = createDuration(posNegDuration).abs(), seconds2 = round(duration.as("s")), minutes2 = round(duration.as("m")), hours2 = round(duration.as("h")), days2 = round(duration.as("d")), months2 = round(duration.as("M")), weeks2 = round(duration.as("w")), years2 = round(duration.as("y")), a = seconds2 <= thresholds2.ss && ["s", seconds2] || seconds2 < thresholds2.s && ["ss", seconds2] || minutes2 <= 1 && ["m"] || minutes2 < thresholds2.m && ["mm", minutes2] || hours2 <= 1 && ["h"] || hours2 < thresholds2.h && ["hh", hours2] || days2 <= 1 && ["d"] || days2 < thresholds2.d && ["dd", days2]; + if (thresholds2.w != null) { + a = a || weeks2 <= 1 && ["w"] || weeks2 < thresholds2.w && ["ww", weeks2]; + } + a = a || months2 <= 1 && ["M"] || months2 < thresholds2.M && ["MM", months2] || years2 <= 1 && ["y"] || ["yy", years2]; + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale2; + return substituteTimeAgo.apply(null, a); + } + function getSetRelativeTimeRounding(roundingFunction) { + if (roundingFunction === void 0) { + return round; + } + if (typeof roundingFunction === "function") { + round = roundingFunction; + return true; + } + return false; + } + function getSetRelativeTimeThreshold(threshold, limit) { + if (thresholds[threshold] === void 0) { + return false; + } + if (limit === void 0) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === "s") { + thresholds.ss = limit - 1; + } + return true; + } + function humanize(argWithSuffix, argThresholds) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + var withSuffix = false, th = thresholds, locale2, output; + if (typeof argWithSuffix === "object") { + argThresholds = argWithSuffix; + argWithSuffix = false; + } + if (typeof argWithSuffix === "boolean") { + withSuffix = argWithSuffix; + } + if (typeof argThresholds === "object") { + th = Object.assign({}, thresholds, argThresholds); + if (argThresholds.s != null && argThresholds.ss == null) { + th.ss = argThresholds.s - 1; + } + } + locale2 = this.localeData(); + output = relativeTime$1(this, !withSuffix, th, locale2); + if (withSuffix) { + output = locale2.pastFuture(+this, output); + } + return locale2.postformat(output); + } + var abs$1 = Math.abs; + function sign(x) { + return (x > 0) - (x < 0) || +x; + } + function toISOString$1() { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + var seconds2 = abs$1(this._milliseconds) / 1e3, days2 = abs$1(this._days), months2 = abs$1(this._months), minutes2, hours2, years2, s, total = this.asSeconds(), totalSign, ymSign, daysSign, hmsSign; + if (!total) { + return "P0D"; + } + minutes2 = absFloor(seconds2 / 60); + hours2 = absFloor(minutes2 / 60); + seconds2 %= 60; + minutes2 %= 60; + years2 = absFloor(months2 / 12); + months2 %= 12; + s = seconds2 ? seconds2.toFixed(3).replace(/\.?0+$/, "") : ""; + totalSign = total < 0 ? "-" : ""; + ymSign = sign(this._months) !== sign(total) ? "-" : ""; + daysSign = sign(this._days) !== sign(total) ? "-" : ""; + hmsSign = sign(this._milliseconds) !== sign(total) ? "-" : ""; + return totalSign + "P" + (years2 ? ymSign + years2 + "Y" : "") + (months2 ? ymSign + months2 + "M" : "") + (days2 ? daysSign + days2 + "D" : "") + (hours2 || minutes2 || seconds2 ? "T" : "") + (hours2 ? hmsSign + hours2 + "H" : "") + (minutes2 ? hmsSign + minutes2 + "M" : "") + (seconds2 ? hmsSign + s + "S" : ""); + } + var proto$2 = Duration.prototype; + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asQuarters = asQuarters; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + proto$2.toIsoString = deprecate( + "toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)", + toISOString$1 + ); + proto$2.lang = lang; + addFormatToken("X", 0, 0, "unix"); + addFormatToken("x", 0, 0, "valueOf"); + addRegexToken("x", matchSigned); + addRegexToken("X", matchTimestamp); + addParseToken("X", function(input, array, config) { + config._d = new Date(parseFloat(input) * 1e3); + }); + addParseToken("x", function(input, array, config) { + config._d = new Date(toInt(input)); + }); + //! moment.js + hooks$1.version = "2.30.1"; + setHookCallback(createLocal); + hooks$1.fn = proto; + hooks$1.min = min; + hooks$1.max = max; + hooks$1.now = now; + hooks$1.utc = createUTC; + hooks$1.unix = createUnix; + hooks$1.months = listMonths; + hooks$1.isDate = isDate; + hooks$1.locale = getSetGlobalLocale; + hooks$1.invalid = createInvalid; + hooks$1.duration = createDuration; + hooks$1.isMoment = isMoment; + hooks$1.weekdays = listWeekdays; + hooks$1.parseZone = createInZone; + hooks$1.localeData = getLocale; + hooks$1.isDuration = isDuration; + hooks$1.monthsShort = listMonthsShort; + hooks$1.weekdaysMin = listWeekdaysMin; + hooks$1.defineLocale = defineLocale; + hooks$1.updateLocale = updateLocale; + hooks$1.locales = listLocales; + hooks$1.weekdaysShort = listWeekdaysShort; + hooks$1.normalizeUnits = normalizeUnits; + hooks$1.relativeTimeRounding = getSetRelativeTimeRounding; + hooks$1.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks$1.calendarFormat = getCalendarFormat; + hooks$1.prototype = proto; + hooks$1.HTML5_FMT = { + DATETIME_LOCAL: "YYYY-MM-DDTHH:mm", + // + DATETIME_LOCAL_SECONDS: "YYYY-MM-DDTHH:mm:ss", + // + DATETIME_LOCAL_MS: "YYYY-MM-DDTHH:mm:ss.SSS", + // + DATE: "YYYY-MM-DD", + // + TIME: "HH:mm", + // + TIME_SECONDS: "HH:mm:ss", + // + TIME_MS: "HH:mm:ss.SSS", + // + WEEK: "GGGG-[W]WW", + // + MONTH: "YYYY-MM" + // + }; + async function getCatalogRaw(bookId) { + const response = await apiFetch(`https://fanqienovel.com/api/reader/directory/detail?bookId=${bookId}`); + const j = response.json(); + if (j.code !== 0 || j.data.chapterListWithVolume.length === 0) { + throw new Error("Empty catalog"); + } + return [j.data.chapterListWithVolume, j.data.allItemIds]; + } + async function getCatalog(bookId) { + const r = await getCatalogRaw(bookId); + const catalogRaw = r[0]; + const allItemIds = r[1]; + const vmap = {}; + const chapters = []; + catalogRaw[0].map((item) => { + const chapterItem = { + item_id: item.itemId, + title: item.title, + // YYYY-MM-DD HH:mm:ss + update_time: hooks$1(item.firstPassTime * 1e3).format("YYYY-MM-DD HH:mm:ss"), + char_count: item.charCount || 0, + volume_title: item.volume_name + }; + chapters.push(chapterItem); + if (!vmap[item.volume_name]) { + vmap[item.volume_name] = { + title: item.volume_name, + book_id: bookId, + chapter_list: [] + }; + } + vmap[item.volume_name].chapter_list.push(chapterItem); + }); + return { + book_id: bookId, + volume_list: Object.values(vmap), + chapter_list: chapters, + all_item_ids: allItemIds + }; + } + function mappingCreationStatus(status) { + switch (status) { + case "0": + return "完结"; + case "1": + return "连载"; + case "4": + return "断更"; + default: + return "未知"; + } + } + async function getBookInfoRaw(bookId) { + const response = await apiFetch(`https://api5-sinfonlinec.jxbhmy.com/reading/bookapi/multi-detail/v?book_id=${bookId}&aid=1967`); + const j = response.json(); + console.log("Book Info:", j); + if (typeof j === "object" && j !== null && "data" in j && Array.isArray(j.data) && j.data.length > 0) return j.data[0]; + return null; + } + async function getBookInfo(bookId) { + const bookInfo = await getBookInfoRaw(bookId); + if (!bookInfo) { + throw new Error("Book not found"); + } + return { + book_id: bookInfo.book_id, + title: bookInfo.book_name || bookInfo.original_book_name, + author: bookInfo.author, + cover_url: bookInfo.thumb_url, + summary: bookInfo.abstract, + // volume_list: bookInfo.volume_list, + update_time: hooks$1(bookInfo.last_chapter_first_pass_time * 1e3).format("YYYY-MM-DD HH:mm:ss"), + status: mappingCreationStatus(bookInfo.creation_status) + // chapter_count: bookInfo.chapter_count, + }; + } + async function getBookInfoAndCatalog(book) { + if (typeof book !== "string") { + book = book.book_id; + } + const bookInfo = await getBookInfo(book); + if (!bookInfo) { + throw new Error("Book not found"); + } + const catalog = await getCatalog(bookInfo.book_id); + console.log("Catalog:", catalog); + bookInfo.volume_list = catalog.volume_list; + bookInfo.chapter_list = catalog.chapter_list; + return bookInfo; + } + let currentBook = null; + let latestItemId = null; + async function insertContent() { + var _a, _b, _c, _d, _e, _f, _g, _h; + const itemId = ((_a = window.location.pathname.split("/").pop()) == null ? void 0 : _a.substring(0, 19)) || ""; + if (!itemId) { + console.warn("No item_id found in URL"); + return; + } + latestItemId = itemId; + const chapter = await getChapter(itemId); + if (!chapter) { + console.warn("No chapter found for item_id:", itemId); + return; + } + if (latestItemId !== itemId) { + console.debug("Stale chapter response discarded:", itemId); + return; + } + console.log("Chapter:", chapter); + const pageState = unsafeWindow.__INITIAL_STATE__; + const chapterTitle = ((_b = chapter.novel_data) == null ? void 0 : _b.title) || ((_d = (_c = pageState == null ? void 0 : pageState.reader) == null ? void 0 : _c.chapterData) == null ? void 0 : _d.title); + if (typeof chapter.content === "string") { + const dp = new DOMParser(); + const doc = dp.parseFromString(chapter.content, "text/html"); + const body = doc.body; + const article = body.querySelector("article"); + const toProcess = article || body; + for (let i = 0; i < toProcess.childNodes.length; i++) { + if (i < 1 && ((_e = toProcess.childNodes[i]) == null ? void 0 : _e.innerHTML.includes(chapterTitle))) { + toProcess.removeChild(toProcess.childNodes[i]); + } + } + const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)"); + if (readerContainer) { + let scriptContainer = document.getElementById("fqa-reader-content"); + if (!scriptContainer) { + scriptContainer = cloneElement(readerContainer); + scriptContainer.id = "fqa-reader-content"; + scriptContainer.classList.add("fqa"); + scriptContainer.classList.remove("noselect"); + readerContainer.insertAdjacentElement("beforebegin", scriptContainer); + } + scriptContainer.innerHTML = ""; + readerContainer.classList.add("fqa-hide"); + scriptContainer.appendChild(toProcess); + } + } else if (chapter.content.picInfos) { + if (chapter.content.encrypt) { + const imgs = []; + for (let i = 0; i < chapter.content.picInfos.length; i++) { + const picInfo = chapter.content.picInfos[i]; + const img = document.createElement("img"); + img.className = "fqa-comic-img fqa-comic-encrypted"; + img.alt = `第${i + 1}页`; + img.dataset.encryptedUrl = picInfo.picUrl; + img.dataset.encryptKey = chapter.content.encrypt_key; + img.dataset.pageIndex = i.toString(); + img.style.minHeight = "500px"; + img.style.backgroundColor = "#f0f0f0"; + imgs.push(img); + } + const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)"); + if (readerContainer) { + let scriptContainer = document.getElementById("fqa-comic-content"); + if (!scriptContainer) { + scriptContainer = cloneElement(readerContainer); + scriptContainer.id = "fqa-reader-content"; + scriptContainer.classList.add("fqa"); + scriptContainer.classList.add("fqa-comic-reader"); + scriptContainer.classList.remove("noselect"); + readerContainer.insertAdjacentElement("beforebegin", scriptContainer); + } + scriptContainer.innerHTML = ""; + readerContainer.classList.add("fqa-hide"); + imgs.forEach((img) => scriptContainer.appendChild(img)); + const observer = new IntersectionObserver( + async (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const img = entry.target; + if (img.dataset.encryptedUrl && img.dataset.encryptKey && !img.src) { + observer.unobserve(img); + try { + const response = await fetch(img.dataset.encryptedUrl); + const encryptedBuffer = await response.arrayBuffer(); + const decryptedBuffer = await decryptComicImage( + encryptedBuffer, + img.dataset.encryptKey + ); + const blob = new Blob([decryptedBuffer], { type: "image/jpeg" }); + const blobUrl = URL.createObjectURL(blob); + img.src = blobUrl; + img.style.minHeight = ""; + img.style.backgroundColor = ""; + img.onload = () => { + URL.revokeObjectURL(blobUrl); + }; + } catch (error) { + console.error(`解密图片失败 (页 ${img.dataset.pageIndex}):`, error); + img.alt = `第${Number(img.dataset.pageIndex) + 1}页 - 解密失败`; + img.style.backgroundColor = "#ffebee"; + } + } + } + } + }, + { + rootMargin: "200px" + } + ); + imgs.forEach((img) => observer.observe(img)); + } + } else { + const imgs = []; + for (let i = 0; i < chapter.content.picInfos.length; i++) { + const picInfo = chapter.content.picInfos[i]; + const img = document.createElement("img"); + img.className = "fqa-comic-img"; + img.alt = `第${i + 1}页`; + img.src = picInfo.picUrl; + imgs.push(img); + } + const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)"); + if (readerContainer) { + let scriptContainer = document.getElementById("fqa-comic-content"); + if (!scriptContainer) { + scriptContainer = cloneElement(readerContainer); + scriptContainer.id = "fqa-reader-content"; + scriptContainer.classList.add("fqa"); + scriptContainer.classList.add("fqa-comic-reader"); + scriptContainer.classList.remove("noselect"); + readerContainer.insertAdjacentElement("beforebegin", scriptContainer); + } + scriptContainer.innerHTML = ""; + readerContainer.classList.add("fqa-hide"); + imgs.forEach((img) => scriptContainer.appendChild(img)); + } + } + } + const muyeReaderTitle = document.querySelector("h1.muye-reader-title"); + let muyeReaderSubtitle = document.querySelector("div.muye-reader-subtitle"); + (_f = document.querySelector("#fqa-subtitle")) == null ? void 0 : _f.remove(); + if (muyeReaderSubtitle) { + let _cloned = cloneElement(muyeReaderSubtitle); + muyeReaderSubtitle.classList.add("fqa-hide"); + _cloned.id = "fqa-subtitle"; + muyeReaderSubtitle.insertAdjacentElement("afterend", _cloned); + muyeReaderSubtitle = _cloned; + _cloned.classList.remove("fqa-hide"); + console.log("clone subtitle: ", _cloned); + } + if (muyeReaderTitle) { + muyeReaderTitle.textContent = chapterTitle; + } + console.log("Current book:", currentBook); + if (!currentBook || currentBook == null || currentBook.book_id !== ((_g = chapter.novel_data) == null ? void 0 : _g.book_id)) { + currentBook = await getBookInfoAndCatalog((_h = chapter.novel_data) == null ? void 0 : _h.book_id); + console.log("Current book:", currentBook); + } + if (currentBook && currentBook.chapter_list) { + const currentChapterItem = currentBook.chapter_list.find((c) => c.item_id === itemId); + if (currentChapterItem) { + console.log("Current chapter:", currentChapterItem); + document.title = currentChapterItem.title + " - " + currentBook.title + " - 番茄小说"; + if (document.getElementById("fqa-current-chapter-volume")) { + const c = document.getElementById("fqa-current-chapter-volume"); + if (c) { + c.textContent = currentChapterItem.volume_title; + } + } else { + const volSpan = document.createElement("span"); + volSpan.className = "desc-item"; + volSpan.id = "fqa-current-chapter-volume"; + volSpan.textContent = currentChapterItem.volume_title; + const c = muyeReaderSubtitle == null ? void 0 : muyeReaderSubtitle.firstChild; + if (c) { + c.insertAdjacentElement("beforebegin", volSpan); + } + } + let updateTimeSpans = (muyeReaderSubtitle == null ? void 0 : muyeReaderSubtitle.querySelectorAll("span.desc-item")) || []; + if (updateTimeSpans.length >= 2) { + let updateTimeSpan = updateTimeSpans[updateTimeSpans.length - 1]; + let uttspan = updateTimeSpan.firstChild; + uttspan == null ? void 0 : uttspan.remove(); + updateTimeSpan.innerHTML = "更新时间:" + currentChapterItem.update_time; + } else { + let updateTimeSpan = document.createElement("span"); + updateTimeSpan.className = "desc-item"; + updateTimeSpan.textContent = `更新时间:${currentChapterItem.update_time}`; + } + } + } + } + async function onUrlChange$1(_previous) { + await insertContent(); + } + async function onHashChange$1(_previous) { + } + async function onLoad$1() { + document.querySelector("div.muye-reader-btns"); + await insertContent(); + } + function readerFilter(path, _query, _hash) { + return path.startsWith("/reader") || path.startsWith("reader"); + } + const _exports$2 = [ + { + id: "readerHook_load", + event: "load", + handler: onLoad$1, + filter: readerFilter + }, + { + id: "readerHook_urlChange", + event: "onUrlChange", + handler: onUrlChange$1, + filter: readerFilter + }, + { + id: "readerHook_hashChange", + event: "onHashChange", + handler: onHashChange$1, + filter: readerFilter + } + ]; + const blackList = [ + "mcs.zijieapi.com", + "vcs.zijieapi.com/vc/setting", + "mon.zijieapi.com", + "mssdk.bytedance.com/web/common", + "hm.baidu.com" + ]; + function checkBlack(url) { + return blackList.some((black) => url.includes(black)); + } + const originalFetch = unsafeWindow.fetch.bind(unsafeWindow); + unsafeWindow.fetch = function fetch2(input, init2) { + let url; + if (input instanceof Request) { + url = input.url; + } else if (input instanceof URL) { + url = input.href; + } else { + url = input; + } + if (checkBlack(url)) { + console.log("blocked request: " + url); + return new Promise(() => { + return new Response("{}", { status: 403 }); + }); + } + return originalFetch(input, init2); + }; + const originalXMLHttpRequest = unsafeWindow.XMLHttpRequest; + unsafeWindow.XMLHttpRequest = class XMLHttpRequest extends originalXMLHttpRequest { + open(method, url, async = true, user, password) { + if (checkBlack(url)) { + console.log("blocked request: " + url); + this.abort(); + } else { + super.open(method, url, async, user, password); + } + } + }; + const _exports$1 = []; + let userState = { + isLogin: false, + userInfo: null + }; + if (read("userState")) { + userState = read("userState"); + } + async function getDetailedUserInfo() { + if (!(userState == null ? void 0 : userState.isLogin) || !(userState == null ? void 0 : userState.userInfo)) { + return null; + } + if (userState.userInfo.gender !== void 0 && userState.userInfo.recommend_gender !== void 0 && userState.userInfo.fans_num !== void 0 && userState.userInfo.following_num !== void 0 && userState.userInfo.is_author !== void 0 && userState.userInfo.author_desc !== void 0 && userState.userInfo.read_book_num !== void 0 && userState.userInfo.read_book_time !== void 0) { + return userState.userInfo; + } + const response = await fetch("https://fanqienovel.com/reading/user/basic_info/get/v?aid=1967"); + const j = await response.json(); + if (j == null ? void 0 : j.data) { + const data = j.data; + userState.userInfo.gender = data.profile_gender; + userState.userInfo.recommend_gender = data.gender; + userState.userInfo.fans_num = data.fans_num; + userState.userInfo.following_num = data.follow_user_num; + userState.userInfo.is_author = data.is_author; + userState.userInfo.author_desc = data.author_desc; + userState.userInfo.read_book_num = data.read_book_num; + userState.userInfo.read_book_time = BigInt(data.read_book_time); + return userState.userInfo; + } + return userState.userInfo; + } + async function checkLogin() { + var _a, _b, _c, _d, _e, _f; + const response = await fetch("https://fanqienovel.com/api/user/info/v2"); + const j = await response.json(); + const _userInfo = { + id: (_a = j == null ? void 0 : j.data) == null ? void 0 : _a.id, + username: (_b = j == null ? void 0 : j.data) == null ? void 0 : _b.name, + avatar: (_c = j == null ? void 0 : j.data) == null ? void 0 : _c.avatar, + desc: (_d = j == null ? void 0 : j.data) == null ? void 0 : _d.desc, + age: (_e = j == null ? void 0 : j.data) == null ? void 0 : _e.age + }; + if (((_f = j == null ? void 0 : j.data) == null ? void 0 : _f.id) > 1) { + userState.isLogin = true; + userState.userInfo = _userInfo; + write("userState", userState); + return true; + } else { + del("userState"); + return false; + } + } + async function init() { + var _a; + await checkLogin(); + if (userState.isLogin) { + console.log("Hello, ", (_a = userState == null ? void 0 : userState.userInfo) == null ? void 0 : _a.username); + } + } + function formatReadingTime(readBookTime) { + let minutes2 = readBookTime / 60000n; + const hours2 = minutes2 / 60n; + minutes2 = minutes2 % 60n; + return `${hours2} 时 ${minutes2} 分`; + } + function createMenuItem(text) { + const item = document.createElement("div"); + item.role = "menuitem"; + item.classList.add("fqa-menu-item", "arco-menu-item", "serial-menu-item", "slogin-user-avatar__menu-item-wrapper"); + const inner1 = document.createElement("div"); + inner1.classList.add("slogin-user-avatar__menu-item"); + const inner2 = document.createElement("div"); + inner2.classList.add("slogin-user-avatar__menu-item__content"); + inner2.textContent = text; + inner1.appendChild(inner2); + item.appendChild(inner1); + return item; + } + async function mainHook(_previous) { + const userInfo = await getDetailedUserInfo(); + if (!userInfo) { + return; + } + const injected = /* @__PURE__ */ new WeakSet(); + const inject2 = (menuInner) => { + if (injected.has(menuInner)) { + return; + } + injected.add(menuInner); + console.log("user action dialog created", menuInner); + const firstDiv = createMenuItem(`阅读 ${userInfo.read_book_num} 本书`); + menuInner.insertAdjacentElement("afterbegin", firstDiv); + const secondDiv = createMenuItem(formatReadingTime(userInfo.read_book_time ?? 0n)); + firstDiv.insertAdjacentElement("afterend", secondDiv); + }; + const scan = (root) => { + if (root.classList.contains("arco-menu-inner")) { + inject2(root); + } + root.querySelectorAll(".arco-menu-inner").forEach(inject2); + }; + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement) { + scan(node); + } + }); + } + }); + }); + observer.observe(document.body, { childList: true, subtree: true }); + scan(document.body); + } + function filter(_path, _query, _hash) { + return userState.isLogin; + } + const _exports = [ + { + id: "userHook", + event: "load", + filter, + handler: mainHook + } + ]; + const hooks = [ + ..._exports$2, + ..._exports$1, + ..._exports + ]; + async function onEvent(event, previous) { + console.log(`onEvent, event: ${event}, previous: ${previous}`); + const path = window.location.pathname; + const hash = window.location.hash; + const params = new URLSearchParams(window.location.search); + const tasks = []; + for (const hook of hooks) { + if (hook.event === event && hook.filter(path, params, hash)) { + tasks.push(async () => { + try { + await hook.handler(previous); + } catch (err) { + console.error(`[hook:${hook.id}] handler failed:`, err); + } + }); + } + } + console.log(`tasks: `, tasks); + if (tasks.length > 0) { + await Promise.allSettled(tasks.map((task) => task())); + } + } + async function onUrlChange(previous) { + return await onEvent("onUrlChange", previous); + } + async function onHashChange(previous) { + return await onEvent("onHashChange", previous); + } + async function onLoad() { + return await onEvent("load"); + } + const name = "fanqie-assistant"; + const version = "0.0.2"; + const win = unsafeWindow; + let previousUrl = win.location.href; + let previousHash = win.location.hash; + function installNavigationHooks() { + for (const method of ["pushState", "replaceState"]) { + const original = win.history[method]; + win.history[method] = function(...args) { + const result = original.apply(this, args); + console.debug(`history.${method} called with args:`, args); + void onUrlChange(previousUrl); + previousUrl = win.location.href; + return result; + }; + } + win.addEventListener("popstate", () => { + void onUrlChange(previousUrl); + previousUrl = win.location.href; + }); + win.addEventListener("hashchange", () => { + void onHashChange(previousHash); + previousHash = win.location.hash; + }); + } + async function mainInit() { + console.log(`================================================`); + console.log(`== ${name} - ${version} ==`); + console.log(`================================================`); + installNavigationHooks(); + initFontDecrypt(); + await inject(); + await init(); + void onLoad(); + } + mainInit(); + } + }); + require_main_001(); + +})(); \ No newline at end of file diff --git a/package.json b/package.json index 49bdf6f..42cbabf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fanqie-assistant", - "version": "0.0.1", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/api/book.ts b/src/api/book.ts index 9a6ddd7..8bea939 100644 --- a/src/api/book.ts +++ b/src/api/book.ts @@ -17,8 +17,9 @@ export function mappingCreationStatus(status: string): string { } export async function getBookInfoRaw(bookId: string): Promise { - const response = await apiFetch(`https://api5-sinfonlinec.jxbhmy.com/reading/bookapi/multi-detail/v?book_id=${bookId}&aid=13`) - const j: any = await response.json() + const response = await apiFetch(`https://api5-sinfonlinec.jxbhmy.com/reading/bookapi/multi-detail/v?book_id=${bookId}&aid=1967`) + const j: any = response.json() + console.log('Book Info:', j) if ( typeof j === 'object' && j !== null && @@ -41,7 +42,7 @@ export async function getBookInfo(bookId: string): Promise { cover_url: bookInfo.thumb_url, summary: bookInfo.abstract, // volume_list: bookInfo.volume_list, - update_time: moment(bookInfo.last_chapter_first_pass_time).format('YYYY-MM-DD HH:mm:ss'), + update_time: moment(bookInfo.last_chapter_first_pass_time * 1000).format('YYYY-MM-DD HH:mm:ss'), status: mappingCreationStatus(bookInfo.creation_status), // chapter_count: bookInfo.chapter_count, } as Book diff --git a/src/api/catalog.ts b/src/api/catalog.ts index f946ee4..8d12e24 100644 --- a/src/api/catalog.ts +++ b/src/api/catalog.ts @@ -24,8 +24,8 @@ export async function getCatalog(bookId: string): Promise { item_id: item.itemId, title: item.title, // YYYY-MM-DD HH:mm:ss - update_time: moment(item.updateTime).format('YYYY-MM-DD HH:mm:ss') as string, - char_count: item.charCount, + update_time: moment(item.firstPassTime * 1000).format('YYYY-MM-DD HH:mm:ss') as string, + char_count: item.charCount || 0, volume_title: item.volume_name, } as ChapterItem chapters.push(chapterItem) diff --git a/src/api/content.ts b/src/api/content.ts index d587ffa..059cf73 100644 --- a/src/api/content.ts +++ b/src/api/content.ts @@ -79,4 +79,11 @@ export async function getChapter(itemId: string, _retry?: number): Promise } j.content = await decryptChapter(j?.content, config.currentConfig) return j +} + +export async function getChapterInfo(itemId: string): Promise { + // use page fetch (includes bytedance security sdk. we reuse it) + // same origin + const res = await fetch('https://fanqienovel.com/api/reader/full?itemId=' + itemId) + return await res.json() } \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts index 7c00118..797c09c 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,6 +2,14 @@ import apiFetch from '../utils/request' import { getCookie } from '../localStorage' import config from '../config' +/* + 关于 baseUrl: + - baseUrl 是番茄小说旗下红烛小说的接口,与番茄数据基本通用。 + - 但与番茄小说相比,红烛小说的 API 风控更低,不要签名。 + - 该为第一方 API 而非第三方,所以请注意这一点。 + - 之后如果遇到一些功能需要把 Cookie 传到这里的,请不要来造谣“这个脚本把你的凭据发给了第三方”之类的。 + - 本项目重视您的账号数据安全,不会泄露您的账号数据,接受大家审查。 +*/ export const baseUrl = 'https://api5-sinfonlinec.jxbhmy.com/redcandle/search'; export const baseQuery = { aid: '1967', diff --git a/src/api/user.ts b/src/api/user.ts index d5573be..60a2a66 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,4 +1,10 @@ import { fetch } from '../config' +import { del, read, write } from '../localStorage' + +export interface UserTitle { + title: string + desc: string +} export interface UserInfo { id: string @@ -6,35 +12,109 @@ export interface UserInfo { avatar: string desc: string age: number + // detailed + gender?: number // 0: 女 | 1: 男 | 2: 未知/没设置 + recommend_gender?: number // 0: 女频 | 1: 男频 | 2: 我都爱看 + fans_num?: number + following_num?: number + is_author?: boolean + author_desc?: string + read_book_num?: number + read_book_time?: bigint // ms } -export let userInfo: UserInfo | null = null +export interface UserState { + isLogin: boolean + userInfo: UserInfo | null +} -export async function getUserInfo() { - if (userInfo) return userInfo +export let userState: UserState = { + isLogin: false, + userInfo: null, +} +if (read('userState')) { + userState = read('userState') +} + +export async function getDetailedUserInfo(): Promise { + if (!userState?.isLogin || !userState?.userInfo) { + return null + } + if (userState.userInfo.gender !== undefined && + userState.userInfo.recommend_gender !== undefined && + userState.userInfo.fans_num !== undefined && + userState.userInfo.following_num !== undefined && + userState.userInfo.is_author !== undefined && + userState.userInfo.author_desc !== undefined && + userState.userInfo.read_book_num !== undefined && + userState.userInfo.read_book_time !== undefined + ) { + return userState.userInfo + } + const response = await fetch('https://fanqienovel.com/reading/user/basic_info/get/v?aid=1967') + const j = await response.json() + if (j?.data) { + const data = j.data + userState.userInfo.gender = data.profile_gender + userState.userInfo.recommend_gender = data.gender + userState.userInfo.fans_num = data.fans_num + userState.userInfo.following_num = data.follow_user_num + userState.userInfo.is_author = data.is_author + userState.userInfo.author_desc = data.author_desc + userState.userInfo.read_book_num = data.read_book_num + userState.userInfo.read_book_time = BigInt(data.read_book_time) + // userState.userInfo.titles = data.titles + return userState.userInfo + } + return userState.userInfo +} + +export async function getUserInfo(): Promise { + if (userState?.isLogin && userState.userInfo) return userState.userInfo const response = await fetch('https://fanqienovel.com/api/user/info/v2') const j = await response.json() - userInfo = j?.data - return userInfo + if (j?.data?.id > 1) { + userState = { + isLogin: true, + userInfo: { + id: j?.data?.id, + username: j?.data?.name, + avatar: j?.data?.avatar, + desc: j?.data?.desc, + age: j?.data?.age, + }, + } + return userState.userInfo + } else { + return null + } } export async function checkLogin() { // sessionid is HttpOnly. So we use fetch directly to access user api. const response = await fetch('https://fanqienovel.com/api/user/info/v2') const j = await response.json() - userInfo = { + const _userInfo = { id: j?.data?.id, username: j?.data?.name, avatar: j?.data?.avatar, desc: j?.data?.desc, age: j?.data?.age, - } as UserInfo - return j?.data?.id > 1 + } + if (j?.data?.id > 1) { + userState.isLogin = true + userState.userInfo = _userInfo + write('userState', userState) + return true + } else { + del('userState') + return false + } } export default async function init(): Promise { await checkLogin() - if (userInfo) { - console.log('Hello, ', userInfo.username) + if (userState.isLogin) { + console.log('Hello, ', userState?.userInfo?.username) } } \ No newline at end of file diff --git a/src/assets/script.css b/src/assets/script.css index 0ffefda..84d123c 100644 --- a/src/assets/script.css +++ b/src/assets/script.css @@ -16,10 +16,30 @@ display: none!important; } -/* 移除默认阅读器div,让脚本接管。.muye-reader-content且不带fqa标签的div */ -.muye-reader-content:not(.fqa) { +.fqa-hide { display: none!important; } +/* 404 */ .no-content { display: none!important; +} + +.fqa-comic-img { + width: 100%!important; + height: 100%!important; + max-width: 100%!important; + max-height: 100%!important; + padding-top: 0!important; + padding-bottom: 0!important; + margin-top: 0!important; + margin-bottom: 0!important; +} + +.fqa-comic-reader { + line-height: 0!important; +} + +.fqa-menu-item, +.arco-menu-item { + width: 100%!important; } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index ffa0689..c02c4fc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -29,7 +29,7 @@ export interface EncryptionContext { encrypted?: ArrayBuffer; plaintext?: ArrayBuffer; } - */ +*/ // b64decode("rCXGfd2POMGzeiNIgo4iLg==") = 172, 37, 198, 125, 221, 143, 56, 193, 179, 122, 35, 72, 130, 142, 34, 46 export const shared_key: ArrayBuffer = new Uint8Array([172, 37, 198, 125, 221, 143, 56, 193, 179, 122, 35, 72, 130, 142, 34, 46]).buffer; diff --git a/src/hooks/fetchHook.ts b/src/hooks/fetchHook.ts new file mode 100644 index 0000000..1159def --- /dev/null +++ b/src/hooks/fetchHook.ts @@ -0,0 +1,55 @@ +import type { HookConfig } from "../config" + +// disable report +const blackList = [ + 'mcs.zijieapi.com', + 'vcs.zijieapi.com/vc/setting', + 'mon.zijieapi.com', + 'mssdk.bytedance.com/web/common', + 'hm.baidu.com' +] + +function checkBlack(url: string): boolean { + return blackList.some(black => url.includes(black)) +} + +const originalFetch = unsafeWindow.fetch.bind(unsafeWindow) + +unsafeWindow.fetch = function fetch(input: RequestInfo | URL, init?: RequestInit): Promise { + let url: string + + if (input instanceof Request) { + url = input.url + } else if (input instanceof URL) { + url = input.href + } else { + url = input + } + + if (checkBlack(url)) { + console.log('blocked request: ' + url) + return new Promise(() => { + return new Response('{}', { status: 403 }) + }) + } + + return originalFetch(input, init) +} + +const originalXMLHttpRequest = unsafeWindow.XMLHttpRequest + +unsafeWindow.XMLHttpRequest = class XMLHttpRequest extends originalXMLHttpRequest { + open(method: string, url: string, async: boolean = true, user?: string | null, password?: string | null): void { + if (checkBlack(url)) { + console.log('blocked request: ' + url) + this.abort() + } else { + super.open(method, url, async, user, password) + } + } +} + +// 执行期hook,所以不用显式声明事件类hook +const _exports: HookConfig[] = [] + +export default _exports diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 52e5db4..9eaddd6 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,31 +1,46 @@ import type { HookConfig, HookEvent } from "../config"; import readerHook from "./readerHook"; +import fetchHook from "./fetchHook"; +import userHook from "./userHook"; -const hooks = [ +const hooks: HookConfig[] = [ ...readerHook, -] as HookConfig[]; + ...fetchHook, + ...userHook, +]; -async function eventListener(event: HookEvent, previous?: string) { - console.log(`eventListener, event: ${event}, previous: ${previous}`); +async function onEvent(event: HookEvent, previous?: string) { + console.log(`onEvent, event: ${event}, previous: ${previous}`); const path = window.location.pathname; const hash = window.location.hash; const params = new URLSearchParams(window.location.search); + const tasks = [] for (const hook of hooks) { if (hook.event === event && hook.filter(path, params, hash)) { - await hook.handler(); + tasks.push(async () => { + try { + await hook.handler(previous); + } catch (err) { + console.error(`[hook:${hook.id}] handler failed:`, err); + } + }) } } - return Promise.resolve(); + console.log(`tasks: `, tasks); + if (tasks.length > 0) { + // task must be Promise + await Promise.allSettled(tasks.map(task => task())); + } } export async function onUrlChange(previous: string) { - return eventListener('onUrlChange', previous); + return await onEvent('onUrlChange', previous); } export async function onHashChange(previous: string) { - return eventListener('onHashChange', previous); + return await onEvent('onHashChange', previous); } export async function onLoad() { - return eventListener('load'); + return await onEvent('load'); } \ No newline at end of file diff --git a/src/hooks/readerHook.ts b/src/hooks/readerHook.ts index cbe2190..7bdc8a8 100644 --- a/src/hooks/readerHook.ts +++ b/src/hooks/readerHook.ts @@ -1,23 +1,34 @@ import { type HookConfig } from '../config' import { getChapter } from '../api/content' import { getBookInfoAndCatalog } from '../api/book' +import { decryptComicImage } from '../utils/crypto' +import { fetch } from '../config' // import defaultcss from '../assets/default.css?raw'; import { cloneElement } from '../utils'; import { type Book } from '../types' -import moment from 'moment' +// import moment from 'moment' let currentBook: Book | null = null + +let latestItemId: string | null = null + async function insertContent() { const itemId = window.location.pathname.split('/').pop()?.substring(0, 19) || '' if (!itemId) { console.warn('No item_id found in URL') return } + latestItemId = itemId const chapter = await getChapter(itemId) if (!chapter) { console.warn('No chapter found for item_id:', itemId) return } + + if (latestItemId !== itemId) { + console.debug('Stale chapter response discarded:', itemId) + return + } console.log('Chapter:', chapter) const pageState: any = (unsafeWindow as typeof unsafeWindow & { @@ -38,20 +49,136 @@ async function insertContent() { toProcess.removeChild(toProcess.childNodes[i] as HTMLElement) } } - const readerContainer = document.querySelector("div.muye-reader-content") - let scriptContainer: HTMLDivElement | null = null + + const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)") if (readerContainer) { - scriptContainer = cloneElement(readerContainer) as HTMLDivElement - scriptContainer.classList.add('fqa') - scriptContainer.classList.remove('noselect') // allow text selection - // readerContainer应该已经被注入的内容CSS隐藏掉了 + // 防止插入多次 + let scriptContainer = document.getElementById('fqa-reader-content') as HTMLDivElement | null + if (!scriptContainer) { + scriptContainer = cloneElement(readerContainer) as HTMLDivElement + scriptContainer.id = 'fqa-reader-content' + scriptContainer.classList.add('fqa') + scriptContainer.classList.remove('noselect') // allow text selection + readerContainer.insertAdjacentElement('beforebegin', scriptContainer) + } scriptContainer.innerHTML = '' + // 隐藏原来的内容div + readerContainer.classList.add('fqa-hide') scriptContainer.appendChild(toProcess) - readerContainer.insertAdjacentElement('beforebegin', scriptContainer) + } + } else if (chapter.content.picInfos) { // comic + if (chapter.content.encrypt) { + // 加密漫画图片:懒加载解密 + const imgs: HTMLImageElement[] = [] + for (let i = 0; i < chapter.content.picInfos.length; i++) { + const picInfo = chapter.content.picInfos[i] + const img = document.createElement('img') + img.className = 'fqa-comic-img fqa-comic-encrypted' + img.alt = `第${i + 1}页` + img.dataset.encryptedUrl = picInfo.picUrl + img.dataset.encryptKey = chapter.content.encrypt_key + img.dataset.pageIndex = i.toString() + // 占位符:加载中 + img.style.minHeight = '500px' + img.style.backgroundColor = '#f0f0f0' + imgs.push(img) + } + + const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)") + if (readerContainer) { + let scriptContainer = document.getElementById('fqa-comic-content') as HTMLDivElement | null + if (!scriptContainer) { + scriptContainer = cloneElement(readerContainer) as HTMLDivElement + scriptContainer.id = 'fqa-reader-content' + scriptContainer.classList.add('fqa') + scriptContainer.classList.add('fqa-comic-reader') + scriptContainer.classList.remove('noselect') + readerContainer.insertAdjacentElement('beforebegin', scriptContainer) + } + scriptContainer.innerHTML = '' + readerContainer.classList.add('fqa-hide') + imgs.forEach(img => scriptContainer.appendChild(img)) + + const observer = new IntersectionObserver( + async (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const img = entry.target as HTMLImageElement + if (img.dataset.encryptedUrl && img.dataset.encryptKey && !img.src) { + observer.unobserve(img) // 只解密一次 + try { + const response = await fetch(img.dataset.encryptedUrl) + const encryptedBuffer = await response.arrayBuffer() + const decryptedBuffer = await decryptComicImage( + encryptedBuffer, + img.dataset.encryptKey + ) + const blob = new Blob([decryptedBuffer], { type: 'image/jpeg' }) + const blobUrl = URL.createObjectURL(blob) + img.src = blobUrl + img.style.minHeight = '' + img.style.backgroundColor = '' + img.onload = () => { + URL.revokeObjectURL(blobUrl) + } + } catch (error) { + console.error(`解密图片失败 (页 ${img.dataset.pageIndex}):`, error) + img.alt = `第${Number(img.dataset.pageIndex) + 1}页 - 解密失败` + img.style.backgroundColor = '#ffebee' + } + } + } + } + }, + { + rootMargin: '200px' + } + ) + + imgs.forEach(img => observer.observe(img)) + } + } else { + const imgs: HTMLImageElement[] = [] + for (let i = 0; i < chapter.content.picInfos.length; i++) { + const picInfo = chapter.content.picInfos[i] + const img = document.createElement('img') + img.className = 'fqa-comic-img' + img.alt = `第${i + 1}页` + img.src = picInfo.picUrl + imgs.push(img) + } + const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)") + if (readerContainer) { + // 防止插入多次 + let scriptContainer = document.getElementById('fqa-comic-content') as HTMLDivElement | null + if (!scriptContainer) { + scriptContainer = cloneElement(readerContainer) as HTMLDivElement + scriptContainer.id = 'fqa-reader-content' + scriptContainer.classList.add('fqa') + scriptContainer.classList.add('fqa-comic-reader') + scriptContainer.classList.remove('noselect') + readerContainer.insertAdjacentElement('beforebegin', scriptContainer) + } + scriptContainer.innerHTML = '' + // 隐藏原来的内容div + readerContainer.classList.add('fqa-hide') + imgs.forEach(img => scriptContainer.appendChild(img)) + } } } const muyeReaderTitle = document.querySelector("h1.muye-reader-title") - const muyeReaderSubtitle = document.querySelector("div.muye-reader-subtitle") + let muyeReaderSubtitle = document.querySelector("div.muye-reader-subtitle") + document.querySelector('#fqa-subtitle')?.remove() // prevent duplicate subtitle + if (muyeReaderSubtitle) { + // 脱离原页面 Vue tree ,防止被二次修改 + let _cloned = cloneElement(muyeReaderSubtitle) + muyeReaderSubtitle.classList.add('fqa-hide') + _cloned.id = 'fqa-subtitle' + muyeReaderSubtitle.insertAdjacentElement('afterend', _cloned) + muyeReaderSubtitle = _cloned + _cloned.classList.remove('fqa-hide') + console.log('clone subtitle: ', _cloned) + } if (muyeReaderTitle) { muyeReaderTitle.textContent = chapterTitle } @@ -79,7 +206,8 @@ async function insertContent() { } */ - if (muyeReaderSubtitle && pageState?.reader?.chapterData?.itemId === itemId) { + /* + if (muyeReaderSubtitle) { // 写入更精确的更新时间 let updateTimeSpan = muyeReaderSubtitle.querySelectorAll('span.desc-item')[1] if (updateTimeSpan) { @@ -90,6 +218,7 @@ async function insertContent() { moment(pageState?.reader?.chapterData?.firstPassTime * 1000).format('YYYY-MM-DD HH:mm:ss') } } + */ /* for (let i = 0; i < 10; i++) { if (readerContainer) { @@ -110,6 +239,9 @@ async function insertContent() { const currentChapterItem = currentBook.chapter_list.find(c => c.item_id === itemId) if (currentChapterItem) { console.log('Current chapter:', currentChapterItem) + document.title = currentChapterItem.title + ' - ' + currentBook.title + + ' - 番茄小说' + // 防止插入多次 if (document.getElementById('fqa-current-chapter-volume')) { const c = document.getElementById('fqa-current-chapter-volume') if (c) { @@ -126,6 +258,25 @@ async function insertContent() { c.insertAdjacentElement('beforebegin', volSpan) } } + // 写入更精确的更新时间 + let updateTimeSpans = muyeReaderSubtitle?.querySelectorAll('span.desc-item') || [] + if (updateTimeSpans.length >= 2 /* 应该是始终为3 */) { + /* 最后一个一般是更新时间 */ + let updateTimeSpan = updateTimeSpans[updateTimeSpans.length - 1] as HTMLSpanElement + let uttspan = updateTimeSpan.firstChild as HTMLSpanElement + // console.log(uttspan) + uttspan?.remove() + // seconds + updateTimeSpan.innerHTML = + '更新时间:' + + currentChapterItem.update_time + } else { + // 网页端屏蔽章节。只有一个本章字数 + 前面插入的卷名 + let updateTimeSpan = document.createElement('span') + // let updateTimeSubSpan = document.createElement('span') + updateTimeSpan.className = 'desc-item' + updateTimeSpan.textContent = `更新时间:${currentChapterItem.update_time}` + } } } } @@ -170,23 +321,25 @@ function readerFilter(path: string, _query: URLSearchParams, _hash: string) { return path.startsWith('/reader') || path.startsWith('reader') } -export default [ +const _exports: HookConfig[] = [ { id: 'readerHook_load', event: 'load', handler: onLoad, filter: readerFilter - } as HookConfig, + }, { id: 'readerHook_urlChange', event: 'onUrlChange', handler: onUrlChange, filter: readerFilter - } as HookConfig, + }, { id: 'readerHook_hashChange', event: 'onHashChange', handler: onHashChange, filter: readerFilter - } as HookConfig -] as HookConfig[] \ No newline at end of file + } +] + +export default _exports \ No newline at end of file diff --git a/src/hooks/userHook.ts b/src/hooks/userHook.ts new file mode 100644 index 0000000..6514e88 --- /dev/null +++ b/src/hooks/userHook.ts @@ -0,0 +1,91 @@ +import { getDetailedUserInfo, userState } from '../api/user' +import type { HookConfig } from '../config' + +function formatReadingTime(readBookTime: bigint): string { + let minutes = readBookTime / 60_000n + const hours = minutes / 60n + minutes = minutes % 60n + return `${hours} 时 ${minutes} 分` +} + +function createMenuItem(text: string): HTMLDivElement { + // + const item = document.createElement('div') + item.role = 'menuitem' + item.classList.add('fqa-menu-item', 'arco-menu-item', 'serial-menu-item', 'slogin-user-avatar__menu-item-wrapper') + const inner1 = document.createElement('div') + inner1.classList.add('slogin-user-avatar__menu-item') + const inner2 = document.createElement('div') + inner2.classList.add('slogin-user-avatar__menu-item__content') + inner2.textContent = text + inner1.appendChild(inner2) + item.appendChild(inner1) + return item +} + +async function mainHook(_previous?: string): Promise { + const userInfo = await getDetailedUserInfo() + if (!userInfo) { + return + } + + const injected = new WeakSet() + + const inject = (menuInner: HTMLElement) => { + if (injected.has(menuInner)) { + return + } + injected.add(menuInner) + console.log('user action dialog created', menuInner) + const firstDiv = createMenuItem(`阅读 ${userInfo.read_book_num} 本书`) + menuInner.insertAdjacentElement('afterbegin', firstDiv) + const secondDiv = createMenuItem(formatReadingTime(userInfo.read_book_time ?? 0n)) + firstDiv.insertAdjacentElement('afterend', secondDiv) + } + + const scan = (root: HTMLElement) => { + if (root.classList.contains('arco-menu-inner')) { + inject(root) + } + root.querySelectorAll('.arco-menu-inner').forEach(inject) + } + + // watch create of div.arco-menu-inner + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement) { + scan(node) + } + }) + } + }) + }) + observer.observe(document.body, { childList: true, subtree: true }) + + // 请求 userInfo 期间菜单可能已经创建 + scan(document.body) +} + +function filter(_path: string, _query: URLSearchParams, _hash: string): boolean { + return userState.isLogin +} + +const _exports: HookConfig[] = [ + { + id: 'userHook', + event: 'load', + filter, + handler: mainHook + } +] + +export default _exports \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 1984ef0..b520bb0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,43 +1,55 @@ +import _config from './config' import injectCSS from './cssInject' import initFontDecrypt from './fontDecrypt' import { onLoad, onUrlChange, onHashChange } from './hooks' import { version, name } from '../package.json' import initUser from './api/user' -let previousUrl = window.location.href -let previousHash = window.location.hash +const win = unsafeWindow -async function mainInit() { - console.log(`================================================`) - console.log(`== ${name} - ${version} ==`) - console.log(`================================================`) - initFontDecrypt() - await injectCSS() +let previousUrl = win.location.href +let previousHash = win.location.hash + +function installNavigationHooks() { for (const method of ['pushState', 'replaceState'] as const) { - const original = history[method]; - history[method] = function ( + const original = win.history[method] + win.history[method] = function ( this: History, ...args: Parameters ) { - const result = original.apply(this, args); - console.debug(`history.${method} called with args:`, args); - onUrlChange(previousUrl); - previousUrl = window.location.href; - return result; - }; + const result = original.apply(this, args) + console.debug(`history.${method} called with args:`, args) + void onUrlChange(previousUrl) + previousUrl = win.location.href + return result + } } - window.addEventListener('load', async () => { - await onLoad() + win.addEventListener('popstate', () => { + void onUrlChange(previousUrl) + previousUrl = win.location.href }) - window.addEventListener('hashchange', async () => { - await onHashChange(previousHash) - previousHash = window.location.hash + win.addEventListener('hashchange', () => { + void onHashChange(previousHash) + previousHash = win.location.hash }) - window.addEventListener('popstate', async () => { - await onUrlChange(previousUrl) - previousUrl = window.location.href - }) - await initUser() } -mainInit() \ No newline at end of file +async function mainInit() { + // config 必须最先初始化以确保一些全局函数没有被劫持 + if (_config.currentConfig) {} // force load + console.log(`================================================`) + console.log(`== ${name} - ${version} ==`) + console.log(`================================================`) + + installNavigationHooks() + + initFontDecrypt() + + await injectCSS() + + await initUser() + + void onLoad() +} + +mainInit() diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index a5759b0..14b549b 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -170,7 +170,7 @@ export async function decryptKeyinfoResponse(encrypted: string): Promise { +export async function decryptComicImage(image: ArrayBuffer, key: string /* hex */): Promise { const subtle = getSubtle(); // AESGCM const cryptoKey = await subtle.importKey(