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 @@
+
+
+
今日热榜
+
一个聚合热门数据的 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 @@
+
\ 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 =
+ /