diff --git a/.env b/.env new file mode 100644 index 0000000..0fa82a2 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +# 服务端口 +PORT=6688 + +# 允许的域名 +ALLOWED_DOMAIN = '*' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f636f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +test.md diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..f301c61 --- /dev/null +++ b/.hintrc @@ -0,0 +1,13 @@ +{ + "extends": [ + "development" + ], + "hints": { + "meta-viewport": "off", + "disown-opener": "off", + "button-type": "off", + "axe/aria": "off", + "no-inline-styles": "off", + "axe/text-alternatives": "off" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f7eab9 --- /dev/null +++ b/README.md @@ -0,0 +1,154 @@ +
+logo +

今日热榜

+

一个聚合热门数据的 API 接口

+
+ +## 示例 + +> 这里是使用该 API 的示例站点 + +- [今日热榜 - https://hot.imsyy.top/](https://hot.imsyy.top/) + +## 总览 + +> 🟢 状态正常 +> 🟠 可能失效 +> 🔴 无法使用 + +| **站点** | **类别** | **调用名称** | **状态** | +|--------|---------|-----------|--------| +| 哔哩哔哩 | 热门榜 | bilibili | 🟢 | +| 知乎 | 热榜 | zhihu | 🟢 | +| 百度 | 热搜榜 | baidu | 🟢 | +| 百度贴吧 | 热议榜 | tieba | 🟢 | +| 少数派 | 热榜 | sspai | 🟢 | +| IT之家 | 热榜 | ithome | 🟠 | +| 澎湃新闻 | 热榜 | thepaper | 🟢 | +| 今日头条 | 热榜 | toutiao | 🟢 | +| 微博热搜 | 热搜榜 | weibo | 🟢 | +| 36氪 | 热榜 | 36kr | 🟢 | +| 腾讯新闻 | 热点榜 | newsqq | 🔴 | + +## 调用 + +### 获取榜单数据 + +> 获取数据只需在域名后面加上上方列表中的调用名称即可 + +```http +GET https://api-hot.imsyy.top/bilibili/ +``` + +
+调用示例 + +```json +{ + "code": 200, + "message": "获取成功", + "title": "哔哩哔哩", // 榜单名称 + "subtitle": "热门榜", // 榜单类别 + "from": "server", // 此处返回是最新数据还是缓存 + "total": 100, // 数据总数 + "updateTime": "2023-03-14T07:40:51.846Z", // 数据获取时间 + "data": [ + { + "id": "BV1E84y1A7z2", + "title": "假如我的校园是一款RPG游戏!", + "desc": "所有取景都是在学校里面拍的,都是真实存在的场景哦!", + "pic": "http://i2.hdslb.com/bfs/archive/a24e442d0aae6d488db023c4ddcb450e9f2bf5f3.jpg", + "owner": { + "mid": 424658638, + "name": "四夕小田木_已黑化_", + "face": "https://i1.hdslb.com/bfs/face/afd9ba47933edc4842ccbeba2891a25465d1cf77.jpg" + }, + "data": { + "aid": 610872610, + "view": 4178745, + "danmaku": 4229, + "reply": 5317, + "favorite": 91020, + "coin": 133596, + "share": 46227, + "now_rank": 0, + "his_rank": 1, + "like": 616519, + "dislike": 0, + "vt": 0, + "vv": 0 + }, + "url": "https://b23.tv/BV1E84y1A7z2", + "mobileUrl": "https://m.bilibili.com/video/BV1E84y1A7z2" + }, + ... + ] +} +``` +
+ +### 获取榜单最新数据 + +> 获取最新数据只需在原链接后面加上 `/new`,这样就会直接从服务端拉取最新数据,不会从本地缓存中读取 + +```http +GET https://api-hot.imsyy.top/bilibili/new +``` + +
+调用示例 + +```json +{ + "code": 200, + "message": "获取成功", + "title": "哔哩哔哩", // 榜单名称 + "subtitle": "热门榜", // 榜单类别 + "total": 100, // 数据总数 + "updateTime": "2023-03-14T07:40:51.846Z", // 数据获取时间 + "data": [ + { + "id": "BV1E84y1A7z2", + "title": "假如我的校园是一款RPG游戏!", + "desc": "所有取景都是在学校里面拍的,都是真实存在的场景哦!", + "pic": "http://i2.hdslb.com/bfs/archive/a24e442d0aae6d488db023c4ddcb450e9f2bf5f3.jpg", + "owner": { + "mid": 424658638, + "name": "四夕小田木_已黑化_", + "face": "https://i1.hdslb.com/bfs/face/afd9ba47933edc4842ccbeba2891a25465d1cf77.jpg" + }, + "data": { + "aid": 610872610, + "view": 4178745, + "danmaku": 4229, + "reply": 5317, + "favorite": 91020, + "coin": 133596, + "share": 46227, + "now_rank": 0, + "his_rank": 1, + "like": 616519, + "dislike": 0, + "vt": 0, + "vv": 0 + }, + "url": "https://b23.tv/BV1E84y1A7z2", + "mobileUrl": "https://m.bilibili.com/video/BV1E84y1A7z2" + }, + ... + ] +} +``` +
+ +## 其他 + +- 本项目为了避免频繁请求官方数据,默认对数据做了缓存处理,默认为 `30` 分钟,如需更改,请自行前往 `utils\cacheData.js` 文件修改 +- 本项目部分接口使用了 **页面爬虫**,若违反对应页面的相关规则,请 **及时通知我去除该接口** + +## 免责声明 + +- 本项目提供的 `API` 仅供开发者进行技术研究和开发测试使用。使用该 `API` 获取的信息仅供参考,不代表本项目对信息的准确性、可靠性、合法性、完整性作出任何承诺或保证。本项目不对任何因使用该 `API` 获取信息而导致的任何直接或间接损失负责。本项目保留随时更改 `API` 接口地址、接口协议、接口参数及其他相关内容的权利。本项目对使用者使用 `API` 的行为不承担任何直接或间接的法律责任 +- 本项目并未与相关信息提供方建立任何关联或合作关系,获取的信息均来自公开渠道,如因使用该 `API` 获取信息而产生的任何法律责任,由使用者自行承担 +- 本项目对使用 `API` 获取的信息进行了最大限度的筛选和整理,但不保证信息的准确性和完整性。使用 `API` 获取信息时,请务必自行核实信息的真实性和可靠性,谨慎处理相关事项 +- 本项目保留对 `API` 的随时更改、停用、限制使用等措施的权利。任何因使用本 `API` 产生的损失,本项目不负担任何赔偿和责任 \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..cc58ce1 --- /dev/null +++ b/app.js @@ -0,0 +1,89 @@ +require("dotenv").config(); +const Koa = require("koa"); +const bodyParser = require("koa-bodyparser"); +const cors = require("koa2-cors"); +const serve = require("koa-static"); +const views = require("koa-views"); + +const app = new Koa(); +const net = require("net"); +const router = require("./routes"); + +// 配置信息 +let domain = process.env.ALLOWED_DOMAIN || "*"; +let port = process.env.PORT || 6688; + +// 解析请求体 +app.use(bodyParser()); + +// 静态文件目录 +app.use(serve(__dirname + "/public")); +app.use(views(__dirname + "/public")); + +// 跨域 +app.use( + cors({ + origin: domain, + }) +); + +app.use(async (ctx, next) => { + if (domain === "*") { + await next(); + } else { + if (ctx.headers.referer !== domain) { + ctx.status = 400; + ctx.body = { + code: 400, + message: "请通过正确的域名访问", + }; + } else { + await next(); + } + } +}); + +// 使用路由中间件 +app.use(router.routes()); +app.use(router.allowedMethods()); + +// 启动应用程序并监听端口 +const startApp = (port) => { + app.listen(port, () => { + console.log(`成功在 ${port} 端口上运行`); + }); +}; + +// 检测端口是否被占用 +const checkPort = (port) => { + return new Promise((resolve, reject) => { + const server = net + .createServer() + .once("error", (err) => { + if (err.code === "EADDRINUSE") { + console.log(`端口 ${port} 已被占用, 正在尝试其他端口...`); + server.close(); + resolve(false); + } else { + reject(err); + } + }) + .once("listening", () => { + server.close(); + resolve(true); + }) + .listen(port); + }); +}; + +// 尝试启动应用程序 +const tryStartApp = async (port) => { + let isPortAvailable = await checkPort(port); + while (!isPortAvailable) { + port++; + isPortAvailable = await checkPort(port); + } + startApp(port); +}; + +tryStartApp(port); diff --git a/package.json b/package.json new file mode 100644 index 0000000..003da87 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "dailyhot_api", + "version": "0.0.1", + "description": "一个今日热榜 .", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "author": "imsyy", + "license": "ISC", + "dependencies": { + "axios": "^1.3.4", + "cheerio": "1.0.0-rc.12", + "dotenv": "^16.0.3", + "koa": "^2.14.1", + "koa-bodyparser": "^4.3.0", + "koa-router": "^12.0.0", + "koa-static": "^5.0.0", + "koa-views": "^8.0.0", + "koa2-cors": "^2.0.6", + "node-cache": "^5.1.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..eafd7e0 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1131 @@ +lockfileVersion: 5.4 + +specifiers: + axios: ^1.3.4 + cheerio: 1.0.0-rc.12 + dotenv: ^16.0.3 + koa: ^2.14.1 + koa-bodyparser: ^4.3.0 + koa-router: ^12.0.0 + koa-static: ^5.0.0 + koa-views: ^8.0.0 + koa2-cors: ^2.0.6 + node-cache: ^5.1.2 + +dependencies: + axios: 1.3.4 + cheerio: 1.0.0-rc.12 + dotenv: 16.0.3 + koa: 2.14.1 + koa-bodyparser: 4.3.0 + koa-router: 12.0.0 + koa-static: 5.0.0 + koa-views: 8.0.0 + koa2-cors: 2.0.6 + node-cache: 5.1.2 + +packages: + + /abbrev/1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: false + + /accepts/1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /any-promise/1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false + + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /axios/1.3.4: + resolution: {integrity: sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false + + /bluebird/3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: false + + /boolbase/1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: false + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /cache-content-type/1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + dependencies: + mime-types: 2.1.35 + ylru: 1.3.2 + dev: false + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.0 + dev: false + + /cheerio-select/2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + dev: false + + /cheerio/1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.0.1 + htmlparser2: 8.0.1 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + dev: false + + /clone/2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + dev: false + + /co-body/6.1.0: + resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} + dependencies: + inflation: 2.0.0 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + dev: false + + /co/4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: false + + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /commander/2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: false + + /condense-newlines/0.2.1: + resolution: {integrity: sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + is-whitespace: 0.3.0 + kind-of: 3.2.2 + dev: false + + /config-chain/1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: false + + /consolidate/0.16.0: + resolution: {integrity: sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==} + engines: {node: '>= 0.10.0'} + peerDependencies: + arc-templates: ^0.5.3 + atpl: '>=0.7.6' + babel-core: ^6.26.3 + bracket-template: ^1.1.5 + coffee-script: ^1.12.7 + dot: ^1.1.3 + dust: ^0.3.0 + dustjs-helpers: ^1.7.4 + dustjs-linkedin: ^2.7.5 + eco: ^1.1.0-rc-3 + ect: ^0.5.9 + ejs: ^3.1.5 + haml-coffee: ^1.14.1 + hamlet: ^0.3.3 + hamljs: ^0.6.2 + handlebars: ^4.7.6 + hogan.js: ^3.0.2 + htmling: ^0.0.8 + jade: ^1.11.0 + jazz: ^0.0.18 + jqtpl: ~1.1.0 + just: ^0.1.8 + liquid-node: ^3.0.1 + liquor: ^0.0.5 + lodash: ^4.17.20 + marko: ^3.14.4 + mote: ^0.2.0 + mustache: ^4.0.1 + nunjucks: ^3.2.2 + plates: ~0.4.11 + pug: ^3.0.0 + qejs: ^3.0.5 + ractive: ^1.3.12 + razor-tmpl: ^1.3.1 + react: ^16.13.1 + react-dom: ^16.13.1 + slm: ^2.0.0 + squirrelly: ^5.1.0 + swig: ^1.4.2 + swig-templates: ^2.0.3 + teacup: ^2.0.0 + templayed: '>=0.2.3' + then-jade: '*' + then-pug: '*' + tinyliquid: ^0.2.34 + toffee: ^0.3.6 + twig: ^1.15.2 + twing: ^5.0.2 + underscore: ^1.11.0 + vash: ^0.13.0 + velocityjs: ^2.0.1 + walrus: ^0.10.1 + whiskers: ^0.4.0 + peerDependenciesMeta: + arc-templates: + optional: true + atpl: + optional: true + babel-core: + optional: true + bracket-template: + optional: true + coffee-script: + optional: true + dot: + optional: true + dust: + optional: true + dustjs-helpers: + optional: true + dustjs-linkedin: + optional: true + eco: + optional: true + ect: + optional: true + ejs: + optional: true + haml-coffee: + optional: true + hamlet: + optional: true + hamljs: + optional: true + handlebars: + optional: true + hogan.js: + optional: true + htmling: + optional: true + jade: + optional: true + jazz: + optional: true + jqtpl: + optional: true + just: + optional: true + liquid-node: + optional: true + liquor: + optional: true + lodash: + optional: true + marko: + optional: true + mote: + optional: true + mustache: + optional: true + nunjucks: + optional: true + plates: + optional: true + pug: + optional: true + qejs: + optional: true + ractive: + optional: true + razor-tmpl: + optional: true + react: + optional: true + react-dom: + optional: true + slm: + optional: true + squirrelly: + optional: true + swig: + optional: true + swig-templates: + optional: true + teacup: + optional: true + templayed: + optional: true + then-jade: + optional: true + then-pug: + optional: true + tinyliquid: + optional: true + toffee: + optional: true + twig: + optional: true + twing: + optional: true + underscore: + optional: true + vash: + optional: true + velocityjs: + optional: true + walrus: + optional: true + whiskers: + optional: true + dependencies: + bluebird: 3.7.2 + dev: false + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type/1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /cookies/0.8.0: + resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + dev: false + + /copy-to/2.0.1: + resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + dev: false + + /css-select/5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.0.1 + nth-check: 2.1.1 + dev: false + + /css-what/6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: false + + /debug/3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: false + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + /deep-equal/1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + dev: false + + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /delegates/1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: false + + /depd/1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: false + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /dom-serializer/2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.4.0 + dev: false + + /domelementtype/2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler/5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils/3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: false + + /dotenv/16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: false + + /editorconfig/0.15.3: + resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==} + hasBin: true + dependencies: + commander: 2.20.3 + lru-cache: 4.1.5 + semver: 5.7.1 + sigmund: 1.0.1 + dev: false + + /ee-first/1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /encodeurl/1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /entities/4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} + dev: false + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /extend-shallow/2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + dependencies: + is-extendable: 0.1.1 + dev: false + + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /fresh/0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: false + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false + + /get-intrinsic/1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: false + + /get-paths/0.0.7: + resolution: {integrity: sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA==} + engines: {node: '>=6.4'} + dependencies: + pify: 4.0.1 + dev: false + + /glob/8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: false + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: false + + /htmlparser2/8.0.1: + resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + entities: 4.4.0 + dev: false + + /http-assert/1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + dev: false + + /http-errors/1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + dev: false + + /http-errors/1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + dev: false + + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /inflation/2.0.0: + resolution: {integrity: sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==} + engines: {node: '>= 0.8.0'} + dev: false + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: false + + /inherits/2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + dev: false + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /ini/1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + + /is-buffer/1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: false + + /is-extendable/0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + dev: false + + /is-generator-function/1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + + /is-whitespace/0.3.0: + resolution: {integrity: sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==} + engines: {node: '>=0.10.0'} + dev: false + + /js-beautify/1.14.7: + resolution: {integrity: sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + config-chain: 1.1.13 + editorconfig: 0.15.3 + glob: 8.1.0 + nopt: 6.0.0 + dev: false + + /keygrip/1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + dependencies: + tsscmp: 1.0.6 + dev: false + + /kind-of/3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-buffer: 1.1.6 + dev: false + + /koa-bodyparser/4.3.0: + resolution: {integrity: sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==} + engines: {node: '>=8.0.0'} + dependencies: + co-body: 6.1.0 + copy-to: 2.0.1 + dev: false + + /koa-compose/4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + dev: false + + /koa-convert/2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + dev: false + + /koa-router/12.0.0: + resolution: {integrity: sha512-zGrdiXygGYW8WvrzeGsHZvKnHs4DzyGoqJ9a8iHlRkiwuEAOAPyI27//OlhoWdgFAEIM3qbUgr0KCuRaP/TCag==} + engines: {node: '>= 12'} + dependencies: + http-errors: 2.0.0 + koa-compose: 4.1.0 + methods: 1.1.2 + path-to-regexp: 6.2.1 + dev: false + + /koa-send/5.0.1: + resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} + engines: {node: '>= 8'} + dependencies: + debug: 4.3.4 + http-errors: 1.8.1 + resolve-path: 1.4.0 + transitivePeerDependencies: + - supports-color + dev: false + + /koa-static/5.0.0: + resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} + engines: {node: '>= 7.6.0'} + dependencies: + debug: 3.2.7 + koa-send: 5.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /koa-views/8.0.0: + resolution: {integrity: sha512-nvGKdG8yVyomhouwN060SWd7Q9kGYRr322dwN6jvI04hgiDEW9vsPXEje9OEtBgGXxHURe7xOzo3bxQ3DBc1Nw==} + peerDependencies: + '@types/koa': ^2.13.1 + peerDependenciesMeta: + '@types/koa': + optional: true + dependencies: + consolidate: 0.16.0 + debug: 4.3.4 + get-paths: 0.0.7 + koa-send: 5.0.1 + mz: 2.7.0 + pretty: 2.0.0 + resolve-path: 1.4.0 + transitivePeerDependencies: + - arc-templates + - atpl + - babel-core + - bracket-template + - coffee-script + - dot + - dust + - dustjs-helpers + - dustjs-linkedin + - eco + - ect + - ejs + - haml-coffee + - hamlet + - hamljs + - handlebars + - hogan.js + - htmling + - jade + - jazz + - jqtpl + - just + - liquid-node + - liquor + - lodash + - marko + - mote + - mustache + - nunjucks + - plates + - pug + - qejs + - ractive + - razor-tmpl + - react + - react-dom + - slm + - squirrelly + - supports-color + - swig + - swig-templates + - teacup + - templayed + - then-jade + - then-pug + - tinyliquid + - toffee + - twig + - twing + - underscore + - vash + - velocityjs + - walrus + - whiskers + dev: false + + /koa/2.14.1: + resolution: {integrity: sha512-USJFyZgi2l0wDgqkfD27gL4YGno7TfUkcmOe6UOLFOVuN+J7FwnNu4Dydl4CUQzraM1lBAiGed0M9OVJoT0Kqw==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.8.0 + debug: 4.3.4 + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.0.10 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /koa2-cors/2.0.6: + resolution: {integrity: sha512-JRCcSM4lamM+8kvKGDKlesYk2ASrmSTczDtGUnIadqMgnHU4Ct5Gw7Bxt3w3m6d6dy3WN0PU4oMP43HbddDEWg==} + engines: {node: '>= 7.6.0'} + dev: false + + /lru-cache/4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: false + + /media-typer/0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /methods/1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /minimatch/5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /mz/2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: false + + /negotiator/0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /node-cache/5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + dependencies: + clone: 2.1.2 + dev: false + + /nopt/6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + + /nth-check/2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: false + + /object-assign/4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /object-inspect/1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: false + + /on-finished/2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: false + + /only/0.0.2: + resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + dev: false + + /parse5-htmlparser2-tree-adapter/7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: false + + /parse5/7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.4.0 + dev: false + + /parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: false + + /path-to-regexp/6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: false + + /pify/4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: false + + /pretty/2.0.0: + resolution: {integrity: sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==} + engines: {node: '>=0.10.0'} + dependencies: + condense-newlines: 0.2.1 + extend-shallow: 2.0.1 + js-beautify: 1.14.7 + dev: false + + /proto-list/1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: false + + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + + /pseudomap/1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: false + + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /raw-body/2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /resolve-path/1.4.0: + resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} + engines: {node: '>= 0.8'} + dependencies: + http-errors: 1.6.3 + path-is-absolute: 1.0.1 + dev: false + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: false + + /setprototypeof/1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + dev: false + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + object-inspect: 1.12.3 + dev: false + + /sigmund/1.0.1: + resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==} + dev: false + + /statuses/1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + dev: false + + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /thenify-all/1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: false + + /thenify/3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: false + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /tsscmp/1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + dev: false + + /type-is/1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /unpipe/1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /vary/1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: false + + /yallist/2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + dev: false + + /ylru/1.3.2: + resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} + engines: {node: '>= 4.0.0'} + dev: false diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..73f0bc8 --- /dev/null +++ b/public/404.html @@ -0,0 +1,195 @@ + + + + + 404 | Linkbook API + + + + + +
+
+
404 Not Found
+
请检查您的路径
+
+ +
+
+ + + + diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..e74ac2f Binary files /dev/null and b/public/favicon.png differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..c0df2a1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,11 @@ + + favicon-svg + + + + + + + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..ac93312 --- /dev/null +++ b/public/index.html @@ -0,0 +1,201 @@ + + + + + DailyHot API + + + + + +
+
+
HotDaily API
+
服务已正常运行
+
+ + +
+
+ + + + diff --git a/routes/36kr.js b/routes/36kr.js new file mode 100644 index 0000000..f6e879f --- /dev/null +++ b/routes/36kr.js @@ -0,0 +1,129 @@ +const Router = require("koa-router"); +const krRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "krData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.itemId, + title: v.templateMaterial.widgetTitle, + pic: v.templateMaterial.widgetImage, + owner: v.templateMaterial.authorName, + hot: v.templateMaterial.statRead, + data: v.templateMaterial, + url: `https://www.36kr.com/p/${v.itemId}`, + mobileUrl: `https://www.36kr.com/p/${v.itemId}`, + }; + }); +}; + +// 36氪热榜 +krRouter.get("/36kr", async (ctx) => { + console.log("获取36氪热榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取36氪热榜"); + // 从服务器拉取数据 + const response = await axios.post(url, { + partner_id: "wap", + param: { + siteId: 1, + platformId: 2, + }, + }); + data = getData(response.data.data.hotRankList); + updateTime = new Date().toISOString(); + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "36氪", + subtitle: "热榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 36氪热榜 - 获取最新数据 +krRouter.get("/36kr/new", async (ctx) => { + console.log("获取36氪热榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.post(url, { + partner_id: "wap", + param: { + siteId: 1, + platformId: 2, + }, + }); + const newData = getData(response.data.data.hotRankList); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取36氪热榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "36氪", + subtitle: "热榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "36氪", + subtitle: "热榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = krRouter; diff --git a/routes/baidu.js b/routes/baidu.js new file mode 100644 index 0000000..fe87f9f --- /dev/null +++ b/routes/baidu.js @@ -0,0 +1,132 @@ +const Router = require("koa-router"); +const baiduRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "baiduData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://top.baidu.com/board?tab=realtime"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + const dataList = []; + try { + const pattern = /<\!--s-data:(.*?)-->/s; + const matchResult = data.match(pattern); + const jsonObject = JSON.parse(matchResult[1]).cards[0].content; + jsonObject.forEach((v) => { + dataList.push({ + title: v.query, + desc: v.desc, + pic: v.img, + hot: v.hotScore, + url: `https://www.baidu.com/s?wd=${encodeURIComponent(v.query)}`, + mobileUrl: v.url, + }); + }); + return dataList; + } catch (error) { + console.error("数据处理出错" + error); + return false; + } +}; + +// 百度热搜 +baiduRouter.get("/baidu", async (ctx) => { + console.log("获取百度热搜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取百度热搜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data); + updateTime = new Date().toISOString(); + if (!data) { + ctx.body = { + code: 500, + message: "获取失败", + }; + return false; + } + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "百度", + subtitle: "热搜榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 百度热搜 - 获取最新数据 +baiduRouter.get("/baidu/new", async (ctx) => { + console.log("获取百度热搜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取百度热搜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "百度", + subtitle: "热搜榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "百度", + subtitle: "热搜榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = baiduRouter; diff --git a/routes/bilibili.js b/routes/bilibili.js new file mode 100644 index 0000000..85e2ff1 --- /dev/null +++ b/routes/bilibili.js @@ -0,0 +1,117 @@ +const Router = require("koa-router"); +const bilibiliRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "bilibiliData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://api.bilibili.com/x/web-interface/ranking/v2"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.bvid, + title: v.title, + desc: v.desc, + pic: v.pic, + owner: v.owner, + data: v.stat, + url: v.short_link, + mobileUrl: `https://m.bilibili.com/video/${v.bvid}`, + }; + }); +}; + +// 哔哩哔哩热门榜 +bilibiliRouter.get("/bilibili", async (ctx) => { + console.log("获取哔哩哔哩热门榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取哔哩哔哩热门榜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data.data.list); + updateTime = new Date().toISOString(); + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "哔哩哔哩", + subtitle: "热门榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "哔哩哔哩热门榜获取失败", + }; + } +}); + +// 哔哩哔哩热门榜 - 获取最新数据 +bilibiliRouter.get("/bilibili/new", async (ctx) => { + console.log("获取哔哩哔哩热门榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data.data.list); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取哔哩哔哩热门榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "哔哩哔哩", + subtitle: "热门榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "哔哩哔哩", + subtitle: "热门榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "哔哩哔哩热门榜获取失败", + }; + } + } +}); + +module.exports = bilibiliRouter; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..6949a46 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,37 @@ +const Router = require("koa-router"); + +const bilibiliRouter = require("./bilibili"); +const zhihuRouter = require("./zhihu"); +const baiduRouter = require("./baidu"); +const weiboRouter = require("./weibo"); +const itHomeRouter = require("./ithome"); +const krRouter = require("./36kr"); +const sspaiRouter = require("./sspai"); +const tiebaRouter = require("./tieba"); +const toutiaoRouter = require("./toutiao"); +const thepaperRouter = require("./thepaper"); + +const router = new Router(); + +// 根目录 +router.get("/", async (ctx) => { + await ctx.render("index"); +}); + +router.use(bilibiliRouter.routes()); +router.use(zhihuRouter.routes()); +router.use(baiduRouter.routes()); +router.use(weiboRouter.routes()); +router.use(itHomeRouter.routes()); +router.use(krRouter.routes()); +router.use(sspaiRouter.routes()); +router.use(tiebaRouter.routes()); +router.use(toutiaoRouter.routes()); +router.use(thepaperRouter.routes()); + +// 404 路由 +router.use(async (ctx) => { + await ctx.render("404"); +}); + +module.exports = router; diff --git a/routes/ithome.js b/routes/ithome.js new file mode 100644 index 0000000..3c1d7b1 --- /dev/null +++ b/routes/ithome.js @@ -0,0 +1,150 @@ +const Router = require("koa-router"); +const itHomeRouter = new Router(); +const axios = require("axios"); +const cheerio = require("cheerio"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "itHomeData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://m.ithome.com/rankm/"; +const headers = { + "User-Agent": + "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1", +}; + +// it之家特殊处理 - url +const replaceLink = (url) => { + const match = url.match(/html\/(\d+)\.htm/)[1]; + return `https://www.ithome.com/0/${match.slice(0, 3)}/${match.slice(3)}.htm`; +}; + +// 数据处理 +const getData = (data) => { + if (!data) return false; + const dataList = {}; + const $ = cheerio.load(data); + try { + $(".rank-name").each(function () { + const type = $(this).data("rank-type"); + const newListHtml = $(this).next(".rank-box").html(); + const newsList = cheerio + .load(newListHtml)(".placeholder") + .get() + .map((v) => { + return { + title: $(v).find(".plc-title").text(), + img: $(v).find("img").attr("data-original"), + time: $(v).find(".post-time").text(), + hot: $(v).find(".review-num").text(), + url: replaceLink($(v).find("a").attr("href")), + mobileUrl: $(v).find("a").attr("href"), + }; + }); + dataList[type] = { + name: $(this).text(), + total: newsList.length, + list: newsList, + }; + }); + return dataList; + } catch (error) { + console.error("数据处理出错" + error); + return false; + } +}; + +// IT之家热榜 +itHomeRouter.get("/ithome", async (ctx) => { + console.log("获取IT之家热榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取IT之家热榜"); + // 从服务器拉取数据 + const response = await axios.get(url, { headers }); + data = getData(response.data); + updateTime = new Date().toISOString(); + if (!data) { + ctx.body = { + code: 500, + message: "获取失败", + }; + return false; + } + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "IT之家", + subtitle: "热榜", + from, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// IT之家热榜 - 获取最新数据 +itHomeRouter.get("/ithome/new", async (ctx) => { + console.log("获取IT之家热榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url, { headers }); + const newData = getData(response.data); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取IT之家热榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "IT之家", + subtitle: "热榜", + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "IT之家", + subtitle: "热榜", + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = itHomeRouter; diff --git a/routes/sspai.js b/routes/sspai.js new file mode 100644 index 0000000..835453c --- /dev/null +++ b/routes/sspai.js @@ -0,0 +1,117 @@ +const Router = require("koa-router"); +const sspaiRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "krData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = `https://sspai.com/api/v1/article/tag/page/get?limit=40&tag=热门文章`; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.id, + title: v.title, + desc: v.summary, + pic: `https://cdn.sspai.com/${v.banner}`, + owner: v.author, + hot: v.like_count, + url: `https://sspai.com/post/${v.id}`, + mobileUrl: `https://sspai.com/post/${v.itemId}`, + }; + }); +}; + +// 少数派热榜 +sspaiRouter.get("/sspai", async (ctx) => { + console.log("获取少数派热榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取少数派热榜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data.data); + updateTime = new Date().toISOString(); + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "少数派", + subtitle: "最热", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 少数派热榜 - 获取最新数据 +sspaiRouter.get("/sspai/new", async (ctx) => { + console.log("获取少数派热榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data.data); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取少数派热榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "少数派", + subtitle: "最热", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "少数派", + subtitle: "最热", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = sspaiRouter; diff --git a/routes/thepaper.js b/routes/thepaper.js new file mode 100644 index 0000000..2092aed --- /dev/null +++ b/routes/thepaper.js @@ -0,0 +1,115 @@ +const Router = require("koa-router"); +const thepaperRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "thepaperData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.contId, + title: v.name, + pic: v.pic, + time: v.pubTime, + url: `https://www.thepaper.cn/newsDetail_forward_${v.contId}`, + mobileUrl: `https://m.thepaper.cn/newsDetail_forward_${v.contId}`, + }; + }); +}; + +// 澎湃热榜 +thepaperRouter.get("/thepaper", async (ctx) => { + console.log("获取澎湃热榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取澎湃热榜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data.data.hotNews); + updateTime = new Date().toISOString(); + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "澎湃新闻", + subtitle: "热榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 澎湃热榜 - 获取最新数据 +thepaperRouter.get("/thepaper/new", async (ctx) => { + console.log("获取澎湃热榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data.data.hotNews); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取澎湃热榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "澎湃新闻", + subtitle: "热榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "澎湃新闻", + subtitle: "热榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = thepaperRouter; diff --git a/routes/tieba.js b/routes/tieba.js new file mode 100644 index 0000000..52f2235 --- /dev/null +++ b/routes/tieba.js @@ -0,0 +1,116 @@ +const Router = require("koa-router"); +const tiebaRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "tiebaData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://tieba.baidu.com/hottopic/browse/topicList"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.topic_id, + title: v.topic_name, + desc: v.topic_desc, + pic: v.topic_pic, + hot: v.discuss_num, + url: v.topic_url, + mobileUrl: v.topic_url, + }; + }); +}; + +// 贴吧热议榜 +tiebaRouter.get("/tieba", async (ctx) => { + console.log("获取贴吧热议榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取贴吧热议榜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data.data.bang_topic.topic_list); + updateTime = new Date().toISOString(); + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "百度贴吧", + subtitle: "热议榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 贴吧热议榜 - 获取最新数据 +tiebaRouter.get("/tieba/new", async (ctx) => { + console.log("获取贴吧热议榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data.data.bang_topic.topic_list); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取贴吧热议榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "百度贴吧", + subtitle: "热议榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "百度贴吧", + subtitle: "热议榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = tiebaRouter; diff --git a/routes/toutiao.js b/routes/toutiao.js new file mode 100644 index 0000000..657136a --- /dev/null +++ b/routes/toutiao.js @@ -0,0 +1,115 @@ +const Router = require("koa-router"); +const toutiaoRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "toutiaoData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.ClusterId, + title: v.Title, + pic: v.Image.url, + hot: v.HotValue, + url: `https://www.toutiao.com/trending/${v.ClusterIdStr}/`, + mobileUrl: `https://api.toutiaoapi.com/feoffline/amos_land/new/html/main/index.html?topic_id=${v.ClusterIdStr}`, + }; + }); +}; + +// 头条热榜 +toutiaoRouter.get("/toutiao", async (ctx) => { + console.log("获取头条热榜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取头条热榜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data.data); + updateTime = new Date().toISOString(); + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "今日头条", + subtitle: "热榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 头条热榜 - 获取最新数据 +toutiaoRouter.get("/toutiao/new", async (ctx) => { + console.log("获取头条热榜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data.data); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取头条热榜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "今日头条", + subtitle: "热榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "今日头条", + subtitle: "热榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = toutiaoRouter; diff --git a/routes/weibo.js b/routes/weibo.js new file mode 100644 index 0000000..50b0ba3 --- /dev/null +++ b/routes/weibo.js @@ -0,0 +1,128 @@ +const Router = require("koa-router"); +const weiboRouter = new Router(); +const axios = require("axios"); +// const cheerio = require("cheerio"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "weiboData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://weibo.com/ajax/side/hotSearch"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + // return data; + return data.map((v) => { + const key = v.word_scheme ? v.word_scheme : `#${v.word}`; + return { + title: v.word, + desc: key, + hot: v.raw_hot, + url: `https://s.weibo.com/weibo?q=${encodeURIComponent( + key + )}&t=31&band_rank=1&Refer=top`, + mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent( + key + )}&t=31&band_rank=1&Refer=top`, + }; + }); +}; + +// 微博热搜 +weiboRouter.get("/weibo", async (ctx) => { + console.log("获取微博热搜"); + try { + // 从缓存中获取数据 + let data = await get(cacheKey); + const from = data ? "cache" : "server"; + if (!data) { + // 如果缓存中不存在数据 + console.log("从服务端重新获取微博热搜"); + // 从服务器拉取数据 + const response = await axios.get(url); + data = getData(response.data.data.realtime); + updateTime = new Date().toISOString(); + if (!data) { + ctx.body = { + code: 500, + message: "获取失败", + }; + return false; + } + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + title: "微博", + subtitle: "热搜榜", + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + message: "获取失败", + }; + } +}); + +// 微博热搜 - 获取最新数据 +weiboRouter.get("/weibo/new", async (ctx) => { + console.log("获取微博热搜 - 最新数据"); + try { + // 从服务器拉取最新数据 + const response = await axios.get(url); + const newData = getData(response.data.data.realtime); + updateTime = new Date().toISOString(); + console.log("从服务端重新获取微博热搜"); + + // 返回最新数据 + ctx.body = { + code: 200, + message: "获取成功", + title: "微博", + subtitle: "热搜榜", + total: newData.length, + updateTime, + data: newData, + }; + + // 删除旧数据 + await del(cacheKey); + // 将最新数据写入缓存 + await set(cacheKey, newData); + } catch (error) { + // 如果拉取最新数据失败,尝试从缓存中获取数据 + console.error(error); + const cachedData = await get(cacheKey); + if (cachedData) { + ctx.body = { + code: 200, + message: "获取成功", + title: "微博", + subtitle: "热搜榜", + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + message: "获取失败", + }; + } + } +}); + +module.exports = weiboRouter; diff --git a/routes/zhihu.js b/routes/zhihu.js new file mode 100644 index 0000000..5f6398a --- /dev/null +++ b/routes/zhihu.js @@ -0,0 +1,137 @@ +const Router = require("koa-router"); +const zhihuRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 缓存键名 +const cacheKey = "zhihuData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://www.zhihu.com/hot"; +const headers = { + "User-Agent": + "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1", +}; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + const dataList = []; + try { + const pattern = + /