├── .gitattributes ├── .gitignore ├── HowToWork.md ├── LICENSE ├── README.md ├── config.js ├── core ├── api │ ├── app.js │ └── module │ │ ├── follow │ │ ├── README.md │ │ ├── follow.js │ │ ├── index.js │ │ └── update-push.js │ │ ├── illust │ │ ├── README.md │ │ ├── index.js │ │ ├── pid.js │ │ └── recommend.js │ │ ├── index.js │ │ ├── manga │ │ ├── README.md │ │ ├── index.js │ │ ├── manga-pid.js │ │ └── manga-series-pid.js │ │ ├── novel │ │ ├── README.md │ │ ├── index.js │ │ ├── pid.js │ │ └── series-pid.js │ │ ├── search │ │ ├── README.md │ │ ├── index.js │ │ ├── no-premium.js │ │ └── search.js │ │ └── user │ │ └── pid.js └── pixiv-fetch │ ├── agent.js │ ├── base-option.js │ ├── default-option.js │ ├── fetch.js │ ├── index.js │ └── replace-url.js ├── index.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | 129 | .idea/ 130 | cache/ 131 | -------------------------------------------------------------------------------- /HowToWork.md: -------------------------------------------------------------------------------- 1 | # 工作方式 2 | 3 | ## 绕过防火墙 4 | 5 | 防火墙对Pixiv的封锁使用两种技术: 6 | 7 | 1. **DNS欺骗** 审查DNS查询目标域名, 返回错误的数据, 使应用无法获得到正确的目标主机IP地址 8 | 2. **SNI阻断** 审查`Client Hello`报文中的SNI信息, 对双方发送大量RST报文, 进行**TCP重置攻击**阻断连接 9 | 10 | #### 解决方案: 11 | 12 | 1. **DNS欺骗** 使用查询到的CDN静态IP直接访问, 不进行DNS查询 13 | 2. **SNI阻断** 擦去SNI信息中的`Server Name`数据 (域前置) 绕过审查, 使CDN返回默认证书`*.pixiv.net`继续连接 14 | 15 | ## 免会员搜索 16 | 17 | Pixiv为热门Tag的作品自动添加`xxusers入り`标签, 可利用此标签实现免会员搜索 18 | 19 | 标签必须严格匹配, 如 `东方project1000users入り` 与 `東方Project1000users入り` 是完全不同的, 后者为Pixiv自动添加 20 | 21 | #### 有效标签 22 | 23 | - `100users入り` 24 | - `250users入り` 25 | - `300users入り` 26 | - `500users入り` 27 | - `1000users入り` 28 | - `3000users入り` 29 | - `5000users入り` 30 | - `10000users入り` 31 | - `20000users入り` 32 | - `30000users入り` 33 | 34 | #### 工作流程 35 | 36 | 1. 将传入的收藏过滤器参数向上取最接近的值; 如 `blt=7200` 取 `10000users入り` 37 | 2. 进行**或搜索**, 向后取值; 如 `20000users入り` 处理后关键词为 `(30000users入り OR 20000users入り)` 38 | 39 | #### 注意 40 | 41 | 实验性质, 适用于单个热门标签, 多关键词搜索结果可能不尽人意 42 | 43 | 更改配置文件 `config.js` 中 `pixiv.premium = false` 启用此功能 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dituon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pixiv-api-http 2 | 3 | > [!IMPORTANT] 4 | > 请使用 `Node LTS` 版本 (例如 `18.20.0`), 经测试 `Node 20+` 存在LTS代理问题 5 | 6 | ## Intro 7 | 8 | 此API旨在为 Pixiv 开发者提供终极解决方案 9 | 10 | 可直接访问 Pixiv, 绕过防火墙封锁 (如 天朝 伊朗 等) 11 | 12 | **[工作原理](HowToWork.md)** 13 | 14 | 支持 `GET` `POST` `WebSocket` 协议 15 | 16 | 可选: 17 | 18 | - 绕过政府防火墙封锁 19 | - 使用图床原始IP地址 20 | - 非会员搜索优化 21 | - 本地图片反向代理 22 | 23 | ## API 24 | 25 | - **[Illust](./core/api/module/illust/README.md)** 26 | - **[Manga](core/api/module/manga/README.md)** 27 | - **[Novel](./core/api/module/novel/README.md)** 28 | - **[Search](./core/api/module/search/README.md)** 29 | - **[Follow](core/api/module/follow/README.md)** 30 | - **Proxy** 31 | 32 | *(可混用 `POST` `GET` 请求)* 33 | 34 | ## Config 35 | 36 | 配置文件 `config.js` 37 | 38 | ### `proxy` 39 | 40 | 代理设置 41 | 42 | **应用服务器 (`www.pixiv.net` `*.pixiv.net`)** 43 | 44 | - `bypassSNI` (boolean): 绕过防火墙的SNI封锁 (如天朝, 韩国, 伊朗等) 45 | - `serverHost` (IP[]): 服务器原始IP, 用于绕过DNS欺骗 46 | 47 | **图片服务器 (`i.pximg.net`)** 48 | 49 | - `useOriginIP` (boolean): 替换图片链接为图床原始IP, 可在墙内通过原始IP获取图片, 使用http连接加快访问速度 50 | - `useLocalProxy` (boolean): 启动代理服务器, 替换图片链接为本机代理, 免去Referer设置 51 | - `imageHost` (IP[]): 图片服务器原始IP, 用于绕过DNS欺骗 52 | 53 | | **`useOriginIP`** | **`useLocalProxy`** | **行为** | 54 | |-------------------|---------------------|-----------------------------------------| 55 | | `false` | `false` | 返回原始链接 | 56 | | `true` | `false` | 替换图片链接为原始IP | 57 | | `false` | `true` | 替换图片链接为本机代理, 启动代理服务器, 代理使用`HTTPS`访问图床域名 | 58 | | `true` | `true` | 替换图片链接为本机代理, 启动代理服务器, 代理使用`HTTP`访问图床IP | 59 | 60 | ### `pixiv` 61 | 62 | Pixiv设置 63 | 64 | - `lang` (Lang): 语言设置, 用于小说搜索, API报错等 65 | 66 | | `lang` | 语言 | 67 | |---------|---------| 68 | | `ja` | 日本語 | 69 | | `en` | English | 70 | | `zh-cn` | 简体中文 | 71 | | `zh-tw` | 繁體中文 | 72 | 73 | - `cookie` (string): 你的 Cookie (参考 [Tips](#Tips)) 74 | 75 | - `premium` (boolean): 拥有pixiv会员, 填入`false`启用非会员搜索优化, 参考 [工作方式](./HowToWork.md) 76 | 77 | ### `httpServer` 78 | 79 | HTTP API 服务器设置 80 | 81 | - `port` (number): 端口 82 | - `host` (string): IP / 域名 83 | 84 | ### `websocketServer` 85 | 86 | WebSocket API 服务器设置 87 | 88 | - `port` (number): 端口 89 | - `host` (string): IP / 域名 90 | 91 | ## Deploy 92 | 93 | 0. install `node.js` 94 | 1. `git clone` `npm i` 95 | 2. 浏览器登录 `pixiv.net`, 获取 `cookie` (参考 [Tips](#Tips)) 96 | 3. 编辑 `config.js` 97 | 4. `npm start` 98 | 99 | ## Tips 100 | 101 | - 部分功能设置`cookie`后可用 102 | - 获取`cookie`: 详见 **[#2](https://github.com/Dituon/pixiv-api-http/issues/2#issuecomment-2282201060)** 103 | - 部分功能需要Pixiv会员账户, 非会员搜索优化请参考 [工作方式](./HowToWork.md) 104 | - 设置 `Headers: Referer` 为 `https://www.pixiv.net/` 即可直接访问图片 105 | 106 | ## TODO 107 | 108 | - manga-series 109 | - gif 110 | - user 111 | 112 | ## About 113 | 114 | 交流群: `961494251` 115 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | // Proxy setting 3 | // 代理设置 4 | proxy: { 5 | 6 | // bypass government SNI blocking (for China, Korea, Iran etc.) 7 | // 绕过防火墙的SNI封锁 (如天朝, 韩国, 伊朗等) 8 | bypassSNI: true, 9 | // query dns records, by www.nslookup.io 10 | serverHost: [ // *.pixiv.net 11 | '210.140.92.183', 12 | '210.140.92.187', 13 | '210.140.92.193', 14 | '210.140.131.226', 15 | '210.140.131.223', 16 | '210.140.131.218', 17 | '210.140.131.199', 18 | '210.140.131.201' 19 | ], 20 | 21 | // replace img url to original ip, improve access speed 22 | // 替换图片链接为原始IP, 可在墙内通过原始IP获取图片, 使用http连接加快访问速度 23 | useOriginIP: true, 24 | // replace img url to local proxy 25 | // 启动图片代理服务器 (host/proxy), 替换图片链接为本机代理 26 | useLocalProxy: true, 27 | imageHost: [ // i.pximg.net, direct connection by ip available 28 | '210.140.92.141', 29 | '210.140.92.149', 30 | '210.140.92.142', 31 | '210.140.92.148', 32 | '210.140.92.146', 33 | '210.140.92.144', 34 | '210.140.92.147', 35 | '210.140.92.145', 36 | '210.140.92.143' 37 | ] 38 | }, 39 | 40 | pixiv: { 41 | /** @typedef {'ja'|'en'|'zh'|'zh-cn'|'zh-tw'} Lang */ 42 | /** @type {Lang} */ 43 | lang: 'zh', 44 | 45 | // your cookie here (see README.md#Tips) 46 | // 你的 Cookie (参考 README.md#Tips) 47 | cookie: '', 48 | 49 | premium: true, 50 | 51 | followUpdate: { 52 | // If undefined, the cookie will determine if it is enabled or not. 53 | // 如果为undefined, 则根据cookie判断是否启用 54 | enable: undefined, 55 | interval: 300 * 1000 56 | }, 57 | 58 | cachePath: './cache/' 59 | }, 60 | 61 | httpServer: { 62 | host: '127.0.0.1', 63 | port: 1145 64 | }, 65 | 66 | websocketServer: { 67 | host: undefined, 68 | port: 4514 69 | } 70 | } 71 | 72 | if (config.pixiv.followUpdate.enable === undefined) { 73 | config.pixiv.followUpdate.enable = !!config.pixiv.cookie 74 | } 75 | 76 | if (config.websocketServer.host === undefined) { 77 | config.websocketServer.host = config.httpServer.host 78 | } 79 | 80 | export default config 81 | -------------------------------------------------------------------------------- /core/api/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import bodyParser from 'body-parser' 3 | import proxy from 'express-http-proxy' 4 | 5 | import config from '../../config.js' 6 | 7 | import {getPidIllust, getPidImage, getPidImageList, getPidManga} from './module/illust/index.js' 8 | import {getPidNovelSeries, getPidNovelSeriesContent, getPidNovelSeriesInfo, getPidNovel} from './module/novel/index.js' 9 | import {searchFormat} from './module/search/search.js' 10 | import {imageServerHost, rawHost} from "../pixiv-fetch/replace-url.js"; 11 | import {getPidRecommend, getPidRecommendIds} from "./module/illust/recommend.js"; 12 | import {getFollowUpdateFormat} from "./module/follow/follow.js"; 13 | import {initFollowUpdatePush} from "./module/follow/update-push.js"; 14 | 15 | export const app = express() 16 | app.use(bodyParser.json()) 17 | app.use((req, res, next) => { 18 | req.body = {...req.query, ...req.body} 19 | next() 20 | }) 21 | app.listen(config.httpServer.port, config.httpServer.host) 22 | 23 | // illust 24 | app.all('/illust/:id', async (req, res) => { 25 | res.json(await getPidIllust(req.params.id)) 26 | }) 27 | app.all('/illust/:id/images', async (req, res) => { 28 | res.json(await getPidImageList(req.params.id)) 29 | }) 30 | app.all('/illust/:id/images/:page', async (req, res) => { 31 | res.json(await getPidImage(req.params.id, req.params.page)) 32 | }) 33 | app.all('/illust/:id/recommend', async (req, res) => { 34 | res.json(await getPidRecommend(req.params.id, req.body.size)) 35 | }) 36 | 37 | app.all('/illust/:id/recommend/ids', async (req, res) => { 38 | res.json(await getPidRecommendIds(req.params.id, req.body.size)) 39 | }) 40 | 41 | 42 | // manga 43 | app.all('/manga/:id', async (req, res) => { 44 | res.json(await getPidManga(req.params.id)) 45 | }) 46 | 47 | // novel 48 | app.all('/novel/:id', async (req, res) => { 49 | res.json(await getPidNovel(req.params.id)) 50 | }) 51 | app.all('/novel/series/:id', async (req, res) => { 52 | res.json(await getPidNovelSeries(req.params.id)) 53 | }) 54 | app.all('/novel/series/:id/info', async (req, res) => { 55 | res.json(await getPidNovelSeriesInfo(req.params.id)) 56 | }) 57 | app.all('/novel/series/:id/content', async (req, res) => { 58 | res.json(await getPidNovelSeriesContent(req.params.id)) 59 | }) 60 | 61 | // search 62 | app.all('/search', async (req, res) => { 63 | res.json(await searchFormat(req.body)) 64 | }) 65 | 66 | // follow 67 | app.all('/follow', async (req, res) => { 68 | res.json(await getFollowUpdateFormat(req.body)) 69 | }) 70 | 71 | app.all('/follow/:type', async (req, res) => { 72 | res.json(await getFollowUpdateFormat({type: req.params.type, ...req.body})) 73 | }) 74 | 75 | // proxy 76 | if (config.proxy.useLocalProxy) app.use( 77 | '/proxy', 78 | proxy(config.proxy.useOriginIP ? imageServerHost : rawHost, 79 | { 80 | proxyReqOptDecorator: opts => { 81 | opts.headers['Referer'] = 'https://www.pixiv.net/' 82 | return opts 83 | }, 84 | https: !config.proxy.useOriginIP 85 | }) 86 | ) 87 | 88 | // follow update 89 | if (config.pixiv.followUpdate.enable) initFollowUpdatePush() 90 | 91 | app.use((err, req, res, next) => { 92 | console.warn(err.stack) 93 | res.status(400).send(err.message) 94 | }) -------------------------------------------------------------------------------- /core/api/module/follow/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | POST/follow 4 | 5 |
6 | 7 | ### 获取已关注用户的最新作品 8 | 9 | (**需设置Cookie**) 10 | 11 | ### `FollowUpdateParam`: 12 | 13 | | 参数 | 类型 | 描述 | 默认值 | 14 | |------------|---------------|------|----------| 15 | | `type` | `ArtworkType` | 搜索类型 | `illust` | 16 | | `restrict` | `Restrict` | 限制等级 | `safe` | 17 | | `start` | `number` | 起始索引 | `0` | 18 | | `length` | `number` | 索引长度 | `60` | 19 | | `lang` | `Lang` | 语言 | 配置文件 | 20 | 21 | ##### `ArtworkType` 22 | 23 | 限定作品类型 24 | 25 | | Name | Description | 26 | |----------|--------------| 27 | | `illust` | 插画 (动态插画与漫画) | 28 | | `novel` | 小说 | 29 | 30 | ##### `Restrict` 31 | 32 | 指定限制级 33 | 34 | | Name | Description | 35 | |--------|----------------| 36 | | `safe` | 全年龄向 | 37 | | `r18` | `R-18` `R-18G` | 38 | | `all` | 全部 | 39 | 40 | 示例: `http://127.0.0.1:1145/follow` 41 | 42 | 返回: `ResultPreviewDTO[]` 43 | 44 | ``` 45 | [ 46 | { 47 | "id": 105680390, 48 | "title": "天衣無縫", 49 | "cover": "...", 50 | "tags": [ 51 | "東方", 52 | "東方緋想天", 53 | ... 54 | ], 55 | "createTime": 1677250811000, 56 | "updateTime": 1677250811000, 57 | "restrict": 'safe', 58 | "total": 1, 59 | "author": { 60 | "name": "久蒼穹", 61 | "id": 66038798 62 | } 63 | }, 64 | ... 65 | ] 66 | ``` 67 | 68 | --- 69 |
70 | 71 |
72 | 73 | WebSocket/follow-update 74 | 75 |
76 | 77 | ### 获取更新推送 78 | 79 | 每**5分钟**从Pixiv服务器请求一次数据, 如果有已关注用户的最新作品更新, 则推送 `ResultPreviewDTO[]` 80 | 81 | 示例: `ws://127.0.0.1:4514/follow-update` 82 | 83 | 推送: `ResultPreviewDTO[]` 84 | 85 | ``` 86 | [ 87 | { 88 | "id": 105680390, 89 | "title": "天衣無縫", 90 | "cover": "...", 91 | "tags": [ 92 | "東方", 93 | "東方緋想天", 94 | ... 95 | ], 96 | "createTime": 1677250811000, 97 | "updateTime": 1677250811000, 98 | "restrict": 'safe', 99 | "total": 1, 100 | "author": { 101 | "name": "久蒼穹", 102 | "id": 66038798 103 | } 104 | }, 105 | ... 106 | ] 107 | ``` 108 | 109 | --- 110 |
-------------------------------------------------------------------------------- /core/api/module/follow/follow.js: -------------------------------------------------------------------------------- 1 | import {pixivJsonFetch, replaceURL} from "../../../pixiv-fetch/index.js"; 2 | import config from "../../../../config.js"; 3 | import {search, searchFormat} from "../search/index.js"; 4 | import {fixParam} from "../search/no-premium.js"; 5 | 6 | const PAGE_SIZE = 60 7 | const lang = config.pixiv.lang 8 | 9 | /** 10 | * @typedef {object} FollowUpdateParam 11 | * @property { 'illust' | 'novel' } type 12 | * @property {Restrict} [restrict='safe'] 13 | * @property {Restrict} [mode] 14 | * @property {number} [start=0] 15 | * @property {number} [length=60] 16 | * @property {number} [p=1] 17 | * @property {Lang} [lang] 18 | */ 19 | 20 | const defaultParam = { 21 | mode: 'all', 22 | type: 'illust', 23 | start: 0, 24 | length: 60, 25 | lang 26 | } 27 | 28 | /** 29 | * @param {FollowUpdateParam} param 30 | * @return {Promise} 31 | */ 32 | export async function getFollowUpdateFormat(param) { 33 | param.mode = param.restrict ?? 'safe' 34 | param = {...defaultParam, ...param} 35 | if (param.p) return getFollowUpdate(param) 36 | 37 | const promiseArr = [] 38 | 39 | param.start = +param.start 40 | param.length = +param.length 41 | let end = param.start + param.length 42 | let e = Math.ceil(end / PAGE_SIZE) 43 | let s = Math.ceil(param.start / PAGE_SIZE) 44 | delete param.start 45 | delete param.length 46 | for (let p = s + 1 ; p <= e; p++) { 47 | promiseArr.push(getFollowUpdate({...param, p})) 48 | } 49 | return (await Promise.all(promiseArr)).flat(1).slice(param.start, end) 50 | } 51 | 52 | /** 53 | * @param {FollowUpdateParam} param 54 | * @return {Promise} 55 | */ 56 | export async function getFollowUpdate(param) { 57 | const type = param.type 58 | delete param.type 59 | const data = await pixivJsonFetch('/ajax/follow_latest/' + type, param) 60 | const target = data.thumbnails[type] 61 | const results = [] 62 | for (const single of target) { 63 | results.push({ 64 | id: +(single.id), 65 | title: single.title, 66 | cover: replaceURL(single.url), 67 | tags: single.tags, 68 | createTime: new Date(single.createDate).getTime(), 69 | updateTime: new Date(single.updateDate).getTime(), 70 | restrict: single.xRestrict === 0 ? 'safe' : 'r18', 71 | total: single.pageCount, 72 | author: { 73 | name: single.userName, 74 | id: +(single.userId) 75 | } 76 | }) 77 | } 78 | return results 79 | } -------------------------------------------------------------------------------- /core/api/module/follow/index.js: -------------------------------------------------------------------------------- 1 | export * from "./follow.js"; 2 | -------------------------------------------------------------------------------- /core/api/module/follow/update-push.js: -------------------------------------------------------------------------------- 1 | import config from "../../../../config.js" 2 | import {getFollowUpdate} from "./follow.js" 3 | import fs from "fs" 4 | import {WebSocketServer} from "ws" 5 | 6 | export const CACHE_FILE_NAME = 'follow-update-cache.json' 7 | 8 | export function initFollowUpdatePush() { 9 | const wss = new WebSocketServer({ 10 | host: config.websocketServer.host, 11 | port: config.websocketServer.port, 12 | path: '/follow-update' 13 | }) 14 | 15 | const cachePath = config.pixiv.cachePath; 16 | const file = cachePath + CACHE_FILE_NAME 17 | 18 | fs.mkdirSync(cachePath, {recursive: true}) 19 | 20 | const proxyData = new Proxy( 21 | fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf-8')) : { 22 | illust: 0, 23 | novel: 0 24 | }, 25 | { 26 | set(target, key, value) { 27 | target[key] = value 28 | clearTimeout(this.timer) 29 | this.timer = setTimeout( 30 | () => fs.writeFile(file, JSON.stringify(target), console.warn), 31 | 5000 32 | ) 33 | return true 34 | } 35 | } 36 | ) 37 | 38 | /** @param {ResultPreviewDTO[]} data */ 39 | const pushUpdate = data => { 40 | if (!data || !data.length) return 41 | for (const client of wss.clients) { 42 | if (client.readyState !== WebSocket.OPEN) continue 43 | client.send(JSON.stringify(data)) 44 | } 45 | } 46 | 47 | const cacheData = async type => { 48 | const p = 1 49 | const data = await getFollowUpdate({p, type, mode: 'all'}) 50 | const lastId = proxyData[type] 51 | const index = data.findIndex(item => item.id === lastId) 52 | pushUpdate(data.slice(0, index)) 53 | proxyData[type] = data[0].id 54 | } 55 | 56 | setInterval(() => { 57 | cacheData('illust') 58 | cacheData('novel') 59 | }, config.pixiv.followUpdate.interval) 60 | 61 | setImmediate(() => { 62 | cacheData('illust') 63 | cacheData('novel') 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /core/api/module/illust/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP 请求 2 | 3 |
4 | 5 | GET/illust/{id} 6 | 7 |
8 | 9 | ### 获取插画基本信息 10 | 11 | | 参数 | 类型 | 描述 | 12 | |------|----------|-----| 13 | | `id` | `number` | Pid | 14 | 15 | 示例: `http://127.0.0.1:1145/illust/104577879` 16 | 17 | 返回: `IllustDTO` 18 | 19 | | key | 类型 | 描述 | 20 | |-----------------|--------------|-------------| 21 | | `id` | `number` | Pid | 22 | | `title` | `string` | 标题 | 23 | | `total` | `number` | 图片数量 | 24 | | `images` | `ImageDTO[]` | 图片数组 | 25 | | `createTime` | `number` | 创建日期 | 26 | | `updateTime` | `number` | 更新日期 | 27 | | `tags` | `string[]` | 标签数组 | 28 | | `restrict` | `Restrict` | 限制等级 | 29 | | `description` | `string` | 介绍 | 30 | | `bookmarkCount` | `number` | 收藏数 (❤ 图标) | 31 | | `likeCount` | `number` | 喜欢数 (😊 图标) | 32 | | `viewCount` | `number` | 浏览量 (👁 图标) | 33 | | `author` | `AuthorDTO` | 作者 | 34 | 35 | ``` 36 | { 37 | "id": 104577879, 38 | "title": "おでかけ", 39 | "total": 1, 40 | "images": [ 41 | { 42 | "urls": { 43 | "small": "...", 44 | "regular": "...", 45 | "original": "..." 46 | }, 47 | "width": 1620, 48 | "height": 2364 49 | } 50 | ], 51 | "createTime": 1673881211, 52 | "updateTime": 1673881211, 53 | "tags": [ 54 | "東方", 55 | "..." 56 | ], 57 | "restrict": "safe", 58 | "description": "...", 59 | "bookmarkCount": 3802, 60 | "likeCount": 2333, 61 | "viewCount": 14240, 62 | "author": { 63 | "name": "久蒼穹", 64 | "id": 66038798 65 | } 66 | } 67 | ``` 68 | 69 | --- 70 |
71 | 72 |
73 | 74 | GET/illust/{id}/images 75 | 76 |
77 | 78 | ### 获取插画图片 79 | 80 | | 参数 | 类型 | 描述 | 81 | |------|----------|-----| 82 | | `id` | `number` | Pid | 83 | 84 | 示例: `http://127.0.0.1:1145/illust/104577879/images` 85 | 86 | 返回: `ImageDTO[]` 87 | 88 | ``` 89 | [ 90 | { 91 | "urls": { 92 | "small": "...", 93 | "regular": "...", 94 | "original": "..." 95 | }, 96 | "width": 1620, 97 | "height": 2364 98 | } 99 | ] 100 | ``` 101 | 102 | --- 103 |
104 | 105 |
106 | 107 | GET/illust/{id}/images/{page} 108 | 109 |
110 | 111 | ### 获取插画图片(指定页码数) 112 | 113 | | 参数 | 类型 | 描述 | 114 | |--------|----------|-----| 115 | | `id` | `number` | Pid | 116 | | `page` | `number` | 页码数 | 117 | 118 | 示例: `http://127.0.0.1:1145/illust/104577879/images/1` 119 | 120 | 返回: `ImageDTO` 121 | 122 | ``` 123 | { 124 | "urls": { 125 | "small": "...", 126 | "regular": "...", 127 | "original": "..." 128 | }, 129 | "width": 1620, 130 | "height": 2364 131 | } 132 | ``` 133 | 134 | --- 135 |
136 | 137 |
138 | 139 | GET/illust/{id}/recommend 140 | 141 |
142 | 143 | ### 获取插画相关作品信息 144 | 145 | | 参数 | 类型 | 描述 | 默认值 | 146 | |--------|----------|-----|------| 147 | | `id` | `number` | Pid | N/A | 148 | | `size` | `number` | 容量 | `20` | 149 | 150 | 示例: `http://127.0.0.1:1145/illust/105001750/recommend?size=10` 151 | 152 | 返回: `IllustRecommendDTO[]` 153 | 154 | ``` 155 | [ 156 | { 157 | "id": 102773441, 158 | "title": "諏訪子", 159 | "type": "illust", 160 | "tags": [ 161 | "東方", 162 | "東方Project", 163 | "洩矢諏訪子", 164 | ... 165 | ], 166 | "cover": "...", 167 | "restrict": "safe", 168 | "createTime": 1668351819000, 169 | "updateTime": 1668351819000, 170 | "total": 1, 171 | "author": { 172 | "id": 49675420, 173 | "name": "かめぱすた" 174 | } 175 | }, 176 | ... 177 | ] 178 | ``` 179 | 180 |
181 | 182 |
183 | 184 | GET/illust/{id}/recommend/ids 185 | 186 |
187 | 188 | ### 获取插画相关作品ID 189 | 190 | | 参数 | 类型 | 描述 | 默认值 | 191 | |--------|----------|-----|------| 192 | | `id` | `number` | Pid | N/A | 193 | | `size` | `number` | 容量 | `20` | 194 | 195 | 示例: `http://127.0.0.1:1145/illust/105001750/recommend/ids?size=10` 196 | 197 | 返回: `number[]` 198 | 199 | ``` 200 | [ 201 | 104999610, 202 | 104104387, 203 | 104685723, 204 | 105373312, 205 | 104920874, 206 | 105515966, 207 | 104385177, 208 | 104678965, 209 | 105587193, 210 | 105669998 211 | ] 212 | ``` 213 | 214 |
-------------------------------------------------------------------------------- /core/api/module/illust/index.js: -------------------------------------------------------------------------------- 1 | export * from './pid.js' 2 | export * from '../manga/manga-pid.js' -------------------------------------------------------------------------------- /core/api/module/illust/pid.js: -------------------------------------------------------------------------------- 1 | import { pixivJsonFetch, replaceURL } from '../../../pixiv-fetch/index.js' 2 | /** 3 | * @typedef {BaseItemInfoDTO & { 4 | * total: number, 5 | * images: ImageDTO[] 6 | * }} IllustDTO 7 | */ 8 | 9 | /** 10 | * @typedef {object} ImageDTO 11 | * 12 | * @property {object} urls 13 | * @property {string} urls.small 14 | * @property {string} urls.regular 15 | * @property {string} urls.original 16 | * @property {number} width 17 | * @property {number} height 18 | */ 19 | 20 | /** 21 | * @param {number} id 22 | * @return {Promise} 23 | */ 24 | export async function getPidIllust(id) { 25 | const illust = await pixivJsonFetch( 26 | '/touch/ajax/illust/details?illust_id=' + id 27 | ) 28 | return getBaseIllustDTO(illust.illust_details) 29 | } 30 | 31 | /** 32 | * @param {number} id 33 | * @return {Promise} 34 | */ 35 | export async function getPidImageList(id) { 36 | return await pixivJsonFetch( 37 | `/ajax/illust/${id}/pages` 38 | ) 39 | } 40 | 41 | /** 42 | * @param {number} id 43 | * @param {number} page index + 1 of illust 44 | * @return {Promise} 45 | */ 46 | export async function getPidImage(id, page) { 47 | const imgs = await getPidImageList(id) 48 | try { 49 | return imgs[page - 1] 50 | } catch { 51 | throw new RangeError(`no page ${page} in illust ${id}`) 52 | } 53 | } 54 | 55 | /** @return {IllustDTO} */ 56 | export function getBaseIllustDTO(details) { 57 | const authorDetails = details.author_details 58 | const pages = +(details.page_count) 59 | /** @type { ImageDTO[] } */ 60 | const images = [] 61 | if (pages === 1) { 62 | images.push({ 63 | urls: { 64 | small: replaceURL(details.url_s), 65 | regular: replaceURL(details.url), 66 | original: replaceURL(details.url_big) 67 | }, 68 | width: +(details.illust_images[0].illust_image_width), 69 | height: +(details.illust_images[0].illust_image_height) 70 | }) 71 | } else { 72 | for (let i = 0; i < pages; i++) { 73 | const { 74 | illust_image_width: width, 75 | illust_image_height: height 76 | } = details.illust_images[i] 77 | const o = details.manga_a[i] 78 | images.push({ 79 | urls: { 80 | small: replaceURL(o.url_small), 81 | regular: replaceURL(o.url), 82 | original: replaceURL(o.url_big) 83 | }, 84 | width: +(width), 85 | height: +(height) 86 | }) 87 | } 88 | } 89 | return { 90 | id: +(details.id), 91 | title: details.title, 92 | total: pages, 93 | images, 94 | createTime: details.create_timestamp ?? details.upload_timestamp, 95 | updateTime: details.upload_timestamp, 96 | tags: details.tags, 97 | restrict: details.x_restrict == 0 ? 'safe' : 'r18', 98 | description: details.comment, 99 | bookmarkCount: details.bookmark_user_total, 100 | likeCount: +(details.rating_count), 101 | viewCount: +(details.rating_view), 102 | author: { 103 | name: authorDetails.user_name, 104 | id: +(authorDetails.user_id) 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /core/api/module/illust/recommend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {BaseRecommendDTO & {type: string}} IllustRecommendDTO 3 | */ 4 | 5 | import {pixivJsonFetch} from "../../../pixiv-fetch/index.js"; 6 | import replaceUrl from "../../../pixiv-fetch/replace-url.js"; 7 | 8 | /** 9 | * @param {number} id 10 | * @param {number} [size=20] 11 | * @return {Promise} 12 | */ 13 | export async function getPidRecommend(id, size = 20) { 14 | const data = await pixivJsonFetch(`/ajax/illust/${id}/recommend/init?limit=${size}`) 15 | /** @type {IllustRecommendDTO[]} */ 16 | const list = [] 17 | let len = data.illusts.length 18 | for (let i = 0; i < len; i++) { 19 | const single = data.illusts[i] 20 | if (!single.id) continue 21 | list.push({ 22 | id: +(single.id), 23 | title: single.title, 24 | type: single.type, 25 | tags: single.tags, 26 | cover: replaceUrl(single.url), 27 | restrict: single.xRestrict === 0 ? 'safe' : 'r18', 28 | createTime: new Date(single.createDate).getTime(), 29 | updateTime: new Date(single.updateDate).getTime(), 30 | total: single.pageCount, 31 | author: { 32 | id: +(single.userId), 33 | name: single.userName 34 | } 35 | }) 36 | } 37 | return list 38 | } 39 | 40 | export async function getPidRecommendIds(id, size = 20) { 41 | const data = await pixivJsonFetch(`/ajax/illust/${id}/recommend/init?limit=${size}`) 42 | return data.nextIds.slice(0, size).map(i => +(i)) 43 | } -------------------------------------------------------------------------------- /core/api/module/index.js: -------------------------------------------------------------------------------- 1 | /** @typedef {import('../../../../config.js').Lang} Lang */ 2 | 3 | /** 4 | * @typedef {object} BaseItemInfoDTO 5 | * 6 | * @property {number} id 7 | * @property {string} title 8 | * @property {string} description 9 | * @property {number} createTime create timestamp 10 | * @property {number} updateTime update timestamp 11 | * @property {string[]} tags 12 | * @property {Restrict} restrict 13 | * @property {number} bookmarkCount ❤ icon 14 | * @property {number} likeCount 😊 icon 15 | * @property {number} viewCount 👁 icon 16 | */ 17 | 18 | /** 19 | * @typedef {object} BaseItemServiceInfoDTO 20 | * 21 | * @property {number} id 22 | * @property {string} title 23 | * @property {number} order 24 | * @property {BaseItemHeadInfoDTO} prev 25 | * @property {BaseItemHeadInfoDTO} next 26 | */ 27 | 28 | /** @typedef {{id: number, title: string}} BaseItemHeadInfoDTO */ 29 | 30 | /** 31 | * @typedef {'all'|'safe'|'r18'} Restrict 32 | * @typedef { {name: string, id: number} } AuthorDTO 33 | */ 34 | 35 | /** 36 | * @typedef {object} ResultPreviewDTO 37 | * @property {number} id 38 | * @property {string} title 39 | * @property {string} cover 40 | * @property {string[]} tags 41 | * @property {number} createTime 42 | * @property {number} updateTime 43 | * @property {Restrict} restrict 44 | * @property {number} total 45 | * @property {AuthorDTO} author 46 | */ 47 | 48 | /** 49 | * @typedef {ResultPreviewDTO} BaseRecommendDTO 50 | */ 51 | 52 | /** @typedef {'artwork'|'illust'|'gif'|'illust_and_gif'|'manga'|'novel'} WorkType */ -------------------------------------------------------------------------------- /core/api/module/manga/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP 请求 2 | 3 |
4 | 5 | GET/manga/{id} 6 | 7 |
8 | 9 | ### 获取漫画基本信息 10 | 11 | | 参数 | 类型 | 描述 | 12 | | ---- | -------- | ---- | 13 | | `id` | `number` | Pid | 14 | 15 | 示例: `http://127.0.0.1:1145/manga/98019984` 16 | 17 | 返回: `MangaDTO` 18 | 19 | | key | 类型 | 描述 | 20 | | -------- | --------------- | --------------------------------------- | 21 | | ... | `IllustDTO` | 参见 [`IllustDTO`](../illust/README.md) | 22 | | `series` | `ItemSeriesDTO` | 系列数据 | 23 | 24 | ``` 25 | { 26 | "id": 98019984, 27 | "title": "へっぽこ吸血鬼ちゃんは血が欲しい", 28 | "total": 5, 29 | "images": [ 30 | { 31 | "urls": { 32 | "small": "...", 33 | "regular": "...", 34 | "original": "..." 35 | }, 36 | "width": 800, 37 | "height": 1301 38 | }, 39 | ... 40 | ], 41 | "createTime": 1651395658, 42 | "updateTime": 1651395658, 43 | "tags": [ 44 | "漫画", 45 | "..." 46 | ], 47 | "restrict": "safe", 48 | "description": "...", 49 | "bookmarkCount": 16237, 50 | "likeCount": 14008, 51 | "viewCount": 327481, 52 | "author": { 53 | "name": "にいち", 54 | "id": 1035047 55 | }, 56 | "series": { 57 | "id": 718, 58 | "title": "少女アラカルト", 59 | "order": 67, 60 | "prev": { 61 | "id": "97536981", 62 | "title": "花の季節" 63 | }, 64 | "next": null 65 | } 66 | } 67 | ``` 68 | --- 69 |
-------------------------------------------------------------------------------- /core/api/module/manga/index.js: -------------------------------------------------------------------------------- 1 | export * from './pid.js' -------------------------------------------------------------------------------- /core/api/module/manga/manga-pid.js: -------------------------------------------------------------------------------- 1 | import { pixivJsonFetch } from "../../../pixiv-fetch/index.js" 2 | import { getBaseIllustDTO } from "../illust/pid.js" 3 | /** @typedef { import('../illust/pid.js').IllustDTO } IllustDTO */ 4 | 5 | /** 6 | * @typedef {IllustDTO & { 7 | * series: BaseItemServiceInfoDTO 8 | * }} MangaDTO 9 | */ 10 | 11 | /** 12 | * @param {number} id 13 | * @return {Promise} 14 | */ 15 | export async function getPidManga(id) { 16 | const illust = await pixivJsonFetch( 17 | '/touch/ajax/illust/details?illust_id=' + id 18 | ) 19 | const series = illust.illust_details.series 20 | return { 21 | ...getBaseIllustDTO(illust.illust_details), 22 | series: !series ? null : { 23 | id: +(series.id), 24 | title: series.title, 25 | order: +(series.content_order), 26 | prev: !series.prev_illust ? null : { 27 | id: series.prev_illust.illust_id, 28 | title: series.prev_illust.illust_title 29 | }, 30 | next: !series.next_illust ? null : { 31 | id: series.next_illust.illust_id, 32 | title: series.next_illust.illust_title 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /core/api/module/manga/manga-series-pid.js: -------------------------------------------------------------------------------- 1 | //TODO -------------------------------------------------------------------------------- /core/api/module/novel/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP 请求 2 | 3 |
4 | 5 | GET/novel/{id} 6 | 7 |
8 | 9 | ### 获取小说基本信息 10 | 11 | | 参数 | 类型 | 描述 | 12 | | ---- | -------- | ---- | 13 | | `id` | `number` | Pid | 14 | 15 | 示例: `http://127.0.0.1:1145/novel/15927906` 16 | 17 | 返回: `NovelDTO` 18 | 19 | | key | 类型 | 描述 | 20 | | --------------- | --------------- | --------------- | 21 | | `id` | `number` | Pid | 22 | | `title` | `string` | 标题 | 23 | | `description` | `string` | 介绍 | 24 | | `tags` | `string[]` | 标签数组 | 25 | | `lang` | `Language` | 语言枚举 | 26 | | `restrict` | `Restrict` | 限制等级 | 27 | | `charCount` | `number` | 字节数 | 28 | | `wordCount` | `number` | 词数 | 29 | | `createTime` | `number` | 创建日期 | 30 | | `updateTime` | `number` | 更新日期 | 31 | | `readingTime` | `number` | 阅读时间 | 32 | | `bookmarkCount` | `number` | 收藏数 (❤ 图标) | 33 | | `likeCount` | `number` | 喜欢数 (😊 图标) | 34 | | `viewCount` | `number` | 浏览量 (👁 图标) | 35 | | `cover` | `string` | 封面图片 | 36 | | `series` | `ItemSeriesDTO` | 系列数据 | 37 | | `author` | `AuthorDTO` | 作者 | 38 | | `content` | `string` | 正文 | 39 | 40 | ``` 41 | { 42 | "id": 19115002, 43 | "title": "第2話『はじめての登下校』", 44 | "description": "...", 45 | "tags": [ 46 | "百合", 47 | "..." 48 | ], 49 | "lang": "ja", 50 | "restrict": "safe", 51 | "charCount": 4282, 52 | "wordCount": 1888, 53 | "readingTime": 513, 54 | "bookmarkCount": 11, 55 | "likeCount": 9, 56 | "viewCount": 353, 57 | "cover": "...", 58 | "series": { 59 | "id": 9943394, 60 | "title": "言語チート転生〜幼女VTuberは世界を救う〜", 61 | "order": 2, 62 | "prev": { 63 | "id": 19114998, 64 | "title": "第1話『終わりとはじまり』" 65 | }, 66 | "next": { 67 | "id": 19115003, 68 | "title": "第3話『お姉ちゃんのお願い』" 69 | } 70 | }, 71 | "author": { 72 | "id": 90197256, 73 | "name": "可愛ケイ@VTuber兼小説家" 74 | }, 75 | "content": "..." 76 | } 77 | ``` 78 | --- 79 |
80 | 81 |
82 | 83 | GET/novel/series/{id} 84 | 85 |
86 | 87 | ### 获取小说系列基本信息 88 | 89 | | 参数 | 类型 | 描述 | 90 | | ---- | -------- | ---- | 91 | | `id` | `number` | Pid | 92 | 93 | 示例: `http://127.0.0.1:1145/novel/series/9943394` 94 | 95 | 返回: `NovelSericeInfoDTO` 96 | 97 | | key | 类型 | 描述 | 98 | | -------- | -------------------- | ----------------------------- | 99 | | `...` | `NovelSericeInfoDTO` | 参考下文 `NovelSericeInfoDTO` | 100 | | `novels` | `NovelInfoDTO[]` | 参考下文 `NovelInfoDTO` | 101 | 102 | ``` 103 | { 104 | "id": 9943394, 105 | "title": "言語チート転生〜幼女VTuberは世界を救う〜", 106 | "tags": [ 107 | "百合", 108 | "..." 109 | ], 110 | "lang": "ja", 111 | "cover": "...", 112 | "restrict": "safe", 113 | "concluded": true, 114 | "total": 45, 115 | "charCount": 128761, 116 | "wordCount": 58687, 117 | "readingTime": 15451, 118 | "createTime": 1673809337, 119 | "updateTime": 1674504831, 120 | "author": { 121 | "name": "可愛ケイ@VTuber兼小説家", 122 | "id": 90197256 123 | }, 124 | 125 | "novels": [ 126 | { 127 | "id": 19114998, 128 | "title": "第1話『終わりとはじまり』", 129 | "description": "...", 130 | "tags": [ 131 | "百合", 132 | "..." 133 | ], 134 | "restrict": "safe", 135 | "wordCount": 1095, 136 | "readingTime": 296000, 137 | "createTime": 1673810153000, 138 | "updateTime": 1674364856000, 139 | "bookmarkCount": 48, 140 | "author": { 141 | "name": "可愛ケイ@VTuber兼小説家", 142 | "id": 90197256 143 | } 144 | }, 145 | ... 146 | ] 147 | } 148 | ``` 149 | --- 150 |
151 | 152 |
153 | 154 | GET/novel/series/{id}/info 155 | 156 |
157 | 158 | ### 获取小说系列基本信息(不含作品) 159 | 160 | | 参数 | 类型 | 描述 | 161 | | ---- | -------- | ---- | 162 | | `id` | `number` | Pid | 163 | 164 | 示例: `http://127.0.0.1:1145/novel/series/9943394/info` 165 | 166 | 返回: `NovelSericeInfoDTO` 167 | 168 | | key | 类型 | 描述 | 169 | | ------------- | ----------- | -------- | 170 | | `id` | `number` | Pid | 171 | | `title` | `string` | 标题 | 172 | | `description` | `string` | 介绍 | 173 | | `tags` | `string[]` | 标签数组 | 174 | | `lang` | `Language` | 语言枚举 | 175 | | `cover` | `string` | 封面图片 | 176 | | `restrict` | `Restrict` | 限制等级 | 177 | | `concluded` | `boolean` | 完结状态 | 178 | | `total` | `number` | 总篇数 | 179 | | `charCount` | `number` | 字节数 | 180 | | `wordCount` | `number` | 词数 | 181 | | `createTime` | `number` | 创建日期 | 182 | | `updateTime` | `number` | 更新日期 | 183 | | `readingTime` | `number` | 阅读时间 | 184 | | `author` | `AuthorDTO` | 作者 | 185 | 186 | ``` 187 | { 188 | "id": 9943394, 189 | "title": "言語チート転生〜幼女VTuberは世界を救う〜", 190 | "description": "...", 191 | "tags": [ 192 | "百合", 193 | "..." 194 | ], 195 | "lang": "ja", 196 | "cover": "...", 197 | "restrict": "safe", 198 | "concluded": true, 199 | "total": 45, 200 | "charCount": 128761, 201 | "wordCount": 58687, 202 | "readingTime": 15451, 203 | "createTime": 1673809337, 204 | "updateTime": 1674504831, 205 | "author": { 206 | "name": "可愛ケイ@VTuber兼小説家", 207 | "id": 90197256 208 | } 209 | } 210 | ``` 211 | --- 212 |
213 | 214 |
215 | 216 | GET/novel/series/{id}/content 217 | 218 |
219 | 220 | ### 获取小说系列作品表列 221 | 222 | | 参数 | 类型 | 描述 | 223 | | ---- | -------- | ---- | 224 | | `id` | `number` | Pid | 225 | 226 | 示例: `http://127.0.0.1:1145/novel/series/9943394/content` 227 | 228 | 返回: `NovelInfoDTO[]` 229 | 230 | *参考上文 `NovelDTO`* 231 | 232 | ``` 233 | [ 234 | { 235 | "id": 19114998, 236 | "title": "第1話『終わりとはじまり』", 237 | "description": "...", 238 | "tags": [ 239 | "百合", 240 | "..." 241 | ], 242 | "restrict": "safe", 243 | "wordCount": 1095, 244 | "readingTime": 296000, 245 | "createTime": 1673810153000, 246 | "updateTime": 1674364856000, 247 | "bookmarkCount": 48, 248 | "author": { 249 | "name": "可愛ケイ@VTuber兼小説家", 250 | "id": 90197256 251 | } 252 | }, 253 | ... 254 | ] 255 | ``` 256 | --- 257 |
-------------------------------------------------------------------------------- /core/api/module/novel/index.js: -------------------------------------------------------------------------------- 1 | export * from './pid.js' 2 | 3 | export { 4 | default as getPidNovelSeries, 5 | getPidNovelSeriesInfo, 6 | getPidNovelSeriesContent 7 | } from './series-pid.js' -------------------------------------------------------------------------------- /core/api/module/novel/pid.js: -------------------------------------------------------------------------------- 1 | import { pixivJsonFetch, replaceURL } from "../../../pixiv-fetch/index.js"; 2 | /** @typedef {import('../../../../config.js').Lang} Lang */ 3 | 4 | /** 5 | * @typedef {BaseItemInfoDTO & { 6 | * lang: Lang, 7 | * cover: string, 8 | * restrict: Restrict, 9 | * charCount: number, 10 | * wordCount: number, 11 | * readingTime: number 12 | * }} NovelInfoDTO 13 | */ 14 | 15 | /** 16 | * @typedef {NovelInfoDTO & { 17 | * content: string, 18 | * series: BaseItemServiceInfoDTO 19 | * }} NovelItemDTO 20 | */ 21 | 22 | export async function getPidNovel(id) { 23 | const novel = await pixivJsonFetch('/touch/ajax/novel/details?novel_id=' + id) 24 | const details = novel.novel_details 25 | const series = details.series 26 | return { 27 | id: +(id), 28 | title: details.title, 29 | description: details.comment, 30 | tags: details.tags, 31 | lang: details.language, 32 | restrict: details.x_restrict == 0 ? 'safe' : 'r18', 33 | charCount: +(details.character_count), 34 | wordCount: details.word_count, 35 | readingTime: details.reading_time, 36 | createTime: details.create_time, 37 | updateTime: details.update_time, 38 | bookmarkCount: details.bookmark_count, 39 | likeCount: +(details.rating_count), 40 | viewCount: +(details.rating_view), 41 | 42 | cover: replaceURL(details.url), 43 | series: !series ? null : { 44 | id: +(series.id), 45 | title: series.title, 46 | order: series.content_order, 47 | prev: !series.prev_novel ? null : { 48 | id: series.prev_novel.id, 49 | title: series.prev_novel.title 50 | }, 51 | next: !series.next_novel ? null : { 52 | id: series.next_novel.id, 53 | title: series.next_novel.title 54 | } 55 | }, 56 | author: { 57 | id: +(details.user_id), 58 | name: details.user_name 59 | }, 60 | content: details.text 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/api/module/novel/series-pid.js: -------------------------------------------------------------------------------- 1 | import { pixivJsonFetch, replaceURL } from "../../../pixiv-fetch/index.js"; 2 | /** @typedef {import('../../../../config.js').Lang} Lang */ 3 | /** @typedef {import('../illust/pid.js').AuthorDTO} AuthorDTO */ 4 | /** @typedef {import('./pid.js').NovelInfoDTO} NovelInfoDTO */ 5 | 6 | /** 7 | * @typedef {NovelInfoDTO & { 8 | * total: number, 9 | * concluded: boolean 10 | * }} NovelSeriesInfoDTO 11 | */ 12 | 13 | /** 14 | * @param {number} id 15 | * @return {Promise} 16 | */ 17 | export async function getPidNovelSeriesInfo(id) { 18 | const series = await pixivJsonFetch('/ajax/novel/series/' + id) 19 | return { 20 | id: +(id), 21 | title: series.title, 22 | description: series.caption, 23 | tags: series.tags, 24 | lang: series.language, 25 | cover: replaceURL(series.cover.urls.original), 26 | restrict: series.xRestrict == 0 ? 'safe' : 'r18', 27 | 28 | concluded: series.isConcluded, 29 | total: series.publishedContentCount, 30 | charCount: series.publishedTotalCharacterCount, 31 | wordCount: series.publishedTotalWordCount, 32 | readingTime: series.publishedReadingTime, 33 | createTime: series.createdTimestamp, 34 | updateTime: series.updatedTimestamp, 35 | 36 | author: { 37 | name: series.userName, 38 | id: +(series.userId) 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * @param {number} id 45 | */ 46 | export async function getPidNovelSeriesContent(id) { 47 | const { thumbnails } = await pixivJsonFetch('/ajax/novel/series_content/' + id, { 48 | limit: 30, 49 | last_order: 0, 50 | order_by: 'asc' 51 | }) 52 | const resultArr = [] 53 | for (const singleNovel of thumbnails.novel) { 54 | resultArr.push({ 55 | id: +(singleNovel.id), 56 | title: singleNovel.title, 57 | description: singleNovel.description, 58 | tags: singleNovel.tags, 59 | restrict: singleNovel.xRestrict == 0 ? 'safe' : 'r18', 60 | charCount: singleNovel.characterCount, 61 | wordCount: singleNovel.wordCount, 62 | readingTime: singleNovel.readingTime * 1000, 63 | createTime: new Date(singleNovel.createDate).getTime(), 64 | updateTime: new Date(singleNovel.updateDate).getTime(), 65 | bookmarkCount: singleNovel.bookmarkCount, 66 | author: { 67 | name: singleNovel.userName, 68 | id: +(singleNovel.userId) 69 | } 70 | }) 71 | } 72 | return resultArr 73 | } 74 | 75 | export default async function getPidNovelSeries(id) { 76 | const result = await Promise.all([ 77 | getPidNovelSeriesInfo(id), 78 | getPidNovelSeriesContent(id) 79 | ]) 80 | return { 81 | ...result[0], 82 | novels: result[1] 83 | } 84 | } -------------------------------------------------------------------------------- /core/api/module/search/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP 请求 2 | 3 |
4 | 5 | POST/search 6 | 7 | 8 | ### `SearchParam`: 9 | 10 | | 参数 | 类型 | 描述 | 默认值 | 11 | | ---------- | -------------- | ---------- | -------- | 12 | | `word` | `string` | 搜索词 | 必填 | 13 | | `type` | `SearchType` | 搜索类型 | `illust` | 14 | | `template` | `TemplateType` | 预设模板 | 无 | 15 | | `mode` | `SearchMode` | 搜索模式 | `tag` | 16 | | `order` | `SearchOrder` | 排序方法 | `date` | 17 | | `blt` | `number` | 最少收藏数 | `0` | 18 | | `restrict` | `Restrict` | 限制等级 | `safe` | 19 | | `start` | `number` | 起始索引 | `0` | 20 | | `length` | `number` | 索引长度 | `60` | 21 | | `lang` | `Lang` | 语言 | 配置文件 | 22 | 23 | ### ENUM: 24 | 25 | ##### `SearchType` 26 | 27 | 限定搜索类型 28 | 29 | | Name | Description | 30 | | ---------------- | -------------------- | 31 | | `illust` | 插画 | 32 | | `gif` | 动态插画 (GIF) | 33 | | `illust_and_gif` | 插画与动态插画 | 34 | | `manga` | 漫画 | 35 | | `artwork` | 插画, 动态插画与漫画 | 36 | | `novel` | 小说 | 37 | 38 | ##### `TemplateType` 39 | 40 | 使用预设模板 41 | 42 | | Name | Description | 43 | | --------- |--------------------| 44 | | `top` | 收数数降序排列 | 45 | | `default` | 时间倒序, 收藏数不少于`1000` | 46 | | `enhance` | 时间倒序, 收藏数不少于`50` | 47 | 48 | ##### `SearchMode` 49 | 50 | 指定搜索模式 51 | 52 | | Name | Description | 53 | | --------- | ------------------ | 54 | | `tag` | 标签 部分匹配 | 55 | | `full` | 标签 完全匹配 | 56 | | `content` | 标题/正文 部分匹配 | 57 | 58 | ##### `SearchOrder` 59 | 60 | 指定排序方式 61 | 62 | | Name | Description | 63 | | --------- | ----------- | 64 | | `date` | 日期倒序 | 65 | | `popular` | 收藏数降序 | 66 | 67 | ##### `Restrict` 68 | 69 | 指定限制级 70 | 71 | | Name | Description | 72 | | ------ | -------------- | 73 | | `safe` | 全年龄向 | 74 | | `r18` | `R-18` `R-18G` | 75 | | `all` | 全部 | 76 | 77 | 示例请求: `http://127.0.0.1:1145/search` 78 | 79 | ``` 80 | { 81 | "word": "東方Project", 82 | "type": "illust", 83 | "template": "default", 84 | "length": 20 85 | } 86 | ``` 87 | 88 | 返回: `SearchResultDTO` 89 | 90 | | key | 类型 | 描述 | 91 | | --------- | -------------------- | ------------ | 92 | | `results` | `ResultPreviewDTO[]` | 作品预览数组 | 93 | | `total` | `number` | 作品总数 | 94 | 95 | ``` 96 | { 97 | "results": [ 98 | { 99 | "id": 105680390, 100 | "title": "天衣無縫", 101 | "cover": "...", 102 | "tags": [ 103 | "東方", 104 | "東方緋想天", 105 | ... 106 | ], 107 | "createTime": 1677250811000, 108 | "updateTime": 1677250811000, 109 | "restrict": 'safe', 110 | "total": 1, 111 | "author": { 112 | "name": "久蒼穹", 113 | "id": 66038798 114 | } 115 | } 116 | ], 117 | "relatedTags": [ 118 | "東方Project", 119 | "東方Project3000users入り", 120 | "博麗霊夢", 121 | ... 122 | ], 123 | "total": 19590 124 | } 125 | ``` 126 | 127 | -------------------------------------------------------------------------------- /core/api/module/search/index.js: -------------------------------------------------------------------------------- 1 | // 插画: illustrations -> type: illust_and_ugoira 2 | // 漫画: manga -> type: manga 3 | // 插画和漫画: artworks -> type: all 4 | // 小说: novels -> notype, work_lang: lang 5 | 6 | /** @typedef {'date'|'popular'} SearchOrder */ 7 | /** @typedef {'date_d'|'popular_male_d'} RawSearchOrder */ 8 | 9 | /** @typedef {'tag'|'full'|'content'} SearchMode */ 10 | /** @typedef {'s_tag'|'s_tag_full'|'s_tc'} RawSearchMode */ 11 | 12 | /** @typedef {'all'|'illust'|'ugoira'|'illust_and_ugoira'|'manga'} RawSearchType */ 13 | 14 | /** @typedef {'top'|'default'|'enhance'} TemplateType */ 15 | 16 | /** @typedef {Object} SearchTypeInfoMap */ 17 | /** @typedef {{path: SearchPath, type: RawSearchType | '', name: string}} SearchTypeInfo */ 18 | /** @typedef {'illustrations'|'artworks'|'manga'|'novels'} SearchPath */ 19 | 20 | /** 21 | * @typedef {object} SearchParam 22 | * @property {string} word 23 | * @property {TemplateType} [template] 24 | * @property {SearchOrder} [order] 25 | * @property {number} [blt=0] 26 | * @property {SearchMode|RawSearchMode} [mode] 27 | * @property {WorkType|RawSearchType} [type] 28 | * @property {Restrict} [restrict='safe'] 29 | * @property {number} [start=0] 30 | * @property {number} [length=60] 31 | * @property {number} [p=1] 32 | * @property {Lang} [lang] 33 | */ 34 | 35 | /** 36 | * @typedef {object} RawSearchParam 37 | * @property {string} word 38 | * @property {RawSearchOrder} order 39 | * @property {number} blt 40 | * @property {RawSearchMode} s_mode 41 | * @property {RawSearchType} type 42 | * @property {Restrict} mode 43 | * @property {number} p 44 | * @property {Lang} lang 45 | */ 46 | 47 | /** 48 | * @typedef {object} SearchResultDTO 49 | * @property {ResultPreviewDTO[]} results 50 | * @property {string[]} relatedTags 51 | * @property {number} total 52 | */ 53 | 54 | export {search, searchFormat} from './search.js' -------------------------------------------------------------------------------- /core/api/module/search/no-premium.js: -------------------------------------------------------------------------------- 1 | // import {pixivJsonFetch} from "../../../pixiv-fetch/index.js"; 2 | import {search} from "./search.js"; 3 | 4 | const arr = [100, 250, 300, 500, 1000, 3000, 5000, 10000, 20000, 30000] 5 | const map = new Map(arr.reduceRight( 6 | (res, i) => 7 | [...res, [i, res.push([i, i + 'users入り']) && res.slice(-2).map(s => s[1]).join(' OR ')]], 8 | [] 9 | ).map(s => [s[0], `(${s[1]})`])) 10 | 11 | /** 12 | * @param {SearchParam} param 13 | * @param {SearchTypeInfo} typeInfo 14 | */ 15 | export async function fixParam(param, typeInfo) { 16 | // let singleWordFlag = !param.word.includes(' ') 17 | param.word += ' ' + map.get(param.blt) ?? map.get( 18 | arr.find(n => !(n - param.blt & 0x80000000)) ?? 30000 19 | ) 20 | // const data = await searchNoPremium({...param, p: 1}, typeInfo) 21 | // let mainTag = data.relatedTags.find(w => w.endsWith(extraTag)) 22 | // if (mainTag === param.word.replace(' ', '')) return data.raw 23 | // if (singleWordFlag) param.word = mainTag 24 | // console.log(param.word) 25 | return search(typeInfo.path, typeInfo.name, param) 26 | } 27 | 28 | // /** 29 | // * @param {SearchParam} param 30 | // * @param {SearchTypeInfo} typeInfo 31 | // * @return {Promise<{relatedTags: string[], raw: SearchResultDTO}>} 32 | // */ 33 | // async function searchNoPremium(param, typeInfo) { 34 | // const data = await pixivJsonFetch(`/ajax/search/${typeInfo.path}/${encodeURIComponent(param.word)}`, param) 35 | // const results = [] 36 | // for (const single of data[typeInfo.name].data) { 37 | // results.push({ 38 | // id: +(single.id), 39 | // title: single.title, 40 | // cover: single.url, 41 | // tags: single.tags, 42 | // createTime: new Date(single.createDate).getTime(), 43 | // updateTime: new Date(single.updateDate).getTime(), 44 | // author: { 45 | // name: single.userName, 46 | // id: +(single.userId) 47 | // } 48 | // }) 49 | // } 50 | // return { 51 | // relatedTags: data.relatedTags, 52 | // raw: { 53 | // results, 54 | // total: data[typeInfo.name].total 55 | // } 56 | // } 57 | // } -------------------------------------------------------------------------------- /core/api/module/search/search.js: -------------------------------------------------------------------------------- 1 | import {pixivJsonFetch, replaceURL} from '../../../pixiv-fetch/index.js' 2 | import config from '../../../../config.js' 3 | import {fixParam} from "./no-premium.js"; 4 | 5 | /** @private @readonly */ 6 | const PAGE_SIZE = 60 7 | const lang = config.pixiv.lang 8 | const noPremium = !config.pixiv.premium 9 | 10 | const defaultParams = { 11 | word: '', 12 | order: 'date_d', 13 | start: 0, 14 | length: PAGE_SIZE, 15 | blt: 0, 16 | s_mode: 's_tag', 17 | type: 'illust', 18 | mode: 'safe', 19 | lang 20 | } 21 | 22 | const templates = { 23 | top: { 24 | order: 'popular_male_d', 25 | blt: 0 26 | }, 27 | default: { 28 | order: 'date_d', 29 | blt: 1000 30 | }, 31 | enhance: { 32 | order: 'date_d', 33 | blt: 50 34 | } 35 | } 36 | 37 | const searchOrders = { 38 | date: 'date_d', popular: 'popular_male_d' 39 | } 40 | 41 | const searchModes = { 42 | tag: 's_tag', full: 's_tag_full', content: 's_tc' 43 | } 44 | 45 | /** @type {SearchTypeInfoMap} */ 46 | const searchTypes = { 47 | artwork: {path: 'artworks', type: 'all', name: 'illustManga'}, 48 | illust: {path: 'illustrations', type: 'illust', name: 'illust'}, 49 | gif: {path: 'illustrations', type: 'ugoira', name: 'illust'}, 50 | illust_and_gif: {path: 'illustrations', type: 'illust_and_ugoira', name: 'illust'}, 51 | manga: {path: 'manga', type: 'manga', name: 'manga'}, 52 | novel: {path: 'novels', type: '', name: 'novel'} 53 | } 54 | 55 | /** 56 | * @param {SearchParam} param 57 | * @return {Promise} 58 | */ 59 | export async function searchFormat(param) { 60 | param = {...defaultParams, ...templates[param.template], ...param} 61 | param.order = searchOrders[param.order] ?? param.order 62 | param.s_mode = searchModes[param.mode] ?? param.mode 63 | param.mode = param.restrict ?? 'safe' 64 | const inf = searchTypes[param.type] 65 | param.type = inf?.type ?? param.type 66 | const path = inf?.path ?? 'illustrations' 67 | 68 | if (param.type === 'novel') param.work_lang = param.lang 69 | 70 | const promiseArr = [] 71 | if (noPremium) { 72 | promiseArr.push(await fixParam(param, inf)) 73 | } else if (param.p) { 74 | return search(path, inf.name, param) 75 | } 76 | 77 | param.start = +param.start 78 | param.length = +param.length 79 | let end = param.start + param.length 80 | let e = Math.ceil(end / PAGE_SIZE) 81 | let s = Math.ceil(param.start / PAGE_SIZE) 82 | // delete param.start 83 | // delete param.length 84 | for (let p = s + 1 + noPremium; p <= e; p++) { 85 | promiseArr.push(search(path, inf.name, {...param, p})) 86 | } 87 | const res = (await Promise.all(promiseArr)).reduce((result, pageData) => { 88 | result.results.push(...pageData.results) 89 | result.relatedTags = pageData.relatedTags 90 | result.total = pageData.total 91 | return result 92 | }, { 93 | results: [], 94 | relatedTags: [], 95 | total: 0 96 | }) 97 | res.results = res.results.slice(param.start, end) 98 | return res 99 | } 100 | 101 | /** 102 | * @param {SearchPath} path 103 | * @param {string} dataName 104 | * @param {RawSearchParam} param 105 | * @return {Promise} 106 | */ 107 | export async function search(path, dataName, param) { 108 | // param = { ...defaultParams, ...templates[param.template], ...param } 109 | const data = await pixivJsonFetch(`/ajax/search/${path}/${encodeURIComponent(param.word)}`, param) 110 | const results = [] 111 | for (const single of data[dataName].data) { 112 | results.push({ 113 | id: +(single.id), 114 | title: single.title, 115 | cover: replaceURL(single.url), 116 | tags: single.tags, 117 | createTime: new Date(single.createDate).getTime(), 118 | updateTime: new Date(single.updateDate).getTime(), 119 | restrict: single.xRestrict === 0 ? 'safe' : 'r18', 120 | total: single.pageCount, 121 | author: { 122 | name: single.userName, 123 | id: +(single.userId) 124 | } 125 | }) 126 | } 127 | return { 128 | results, 129 | relatedTags: data.relatedTags, 130 | total: data[dataName].total 131 | } 132 | } -------------------------------------------------------------------------------- /core/api/module/user/pid.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dituon/pixiv-api-http/51ddcb9c6852b742a851a257a3884b91bd5136ca/core/api/module/user/pid.js -------------------------------------------------------------------------------- /core/pixiv-fetch/agent.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | import config from "../../config.js" 3 | import baseOpiton from './base-option.js' 4 | 5 | const bypassSNI = config.proxy.bypassSNI 6 | 7 | export const staticAgent = ip => new https.Agent({ 8 | rejectUnauthorized: true, 9 | servername: '', 10 | lookup: (_0, _1, callback) => callback(null, ip, 4) 11 | }) 12 | 13 | export let agent = !bypassSNI ? 14 | https.globalAgent : staticAgent(config.proxy.serverHost[0]) 15 | 16 | if (bypassSNI) { //test ip 17 | for (const hostIp of config.proxy.serverHost) { 18 | const { status, ip } = await new Promise((res, rej) => { 19 | const request = https.request({ 20 | ...baseOpiton, 21 | agent: staticAgent(hostIp) 22 | }, r => res({ status: r.statusCode, ip: hostIp })) 23 | 24 | request.on('error', rej) 25 | request.end() 26 | }).catch(err => { 27 | throw new Error(err, 'can not connect pixiv server, retrying...') 28 | }) 29 | 30 | if (status !== 200) continue 31 | 32 | console.log('pixiv connect success, server ip: %s', ip) 33 | agent = staticAgent(ip) 34 | break 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/pixiv-fetch/base-option.js: -------------------------------------------------------------------------------- 1 | import config from '../../config.js' 2 | const cookie = config.pixiv.cookie 3 | 4 | export default { 5 | host: 'www.pixiv.net', 6 | port: 443, 7 | path: '/', 8 | method: 'GET', 9 | headers: { 10 | 'Referer': 'https://www.pixiv.net', 11 | 'Cookie': cookie, 12 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41' 13 | } 14 | } -------------------------------------------------------------------------------- /core/pixiv-fetch/default-option.js: -------------------------------------------------------------------------------- 1 | import { agent } from "./agent.js" 2 | import baseOption from "./base-option.js" 3 | 4 | export default { 5 | ...baseOption, agent 6 | } 7 | 8 | -------------------------------------------------------------------------------- /core/pixiv-fetch/fetch.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | import url from 'url' 3 | import defaultOption from "./default-option.js" 4 | 5 | /** 6 | * @param { https.RequestOptions | string } option 7 | * @return { Promise } 8 | */ 9 | export default async function pixivFetch(option) { 10 | if (typeof option === 'string') { 11 | const u = url.parse(option) 12 | option = { 13 | host: u.host, 14 | path: u.pathname || '/' + u.search || '' 15 | } 16 | } 17 | const connectOption = { ...defaultOption, ...option } 18 | return new Promise((res, rej) => { 19 | const request = https.request(connectOption, (response) => { 20 | let data = [] 21 | response.on('data', chunk => data.push(chunk)) 22 | response.on('end', () => res(Buffer.concat(data).toString())) 23 | }) 24 | request.on('error', rej) 25 | request.end() 26 | }) 27 | } 28 | 29 | /** 30 | * www.pixiv.net + $path + ?$query 31 | * @param {string} path 32 | * @param {object} [query] 33 | * @return { Promise } 34 | */ 35 | export async function pixivJsonFetch(path, query) { 36 | if (query) path += '?' + new URLSearchParams(query).toString() 37 | const json = await pixivFetch({path}).then(JSON.parse) 38 | if (json.error) throw new Error(json.message) 39 | return json.body 40 | } -------------------------------------------------------------------------------- /core/pixiv-fetch/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | default as pixivFetch, 3 | pixivJsonFetch 4 | } from './fetch.js' 5 | 6 | export { 7 | default as replaceURL 8 | } from './replace-url.js' -------------------------------------------------------------------------------- /core/pixiv-fetch/replace-url.js: -------------------------------------------------------------------------------- 1 | import http from 'http' 2 | import config from "../../config.js" 3 | 4 | export const rawHost = 'i.pximg.net' 5 | export const rawURL = 'https://' + rawHost 6 | 7 | const useOriginIP = config.proxy.useOriginIP 8 | const useLocalProxy = config.proxy.useLocalProxy 9 | 10 | export const localProxyURL = `http://${config.httpServer.host}:${config.httpServer.port}/proxy` 11 | export let imageServerOriginURL = 'http://' + config.proxy.imageHost[0] 12 | export let imageServerHost = config.proxy.imageHost[0] 13 | 14 | /** 15 | * @param {string} url 16 | * @return {string} 17 | */ 18 | export default !(useOriginIP || useLocalProxy) ? 19 | url => url : (useLocalProxy ? 20 | url => url.replace(rawURL, localProxyURL) : 21 | url => url.replace(rawURL, imageServerOriginURL) 22 | ) 23 | 24 | if (useOriginIP) { 25 | for (const hostIp of config.proxy.imageHost) { 26 | const {status, ip} = await new Promise((res, rej) => { 27 | const request = http.get( 28 | 'http://' + hostIp, 29 | r => res({status: r.statusCode, ip: hostIp}) 30 | ) 31 | 32 | request.on('error', rej) 33 | request.end() 34 | }).catch(err => { 35 | throw new Error(err, 'can not connect image server, retrying...') 36 | }) 37 | 38 | if (status !== 200 && status !== 304) continue 39 | 40 | console.log('image server connect success, server ip: %s', ip) 41 | imageServerOriginURL = 'http://' + ip 42 | imageServerHost = ip 43 | break 44 | } 45 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { pixivFetch, pixivJsonFetch } from './core/pixiv-fetch/index.js' 2 | export * from './core/api/module/index.js' -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixiv-api-http", 3 | "version": "0.0.10", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "pixiv-api-http", 9 | "version": "0.0.10", 10 | "license": "MIT", 11 | "dependencies": { 12 | "body-parser": "^1.20.1", 13 | "express": "^5.0.0-beta.1", 14 | "express-http-proxy": "^1.6.3", 15 | "ws": "^8.13.0" 16 | } 17 | }, 18 | "node_modules/accepts": { 19 | "version": "1.3.8", 20 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 21 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 22 | "dependencies": { 23 | "mime-types": "~2.1.34", 24 | "negotiator": "0.6.3" 25 | }, 26 | "engines": { 27 | "node": ">= 0.6" 28 | } 29 | }, 30 | "node_modules/array-flatten": { 31 | "version": "3.0.0", 32 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", 33 | "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" 34 | }, 35 | "node_modules/body-parser": { 36 | "version": "1.20.1", 37 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 38 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 39 | "dependencies": { 40 | "bytes": "3.1.2", 41 | "content-type": "~1.0.4", 42 | "debug": "2.6.9", 43 | "depd": "2.0.0", 44 | "destroy": "1.2.0", 45 | "http-errors": "2.0.0", 46 | "iconv-lite": "0.4.24", 47 | "on-finished": "2.4.1", 48 | "qs": "6.11.0", 49 | "raw-body": "2.5.1", 50 | "type-is": "~1.6.18", 51 | "unpipe": "1.0.0" 52 | }, 53 | "engines": { 54 | "node": ">= 0.8", 55 | "npm": "1.2.8000 || >= 1.4.16" 56 | } 57 | }, 58 | "node_modules/bytes": { 59 | "version": "3.1.2", 60 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 61 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 62 | "engines": { 63 | "node": ">= 0.8" 64 | } 65 | }, 66 | "node_modules/call-bind": { 67 | "version": "1.0.2", 68 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 69 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 70 | "dependencies": { 71 | "function-bind": "^1.1.1", 72 | "get-intrinsic": "^1.0.2" 73 | }, 74 | "funding": { 75 | "url": "https://github.com/sponsors/ljharb" 76 | } 77 | }, 78 | "node_modules/content-disposition": { 79 | "version": "0.5.4", 80 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 81 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 82 | "dependencies": { 83 | "safe-buffer": "5.2.1" 84 | }, 85 | "engines": { 86 | "node": ">= 0.6" 87 | } 88 | }, 89 | "node_modules/content-type": { 90 | "version": "1.0.5", 91 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 92 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 93 | "engines": { 94 | "node": ">= 0.6" 95 | } 96 | }, 97 | "node_modules/cookie": { 98 | "version": "0.4.1", 99 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 100 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 101 | "engines": { 102 | "node": ">= 0.6" 103 | } 104 | }, 105 | "node_modules/cookie-signature": { 106 | "version": "1.0.6", 107 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 108 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 109 | }, 110 | "node_modules/debug": { 111 | "version": "2.6.9", 112 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 113 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 114 | "dependencies": { 115 | "ms": "2.0.0" 116 | } 117 | }, 118 | "node_modules/depd": { 119 | "version": "2.0.0", 120 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 121 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 122 | "engines": { 123 | "node": ">= 0.8" 124 | } 125 | }, 126 | "node_modules/destroy": { 127 | "version": "1.2.0", 128 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 129 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 130 | "engines": { 131 | "node": ">= 0.8", 132 | "npm": "1.2.8000 || >= 1.4.16" 133 | } 134 | }, 135 | "node_modules/ee-first": { 136 | "version": "1.1.1", 137 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 138 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 139 | }, 140 | "node_modules/encodeurl": { 141 | "version": "1.0.2", 142 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 143 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 144 | "engines": { 145 | "node": ">= 0.8" 146 | } 147 | }, 148 | "node_modules/es6-promise": { 149 | "version": "4.2.8", 150 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 151 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 152 | }, 153 | "node_modules/escape-html": { 154 | "version": "1.0.3", 155 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 156 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 157 | }, 158 | "node_modules/etag": { 159 | "version": "1.8.1", 160 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 161 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 162 | "engines": { 163 | "node": ">= 0.6" 164 | } 165 | }, 166 | "node_modules/express": { 167 | "version": "5.0.0-beta.1", 168 | "resolved": "https://registry.npmjs.org/express/-/express-5.0.0-beta.1.tgz", 169 | "integrity": "sha512-KPtBrlZoQu2Ps0Ce/Imqtq73AB0KBJ8Gx59yZQ3pmDJU2/LhcoZETo03oSgtTQufbcLXt/WBITk/jMjl/WMyrQ==", 170 | "dependencies": { 171 | "accepts": "~1.3.7", 172 | "array-flatten": "3.0.0", 173 | "body-parser": "2.0.0-beta.1", 174 | "content-disposition": "0.5.4", 175 | "content-type": "~1.0.4", 176 | "cookie": "0.4.1", 177 | "cookie-signature": "1.0.6", 178 | "debug": "3.1.0", 179 | "depd": "~1.1.2", 180 | "encodeurl": "~1.0.2", 181 | "escape-html": "~1.0.3", 182 | "etag": "~1.8.1", 183 | "finalhandler": "~1.1.2", 184 | "fresh": "0.5.2", 185 | "merge-descriptors": "1.0.1", 186 | "methods": "~1.1.2", 187 | "mime-types": "~2.1.34", 188 | "on-finished": "~2.3.0", 189 | "parseurl": "~1.3.3", 190 | "path-is-absolute": "1.0.1", 191 | "proxy-addr": "~2.0.7", 192 | "qs": "6.9.6", 193 | "range-parser": "~1.2.1", 194 | "router": "2.0.0-beta.1", 195 | "safe-buffer": "5.2.1", 196 | "send": "1.0.0-beta.1", 197 | "serve-static": "2.0.0-beta.1", 198 | "setprototypeof": "1.2.0", 199 | "statuses": "~1.5.0", 200 | "type-is": "~1.6.18", 201 | "utils-merge": "1.0.1", 202 | "vary": "~1.1.2" 203 | }, 204 | "engines": { 205 | "node": ">= 4" 206 | } 207 | }, 208 | "node_modules/express-http-proxy": { 209 | "version": "1.6.3", 210 | "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-1.6.3.tgz", 211 | "integrity": "sha512-/l77JHcOUrDUX8V67E287VEUQT0lbm71gdGVoodnlWBziarYKgMcpqT7xvh/HM8Jv52phw8Bd8tY+a7QjOr7Yg==", 212 | "dependencies": { 213 | "debug": "^3.0.1", 214 | "es6-promise": "^4.1.1", 215 | "raw-body": "^2.3.0" 216 | }, 217 | "engines": { 218 | "node": ">=6.0.0" 219 | } 220 | }, 221 | "node_modules/express-http-proxy/node_modules/debug": { 222 | "version": "3.2.7", 223 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 224 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 225 | "dependencies": { 226 | "ms": "^2.1.1" 227 | } 228 | }, 229 | "node_modules/express-http-proxy/node_modules/ms": { 230 | "version": "2.1.3", 231 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 232 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 233 | }, 234 | "node_modules/express/node_modules/body-parser": { 235 | "version": "2.0.0-beta.1", 236 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.0-beta.1.tgz", 237 | "integrity": "sha512-I1v2bt2OdYqtmk8nEFZuEf+9Opb30DphYwTPDbgg/OorSAoJOuTpWyDrZaSWQw7FdoevbBRCP2+9z/halXSWcA==", 238 | "dependencies": { 239 | "bytes": "3.1.1", 240 | "content-type": "~1.0.4", 241 | "debug": "2.6.9", 242 | "depd": "~1.1.2", 243 | "http-errors": "1.8.1", 244 | "iconv-lite": "0.4.24", 245 | "on-finished": "~2.3.0", 246 | "qs": "6.9.6", 247 | "raw-body": "2.4.2", 248 | "type-is": "~1.6.18" 249 | }, 250 | "engines": { 251 | "node": ">= 0.10" 252 | } 253 | }, 254 | "node_modules/express/node_modules/body-parser/node_modules/debug": { 255 | "version": "2.6.9", 256 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 257 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 258 | "dependencies": { 259 | "ms": "2.0.0" 260 | } 261 | }, 262 | "node_modules/express/node_modules/bytes": { 263 | "version": "3.1.1", 264 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", 265 | "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", 266 | "engines": { 267 | "node": ">= 0.8" 268 | } 269 | }, 270 | "node_modules/express/node_modules/debug": { 271 | "version": "3.1.0", 272 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 273 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 274 | "dependencies": { 275 | "ms": "2.0.0" 276 | } 277 | }, 278 | "node_modules/express/node_modules/depd": { 279 | "version": "1.1.2", 280 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 281 | "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", 282 | "engines": { 283 | "node": ">= 0.6" 284 | } 285 | }, 286 | "node_modules/express/node_modules/http-errors": { 287 | "version": "1.8.1", 288 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 289 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 290 | "dependencies": { 291 | "depd": "~1.1.2", 292 | "inherits": "2.0.4", 293 | "setprototypeof": "1.2.0", 294 | "statuses": ">= 1.5.0 < 2", 295 | "toidentifier": "1.0.1" 296 | }, 297 | "engines": { 298 | "node": ">= 0.6" 299 | } 300 | }, 301 | "node_modules/express/node_modules/on-finished": { 302 | "version": "2.3.0", 303 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 304 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 305 | "dependencies": { 306 | "ee-first": "1.1.1" 307 | }, 308 | "engines": { 309 | "node": ">= 0.8" 310 | } 311 | }, 312 | "node_modules/express/node_modules/qs": { 313 | "version": "6.9.6", 314 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", 315 | "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", 316 | "engines": { 317 | "node": ">=0.6" 318 | }, 319 | "funding": { 320 | "url": "https://github.com/sponsors/ljharb" 321 | } 322 | }, 323 | "node_modules/express/node_modules/raw-body": { 324 | "version": "2.4.2", 325 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", 326 | "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", 327 | "dependencies": { 328 | "bytes": "3.1.1", 329 | "http-errors": "1.8.1", 330 | "iconv-lite": "0.4.24", 331 | "unpipe": "1.0.0" 332 | }, 333 | "engines": { 334 | "node": ">= 0.8" 335 | } 336 | }, 337 | "node_modules/express/node_modules/statuses": { 338 | "version": "1.5.0", 339 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 340 | "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", 341 | "engines": { 342 | "node": ">= 0.6" 343 | } 344 | }, 345 | "node_modules/finalhandler": { 346 | "version": "1.1.2", 347 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 348 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 349 | "dependencies": { 350 | "debug": "2.6.9", 351 | "encodeurl": "~1.0.2", 352 | "escape-html": "~1.0.3", 353 | "on-finished": "~2.3.0", 354 | "parseurl": "~1.3.3", 355 | "statuses": "~1.5.0", 356 | "unpipe": "~1.0.0" 357 | }, 358 | "engines": { 359 | "node": ">= 0.8" 360 | } 361 | }, 362 | "node_modules/finalhandler/node_modules/on-finished": { 363 | "version": "2.3.0", 364 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 365 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 366 | "dependencies": { 367 | "ee-first": "1.1.1" 368 | }, 369 | "engines": { 370 | "node": ">= 0.8" 371 | } 372 | }, 373 | "node_modules/finalhandler/node_modules/statuses": { 374 | "version": "1.5.0", 375 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 376 | "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", 377 | "engines": { 378 | "node": ">= 0.6" 379 | } 380 | }, 381 | "node_modules/forwarded": { 382 | "version": "0.2.0", 383 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 384 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 385 | "engines": { 386 | "node": ">= 0.6" 387 | } 388 | }, 389 | "node_modules/fresh": { 390 | "version": "0.5.2", 391 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 392 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 393 | "engines": { 394 | "node": ">= 0.6" 395 | } 396 | }, 397 | "node_modules/function-bind": { 398 | "version": "1.1.1", 399 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 400 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 401 | }, 402 | "node_modules/get-intrinsic": { 403 | "version": "1.2.0", 404 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 405 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 406 | "dependencies": { 407 | "function-bind": "^1.1.1", 408 | "has": "^1.0.3", 409 | "has-symbols": "^1.0.3" 410 | }, 411 | "funding": { 412 | "url": "https://github.com/sponsors/ljharb" 413 | } 414 | }, 415 | "node_modules/has": { 416 | "version": "1.0.3", 417 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 418 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 419 | "dependencies": { 420 | "function-bind": "^1.1.1" 421 | }, 422 | "engines": { 423 | "node": ">= 0.4.0" 424 | } 425 | }, 426 | "node_modules/has-symbols": { 427 | "version": "1.0.3", 428 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 429 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 430 | "engines": { 431 | "node": ">= 0.4" 432 | }, 433 | "funding": { 434 | "url": "https://github.com/sponsors/ljharb" 435 | } 436 | }, 437 | "node_modules/http-errors": { 438 | "version": "2.0.0", 439 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 440 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 441 | "dependencies": { 442 | "depd": "2.0.0", 443 | "inherits": "2.0.4", 444 | "setprototypeof": "1.2.0", 445 | "statuses": "2.0.1", 446 | "toidentifier": "1.0.1" 447 | }, 448 | "engines": { 449 | "node": ">= 0.8" 450 | } 451 | }, 452 | "node_modules/iconv-lite": { 453 | "version": "0.4.24", 454 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 455 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 456 | "dependencies": { 457 | "safer-buffer": ">= 2.1.2 < 3" 458 | }, 459 | "engines": { 460 | "node": ">=0.10.0" 461 | } 462 | }, 463 | "node_modules/inherits": { 464 | "version": "2.0.4", 465 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 466 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 467 | }, 468 | "node_modules/ipaddr.js": { 469 | "version": "1.9.1", 470 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 471 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 472 | "engines": { 473 | "node": ">= 0.10" 474 | } 475 | }, 476 | "node_modules/media-typer": { 477 | "version": "0.3.0", 478 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 479 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 480 | "engines": { 481 | "node": ">= 0.6" 482 | } 483 | }, 484 | "node_modules/merge-descriptors": { 485 | "version": "1.0.1", 486 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 487 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 488 | }, 489 | "node_modules/methods": { 490 | "version": "1.1.2", 491 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 492 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 493 | "engines": { 494 | "node": ">= 0.6" 495 | } 496 | }, 497 | "node_modules/mime-db": { 498 | "version": "1.52.0", 499 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 500 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 501 | "engines": { 502 | "node": ">= 0.6" 503 | } 504 | }, 505 | "node_modules/mime-types": { 506 | "version": "2.1.35", 507 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 508 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 509 | "dependencies": { 510 | "mime-db": "1.52.0" 511 | }, 512 | "engines": { 513 | "node": ">= 0.6" 514 | } 515 | }, 516 | "node_modules/ms": { 517 | "version": "2.0.0", 518 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 519 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 520 | }, 521 | "node_modules/negotiator": { 522 | "version": "0.6.3", 523 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 524 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 525 | "engines": { 526 | "node": ">= 0.6" 527 | } 528 | }, 529 | "node_modules/object-inspect": { 530 | "version": "1.12.3", 531 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 532 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 533 | "funding": { 534 | "url": "https://github.com/sponsors/ljharb" 535 | } 536 | }, 537 | "node_modules/on-finished": { 538 | "version": "2.4.1", 539 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 540 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 541 | "dependencies": { 542 | "ee-first": "1.1.1" 543 | }, 544 | "engines": { 545 | "node": ">= 0.8" 546 | } 547 | }, 548 | "node_modules/parseurl": { 549 | "version": "1.3.3", 550 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 551 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 552 | "engines": { 553 | "node": ">= 0.8" 554 | } 555 | }, 556 | "node_modules/path-is-absolute": { 557 | "version": "1.0.1", 558 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 559 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 560 | "engines": { 561 | "node": ">=0.10.0" 562 | } 563 | }, 564 | "node_modules/path-to-regexp": { 565 | "version": "3.2.0", 566 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", 567 | "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" 568 | }, 569 | "node_modules/proxy-addr": { 570 | "version": "2.0.7", 571 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 572 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 573 | "dependencies": { 574 | "forwarded": "0.2.0", 575 | "ipaddr.js": "1.9.1" 576 | }, 577 | "engines": { 578 | "node": ">= 0.10" 579 | } 580 | }, 581 | "node_modules/qs": { 582 | "version": "6.11.0", 583 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 584 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 585 | "dependencies": { 586 | "side-channel": "^1.0.4" 587 | }, 588 | "engines": { 589 | "node": ">=0.6" 590 | }, 591 | "funding": { 592 | "url": "https://github.com/sponsors/ljharb" 593 | } 594 | }, 595 | "node_modules/range-parser": { 596 | "version": "1.2.1", 597 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 598 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 599 | "engines": { 600 | "node": ">= 0.6" 601 | } 602 | }, 603 | "node_modules/raw-body": { 604 | "version": "2.5.1", 605 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 606 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 607 | "dependencies": { 608 | "bytes": "3.1.2", 609 | "http-errors": "2.0.0", 610 | "iconv-lite": "0.4.24", 611 | "unpipe": "1.0.0" 612 | }, 613 | "engines": { 614 | "node": ">= 0.8" 615 | } 616 | }, 617 | "node_modules/router": { 618 | "version": "2.0.0-beta.1", 619 | "resolved": "https://registry.npmjs.org/router/-/router-2.0.0-beta.1.tgz", 620 | "integrity": "sha512-GLoYgkhAGAiwVda5nt6Qd4+5RAPuQ4WIYLlZ+mxfYICI+22gnIB3eCfmhgV8+uJNPS1/39DOYi/vdrrz0/ouKA==", 621 | "dependencies": { 622 | "array-flatten": "3.0.0", 623 | "methods": "~1.1.2", 624 | "parseurl": "~1.3.3", 625 | "path-to-regexp": "3.2.0", 626 | "setprototypeof": "1.2.0", 627 | "utils-merge": "1.0.1" 628 | }, 629 | "engines": { 630 | "node": ">= 0.10" 631 | } 632 | }, 633 | "node_modules/safe-buffer": { 634 | "version": "5.2.1", 635 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 636 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 637 | "funding": [ 638 | { 639 | "type": "github", 640 | "url": "https://github.com/sponsors/feross" 641 | }, 642 | { 643 | "type": "patreon", 644 | "url": "https://www.patreon.com/feross" 645 | }, 646 | { 647 | "type": "consulting", 648 | "url": "https://feross.org/support" 649 | } 650 | ] 651 | }, 652 | "node_modules/safer-buffer": { 653 | "version": "2.1.2", 654 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 655 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 656 | }, 657 | "node_modules/send": { 658 | "version": "1.0.0-beta.1", 659 | "resolved": "https://registry.npmjs.org/send/-/send-1.0.0-beta.1.tgz", 660 | "integrity": "sha512-OKTRokcl/oo34O8+6aUpj8Jf2Bjw2D0tZzmX0/RvyfVC9ZOZW+HPAWAlhS817IsRaCnzYX1z++h2kHFr2/KNRg==", 661 | "dependencies": { 662 | "debug": "3.1.0", 663 | "destroy": "~1.0.4", 664 | "encodeurl": "~1.0.2", 665 | "escape-html": "~1.0.3", 666 | "etag": "~1.8.1", 667 | "fresh": "0.5.2", 668 | "http-errors": "1.8.1", 669 | "mime-types": "~2.1.34", 670 | "ms": "2.1.3", 671 | "on-finished": "~2.3.0", 672 | "range-parser": "~1.2.1", 673 | "statuses": "~1.5.0" 674 | }, 675 | "engines": { 676 | "node": ">= 0.10" 677 | } 678 | }, 679 | "node_modules/send/node_modules/debug": { 680 | "version": "3.1.0", 681 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 682 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 683 | "dependencies": { 684 | "ms": "2.0.0" 685 | } 686 | }, 687 | "node_modules/send/node_modules/debug/node_modules/ms": { 688 | "version": "2.0.0", 689 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 690 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 691 | }, 692 | "node_modules/send/node_modules/depd": { 693 | "version": "1.1.2", 694 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 695 | "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", 696 | "engines": { 697 | "node": ">= 0.6" 698 | } 699 | }, 700 | "node_modules/send/node_modules/destroy": { 701 | "version": "1.0.4", 702 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 703 | "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" 704 | }, 705 | "node_modules/send/node_modules/http-errors": { 706 | "version": "1.8.1", 707 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 708 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 709 | "dependencies": { 710 | "depd": "~1.1.2", 711 | "inherits": "2.0.4", 712 | "setprototypeof": "1.2.0", 713 | "statuses": ">= 1.5.0 < 2", 714 | "toidentifier": "1.0.1" 715 | }, 716 | "engines": { 717 | "node": ">= 0.6" 718 | } 719 | }, 720 | "node_modules/send/node_modules/ms": { 721 | "version": "2.1.3", 722 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 723 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 724 | }, 725 | "node_modules/send/node_modules/on-finished": { 726 | "version": "2.3.0", 727 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 728 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 729 | "dependencies": { 730 | "ee-first": "1.1.1" 731 | }, 732 | "engines": { 733 | "node": ">= 0.8" 734 | } 735 | }, 736 | "node_modules/send/node_modules/statuses": { 737 | "version": "1.5.0", 738 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 739 | "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", 740 | "engines": { 741 | "node": ">= 0.6" 742 | } 743 | }, 744 | "node_modules/serve-static": { 745 | "version": "2.0.0-beta.1", 746 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.0.0-beta.1.tgz", 747 | "integrity": "sha512-DEJ9on/tQeFO2Omj7ovT02lCp1YgP4Kb8W2lv2o/4keTFAbgc8HtH3yPd47++2wv9lvQeqiA7FHFDe5+8c4XpA==", 748 | "dependencies": { 749 | "encodeurl": "~1.0.2", 750 | "escape-html": "~1.0.3", 751 | "parseurl": "~1.3.3", 752 | "send": "1.0.0-beta.1" 753 | }, 754 | "engines": { 755 | "node": ">= 0.10" 756 | } 757 | }, 758 | "node_modules/setprototypeof": { 759 | "version": "1.2.0", 760 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 761 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 762 | }, 763 | "node_modules/side-channel": { 764 | "version": "1.0.4", 765 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 766 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 767 | "dependencies": { 768 | "call-bind": "^1.0.0", 769 | "get-intrinsic": "^1.0.2", 770 | "object-inspect": "^1.9.0" 771 | }, 772 | "funding": { 773 | "url": "https://github.com/sponsors/ljharb" 774 | } 775 | }, 776 | "node_modules/statuses": { 777 | "version": "2.0.1", 778 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 779 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 780 | "engines": { 781 | "node": ">= 0.8" 782 | } 783 | }, 784 | "node_modules/toidentifier": { 785 | "version": "1.0.1", 786 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 787 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 788 | "engines": { 789 | "node": ">=0.6" 790 | } 791 | }, 792 | "node_modules/type-is": { 793 | "version": "1.6.18", 794 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 795 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 796 | "dependencies": { 797 | "media-typer": "0.3.0", 798 | "mime-types": "~2.1.24" 799 | }, 800 | "engines": { 801 | "node": ">= 0.6" 802 | } 803 | }, 804 | "node_modules/unpipe": { 805 | "version": "1.0.0", 806 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 807 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 808 | "engines": { 809 | "node": ">= 0.8" 810 | } 811 | }, 812 | "node_modules/utils-merge": { 813 | "version": "1.0.1", 814 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 815 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 816 | "engines": { 817 | "node": ">= 0.4.0" 818 | } 819 | }, 820 | "node_modules/vary": { 821 | "version": "1.1.2", 822 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 823 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 824 | "engines": { 825 | "node": ">= 0.8" 826 | } 827 | }, 828 | "node_modules/ws": { 829 | "version": "8.13.0", 830 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", 831 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", 832 | "engines": { 833 | "node": ">=10.0.0" 834 | }, 835 | "peerDependencies": { 836 | "bufferutil": "^4.0.1", 837 | "utf-8-validate": ">=5.0.2" 838 | }, 839 | "peerDependenciesMeta": { 840 | "bufferutil": { 841 | "optional": true 842 | }, 843 | "utf-8-validate": { 844 | "optional": true 845 | } 846 | } 847 | } 848 | }, 849 | "dependencies": { 850 | "accepts": { 851 | "version": "1.3.8", 852 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 853 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 854 | "requires": { 855 | "mime-types": "~2.1.34", 856 | "negotiator": "0.6.3" 857 | } 858 | }, 859 | "array-flatten": { 860 | "version": "3.0.0", 861 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", 862 | "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" 863 | }, 864 | "body-parser": { 865 | "version": "1.20.1", 866 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 867 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 868 | "requires": { 869 | "bytes": "3.1.2", 870 | "content-type": "~1.0.4", 871 | "debug": "2.6.9", 872 | "depd": "2.0.0", 873 | "destroy": "1.2.0", 874 | "http-errors": "2.0.0", 875 | "iconv-lite": "0.4.24", 876 | "on-finished": "2.4.1", 877 | "qs": "6.11.0", 878 | "raw-body": "2.5.1", 879 | "type-is": "~1.6.18", 880 | "unpipe": "1.0.0" 881 | } 882 | }, 883 | "bytes": { 884 | "version": "3.1.2", 885 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 886 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 887 | }, 888 | "call-bind": { 889 | "version": "1.0.2", 890 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 891 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 892 | "requires": { 893 | "function-bind": "^1.1.1", 894 | "get-intrinsic": "^1.0.2" 895 | } 896 | }, 897 | "content-disposition": { 898 | "version": "0.5.4", 899 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 900 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 901 | "requires": { 902 | "safe-buffer": "5.2.1" 903 | } 904 | }, 905 | "content-type": { 906 | "version": "1.0.5", 907 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 908 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" 909 | }, 910 | "cookie": { 911 | "version": "0.4.1", 912 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 913 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 914 | }, 915 | "cookie-signature": { 916 | "version": "1.0.6", 917 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 918 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 919 | }, 920 | "debug": { 921 | "version": "2.6.9", 922 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 923 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 924 | "requires": { 925 | "ms": "2.0.0" 926 | } 927 | }, 928 | "depd": { 929 | "version": "2.0.0", 930 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 931 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 932 | }, 933 | "destroy": { 934 | "version": "1.2.0", 935 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 936 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 937 | }, 938 | "ee-first": { 939 | "version": "1.1.1", 940 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 941 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 942 | }, 943 | "encodeurl": { 944 | "version": "1.0.2", 945 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 946 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 947 | }, 948 | "es6-promise": { 949 | "version": "4.2.8", 950 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 951 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 952 | }, 953 | "escape-html": { 954 | "version": "1.0.3", 955 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 956 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 957 | }, 958 | "etag": { 959 | "version": "1.8.1", 960 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 961 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 962 | }, 963 | "express": { 964 | "version": "5.0.0-beta.1", 965 | "resolved": "https://registry.npmjs.org/express/-/express-5.0.0-beta.1.tgz", 966 | "integrity": "sha512-KPtBrlZoQu2Ps0Ce/Imqtq73AB0KBJ8Gx59yZQ3pmDJU2/LhcoZETo03oSgtTQufbcLXt/WBITk/jMjl/WMyrQ==", 967 | "requires": { 968 | "accepts": "~1.3.7", 969 | "array-flatten": "3.0.0", 970 | "body-parser": "2.0.0-beta.1", 971 | "content-disposition": "0.5.4", 972 | "content-type": "~1.0.4", 973 | "cookie": "0.4.1", 974 | "cookie-signature": "1.0.6", 975 | "debug": "3.1.0", 976 | "depd": "~1.1.2", 977 | "encodeurl": "~1.0.2", 978 | "escape-html": "~1.0.3", 979 | "etag": "~1.8.1", 980 | "finalhandler": "~1.1.2", 981 | "fresh": "0.5.2", 982 | "merge-descriptors": "1.0.1", 983 | "methods": "~1.1.2", 984 | "mime-types": "~2.1.34", 985 | "on-finished": "~2.3.0", 986 | "parseurl": "~1.3.3", 987 | "path-is-absolute": "1.0.1", 988 | "proxy-addr": "~2.0.7", 989 | "qs": "6.9.6", 990 | "range-parser": "~1.2.1", 991 | "router": "2.0.0-beta.1", 992 | "safe-buffer": "5.2.1", 993 | "send": "1.0.0-beta.1", 994 | "serve-static": "2.0.0-beta.1", 995 | "setprototypeof": "1.2.0", 996 | "statuses": "~1.5.0", 997 | "type-is": "~1.6.18", 998 | "utils-merge": "1.0.1", 999 | "vary": "~1.1.2" 1000 | }, 1001 | "dependencies": { 1002 | "body-parser": { 1003 | "version": "2.0.0-beta.1", 1004 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.0-beta.1.tgz", 1005 | "integrity": "sha512-I1v2bt2OdYqtmk8nEFZuEf+9Opb30DphYwTPDbgg/OorSAoJOuTpWyDrZaSWQw7FdoevbBRCP2+9z/halXSWcA==", 1006 | "requires": { 1007 | "bytes": "3.1.1", 1008 | "content-type": "~1.0.4", 1009 | "debug": "2.6.9", 1010 | "depd": "~1.1.2", 1011 | "http-errors": "1.8.1", 1012 | "iconv-lite": "0.4.24", 1013 | "on-finished": "~2.3.0", 1014 | "qs": "6.9.6", 1015 | "raw-body": "2.4.2", 1016 | "type-is": "~1.6.18" 1017 | }, 1018 | "dependencies": { 1019 | "debug": { 1020 | "version": "2.6.9", 1021 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1022 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1023 | "requires": { 1024 | "ms": "2.0.0" 1025 | } 1026 | } 1027 | } 1028 | }, 1029 | "bytes": { 1030 | "version": "3.1.1", 1031 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", 1032 | "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" 1033 | }, 1034 | "debug": { 1035 | "version": "3.1.0", 1036 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 1037 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 1038 | "requires": { 1039 | "ms": "2.0.0" 1040 | } 1041 | }, 1042 | "depd": { 1043 | "version": "1.1.2", 1044 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1045 | "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" 1046 | }, 1047 | "http-errors": { 1048 | "version": "1.8.1", 1049 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 1050 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 1051 | "requires": { 1052 | "depd": "~1.1.2", 1053 | "inherits": "2.0.4", 1054 | "setprototypeof": "1.2.0", 1055 | "statuses": ">= 1.5.0 < 2", 1056 | "toidentifier": "1.0.1" 1057 | } 1058 | }, 1059 | "on-finished": { 1060 | "version": "2.3.0", 1061 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1062 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 1063 | "requires": { 1064 | "ee-first": "1.1.1" 1065 | } 1066 | }, 1067 | "qs": { 1068 | "version": "6.9.6", 1069 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", 1070 | "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" 1071 | }, 1072 | "raw-body": { 1073 | "version": "2.4.2", 1074 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", 1075 | "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", 1076 | "requires": { 1077 | "bytes": "3.1.1", 1078 | "http-errors": "1.8.1", 1079 | "iconv-lite": "0.4.24", 1080 | "unpipe": "1.0.0" 1081 | } 1082 | }, 1083 | "statuses": { 1084 | "version": "1.5.0", 1085 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1086 | "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" 1087 | } 1088 | } 1089 | }, 1090 | "express-http-proxy": { 1091 | "version": "1.6.3", 1092 | "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-1.6.3.tgz", 1093 | "integrity": "sha512-/l77JHcOUrDUX8V67E287VEUQT0lbm71gdGVoodnlWBziarYKgMcpqT7xvh/HM8Jv52phw8Bd8tY+a7QjOr7Yg==", 1094 | "requires": { 1095 | "debug": "^3.0.1", 1096 | "es6-promise": "^4.1.1", 1097 | "raw-body": "^2.3.0" 1098 | }, 1099 | "dependencies": { 1100 | "debug": { 1101 | "version": "3.2.7", 1102 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 1103 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 1104 | "requires": { 1105 | "ms": "^2.1.1" 1106 | } 1107 | }, 1108 | "ms": { 1109 | "version": "2.1.3", 1110 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1111 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1112 | } 1113 | } 1114 | }, 1115 | "finalhandler": { 1116 | "version": "1.1.2", 1117 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 1118 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 1119 | "requires": { 1120 | "debug": "2.6.9", 1121 | "encodeurl": "~1.0.2", 1122 | "escape-html": "~1.0.3", 1123 | "on-finished": "~2.3.0", 1124 | "parseurl": "~1.3.3", 1125 | "statuses": "~1.5.0", 1126 | "unpipe": "~1.0.0" 1127 | }, 1128 | "dependencies": { 1129 | "on-finished": { 1130 | "version": "2.3.0", 1131 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1132 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 1133 | "requires": { 1134 | "ee-first": "1.1.1" 1135 | } 1136 | }, 1137 | "statuses": { 1138 | "version": "1.5.0", 1139 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1140 | "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" 1141 | } 1142 | } 1143 | }, 1144 | "forwarded": { 1145 | "version": "0.2.0", 1146 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1147 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 1148 | }, 1149 | "fresh": { 1150 | "version": "0.5.2", 1151 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1152 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 1153 | }, 1154 | "function-bind": { 1155 | "version": "1.1.1", 1156 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1157 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 1158 | }, 1159 | "get-intrinsic": { 1160 | "version": "1.2.0", 1161 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 1162 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 1163 | "requires": { 1164 | "function-bind": "^1.1.1", 1165 | "has": "^1.0.3", 1166 | "has-symbols": "^1.0.3" 1167 | } 1168 | }, 1169 | "has": { 1170 | "version": "1.0.3", 1171 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1172 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1173 | "requires": { 1174 | "function-bind": "^1.1.1" 1175 | } 1176 | }, 1177 | "has-symbols": { 1178 | "version": "1.0.3", 1179 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1180 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 1181 | }, 1182 | "http-errors": { 1183 | "version": "2.0.0", 1184 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1185 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1186 | "requires": { 1187 | "depd": "2.0.0", 1188 | "inherits": "2.0.4", 1189 | "setprototypeof": "1.2.0", 1190 | "statuses": "2.0.1", 1191 | "toidentifier": "1.0.1" 1192 | } 1193 | }, 1194 | "iconv-lite": { 1195 | "version": "0.4.24", 1196 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1197 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1198 | "requires": { 1199 | "safer-buffer": ">= 2.1.2 < 3" 1200 | } 1201 | }, 1202 | "inherits": { 1203 | "version": "2.0.4", 1204 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1205 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1206 | }, 1207 | "ipaddr.js": { 1208 | "version": "1.9.1", 1209 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1210 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1211 | }, 1212 | "media-typer": { 1213 | "version": "0.3.0", 1214 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1215 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 1216 | }, 1217 | "merge-descriptors": { 1218 | "version": "1.0.1", 1219 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1220 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1221 | }, 1222 | "methods": { 1223 | "version": "1.1.2", 1224 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1225 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 1226 | }, 1227 | "mime-db": { 1228 | "version": "1.52.0", 1229 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1230 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 1231 | }, 1232 | "mime-types": { 1233 | "version": "2.1.35", 1234 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1235 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1236 | "requires": { 1237 | "mime-db": "1.52.0" 1238 | } 1239 | }, 1240 | "ms": { 1241 | "version": "2.0.0", 1242 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1243 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1244 | }, 1245 | "negotiator": { 1246 | "version": "0.6.3", 1247 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1248 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 1249 | }, 1250 | "object-inspect": { 1251 | "version": "1.12.3", 1252 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 1253 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" 1254 | }, 1255 | "on-finished": { 1256 | "version": "2.4.1", 1257 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1258 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1259 | "requires": { 1260 | "ee-first": "1.1.1" 1261 | } 1262 | }, 1263 | "parseurl": { 1264 | "version": "1.3.3", 1265 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1266 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1267 | }, 1268 | "path-is-absolute": { 1269 | "version": "1.0.1", 1270 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1271 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 1272 | }, 1273 | "path-to-regexp": { 1274 | "version": "3.2.0", 1275 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", 1276 | "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" 1277 | }, 1278 | "proxy-addr": { 1279 | "version": "2.0.7", 1280 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1281 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1282 | "requires": { 1283 | "forwarded": "0.2.0", 1284 | "ipaddr.js": "1.9.1" 1285 | } 1286 | }, 1287 | "qs": { 1288 | "version": "6.11.0", 1289 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 1290 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 1291 | "requires": { 1292 | "side-channel": "^1.0.4" 1293 | } 1294 | }, 1295 | "range-parser": { 1296 | "version": "1.2.1", 1297 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1298 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1299 | }, 1300 | "raw-body": { 1301 | "version": "2.5.1", 1302 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 1303 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 1304 | "requires": { 1305 | "bytes": "3.1.2", 1306 | "http-errors": "2.0.0", 1307 | "iconv-lite": "0.4.24", 1308 | "unpipe": "1.0.0" 1309 | } 1310 | }, 1311 | "router": { 1312 | "version": "2.0.0-beta.1", 1313 | "resolved": "https://registry.npmjs.org/router/-/router-2.0.0-beta.1.tgz", 1314 | "integrity": "sha512-GLoYgkhAGAiwVda5nt6Qd4+5RAPuQ4WIYLlZ+mxfYICI+22gnIB3eCfmhgV8+uJNPS1/39DOYi/vdrrz0/ouKA==", 1315 | "requires": { 1316 | "array-flatten": "3.0.0", 1317 | "methods": "~1.1.2", 1318 | "parseurl": "~1.3.3", 1319 | "path-to-regexp": "3.2.0", 1320 | "setprototypeof": "1.2.0", 1321 | "utils-merge": "1.0.1" 1322 | } 1323 | }, 1324 | "safe-buffer": { 1325 | "version": "5.2.1", 1326 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1327 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1328 | }, 1329 | "safer-buffer": { 1330 | "version": "2.1.2", 1331 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1332 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1333 | }, 1334 | "send": { 1335 | "version": "1.0.0-beta.1", 1336 | "resolved": "https://registry.npmjs.org/send/-/send-1.0.0-beta.1.tgz", 1337 | "integrity": "sha512-OKTRokcl/oo34O8+6aUpj8Jf2Bjw2D0tZzmX0/RvyfVC9ZOZW+HPAWAlhS817IsRaCnzYX1z++h2kHFr2/KNRg==", 1338 | "requires": { 1339 | "debug": "3.1.0", 1340 | "destroy": "~1.0.4", 1341 | "encodeurl": "~1.0.2", 1342 | "escape-html": "~1.0.3", 1343 | "etag": "~1.8.1", 1344 | "fresh": "0.5.2", 1345 | "http-errors": "1.8.1", 1346 | "mime-types": "~2.1.34", 1347 | "ms": "2.1.3", 1348 | "on-finished": "~2.3.0", 1349 | "range-parser": "~1.2.1", 1350 | "statuses": "~1.5.0" 1351 | }, 1352 | "dependencies": { 1353 | "debug": { 1354 | "version": "3.1.0", 1355 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 1356 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 1357 | "requires": { 1358 | "ms": "2.0.0" 1359 | }, 1360 | "dependencies": { 1361 | "ms": { 1362 | "version": "2.0.0", 1363 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1364 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1365 | } 1366 | } 1367 | }, 1368 | "depd": { 1369 | "version": "1.1.2", 1370 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1371 | "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" 1372 | }, 1373 | "destroy": { 1374 | "version": "1.0.4", 1375 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 1376 | "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" 1377 | }, 1378 | "http-errors": { 1379 | "version": "1.8.1", 1380 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 1381 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 1382 | "requires": { 1383 | "depd": "~1.1.2", 1384 | "inherits": "2.0.4", 1385 | "setprototypeof": "1.2.0", 1386 | "statuses": ">= 1.5.0 < 2", 1387 | "toidentifier": "1.0.1" 1388 | } 1389 | }, 1390 | "ms": { 1391 | "version": "2.1.3", 1392 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1393 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1394 | }, 1395 | "on-finished": { 1396 | "version": "2.3.0", 1397 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1398 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 1399 | "requires": { 1400 | "ee-first": "1.1.1" 1401 | } 1402 | }, 1403 | "statuses": { 1404 | "version": "1.5.0", 1405 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1406 | "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" 1407 | } 1408 | } 1409 | }, 1410 | "serve-static": { 1411 | "version": "2.0.0-beta.1", 1412 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.0.0-beta.1.tgz", 1413 | "integrity": "sha512-DEJ9on/tQeFO2Omj7ovT02lCp1YgP4Kb8W2lv2o/4keTFAbgc8HtH3yPd47++2wv9lvQeqiA7FHFDe5+8c4XpA==", 1414 | "requires": { 1415 | "encodeurl": "~1.0.2", 1416 | "escape-html": "~1.0.3", 1417 | "parseurl": "~1.3.3", 1418 | "send": "1.0.0-beta.1" 1419 | } 1420 | }, 1421 | "setprototypeof": { 1422 | "version": "1.2.0", 1423 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1424 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1425 | }, 1426 | "side-channel": { 1427 | "version": "1.0.4", 1428 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1429 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1430 | "requires": { 1431 | "call-bind": "^1.0.0", 1432 | "get-intrinsic": "^1.0.2", 1433 | "object-inspect": "^1.9.0" 1434 | } 1435 | }, 1436 | "statuses": { 1437 | "version": "2.0.1", 1438 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1439 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 1440 | }, 1441 | "toidentifier": { 1442 | "version": "1.0.1", 1443 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1444 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 1445 | }, 1446 | "type-is": { 1447 | "version": "1.6.18", 1448 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1449 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1450 | "requires": { 1451 | "media-typer": "0.3.0", 1452 | "mime-types": "~2.1.24" 1453 | } 1454 | }, 1455 | "unpipe": { 1456 | "version": "1.0.0", 1457 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1458 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 1459 | }, 1460 | "utils-merge": { 1461 | "version": "1.0.1", 1462 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1463 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 1464 | }, 1465 | "vary": { 1466 | "version": "1.1.2", 1467 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1468 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 1469 | }, 1470 | "ws": { 1471 | "version": "8.13.0", 1472 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", 1473 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", 1474 | "requires": {} 1475 | } 1476 | } 1477 | } 1478 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixiv-api-http", 3 | "version": "0.0.10", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node core/api/app" 8 | }, 9 | "author": "Dituon", 10 | "license": "MIT", 11 | "type": "module", 12 | "dependencies": { 13 | "body-parser": "^1.20.1", 14 | "express": "^5.0.0-beta.1", 15 | "express-http-proxy": "^1.6.3", 16 | "ws": "^8.13.0" 17 | } 18 | } 19 | --------------------------------------------------------------------------------