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}