feat(route): add 云听专辑 & 直播 (#10926)
* feat(route): add 云听专辑 & 直播 * fix: wrong enclosure_url * update lib/v2/radio/album.js Co-authored-by: Tony <TonyRL@users.noreply.github.com> * update lib/v2/radio/album.js Co-authored-by: Tony <TonyRL@users.noreply.github.com> * update lib/v2/radio/album.js Co-authored-by: Tony <TonyRL@users.noreply.github.com> * update lib/v2/radio/index.js
This commit is contained in:
parent
5af05ea618
commit
d1b4a05105
|
|
@ -1656,9 +1656,55 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
|
|||
|
||||
## 云听
|
||||
|
||||
### 电台节目
|
||||
### 专辑
|
||||
|
||||
<Route author="kt286 nczitzk" example="/radio/1552135" path="/radio/:id" :paramsDesc="['节目ID,可在对应专辑页面的 URL 中找到']" supportPodcast="1"/>
|
||||
<Route author="nczitzk" example="/radio/album/15682090498666" path="/radio/album/:id" :paramsDesc="['专辑 id,可在对应专辑页面的 URL 中找到']" supportPodcast="1">
|
||||
|
||||
如果订阅 [中国相声榜](https://www.radio.cn/pc-portal/sanji/detail.html?columnId=15682090498666),其 URL 为 <https://www.radio.cn/pc-portal/sanji/detail.html?columnId=15682090498666>,可以得到 `columnId` 为 `15682090498666`
|
||||
|
||||
所以对应路由为 [`/radio/album/15682090498666`](https://rsshub.app/radio/album/15682090498666)
|
||||
|
||||
::: tip 提示
|
||||
|
||||
部分专辑不适用该路由,此时可以尝试 [节目](#yun-ting-jie-mu) 路由
|
||||
|
||||
:::
|
||||
|
||||
</Route>
|
||||
|
||||
### 节目
|
||||
|
||||
<Route author="kt286 nczitzk" example="/radio/1552135" path="/radio/:id" :paramsDesc="['专辑 id,可在对应专辑页面的 URL 中找到']" supportPodcast="1">
|
||||
|
||||
如果订阅 [共和国追梦人](http://www.radio.cn/pc-portal/sanji/detail.html?columnId=1552135),其 URL 为 <https://www.radio.cn/pc-portal/sanji/detail.html?columnId=1552135>,可以得到 `columnId` 为 `1552135`
|
||||
|
||||
所以对应路由为 [`/radio/1552135`](https://rsshub.app/radio/1552135)
|
||||
|
||||
::: tip 提示
|
||||
|
||||
该路由仅适用于更新时间较早的电台节目,如 [共和国追梦人](http://www.radio.cn/pc-portal/sanji/detail.html?columnId=1552135)
|
||||
|
||||
与适用于 [专辑](#yun-ting-zhuan-ji) 路由的专辑其 `columnId` 长度相比,它们的 `columnId` 长度较短
|
||||
|
||||
:::
|
||||
|
||||
</Route>
|
||||
|
||||
### 直播
|
||||
|
||||
<Route author="nczitzk" example="/radio/zhibo/1395528" path="/radio/zhibo/:id" :paramsDesc="['直播 id,可在对应点播页面的 URL 中找到']" supportPodcast="1">
|
||||
|
||||
如果订阅 [新闻和报纸摘要](http://www.radio.cn/pc-portal/sanji/zhibo\_2.html?name=1395528),其 URL 为 <http://www.radio.cn/pc-portal/sanji/zhibo_2.html?name=1395528>,可以得到 `name` 为 `1395528`
|
||||
|
||||
所以对应路由为 [`/radio/zhibo/1395528`](https://rsshub.app/radio/zhibo/1395528)
|
||||
|
||||
::: tip 提示
|
||||
|
||||
查看更多电台直播节目,可前往 [电台直播](http://www.radio.cn/pc-portal/erji/radioStation.html)
|
||||
|
||||
:::
|
||||
|
||||
</Route>
|
||||
|
||||
## 中国高清网
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { art } = require('@/utils/render');
|
||||
const path = require('path');
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const KEY = 'f0fc4c668392f9f9a447e48584c214ee';
|
||||
|
||||
const id = ctx.params.id;
|
||||
const size = ctx.query.limit ?? '100';
|
||||
|
||||
const params = `albumId=${id}&pageNo=0&pageSize=${size}`;
|
||||
|
||||
const rootUrl = 'https://www.radio.cn';
|
||||
const apiRootUrl = 'https://ytmsout.radio.cn';
|
||||
const detailRootUrl = 'https://ytapi.radio.cn';
|
||||
|
||||
const iconUrl = `${rootUrl}/pc-portal/image/icon_32.jpg`;
|
||||
const currentUrl = `${rootUrl}/pc-portal/sanji/detail.html?columnId=${id}`;
|
||||
const apiUrl = `${apiRootUrl}/web/appSingle/pageByAlbum?${params}`;
|
||||
const detailUrl = `${detailRootUrl}/ytsrv/srv/wifimusicbox/demand/detail`;
|
||||
|
||||
const detailResponse = await got({
|
||||
method: 'post',
|
||||
url: detailUrl,
|
||||
form: {
|
||||
pageIndex: '0',
|
||||
sortType: '1',
|
||||
mobileId: '',
|
||||
providerCode: '25010',
|
||||
pid: id,
|
||||
paySongFlag: '1',
|
||||
richText: '1',
|
||||
h5flag: '1',
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
version: '4.0.0',
|
||||
providerCode: '25010',
|
||||
equipmentSource: 'WEB',
|
||||
},
|
||||
});
|
||||
|
||||
const data = detailResponse.data;
|
||||
|
||||
const timestamp = new Date().getTime();
|
||||
const sign = CryptoJS.MD5(`${params}×tamp=${timestamp}&key=${KEY}`).toString().toUpperCase();
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
sign,
|
||||
timestamp,
|
||||
'Content-Type': 'application/json',
|
||||
equipmentId: '0000',
|
||||
platformCode: 'WEB',
|
||||
},
|
||||
});
|
||||
|
||||
const items = response.data.data.data.map((item) => {
|
||||
let enclosure_url = item.playUrlHigh ?? item.playUrlLow;
|
||||
enclosure_url = /\.m3u8$/.test(enclosure_url) ? item.downloadUrl : enclosure_url;
|
||||
|
||||
const enclosure_type = `audio/${enclosure_url.match(/\.(\w+)$/)[1]}`;
|
||||
|
||||
return {
|
||||
guid: item.id,
|
||||
title: item.name,
|
||||
link: enclosure_url,
|
||||
description: art(path.join(__dirname, 'templates/description.art'), {
|
||||
description: item.des,
|
||||
enclosure_url,
|
||||
enclosure_type,
|
||||
}),
|
||||
author: data.anchorpersons,
|
||||
pubDate: parseDate(item.publishTime),
|
||||
enclosure_url,
|
||||
enclosure_type,
|
||||
enclosure_length: item.fileSize,
|
||||
itunes_duration: item.duration,
|
||||
itunes_item_image: iconUrl,
|
||||
};
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `云听 - ${data.columnName}`,
|
||||
link: currentUrl,
|
||||
item: items,
|
||||
image: iconUrl,
|
||||
itunes_author: data.anchorpersons,
|
||||
description: data.descriptions ?? data.descriptionSimple,
|
||||
itunes_category: data.atypeInfo.map((c) => c.categoryName).join(),
|
||||
};
|
||||
};
|
||||
|
|
@ -5,12 +5,12 @@ const path = require('path');
|
|||
|
||||
module.exports = async (ctx) => {
|
||||
const id = ctx.params.id;
|
||||
const size = ctx.query.limit ?? '100';
|
||||
|
||||
const rootUrl = 'http://www.radio.cn';
|
||||
const currentUrl = `${rootUrl}/pc-portal/sanji/detail.html?columnId=${id}`;
|
||||
|
||||
const rootUrl = 'https://www.radio.cn';
|
||||
const apiRootUrl = 'http://tacc.radio.cn';
|
||||
const apiUrl = `${apiRootUrl}/pcpages/odchannelpages?od_id=${id}&start=1&rows=50`;
|
||||
const apiUrl = `${apiRootUrl}/pcpages/odchannelpages?od_id=${id}&start=1&rows=${size}`;
|
||||
const currentUrl = `${rootUrl}/pc-portal/sanji/detail.html?columnId=${id}`;
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
|
|
@ -18,24 +18,31 @@ module.exports = async (ctx) => {
|
|||
});
|
||||
|
||||
if (/^\(.*\)$/.test(response.data)) {
|
||||
response.data = JSON.parse(response.data)[0];
|
||||
response.data = JSON.parse(response.data.match(/^\((.*)\)$/)[1]);
|
||||
}
|
||||
|
||||
const data = response.data.data;
|
||||
|
||||
const items = data.program.map((item) => ({
|
||||
guid: item.id,
|
||||
title: item.name,
|
||||
link: item.streams[0].url,
|
||||
description: `${item.description}${art(path.join(__dirname, 'templates/description.art'), {
|
||||
streams: item.streams,
|
||||
})}`,
|
||||
pubDate: parseDate(item.onlinetime),
|
||||
enclosure_url: item.streams[0].url,
|
||||
enclosure_type: 'audio/x-m4a',
|
||||
itunes_duration: item.duration,
|
||||
itunes_item_image: data.odchannel.imageUrl[0].url,
|
||||
}));
|
||||
const items = data.program.map((item) => {
|
||||
const enclosure_url = item.streams[0].url;
|
||||
const enclosure_type = `audio/${enclosure_url.match(/\.(\w+)$/)[1]}`;
|
||||
|
||||
return {
|
||||
guid: item.id,
|
||||
title: item.name,
|
||||
link: item.streams[0].url,
|
||||
description: art(path.join(__dirname, 'templates/description.art'), {
|
||||
description: item.description,
|
||||
enclosure_url,
|
||||
enclosure_type,
|
||||
}),
|
||||
pubDate: parseDate(item.onlinetime),
|
||||
enclosure_url,
|
||||
enclosure_type,
|
||||
itunes_duration: item.duration,
|
||||
itunes_item_image: data.odchannel.imageUrl[0].url,
|
||||
};
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `云听 - ${data.odchannel.name}`,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
module.exports = {
|
||||
'/album/:id': ['nczitzk'],
|
||||
'/zhibo/:id': ['nczitzk'],
|
||||
'/:id': ['kt286', 'nczitzk'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,11 +3,23 @@ module.exports = {
|
|||
_name: '云听',
|
||||
'.': [
|
||||
{
|
||||
title: '电台节目',
|
||||
docs: 'https://docs.rsshub.app/multimedia.html#yun-ting-dian-tai-jie-mu',
|
||||
source: ['/pc-portal/sanji/detail.html', '/'],
|
||||
title: '专辑',
|
||||
docs: 'https://docs.rsshub.app/multimedia.html#yun-ting-zhuan-ji',
|
||||
source: ['/pc-portal/sanji/detail.html', '/share/albumDetail', '/'],
|
||||
target: (params, url) => `/radio/album/${new URL(url).searchParams.get('columnId')}`,
|
||||
},
|
||||
{
|
||||
title: '节目',
|
||||
docs: 'https://docs.rsshub.app/multimedia.html#yun-ting-jie-mu',
|
||||
source: ['/pc-portal/sanji/detail.html', '/share/albumDetail', '/'],
|
||||
target: (params, url) => `/radio/${new URL(url).searchParams.get('columnId')}`,
|
||||
},
|
||||
{
|
||||
title: '直播',
|
||||
docs: 'https://docs.rsshub.app/multimedia.html#yun-ting-zhi-bo',
|
||||
source: ['/pc-portal/sanji/zhibo_2.html', '/'],
|
||||
target: (params, url) => `/radio/zhibo/${new URL(url).searchParams.get('name')}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
const got = require('@/utils/got');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const channelname = ctx.params.channelname; // 专辑id
|
||||
const name = ctx.params.name; // 专辑id
|
||||
|
||||
const api = `http://tacc.radio.cn/pcpages/searchs/livehistory?channelname=${channelname}&name=${name}&start=1&rows=20`;
|
||||
const response = await got.get(api);
|
||||
|
||||
const playList = response.data.passprogram;
|
||||
let title = '';
|
||||
let itunes_category = '';
|
||||
let items = [];
|
||||
if (playList && playList.length > 0) {
|
||||
const first = playList[0];
|
||||
title = `${first.channel_name} ${first.name}`;
|
||||
itunes_category = first.channel_name;
|
||||
|
||||
items = playList.map((item) => {
|
||||
const single = {
|
||||
title: `${item.name} ${item.broadcast_date}`,
|
||||
link: 'http://' + item.stream_domain1 + item.stream_url1,
|
||||
description: `
|
||||
<audio controls="controls">
|
||||
<source src="http://${item.stream_domain1}${item.stream_url1}" type="audio/x-m4a">
|
||||
<source src="http://${item.stream_domain2}${item.stream_url2}" type="audio/x-m4a">
|
||||
<source src="http://${item.stream_domain3}${item.stream_url3}" type="audio/x-m4a">
|
||||
</audio>
|
||||
`,
|
||||
pubDate: item.update_time,
|
||||
guid: item.id,
|
||||
itunes_item_image: 'http://www.radio.cn/pc-portal/image/icon_32.jpg',
|
||||
enclosure_url: 'http://' + item.stream_domain1 + item.stream_url1,
|
||||
itunes_duration: item.resource_length,
|
||||
enclosure_type: 'audio/x-m4a',
|
||||
};
|
||||
return single;
|
||||
});
|
||||
}
|
||||
|
||||
ctx.state.data = {
|
||||
title,
|
||||
link: `http://www.radio.cn/pc-portal/sanji/zhibo_2.html?channelname=${channelname}&name=${name}&title=radio`,
|
||||
description: title,
|
||||
image: 'http://www.radio.cn/pc-portal/image/icon_32.jpg',
|
||||
itunes_author: 'radio.cn',
|
||||
itunes_category,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
module.exports = function (router) {
|
||||
router.get('/album/:id', require('./album'));
|
||||
router.get('/zhibo/:id', require('./zhibo'));
|
||||
router.get('/:id', require('./index'));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
{{ if description }}
|
||||
<p>{{ description }}</p>
|
||||
{{ /if }}
|
||||
{{ if enclosure_url && enclosure_type }}
|
||||
<audio controls="controls">
|
||||
{{each streams stream}}
|
||||
<source src="{{ stream.url }}" type="audio/x-m4a">
|
||||
{{/each}}
|
||||
</audio>
|
||||
<source src="{{ enclosure_url }}" type="{{ enclosure_type }}">
|
||||
</audio>
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { art } = require('@/utils/render');
|
||||
const path = require('path');
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const KEY = 'f0fc4c668392f9f9a447e48584c214ee';
|
||||
|
||||
const id = ctx.params.id;
|
||||
const size = ctx.query.limit ?? '100';
|
||||
|
||||
const params = `columnId=${id}&pageNo=0&pageSize=${size}`;
|
||||
|
||||
const rootUrl = 'https://www.radio.cn';
|
||||
const apiRootUrl = 'https://ytmsout.radio.cn';
|
||||
|
||||
const iconUrl = `${rootUrl}/pc-portal/image/icon_32.jpg`;
|
||||
const currentUrl = `${rootUrl}/pc-portal/sanji/zhibo_2.html?name=${id}`;
|
||||
const apiUrl = `${apiRootUrl}/web/appProgram/pageByColumn?${params}`;
|
||||
|
||||
const timestamp = new Date().getTime();
|
||||
const sign = CryptoJS.MD5(`${params}×tamp=${timestamp}&key=${KEY}`).toString().toUpperCase();
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
sign,
|
||||
timestamp,
|
||||
'Content-Type': 'application/json',
|
||||
equipmentId: '0000',
|
||||
platformCode: 'WEB',
|
||||
},
|
||||
});
|
||||
|
||||
const data = response.data.data.data;
|
||||
|
||||
const items = data.map((item) => {
|
||||
let enclosure_url = item.playUrlHigh ?? item.playUrlLow;
|
||||
enclosure_url = /\.m3u8$/.test(enclosure_url) ? item.downloadUrl : enclosure_url;
|
||||
|
||||
const enclosure_type = `audio/${enclosure_url.match(/\.(\w+)$/)[1]}`;
|
||||
|
||||
return {
|
||||
guid: item.id,
|
||||
title: item.name,
|
||||
link: enclosure_url,
|
||||
description: art(path.join(__dirname, 'templates/description.art'), {
|
||||
description: item.des,
|
||||
enclosure_url,
|
||||
enclosure_type,
|
||||
}),
|
||||
pubDate: parseDate(item.startTime),
|
||||
enclosure_url,
|
||||
enclosure_type,
|
||||
itunes_duration: item.durationStr,
|
||||
itunes_item_image: iconUrl,
|
||||
};
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `云听 - ${data[0].name}`,
|
||||
link: currentUrl,
|
||||
item: items,
|
||||
image: iconUrl,
|
||||
itunes_author: 'radio.cn',
|
||||
description: data[0].des,
|
||||
};
|
||||
};
|
||||
Loading…
Reference in New Issue