fix: 喜马拉雅 (#5867)

This commit is contained in:
prnake
2020-10-13 18:07:37 +08:00
committed by GitHub
parent 899701408e
commit 35ec6e9272
5 changed files with 308 additions and 10 deletions

View File

@@ -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 = ***`

View File

@@ -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 支持,也就是说你**只能使用**以下类型的链接来订阅播客:

View File

@@ -163,6 +163,9 @@ const calculateValue = () => {
newrank: {
cookie: envs.NEWRANK_COOKIE,
},
ximalaya: {
token: envs.XIMALAYA_TOKEN,
},
};
};
calculateValue();

View File

@@ -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 = {

View 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,
};