From 46d32af2c57061a70114536d1f4514eb5b35dff2 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 21 Sep 2023 08:57:26 +0800 Subject: [PATCH] feat(route): add ainvest (#13348) * feat(route): add ainvest * style: fix code style * fix: feed title --- lib/v2/ainvest/article.js | 42 +++++++++++++++++++ lib/v2/ainvest/maintainer.js | 4 ++ lib/v2/ainvest/news.js | 39 ++++++++++++++++++ lib/v2/ainvest/radar.js | 19 +++++++++ lib/v2/ainvest/router.js | 4 ++ lib/v2/ainvest/utils.js | 73 ++++++++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 7 ++++ website/docs/routes/finance.md | 10 +++++ 9 files changed, 199 insertions(+) create mode 100644 lib/v2/ainvest/article.js create mode 100644 lib/v2/ainvest/maintainer.js create mode 100644 lib/v2/ainvest/news.js create mode 100644 lib/v2/ainvest/radar.js create mode 100644 lib/v2/ainvest/router.js create mode 100644 lib/v2/ainvest/utils.js diff --git a/lib/v2/ainvest/article.js b/lib/v2/ainvest/article.js new file mode 100644 index 0000000000..45fc227d44 --- /dev/null +++ b/lib/v2/ainvest/article.js @@ -0,0 +1,42 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const { getHeaders, randomString, encryptAES, decryptAES } = require('./utils'); + +module.exports = async (ctx) => { + const key = randomString(16); + + const { data: response } = await got.post('https://api.ainvest.com/gw/socialcenter/v1/edu/article/listArticle', { + headers: getHeaders(key), + searchParams: { + timestamp: Date.now(), + }, + data: encryptAES( + JSON.stringify({ + batch: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + startId: null, + tags: { + in: ['markettrends', 'premarket', 'companyinsights', 'macro'], + and: ['web', 'creationplatform'], + }, + }), + key + ), + }); + + const { data } = JSON.parse(decryptAES(response, key)); + + const items = data.map((item) => ({ + title: item.title, + description: item.content, + link: item.sourceUrl, + pubDate: parseDate(item.postDate, 'x'), + category: [item.nickName, ...item.tags.map((tag) => tag.code)], + })); + + ctx.state.data = { + title: 'AInvest - Latest Articles', + link: 'https://www.ainvest.com/news', + language: 'en', + item: items, + }; +}; diff --git a/lib/v2/ainvest/maintainer.js b/lib/v2/ainvest/maintainer.js new file mode 100644 index 0000000000..4ac0e425c4 --- /dev/null +++ b/lib/v2/ainvest/maintainer.js @@ -0,0 +1,4 @@ +module.exports = { + '/article': ['TonyRL'], + '/news': ['TonyRL'], +}; diff --git a/lib/v2/ainvest/news.js b/lib/v2/ainvest/news.js new file mode 100644 index 0000000000..296cf6914d --- /dev/null +++ b/lib/v2/ainvest/news.js @@ -0,0 +1,39 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const { getHeaders, randomString, decryptAES } = require('./utils'); + +module.exports = async (ctx) => { + const key = randomString(16); + + const { data: response } = await got('https://api.ainvest.com/gw/news_f10/v1/newsFlash/getNewsData', { + headers: getHeaders(key), + searchParams: { + terminal: 'web', + tab: 'all', + page: 1, + size: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 50, + lastId: '', + timestamp: Date.now(), + }, + }); + + const { data } = JSON.parse(decryptAES(response, key)); + + const items = data.content.map((item) => ({ + title: item.title, + description: item.content, + link: item.sourceUrl, + pubDate: parseDate(item.publishTime, 'x'), + category: item.tagList.map((tag) => tag.nameEn), + author: item.userInfo.nickname, + upvotes: item.likeCount, + comments: item.commentCount, + })); + + ctx.state.data = { + title: 'AInvest - Latest News', + link: 'https://www.ainvest.com/news', + language: 'en', + item: items, + }; +}; diff --git a/lib/v2/ainvest/radar.js b/lib/v2/ainvest/radar.js new file mode 100644 index 0000000000..a81c7dc87a --- /dev/null +++ b/lib/v2/ainvest/radar.js @@ -0,0 +1,19 @@ +module.exports = { + 'ainvest.com': { + _name: 'AInvest', + '.': [ + { + title: 'Latest Article', + docs: 'https://docs.rsshub.app/finance#ainvest', + source: ['/news'], + target: '/ainvest/article', + }, + { + title: 'Latest News', + docs: 'https://docs.rsshub.app/finance#ainvest', + source: ['/news'], + target: '/ainvest/news', + }, + ], + }, +}; diff --git a/lib/v2/ainvest/router.js b/lib/v2/ainvest/router.js new file mode 100644 index 0000000000..bba60f8e25 --- /dev/null +++ b/lib/v2/ainvest/router.js @@ -0,0 +1,4 @@ +module.exports = (router) => { + router.get('/article', require('./article')); + router.get('/news', require('./news')); +}; diff --git a/lib/v2/ainvest/utils.js b/lib/v2/ainvest/utils.js new file mode 100644 index 0000000000..80d74bbaf3 --- /dev/null +++ b/lib/v2/ainvest/utils.js @@ -0,0 +1,73 @@ +const crypto = require('crypto'); +const CryptoJS = require('crypto-js'); +const { KJUR, KEYUTIL, hextob64 } = require('jsrsasign'); + +const publicKey = + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCARnxLlrhTK28bEV7s2IROjT73KLSjfqpKIvV8L+Yhe4BrF0Ut4oOH728HZlbSF0C3N0vXZjLAFesoS4v1pYOjVCPXl920Lh2seCv82m0cK78WMGuqZTfA44Nv7JsQMHC3+J6IZm8YD53ft2d8mYBFgKektduucjx8sObe7eRyoQIDAQAB'; + +const randomString = (length) => { + if (length > 32) { + throw Error('Max length is 32.'); + } + return uuidv4().replace(/-/g, '').substring(0, length); +}; + +const uuidv4 = () => crypto.randomUUID(); + +/** + * @param {string} str + * @returns {CryptoJS.lib.WordArray} + */ +const MD5 = (str) => CryptoJS.MD5(str); + +const encryptAES = (data, key) => { + if (typeof key === 'string') { + key = MD5(key); + } + return CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(data), key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }).toString(); +}; + +const decryptAES = (data, key) => { + if (typeof key === 'string') { + key = MD5(key); + } + return CryptoJS.AES.decrypt(data, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }).toString(CryptoJS.enc.Utf8); +}; + +const encryptRSA = (data) => { + // Original code: + // var n = new JSEncrypt(); + // n.setPublicKey(pubKey); + // return n.encrypt(message); + // Note: Server will reject the public key if it's encrypted using crypto.publicEncrypt(). + let pubKey = `-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----`; + pubKey = KEYUTIL.getKey(pubKey); + return hextob64(KJUR.crypto.Cipher.encrypt(data, pubKey)); +}; + +const getHeaders = (key) => { + const fingerPrint = uuidv4(); + + return { + 'content-type': 'application/json', + 'ovse-trace': uuidv4(), + callertype: 'USER', + fingerprint: encryptAES(fingerPrint, MD5(key)), + onetimeskey: encryptRSA(key), + timestamp: encryptAES(Date.now(), key), + referer: 'https://www.ainvest.com/', + }; +}; + +module.exports = { + randomString, + encryptAES, + decryptAES, + getHeaders, +}; diff --git a/package.json b/package.json index f2e1e6803b..44b5fc8478 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "jsdom": "22.1.0", "json-bigint": "1.0.0", "json5": "2.2.3", + "jsrsasign": "10.8.6", "koa": "2.14.2", "koa-basic-auth": "4.0.0", "koa-favicon": "2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e33165116c..6021f8d088 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ dependencies: json5: specifier: 2.2.3 version: 2.2.3 + jsrsasign: + specifier: 10.8.6 + version: 10.8.6 koa: specifier: 2.14.2 version: 2.14.2 @@ -5096,6 +5099,10 @@ packages: verror: 1.10.0 dev: false + /jsrsasign@10.8.6: + resolution: {integrity: sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==} + dev: false + /jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} dependencies: diff --git a/website/docs/routes/finance.md b/website/docs/routes/finance.md index 42d5554e79..9102cff9a0 100644 --- a/website/docs/routes/finance.md +++ b/website/docs/routes/finance.md @@ -38,6 +38,16 @@ +## AInvest {#ainvest} + +### Latest Article {#ainvest-latest-article} + + + +### Latest News {#ainvest-latest-news} + + + ## BigQuant {#bigquant} ### 专题报告 {#bigquant-zhuan-ti-bao-gao}