mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-01 17:48:15 +08:00
feat(route): add mixcloud (#11672)
* feat(route): add mixcloud * docs * feat(route): add mixcloud * convert linebreaks to <br> * docs: add supportPodcast * codestyle: use template literals * refactor: split graph ql queries
This commit is contained in:
@@ -387,6 +387,18 @@ See [Directory](https://www.javlibrary.com/en/star_list.php) to view all stars.
|
||||
|
||||
</RouteEn>
|
||||
|
||||
## Mixcloud
|
||||
|
||||
### User
|
||||
|
||||
<RouteEn author="Misaka13514" example="/mixcloud/dholbach/uploads" path="/mixcloud/:username/:type?" :paramsDesc="['Username, can be found in URL', 'Type, see below, uploads by default']" radar="1" rssbud="1" supportPodcast="1">
|
||||
|
||||
| Shows | Favorites | History | Stream |
|
||||
| ------- | --------- | ------- | ------ |
|
||||
| uploads | favorites | listens | stream |
|
||||
|
||||
</RouteEn>
|
||||
|
||||
## Nyaa
|
||||
|
||||
### Search Result
|
||||
|
||||
@@ -880,6 +880,18 @@ JavDB 有多个备用域名,本路由默认使用永久域名 <https://javdb.c
|
||||
|
||||
</Route>
|
||||
|
||||
## Mixcloud
|
||||
|
||||
### 用户
|
||||
|
||||
<Route author="Misaka13514" example="/mixcloud/dholbach/uploads" path="/mixcloud/:username/:type?" :paramsDesc="['用户名,可在对应用户页 URL 中找到', '分类,见下表,默认为 uploads']" radar="1" rssbud="1" supportPodcast="1">
|
||||
|
||||
| Shows | Favorites | History | Stream |
|
||||
| ------- | --------- | ------- | ------ |
|
||||
| uploads | favorites | listens | stream |
|
||||
|
||||
</Route>
|
||||
|
||||
## Mp4Ba
|
||||
|
||||
### 影视分类
|
||||
|
||||
130
lib/v2/mixcloud/index.js
Normal file
130
lib/v2/mixcloud/index.js
Normal file
@@ -0,0 +1,130 @@
|
||||
const got = require('@/utils/got');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { queries } = require('./queries');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const host = 'https://www.mixcloud.com';
|
||||
const imageBaseURL = 'https://thumbnailer.mixcloud.com/unsafe/480x480/';
|
||||
const headers = {
|
||||
Referer: host,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
};
|
||||
|
||||
const type = ctx.params.type ?? 'uploads';
|
||||
if (!['stream', 'uploads', 'favorites', 'listens'].includes(type)) {
|
||||
throw Error(`Invalid type: ${type}`);
|
||||
}
|
||||
const username = ctx.params.username;
|
||||
|
||||
const config = {
|
||||
stream: { name: 'Stream', node: 'stream' },
|
||||
uploads: { name: 'Shows', node: 'uploads' },
|
||||
favorites: { name: 'Favorites', node: 'favorites' },
|
||||
listens: { name: 'History', node: 'listeningHistory' },
|
||||
};
|
||||
const payloads = {
|
||||
stream: {
|
||||
query: queries.stream.query,
|
||||
variables: {
|
||||
lookup: {
|
||||
username: ctx.params.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
uploads: {
|
||||
query: queries.uploads.query,
|
||||
variables: {
|
||||
lookup: {
|
||||
username: ctx.params.username,
|
||||
},
|
||||
orderBy: 'LATEST',
|
||||
audioTypes: ['SHOW'],
|
||||
},
|
||||
},
|
||||
favorites: {
|
||||
query: queries.favorites.query,
|
||||
variables: {
|
||||
lookup: {
|
||||
username: ctx.params.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
listens: {
|
||||
query: queries.listens.query,
|
||||
variables: {
|
||||
lookup: {
|
||||
username: ctx.params.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
profile: {
|
||||
query: queries.profile.query,
|
||||
variables: {
|
||||
lookup: {
|
||||
username: ctx.params.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const profile = (
|
||||
await got({
|
||||
method: 'post',
|
||||
url: `${host}/graphql`,
|
||||
json: payloads.profile,
|
||||
headers,
|
||||
})
|
||||
).data.data;
|
||||
|
||||
const biog = profile.user.biog;
|
||||
const image = `${imageBaseURL}${profile.user.picture.urlRoot}`;
|
||||
|
||||
const data = (
|
||||
await got({
|
||||
method: 'post',
|
||||
url: `${host}/graphql`,
|
||||
json: payloads[type],
|
||||
headers,
|
||||
})
|
||||
).data.data;
|
||||
|
||||
// https://github.com/ytdl-org/youtube-dl/blob/f1487d4fca40fd37d735753e24a7bae53a1b1513/youtube_dl/extractor/mixcloud.py#L72-L79
|
||||
const decryptionKey = 'IFYOUWANTTHEARTISTSTOGETPAIDDONOTDOWNLOADFROMMIXCLOUD';
|
||||
const decryptXorCipher = (key, cipherText) => {
|
||||
const cipher = CryptoJS.enc.Base64.parse(cipherText);
|
||||
const decrypted = cipher.toString(CryptoJS.enc.Utf8);
|
||||
let result = '';
|
||||
for (let i = 0; i < decrypted.length; i++) {
|
||||
result += String.fromCharCode(decrypted.charCodeAt(i) ^ key.charCodeAt(i % key.length));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const items = data.user[config[type].node].edges.map((edge) => {
|
||||
const item = type === 'listens' ? edge.node.cloudcast : edge.node;
|
||||
return {
|
||||
title: item.name,
|
||||
author: item.owner.displayName,
|
||||
description: item.description.replace(/\n/g, '<br>'),
|
||||
pubDate: parseDate(item.publishDate),
|
||||
guid: Buffer.from(item.id, 'base64').toString('utf8'),
|
||||
link: `${host}/${username}/${item.slug}`,
|
||||
itunes_item_image: `${imageBaseURL}${item.picture.urlRoot}`,
|
||||
itunes_duration: item.audioLength,
|
||||
enclosure_url: decryptXorCipher(decryptionKey, item.streamInfo.url),
|
||||
enclosure_type: 'audio/x-m4a',
|
||||
upvotes: item.favorites.totalCount,
|
||||
};
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `Mixcloud - ${data.user.displayName}'s ${config[type].name}`,
|
||||
description: biog.replace(/\n/g, '<br>'),
|
||||
itunes_author: data.user.displayName,
|
||||
image,
|
||||
link: `${host}/${username}/${type}/`,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
3
lib/v2/mixcloud/maintainer.js
Normal file
3
lib/v2/mixcloud/maintainer.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'/:username/:type?': ['Misaka13514'],
|
||||
};
|
||||
1520
lib/v2/mixcloud/queries.js
Normal file
1520
lib/v2/mixcloud/queries.js
Normal file
File diff suppressed because it is too large
Load Diff
37
lib/v2/mixcloud/radar.js
Normal file
37
lib/v2/mixcloud/radar.js
Normal file
@@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
'mixcloud.com': {
|
||||
_name: 'Mixcloud',
|
||||
www: [
|
||||
{
|
||||
title: 'User',
|
||||
docs: 'https://docs.rsshub.app/multimedia.html#mixcloud',
|
||||
source: ['/:username/:type?'],
|
||||
target: (params) => {
|
||||
if (params.username !== undefined) {
|
||||
if (['stream', 'uploads', 'favorites', 'listens'].includes(params.type)) {
|
||||
return `/mixcloud/${params.username}/${params.type}`;
|
||||
} else if (params.type === undefined) {
|
||||
return `/mixcloud/${params.username}/uploads`;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
'.': [
|
||||
{
|
||||
title: 'User',
|
||||
docs: 'https://docs.rsshub.app/multimedia.html#mixcloud',
|
||||
source: ['/:username/:type?'],
|
||||
target: (params) => {
|
||||
if (params.username !== undefined) {
|
||||
if (['stream', 'uploads', 'favorites', 'listens'].includes(params.type)) {
|
||||
return `/mixcloud/${params.username}/${params.type}`;
|
||||
} else if (params.type === undefined) {
|
||||
return `/mixcloud/${params.username}/uploads`;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
3
lib/v2/mixcloud/router.js
Normal file
3
lib/v2/mixcloud/router.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = function (router) {
|
||||
router.get('/:username/:type?', require('./index'));
|
||||
};
|
||||
Reference in New Issue
Block a user