From c42573c37c15e584f967dca2b7975d261f0b44ec Mon Sep 17 00:00:00 2001 From: imsyy Date: Mon, 17 Jul 2023 17:05:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=BF=AB=E6=89=8B?= =?UTF-8?q?=E7=83=AD=E6=A6=9C=E5=8F=8A=E7=BD=91=E6=98=93=E6=96=B0=E9=97=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++- package.json | 4 +- routes/36kr.js | 1 + routes/baidu.js | 2 +- routes/bilibili.js | 1 + routes/douyin.js | 1 + routes/douyin_music.js | 2 +- routes/douyin_new.js | 2 +- routes/genshin.js | 1 + routes/ithome.js | 1 + routes/juejin.js | 1 + routes/kuaishou.js | 163 +++++++++++++++++++++++++++++++++++++++++ routes/lol.js | 1 + routes/netease.js | 130 ++++++++++++++++++++++++++++++++ routes/newsqq.js | 1 + routes/sspai.js | 1 + routes/thepaper.js | 1 + routes/tieba.js | 1 + routes/toutiao.js | 1 + routes/weibo.js | 1 + routes/weread.js | 9 ++- utils/getWereadID.js | 82 +++++++++++++++++++++ 22 files changed, 405 insertions(+), 12 deletions(-) create mode 100644 routes/kuaishou.js create mode 100644 routes/netease.js create mode 100644 utils/getWereadID.js diff --git a/README.md b/README.md index 27a56b3..beff0ef 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ > 🟢 状态正常 > 🟠 可能失效 -> 🔴 无法使用 +> ❌ 无法使用 | **站点** | **类别** | **调用名称** | **状态** | | ------------ | -------- | ------------------- | -------- | @@ -26,15 +26,17 @@ | 抖音 | 热歌榜 | douyin_music | 🟢 | | 百度贴吧 | 热议榜 | tieba | 🟢 | | 少数派 | 热榜 | sspai | 🟢 | -| IT 之家 | 热榜 | ithome | 🟠 | +| IT 之家 | 热榜 | ithome | 🟢 | | 澎湃新闻 | 热榜 | thepaper | 🟢 | | 今日头条 | 热榜 | toutiao | 🟢 | | 36 氪 | 热榜 | 36kr | 🟢 | | 稀土掘金 | 热榜 | juejin | 🟢 | | 腾讯新闻 | 热点榜 | newsqq | 🟢 | +| 网易新闻 | 热点榜 | netease | 🟢 | | 英雄联盟 | 更新公告 | lol | 🟢 | | 原神 | 最新消息 | genshin | 🟢 | | 微信读书 | 飙升榜 | weread | 🟢 | +| 快手 | 热榜 | kuaishou | 🟢 | | 历史上的今天 | 指定日期 | calendar | 🟢 | ### 特殊接口说明 @@ -44,7 +46,7 @@ 获取除了下方特殊接口外的全部接口列表 ```http -GET https://{example.com}/all +GET https://example.com/all ``` #### 历史上的今天(指定日期) @@ -52,7 +54,7 @@ GET https://{example.com}/all 将指定的月份和日期传入即可得到当天数据,请注意格式 ```http -GET https://{example.com}/calendar/date?month=06&day=01 +GET https://example.com/calendar/date?month=06&day=01 ``` ## 部署 diff --git a/package.json b/package.json index 8fa1dd2..d4f7be6 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "dailyhot_api", - "version": "1.0.2", + "version": "1.0.3", "description": "一个今日热榜", "main": "index.js", "scripts": { "start": "node index.js", - "dev": "./node_modules/.bin/nodemon index.js", + "dev": "npx nodemon index.js", "prd": "pm2 start index.js", "build": "node index.js" }, diff --git a/routes/36kr.js b/routes/36kr.js index 3f75350..1003997 100644 --- a/routes/36kr.js +++ b/routes/36kr.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "36kr", title: "36氪", subtitle: "热榜", }; diff --git a/routes/baidu.js b/routes/baidu.js index f9b022e..3dcdabe 100644 --- a/routes/baidu.js +++ b/routes/baidu.js @@ -4,7 +4,7 @@ const axios = require("axios"); const { get, set, del } = require("../utils/cacheData"); // 接口信息 -const routerInfo = { title: "百度", subtitle: "热搜榜" }; +const routerInfo = { name: "baidu", title: "百度", subtitle: "热搜榜" }; // 缓存键名 const cacheKey = "baiduData"; diff --git a/routes/bilibili.js b/routes/bilibili.js index b8ee553..cee725e 100644 --- a/routes/bilibili.js +++ b/routes/bilibili.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "bilibili", title: "哔哩哔哩", subtitle: "热门榜", }; diff --git a/routes/douyin.js b/routes/douyin.js index 856bca1..72edd42 100644 --- a/routes/douyin.js +++ b/routes/douyin.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "douyin", title: "抖音", subtitle: "热点榜", }; diff --git a/routes/douyin_music.js b/routes/douyin_music.js index 39b9a51..fd4696d 100644 --- a/routes/douyin_music.js +++ b/routes/douyin_music.js @@ -1,5 +1,4 @@ /* - * @version: 1.0.0 * @author: WangPeng * @date: 2023-07-11 16:41:48 * @customEditors: imsyy @@ -13,6 +12,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "douyin", title: "抖音", subtitle: "热歌榜", }; diff --git a/routes/douyin_new.js b/routes/douyin_new.js index ba43848..f8f7168 100644 --- a/routes/douyin_new.js +++ b/routes/douyin_new.js @@ -1,5 +1,4 @@ /* - * @version: 1.0.0 * @author: WangPeng * @date: 2023-07-10 16:56:01 * @customEditors: imsyy @@ -13,6 +12,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "douyin", title: "抖音", subtitle: "热点榜", }; diff --git a/routes/genshin.js b/routes/genshin.js index 269f1fa..023b928 100644 --- a/routes/genshin.js +++ b/routes/genshin.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "genshin", title: "原神", subtitle: "最新信息", }; diff --git a/routes/ithome.js b/routes/ithome.js index 246a575..9581d3b 100644 --- a/routes/ithome.js +++ b/routes/ithome.js @@ -6,6 +6,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "ithome", title: "IT之家", subtitle: "热榜", }; diff --git a/routes/juejin.js b/routes/juejin.js index e19c95e..b33c433 100644 --- a/routes/juejin.js +++ b/routes/juejin.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "juejin", title: "稀土掘金", subtitle: "热榜", }; diff --git a/routes/kuaishou.js b/routes/kuaishou.js new file mode 100644 index 0000000..bc7cefc --- /dev/null +++ b/routes/kuaishou.js @@ -0,0 +1,163 @@ +/* + * @author: MCBBC + * @date: 2023-07-17 + * @customEditors: imsyy + * @lastEditTime: 2023-07-17 + */ + +const Router = require("koa-router"); +const kuaishouRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 接口信息 +const routerInfo = { + name: "kuaishou", + title: "快手", + subtitle: "热榜", +}; + +// 缓存键名 +const cacheKey = "kuaishouData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://www.kuaishou.com/?isHome=1"; + +// Unicode 解码 +const decodedString = (encodedString) => { + return encodedString.replace(/\\u([\d\w]{4})/gi, (match, grp) => + String.fromCharCode(parseInt(grp, 16)) + ); +}; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + const dataList = []; + try { + const pattern = /window.__APOLLO_STATE__=(.*);\(function\(\)/s; + const idPattern = /clientCacheKey=([A-Za-z0-9]+)/s; + const matchResult = data.match(pattern); + const jsonObject = JSON.parse(matchResult[1])["defaultClient"]; + + // 获取所有分类 + const allItems = + jsonObject['$ROOT_QUERY.visionHotRank({"page":"home"})']["items"]; + // 遍历所有分类 + allItems.forEach((v) => { + // 基础数据 + const image = jsonObject[v.id]["poster"]; + const id = image.match(idPattern)[1]; + // 数据处理 + dataList.push({ + title: jsonObject[v.id]["name"], + pic: decodedString(image), + hot: jsonObject[v.id]["hotValue"], + url: `https://www.kuaishou.com/short-video/${id}`, + mobileUrl: `https://www.kuaishou.com/short-video/${id}`, + }); + }); + return dataList; + } catch (error) { + console.error("数据处理出错" + error); + return false; + } +}; + +// 快手热榜 +kuaishouRouter.get("/kuaishou", 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, + ...routerInfo, + message: "获取失败", + }; + return false; + } + // 将数据写入缓存 + await set(cacheKey, data); + } + ctx.body = { + code: 200, + message: "获取成功", + ...routerInfo, + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + ...routerInfo, + message: "获取失败", + }; + } +}); + +// 快手热榜 - 获取最新数据 +kuaishouRouter.get("/kuaishou/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: "获取成功", + ...routerInfo, + 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: "获取成功", + ...routerInfo, + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + ...routerInfo, + message: "获取失败", + }; + } + } +}); + +kuaishouRouter.info = routerInfo; +module.exports = kuaishouRouter; diff --git a/routes/lol.js b/routes/lol.js index 40e9f3d..c37b37b 100644 --- a/routes/lol.js +++ b/routes/lol.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "lol", title: "英雄联盟", subtitle: "更新公告", }; diff --git a/routes/netease.js b/routes/netease.js new file mode 100644 index 0000000..b0eff6b --- /dev/null +++ b/routes/netease.js @@ -0,0 +1,130 @@ +/* + * @author: MCBBC + * @date: 2023-07-17 + * @customEditors: imsyy + * @lastEditTime: 2023-07-17 + */ + +const Router = require("koa-router"); +const neteaseRouter = new Router(); +const axios = require("axios"); +const { get, set, del } = require("../utils/cacheData"); + +// 接口信息 +const routerInfo = { + name: "netease", + title: "网易新闻", + subtitle: "热点榜", +}; + +// 缓存键名 +const cacheKey = "neteaseData"; + +// 调用时间 +let updateTime = new Date().toISOString(); + +// 调用路径 +const url = "https://m.163.com/fe/api/hot/news/flow"; + +// 数据处理 +const getData = (data) => { + if (!data) return []; + return data.map((v) => { + return { + id: v.skipID, + title: v.title, + desc: v._keyword, + pic: v.imgsrc, + owner: v.source, + url: `https://www.163.com/dy/article/${v.skipID}.html`, + mobileUrl: v.url, + }; + }); +}; + +// 网易新闻热榜 +neteaseRouter.get("/netease", 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: "获取成功", + ...routerInfo, + from, + total: data.length, + updateTime, + data, + }; + } catch (error) { + console.error(error); + ctx.body = { + code: 500, + ...routerInfo, + message: "获取失败", + }; + } +}); + +// 网易新闻热榜 - 获取最新数据 +neteaseRouter.get("/netease/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: "获取成功", + ...routerInfo, + 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: "获取成功", + ...routerInfo, + total: cachedData.length, + updateTime, + data: cachedData, + }; + } else { + // 如果缓存中也没有数据,则返回错误信息 + ctx.body = { + code: 500, + ...routerInfo, + message: "获取失败", + }; + } + } +}); + +neteaseRouter.info = routerInfo; +module.exports = neteaseRouter; diff --git a/routes/newsqq.js b/routes/newsqq.js index a615546..402b978 100644 --- a/routes/newsqq.js +++ b/routes/newsqq.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "newsqq", title: "腾讯新闻", subtitle: "热点榜", }; diff --git a/routes/sspai.js b/routes/sspai.js index 351b642..2ea520a 100644 --- a/routes/sspai.js +++ b/routes/sspai.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "sspai", title: "少数派", subtitle: "热榜", }; diff --git a/routes/thepaper.js b/routes/thepaper.js index b2b8b3e..12760c5 100644 --- a/routes/thepaper.js +++ b/routes/thepaper.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "thepaper", title: "澎湃新闻", subtitle: "热榜", }; diff --git a/routes/tieba.js b/routes/tieba.js index 16fbbd9..7c3db5d 100644 --- a/routes/tieba.js +++ b/routes/tieba.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "tieba", title: "百度贴吧", subtitle: "热议榜", }; diff --git a/routes/toutiao.js b/routes/toutiao.js index 0ee35ba..15ad568 100644 --- a/routes/toutiao.js +++ b/routes/toutiao.js @@ -5,6 +5,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "toutiao", title: "今日头条", subtitle: "热榜", }; diff --git a/routes/weibo.js b/routes/weibo.js index 9fa0fc6..b036ad8 100644 --- a/routes/weibo.js +++ b/routes/weibo.js @@ -6,6 +6,7 @@ const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "weibo", title: "微博", subtitle: "热搜榜", }; diff --git a/routes/weread.js b/routes/weread.js index 4427888..e35fe57 100644 --- a/routes/weread.js +++ b/routes/weread.js @@ -1,10 +1,12 @@ const Router = require("koa-router"); const wereadRouter = new Router(); const axios = require("axios"); +const getWereadID = require("../utils/getWereadID"); const { get, set, del } = require("../utils/cacheData"); // 接口信息 const routerInfo = { + name: "weread", title: "微信读书", subtitle: "飙升榜", }; @@ -23,7 +25,6 @@ const getData = (data) => { if (!data) return []; return data.map((v) => { const book = v.bookInfo; - console.log(book); return { id: book.bookId, title: book.title, @@ -31,8 +32,10 @@ const getData = (data) => { pic: book.cover.replace("s_", "t9_"), hot: v.readingCount, author: book.author, - url: "https://weread.qq.com/web/category/rising", - mobileUrl: "https://weread.qq.com/web/category/rising", + url: `https://weread.qq.com/web/bookDetail/${getWereadID(book.bookId)}`, + mobileUrl: `https://weread.qq.com/web/bookDetail/${getWereadID( + book.bookId + )}`, }; }); }; diff --git a/utils/getWereadID.js b/utils/getWereadID.js new file mode 100644 index 0000000..d57ba04 --- /dev/null +++ b/utils/getWereadID.js @@ -0,0 +1,82 @@ +const crypto = require("crypto"); + +/** + * 获取微信读书的书籍 ID + * 感谢 @MCBBC 及 ChatGPT + * @param {string} bookId - 书籍 ID + * @returns {string} - 唯一的书籍 ID + */ +const getWereadID = (bookId) => { + try { + // 使用 MD5 哈希算法创建哈希对象 + const hash = crypto.createHash("md5"); + hash.update(bookId); + const str = hash.digest("hex"); + + // 取哈希结果的前三个字符作为初始值 + let strSub = str.substring(0, 3); + + // 判断书籍 ID 的类型并进行转换 + let fa; + if (/^\d*$/.test(bookId)) { + // 如果书籍 ID 只包含数字,则将其拆分成长度为 9 的子字符串,并转换为十六进制表示 + const chunks = []; + for (let i = 0; i < bookId.length; i += 9) { + const chunk = bookId.substring(i, i + 9); + chunks.push(parseInt(chunk).toString(16)); + } + fa = ["3", chunks]; + } else { + // 如果书籍 ID 包含其他字符,则将每个字符的 Unicode 编码转换为十六进制表示 + let hexStr = ""; + for (let i = 0; i < bookId.length; i++) { + hexStr += bookId.charCodeAt(i).toString(16); + } + fa = ["4", [hexStr]]; + } + + // 将类型添加到初始值中 + strSub += fa[0]; + + // 将数字2和哈希结果的后两个字符添加到初始值中 + strSub += "2" + str.substring(str.length - 2); + + // 处理转换后的子字符串数组 + for (let i = 0; i < fa[1].length; i++) { + const sub = fa[1][i]; + const subLength = sub.length.toString(16); + + // 如果长度只有一位数,则在前面添加0 + const subLengthPadded = + subLength.length === 1 ? "0" + subLength : subLength; + + // 将长度和子字符串添加到初始值中 + strSub += subLengthPadded + sub; + + // 如果不是最后一个子字符串,则添加分隔符 'g' + if (i < fa[1].length - 1) { + strSub += "g"; + } + } + + // 如果初始值长度不足 20,从哈希结果中取足够的字符补齐 + if (strSub.length < 20) { + strSub += str.substring(0, 20 - strSub.length); + } + + // 使用 MD5 哈希算法创建新的哈希对象 + const finalHash = crypto.createHash("md5"); + finalHash.update(strSub); + const finalStr = finalHash.digest("hex"); + + // 取最终哈希结果的前三个字符并添加到初始值的末尾 + strSub += finalStr.substring(0, 3); + + return strSub; + } catch (error) { + console.error("处理微信读书 ID 时出现错误:" + error); + return null; + } +}; + +module.exports = getWereadID;