mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-13 16:49:31 +08:00
fix: 喜马拉雅 (#5867)
This commit is contained in:
@@ -560,3 +560,7 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
|
|||||||
- `NGA_PASSPORT_UID`: 对应 cookie 中的 `ngaPassportUid`.
|
- `NGA_PASSPORT_UID`: 对应 cookie 中的 `ngaPassportUid`.
|
||||||
|
|
||||||
- `NGA_PASSPORT_CID`: 对应 cookie 中的 `ngaPassportCid`.
|
- `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 注意
|
::: warning 注意
|
||||||
专辑 id 是跟在**分类拼音**后的那个 id, 不要输成某集的 id 了
|
专辑 id 是跟在**分类拼音**后的那个 id, 不要输成某集的 id 了
|
||||||
|
|
||||||
**付费内容不可收听,但可使用非播客软件 (例如 Inoreader) 获取更新**
|
**付费内容需要登陆才能收听,详情见部署页面的配置模块**
|
||||||
|
|
||||||
目前支持泛用型播客订阅的[输出格式](https://docs.rsshub.app/#输出格式)中标明的格式只有 rss 支持,也就是说你**只能使用**以下类型的链接来订阅播客:
|
目前支持泛用型播客订阅的[输出格式](https://docs.rsshub.app/#输出格式)中标明的格式只有 rss 支持,也就是说你**只能使用**以下类型的链接来订阅播客:
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ const calculateValue = () => {
|
|||||||
newrank: {
|
newrank: {
|
||||||
cookie: envs.NEWRANK_COOKIE,
|
cookie: envs.NEWRANK_COOKIE,
|
||||||
},
|
},
|
||||||
|
ximalaya: {
|
||||||
|
token: envs.XIMALAYA_TOKEN,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
calculateValue();
|
calculateValue();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
const got = require('@/utils/got');
|
const got = require('@/utils/got');
|
||||||
|
const { getUrl, getRandom16 } = require('./utils');
|
||||||
const baseUrl = 'http://www.ximalaya.com';
|
const baseUrl = 'http://www.ximalaya.com';
|
||||||
const got_ins = got.extend({});
|
const got_ins = got.extend({});
|
||||||
|
const config = require('@/config').value;
|
||||||
|
|
||||||
// Find category from: https://help.apple.com/itc/podcasts_connect/?lang=en#/itc9267a2f12
|
// Find category from: https://help.apple.com/itc/podcasts_connect/?lang=en#/itc9267a2f12
|
||||||
const categoryDict = {
|
const categoryDict = {
|
||||||
@@ -14,9 +16,6 @@ const categoryDict = {
|
|||||||
|
|
||||||
module.exports = async (ctx) => {
|
module.exports = async (ctx) => {
|
||||||
const id = ctx.params.id; // 专辑id
|
const id = ctx.params.id; // 专辑id
|
||||||
if (id === '31879246') {
|
|
||||||
throw 'Forbidden';
|
|
||||||
}
|
|
||||||
const isAll = ctx.params.all ? true : false;
|
const isAll = ctx.params.all ? true : false;
|
||||||
const pageSize = isAll ? 200 : 30;
|
const pageSize = isAll ? 200 : 30;
|
||||||
const AlbumInfoApi = `https://www.ximalaya.com/revision/album?albumId=${id}`; // 专辑数据的API
|
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_intro = albuminfo.richIntro; // 专辑介绍
|
||||||
const album_desc = author_intro + ' <br/>' + album_intro;
|
const album_desc = author_intro + ' <br/>' + album_intro;
|
||||||
const albumUrl = '/' + classify + '/' + id + '/'; // 某分类的链接
|
const albumUrl = '/' + classify + '/' + id + '/'; // 某分类的链接
|
||||||
const ispaid = albuminfo.isPaid; // 是否需要付费
|
|
||||||
const isAsc = AlbumInfoResponse.data.data.tracksInfo.sort === 0;
|
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 TrackInfoApi = `http://mobile.ximalaya.com/mobile/v1/album/track/?albumId=${id}&isAsc=${!isAsc}&pageSize=${pageSize}&pageId=`;
|
||||||
const TrackInfoResponse = await got_ins.get(TrackInfoApi + 1);
|
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(
|
const resultItems = await Promise.all(
|
||||||
playList.map(async (item) => {
|
playList.map(async (item) => {
|
||||||
const title = item.title;
|
const title = item.title;
|
||||||
@@ -63,11 +99,10 @@ module.exports = async (ctx) => {
|
|||||||
const pubDate = new Date(item.createdAt).toUTCString();
|
const pubDate = new Date(item.createdAt).toUTCString();
|
||||||
const enclosure_length = item.duration; // 时间长度:单位(秒)
|
const enclosure_length = item.duration; // 时间长度:单位(秒)
|
||||||
const enclosure_url = item.playPathAacv224;
|
const enclosure_url = item.playPathAacv224;
|
||||||
|
let desc = item.desc;
|
||||||
|
|
||||||
let desc = '请在网页查看声音简介:' + `<a href="${link}">${link}</a>`;
|
if (!enclosure_url) {
|
||||||
|
desc = ' [该内容需付费,请打开网页收听] ' + `<br/><a href="${link}">${link}</a>` + desc;
|
||||||
if (ispaid) {
|
|
||||||
desc = ' [该内容需付费,请打开网页收听] ' + '<br/>' + desc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultItem = {
|
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