Skip to content

Commit

Permalink
fix(route): qingting (#15251)
Browse files Browse the repository at this point in the history
* fix(route): qingting

* fix: set default value for `qingtingId`

* fix: typo
  • Loading branch information
KarasuShin authored Apr 17, 2024
1 parent 0456ca9 commit 31f578d
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 39 deletions.
6 changes: 6 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ export type Config = {
pkubbs: {
cookie?: string;
};
qingting: {
id?: string;
};
saraba1st: {
cookie?: string;
};
Expand Down Expand Up @@ -575,6 +578,9 @@ const calculateValue = () => {
pkubbs: {
cookie: envs.PKUBBS_COOKIE,
},
qingting: {
id: envs.QINGTING_ID,
},
saraba1st: {
cookie: envs.SARABA1ST_COOKIE,
},
Expand Down
101 changes: 62 additions & 39 deletions lib/routes/qingting/podcast.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { Route } from '@/types';
import type { DataItem, Route } from '@/types';
import cache from '@/utils/cache';
import crypto from 'crypto';
import got from '@/utils/got';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
import { config } from '@/config';

const qingtingId = config.qingting.id ?? '';

export const route: Route = {
path: '/podcast/:id',
categories: ['multimedia'],
example: '/qingting/podcast/293411',
parameters: { id: '专辑id, 可在专辑页 URL 中找到' },
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: true,
supportScihub: false,
requireConfig: [
{
name: 'QINGTING_ID',
optional: true,
description: '用户id, 部分专辑需要会员身份,用户id可以通过从网页端登录蜻蜓fm后使用开发者工具,在控制台中运行JSON.parse(localStorage.getItem("user")).qingting_id获取',
},
],
},
radar: [
{
Expand All @@ -29,75 +34,93 @@ export const route: Route = {
description: `获取的播放 URL 有效期只有 1 天,需要开启播客 APP 的自动下载功能。`,
};

function getMediaUrl(channelId: string, mediaId: string) {
const path = `/audiostream/redirect/${channelId}/${mediaId}?access_token=&device_id=MOBILESITE&qingting_id=${qingtingId}&t=${Date.now()}`;
const sign = crypto.createHmac('md5', 'fpMn12&38f_2e').update(path).digest('hex').toString();
return `https://audio.qingting.fm${path}&sign=${sign}`;
}

async function handler(ctx) {
const channelUrl = `https://i.qingting.fm/capi/v3/channel/${ctx.req.param('id')}`;
let response = await got({
const channelId = ctx.req.param('id');

const channelUrl = `https://i.qingting.fm/capi/v3/channel/${channelId}`;
const response = await got({
method: 'get',
url: channelUrl,
headers: {
Referer: 'https://www.qingting.fm/',
},
});

const title = response.data.data.title;
const channel_img = response.data.data.thumbs['400_thumb'];
const authors = response.data.data.podcasters.map((author) => author.nick_name).join(',');
const desc = response.data.data.description;
const programUrl = `https://i.qingting.fm/capi/channel/${ctx.req.param('id')}/programs/${response.data.data.v}?curpage=1&pagesize=10&order=asc`;
response = await got({
const programUrl = `https://i.qingting.fm/capi/channel/${channelId}/programs/${response.data.data.v}?curpage=1&pagesize=10&order=asc`;

const {
data: {
data: { programs },
},
} = await got({
method: 'get',
url: programUrl,
headers: {
Referer: 'https://www.qingting.fm/',
},
});

let isPaid = false;

if (qingtingId) {
const {
data: { data: channelInfo },
} = await got(`https://i.qingting.fm/capi/v3/channel/${channelId}?user_id=${qingtingId}`);
isPaid = channelInfo.user_relevance.sale_status === 'paid';
}

const resultItems = await Promise.all(
response.data.data.programs.map((item) =>
cache.tryGet(`qingting:podcast:${ctx.req.param('id')}:${item.id}`, async () => {
const link = `https://www.qingting.fm/channels/${ctx.req.param('id')}/programs/${item.id}/`;

const path = `/audiostream/redirect/${ctx.req.param('id')}/${item.id}?access_token=&device_id=MOBILESITE&qingting_id=&t=${Date.now()}`;
const sign = crypto.createHmac('md5', 'fpMn12&38f_2e').update(path).digest('hex').toString();

const [detailRes, mediaRes] = await Promise.all([
got({
method: 'get',
url: link,
headers: {
Referer: 'https://www.qingting.fm/',
},
}),
got({
method: 'get',
url: `https://audio.qingting.fm${path}&sign=${sign}`,
headers: {
Referer: 'https://www.qingting.fm/',
},
}),
]);
programs.map(async (item) => {
const data = (await cache.tryGet(`qingting:podcast:${channelId}:${item.id}`, async () => {
const link = `https://www.qingting.fm/channels/${channelId}/programs/${item.id}/`;

const detailRes = await got({
method: 'get',
url: link,
headers: {
Referer: 'https://www.qingting.fm/',
},
});

const detail = JSON.parse(detailRes.data.match(/},"program":(.*?),"plist":/)[1]);

return {
const rssItem = {
title: item.title,
link,
itunes_item_image: item.cover,
itunes_duration: item.duration,
pubDate: timezone(parseDate(item.update_time), +8),
description: detail.richtext,
enclosure_url: mediaRes.url,
enclosure_type: 'audio/x-m4a',
};
})
)

return rssItem;
})) as DataItem;

if (isPaid || item.isfree) {
data.enclosure_url = getMediaUrl(channelId, item.id);
data.enclosure_type = 'audio/x-m4a';
}

return data;
})
);

return {
title: `${title} - 蜻蜓FM`,
description: desc,
itunes_author: authors,
image: channel_img,
link: `https://www.qingting.fm/channels/${ctx.req.param('id')}`,
link: `https://www.qingting.fm/channels/${channelId}`,
item: resultItems,
};
}

0 comments on commit 31f578d

Please sign in to comment.