mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-11 15:47:48 +08:00
fix: 喜马拉雅 (#5867)
This commit is contained in:
@@ -560,3 +560,7 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
|
||||
- `NGA_PASSPORT_UID`: 对应 cookie 中的 `ngaPassportUid`.
|
||||
|
||||
- `NGA_PASSPORT_CID`: 对应 cookie 中的 `ngaPassportCid`.
|
||||
|
||||
- 喜马拉雅
|
||||
|
||||
- `XIMALAYA_TOKEN`: 对应 cookie 中的 `1&_token`,获取方式:1. 登陆喜马拉雅网页版 2. 查找名称为`1&_token`的`cookie`,其内容即为`XIMALAYA_TOKEN`的值(即在`cookie` 中查找 `1&_token=***;`,并设置 `XIMALAYA_TOKEN = ***`)
|
||||
|
||||
@@ -675,12 +675,12 @@ pageClass: routes
|
||||
|
||||
### 专辑
|
||||
|
||||
<Route author="lengthmin jjeejj" example="/ximalaya/album/299146" path="/ximalaya/album/:id/:all?" :paramsDesc="['专辑 id, 可在对应专辑页面的 URL 中找到','是否需要获取全部节目,默认不获取,填入该字段则视为获取']" supportPodcast="1" radar="1">
|
||||
<Route author="lengthmin jjeejj prnake" example="/ximalaya/album/299146" path="/ximalaya/album/:id/:all?" :paramsDesc="['专辑 id, 可在对应专辑页面的 URL 中找到','是否需要获取全部节目,默认不获取,填入该字段则视为获取']" supportPodcast="1" radar="1">
|
||||
|
||||
::: warning 注意
|
||||
专辑 id 是跟在**分类拼音**后的那个 id, 不要输成某集的 id 了
|
||||
|
||||
**付费内容不可收听,但可使用非播客软件 (例如 Inoreader) 获取更新**
|
||||
**付费内容需要登陆才能收听,详情见部署页面的配置模块**
|
||||
|
||||
目前支持泛用型播客订阅的[输出格式](https://docs.rsshub.app/#输出格式)中标明的格式只有 rss 支持,也就是说你**只能使用**以下类型的链接来订阅播客:
|
||||
|
||||
|
||||
@@ -163,6 +163,9 @@ const calculateValue = () => {
|
||||
newrank: {
|
||||
cookie: envs.NEWRANK_COOKIE,
|
||||
},
|
||||
ximalaya: {
|
||||
token: envs.XIMALAYA_TOKEN,
|
||||
},
|
||||
};
|
||||
};
|
||||
calculateValue();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const got = require('@/utils/got');
|
||||
const { getUrl, getRandom16 } = require('./utils');
|
||||
const baseUrl = 'http://www.ximalaya.com';
|
||||
const got_ins = got.extend({});
|
||||
const config = require('@/config').value;
|
||||
|
||||
// Find category from: https://help.apple.com/itc/podcasts_connect/?lang=en#/itc9267a2f12
|
||||
const categoryDict = {
|
||||
@@ -14,9 +16,6 @@ const categoryDict = {
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const id = ctx.params.id; // 专辑id
|
||||
if (id === '31879246') {
|
||||
throw 'Forbidden';
|
||||
}
|
||||
const isAll = ctx.params.all ? true : false;
|
||||
const pageSize = isAll ? 200 : 30;
|
||||
const AlbumInfoApi = `https://www.ximalaya.com/revision/album?albumId=${id}`; // 专辑数据的API
|
||||
@@ -34,8 +33,9 @@ module.exports = async (ctx) => {
|
||||
const album_intro = albuminfo.richIntro; // 专辑介绍
|
||||
const album_desc = author_intro + ' <br/>' + album_intro;
|
||||
const albumUrl = '/' + classify + '/' + id + '/'; // 某分类的链接
|
||||
const ispaid = albuminfo.isPaid; // 是否需要付费
|
||||
const isAsc = AlbumInfoResponse.data.data.tracksInfo.sort === 0;
|
||||
const token = config.ximalaya.token;
|
||||
const RandomToken = getRandom16(8) + '-' + getRandom16(4) + '-' + getRandom16(4) + '-' + getRandom16(4) + '-' + getRandom16(12);
|
||||
|
||||
const TrackInfoApi = `http://mobile.ximalaya.com/mobile/v1/album/track/?albumId=${id}&isAsc=${!isAsc}&pageSize=${pageSize}&pageId=`;
|
||||
const TrackInfoResponse = await got_ins.get(TrackInfoApi + 1);
|
||||
@@ -54,6 +54,42 @@ module.exports = async (ctx) => {
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
playList.map(async (item) => {
|
||||
const link = baseUrl + albumUrl + item.trackId;
|
||||
const TrackRichInfoApi = `https://mobile.ximalaya.com/mobile-track/richIntro?trackId=${item.trackId}`;
|
||||
const TrackRichInfoResponse = await got({
|
||||
method: 'get',
|
||||
url: TrackRichInfoApi,
|
||||
});
|
||||
item.desc = TrackRichInfoResponse.data.richIntro;
|
||||
if (!item.desc) {
|
||||
item.desc = '请在网页查看声音简介:' + `<a href="${link}">${link}</a>`;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (token) {
|
||||
await Promise.all(
|
||||
playList.map(async (item) => {
|
||||
const TrackPayInfoApi = `https://mpay.ximalaya.com/mobile/track/pay/${item.trackId}/?device=pc`;
|
||||
const TrackPayInfoResponse = await got({
|
||||
method: 'get',
|
||||
url: TrackPayInfoApi,
|
||||
headers: {
|
||||
'user-agent': 'ting_6.7.9(GM1900,Android29)',
|
||||
cookie: `1&_device=android&${RandomToken}&6.7.9;1&_token=${token}`,
|
||||
},
|
||||
});
|
||||
if (TrackPayInfoResponse.data.ep) {
|
||||
item.playPathAacv224 = getUrl(TrackPayInfoResponse.data);
|
||||
} else if (TrackPayInfoResponse.data.msg) {
|
||||
item.desc += '<br/>' + TrackPayInfoResponse.data.msg;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const resultItems = await Promise.all(
|
||||
playList.map(async (item) => {
|
||||
const title = item.title;
|
||||
@@ -63,11 +99,10 @@ module.exports = async (ctx) => {
|
||||
const pubDate = new Date(item.createdAt).toUTCString();
|
||||
const enclosure_length = item.duration; // 时间长度:单位(秒)
|
||||
const enclosure_url = item.playPathAacv224;
|
||||
let desc = item.desc;
|
||||
|
||||
let desc = '请在网页查看声音简介:' + `<a href="${link}">${link}</a>`;
|
||||
|
||||
if (ispaid) {
|
||||
desc = ' [该内容需付费,请打开网页收听] ' + '<br/>' + desc;
|
||||
if (!enclosure_url) {
|
||||
desc = ' [该内容需付费,请打开网页收听] ' + `<br/><a href="${link}">${link}</a>` + desc;
|
||||
}
|
||||
|
||||
const resultItem = {
|
||||
|
||||
256
lib/routes/ximalaya/utils.js
Normal file
256
lib/routes/ximalaya/utils.js
Normal file
@@ -0,0 +1,256 @@
|
||||
const crypto = require('crypto');
|
||||
const getParams = (ep) => {
|
||||
const a1 = 'xkt3a41psizxrh9l';
|
||||
const a = [
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
62,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
63,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
61,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
];
|
||||
let o = ep.length;
|
||||
let r = 0;
|
||||
let n, e;
|
||||
const a2 = [];
|
||||
while (r < o) {
|
||||
e = a[255 & ep[r].charCodeAt()];
|
||||
r += 1;
|
||||
while (r < o && -1 === e) {
|
||||
e = a[255 & ep[r].charCodeAt()];
|
||||
r += 1;
|
||||
}
|
||||
|
||||
if (-1 === e) {
|
||||
break;
|
||||
}
|
||||
n = a[255 & ep[r].charCodeAt()];
|
||||
r += 1;
|
||||
while (r < o && -1 === n) {
|
||||
n = a[255 & ep[r].charCodeAt()];
|
||||
r += 1;
|
||||
}
|
||||
if (-1 === n) {
|
||||
break;
|
||||
}
|
||||
a2.push((e << 2) | ((48 & n) >> 4));
|
||||
e = 255 & ep[r].charCodeAt();
|
||||
r += 1;
|
||||
if (61 === e) {
|
||||
break;
|
||||
}
|
||||
e = a[e];
|
||||
while (r < o && -1 === e) {
|
||||
e = 255 & ep[r].charCodeAt();
|
||||
if (61 === e) {
|
||||
break;
|
||||
}
|
||||
e = a[e];
|
||||
}
|
||||
if (-1 === e) {
|
||||
break;
|
||||
}
|
||||
a2.push(((15 & n) << 4) | ((60 & e) >> 2));
|
||||
n = 255 & ep[r].charCodeAt();
|
||||
r += 1;
|
||||
if (61 === n) {
|
||||
break;
|
||||
}
|
||||
n = a[n];
|
||||
while (r < o && -1 === n) {
|
||||
n = 255 & ep[r].charCodeAt();
|
||||
if (61 === n) {
|
||||
break;
|
||||
}
|
||||
n = a[n];
|
||||
}
|
||||
if (-1 === n) {
|
||||
break;
|
||||
}
|
||||
a2.push(((3 & e) << 6) | n);
|
||||
}
|
||||
|
||||
const r1 = Array.from(Array(256), (v, i) => i);
|
||||
|
||||
let i = '';
|
||||
o = 0;
|
||||
for (let a = 0; a < 256; a++) {
|
||||
o = (o + r1[a] + a1[a % a1.length].charCodeAt()) % 256;
|
||||
[r1[a], r1[o]] = [r1[o], r1[a]];
|
||||
}
|
||||
|
||||
let a3 = 0;
|
||||
o = 0;
|
||||
for (let u = 0; u < a2.length; u++) {
|
||||
a3 = (a3 + 1) % 256;
|
||||
o = (o + r1[a3]) % 256;
|
||||
[r1[a3], r1[o]] = [r1[o], r1[a3]];
|
||||
i += String.fromCharCode(a2[u] ^ r1[(r1[a3] + r1[o]) % 256]);
|
||||
}
|
||||
i = i.split('-');
|
||||
return {
|
||||
sign: i[1],
|
||||
buy_key: i[0],
|
||||
token: i[2],
|
||||
timestamp: i[3],
|
||||
};
|
||||
};
|
||||
|
||||
const getPath = (seed, fileId) => {
|
||||
let t = String.raw`abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890`;
|
||||
let cgStr = '';
|
||||
const l = t.length;
|
||||
let o;
|
||||
const idKey = fileId.split('*');
|
||||
for (let i = 0; i < l; i++) {
|
||||
seed = (211 * seed + 30031) % 65536;
|
||||
o = parseInt((seed * t.length) / 65536);
|
||||
cgStr += t[o];
|
||||
t = t.split(t[o]).join('');
|
||||
}
|
||||
|
||||
const url = idKey.map((id) => cgStr[id]).join('');
|
||||
return url;
|
||||
};
|
||||
|
||||
const getUrl = (r) => {
|
||||
const params = getParams(r.ep);
|
||||
const paramsArray = [];
|
||||
params.duration = r.duration;
|
||||
Object.keys(params).forEach((key) => params[key] && paramsArray.push(`${key}=${params[key]}`));
|
||||
const url = 'https://audiopay.cos.xmcdn.com/download/' + r.apiVersion + '/' + getPath(r.seed, r.fileId) + '?' + paramsArray.join('&');
|
||||
return url;
|
||||
};
|
||||
|
||||
const getRandom16 = (len) =>
|
||||
crypto
|
||||
.randomBytes(Math.ceil(len / 2))
|
||||
.toString('hex')
|
||||
.slice(0, len);
|
||||
|
||||
module.exports = {
|
||||
getUrl,
|
||||
getRandom16,
|
||||
};
|
||||
Reference in New Issue
Block a user