From 1636b09b71c0e57da154bec11d96ba23c145f90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AC=A0=E9=99=A5=E9=9B=BB=E6=B0=97?= Date: Fri, 27 Jan 2023 23:03:03 +0800 Subject: [PATCH] feat(route): add mixcloud (#11672) * feat(route): add mixcloud * docs * feat(route): add mixcloud * convert linebreaks to
* docs: add supportPodcast * codestyle: use template literals * refactor: split graph ql queries --- docs/en/multimedia.md | 12 + docs/multimedia.md | 12 + lib/v2/mixcloud/index.js | 130 +++ lib/v2/mixcloud/maintainer.js | 3 + lib/v2/mixcloud/queries.js | 1520 +++++++++++++++++++++++++++++++++ lib/v2/mixcloud/radar.js | 37 + lib/v2/mixcloud/router.js | 3 + 7 files changed, 1717 insertions(+) create mode 100644 lib/v2/mixcloud/index.js create mode 100644 lib/v2/mixcloud/maintainer.js create mode 100644 lib/v2/mixcloud/queries.js create mode 100644 lib/v2/mixcloud/radar.js create mode 100644 lib/v2/mixcloud/router.js diff --git a/docs/en/multimedia.md b/docs/en/multimedia.md index 36d6b9545e..0007327460 100644 --- a/docs/en/multimedia.md +++ b/docs/en/multimedia.md @@ -387,6 +387,18 @@ See [Directory](https://www.javlibrary.com/en/star_list.php) to view all stars. +## Mixcloud + +### User + + + +| Shows | Favorites | History | Stream | +| ------- | --------- | ------- | ------ | +| uploads | favorites | listens | stream | + + + ## Nyaa ### Search Result diff --git a/docs/multimedia.md b/docs/multimedia.md index 8c3054eee8..852477256a 100644 --- a/docs/multimedia.md +++ b/docs/multimedia.md @@ -880,6 +880,18 @@ JavDB 有多个备用域名,本路由默认使用永久域名 +## Mixcloud + +### 用户 + + + +| Shows | Favorites | History | Stream | +| ------- | --------- | ------- | ------ | +| uploads | favorites | listens | stream | + + + ## Mp4Ba ### 影视分类 diff --git a/lib/v2/mixcloud/index.js b/lib/v2/mixcloud/index.js new file mode 100644 index 0000000000..b4d4e7d7b1 --- /dev/null +++ b/lib/v2/mixcloud/index.js @@ -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, '
'), + 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, '
'), + itunes_author: data.user.displayName, + image, + link: `${host}/${username}/${type}/`, + item: items, + }; +}; diff --git a/lib/v2/mixcloud/maintainer.js b/lib/v2/mixcloud/maintainer.js new file mode 100644 index 0000000000..a3252a4c37 --- /dev/null +++ b/lib/v2/mixcloud/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/:username/:type?': ['Misaka13514'], +}; diff --git a/lib/v2/mixcloud/queries.js b/lib/v2/mixcloud/queries.js new file mode 100644 index 0000000000..72b652b705 --- /dev/null +++ b/lib/v2/mixcloud/queries.js @@ -0,0 +1,1520 @@ +const queries = { + stream: { + query: `query UserStreamQuery($lookup: UserLookup!) { + user: userLookup(lookup: $lookup) { + username + ...UserStreamPage_user + id + } + viewer { + ...UserStreamPage_viewer + id + } + } + + fragment AudioCardActions_cloudcast on Cloudcast { + id + isPublic + slug + isExclusive + isUnlisted + isScheduled + isDraft + audioType + isDisabledCopyright + owner { + id + username + isSubscribedTo + isViewer + } + ...AudioCardFavoriteButton_cloudcast + ...AudioCardRepostButton_cloudcast + ...AudioCardShareButton_cloudcast + ...AudioCardAddToButton_cloudcast + ...AudioCardHighlightButton_cloudcast + ...AudioCardBoostButton_cloudcast + ...AudioCardStats_cloudcast + } + + fragment AudioCardActions_viewer on Viewer { + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + ...AudioCardFavoriteButton_viewer + ...AudioCardRepostButton_viewer + ...AudioCardHighlightButton_viewer + } + + fragment AudioCardAddToButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + } + + fragment AudioCardBoostButton_cloudcast on Cloudcast { + id + isPublic + owner { + id + isViewer + } + } + + fragment AudioCardFavoriteButton_cloudcast on Cloudcast { + id + isFavorited + isPublic + hiddenStats + favorites { + totalCount + } + slug + owner { + id + isFollowing + username + isSelect + displayName + isViewer + } + } + + fragment AudioCardFavoriteButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardHighlightButton_cloudcast on Cloudcast { + id + isPublic + isHighlighted + owner { + isViewer + id + } + } + + fragment AudioCardHighlightButton_viewer on Viewer { + me { + id + hasProFeatures + highlighted { + totalCount + } + } + } + + fragment AudioCardPlayButton_cloudcast on Cloudcast { + id + restrictedReason + owner { + displayName + country + username + isSubscribedTo + isViewer + id + } + slug + isAwaitingAudio + isDraft + isPlayable + streamInfo { + hlsUrl + dashUrl + url + uuid + } + audioLength + currentPosition + proportionListened + repeatPlayAmount + hasPlayCompleted + seekRestriction + previewUrl + isExclusivePreviewOnly + isExclusive + isDisabledCopyright + } + + fragment AudioCardProgress_cloudcast on Cloudcast { + id + proportionListened + audioLength + } + + fragment AudioCardRepostButton_cloudcast on Cloudcast { + id + isReposted + isExclusive + isPublic + reposts { + totalCount + } + owner { + isViewer + isSubscribedTo + id + } + } + + fragment AudioCardRepostButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardShareButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + slug + description + picture { + urlRoot + } + owner { + displayName + isViewer + username + id + } + } + + fragment AudioCardStats_cloudcast on Cloudcast { + isExclusive + isDraft + hiddenStats + plays + publishDate + qualityScore + listenerMinutes + owner { + isSubscribedTo + id + } + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + ...AudioCardTags_cloudcast + } + + fragment AudioCardTags_cloudcast on Cloudcast { + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + } + + fragment AudioCardTitle_cloudcast on Cloudcast { + id + slug + name + audioType + isLiveRecording + isExclusive + owner { + id + displayName + username + ...Hovercard_user + ...UserBadge_user + } + ...AudioCardPlayButton_cloudcast + ...ExclusiveCloudcastBadgeContainer_cloudcast + ...CloudcastHQAudio_cloudcast + } + + fragment AudioCard_cloudcast on Cloudcast { + id + slug + name + audioType + isAwaitingAudio + isDraft + isScheduled + restrictedReason + publishDate + isLiveRecording + isDisabledCopyright + owner { + isViewer + username + id + } + picture { + ...UGCImage_picture + } + ...AudioCardTitle_cloudcast + ...AudioCardProgress_cloudcast + ...AudioCardActions_cloudcast + ...QuantcastCloudcastTracking_cloudcast + } + + fragment AudioCard_viewer on Viewer { + ...AudioCardActions_viewer + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + } + + fragment CloudcastHQAudio_cloudcast on Cloudcast { + audioQuality + } + + fragment ExclusiveCloudcastBadgeContainer_cloudcast on Cloudcast { + isExclusive + isExclusivePreviewOnly + slug + id + owner { + username + id + } + } + + fragment Hovercard_user on User { + id + } + + fragment QuantcastCloudcastTracking_cloudcast on Cloudcast { + owner { + quantcastTrackingPixel + id + } + } + + fragment UGCImage_picture on Picture { + urlRoot + primaryColor + } + + fragment UserBadge_user on User { + hasProFeatures + isStaff + hasPremiumFeatures + } + + fragment UserStreamPage_user on User { + id + displayName + username + stream(first: 10) { + edges { + cursor + repostedBy + node { + id + ...AudioCard_cloudcast + __typename + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + + fragment UserStreamPage_viewer on Viewer { + ...AudioCard_viewer + }`, + }, + uploads: { + query: `query UserUploadsQuery( + $lookup: UserLookup! + $orderBy: CloudcastOrderByEnum + $audioTypes: [AudioTypeEnum!] + ) { + user: userLookup(lookup: $lookup) { + username + ...UserUploadsPage_user_3HcCKF + id + } + viewer { + ...UserUploadsPage_viewer + id + } + } + + fragment AudioCardActions_cloudcast on Cloudcast { + id + isPublic + slug + isExclusive + isUnlisted + isScheduled + isDraft + audioType + isDisabledCopyright + owner { + id + username + isSubscribedTo + isViewer + } + ...AudioCardFavoriteButton_cloudcast + ...AudioCardRepostButton_cloudcast + ...AudioCardShareButton_cloudcast + ...AudioCardAddToButton_cloudcast + ...AudioCardHighlightButton_cloudcast + ...AudioCardBoostButton_cloudcast + ...AudioCardStats_cloudcast + } + + fragment AudioCardActions_viewer on Viewer { + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + ...AudioCardFavoriteButton_viewer + ...AudioCardRepostButton_viewer + ...AudioCardHighlightButton_viewer + } + + fragment AudioCardAddToButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + } + + fragment AudioCardBoostButton_cloudcast on Cloudcast { + id + isPublic + owner { + id + isViewer + } + } + + fragment AudioCardFavoriteButton_cloudcast on Cloudcast { + id + isFavorited + isPublic + hiddenStats + favorites { + totalCount + } + slug + owner { + id + isFollowing + username + isSelect + displayName + isViewer + } + } + + fragment AudioCardFavoriteButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardHighlightButton_cloudcast on Cloudcast { + id + isPublic + isHighlighted + owner { + isViewer + id + } + } + + fragment AudioCardHighlightButton_viewer on Viewer { + me { + id + hasProFeatures + highlighted { + totalCount + } + } + } + + fragment AudioCardPlayButton_cloudcast on Cloudcast { + id + restrictedReason + owner { + displayName + country + username + isSubscribedTo + isViewer + id + } + slug + isAwaitingAudio + isDraft + isPlayable + streamInfo { + hlsUrl + dashUrl + url + uuid + } + audioLength + currentPosition + proportionListened + repeatPlayAmount + hasPlayCompleted + seekRestriction + previewUrl + isExclusivePreviewOnly + isExclusive + isDisabledCopyright + } + + fragment AudioCardProgress_cloudcast on Cloudcast { + id + proportionListened + audioLength + } + + fragment AudioCardRepostButton_cloudcast on Cloudcast { + id + isReposted + isExclusive + isPublic + reposts { + totalCount + } + owner { + isViewer + isSubscribedTo + id + } + } + + fragment AudioCardRepostButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardShareButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + slug + description + picture { + urlRoot + } + owner { + displayName + isViewer + username + id + } + } + + fragment AudioCardStats_cloudcast on Cloudcast { + isExclusive + isDraft + hiddenStats + plays + publishDate + qualityScore + listenerMinutes + owner { + isSubscribedTo + id + } + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + ...AudioCardTags_cloudcast + } + + fragment AudioCardTags_cloudcast on Cloudcast { + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + } + + fragment AudioCardTitle_cloudcast on Cloudcast { + id + slug + name + audioType + isLiveRecording + isExclusive + owner { + id + displayName + username + ...Hovercard_user + ...UserBadge_user + } + ...AudioCardPlayButton_cloudcast + ...ExclusiveCloudcastBadgeContainer_cloudcast + ...CloudcastHQAudio_cloudcast + } + + fragment AudioCard_cloudcast on Cloudcast { + id + slug + name + audioType + isAwaitingAudio + isDraft + isScheduled + restrictedReason + publishDate + isLiveRecording + isDisabledCopyright + owner { + isViewer + username + id + } + picture { + ...UGCImage_picture + } + ...AudioCardTitle_cloudcast + ...AudioCardProgress_cloudcast + ...AudioCardActions_cloudcast + ...QuantcastCloudcastTracking_cloudcast + } + + fragment AudioCard_viewer on Viewer { + ...AudioCardActions_viewer + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + } + + fragment CloudcastHQAudio_cloudcast on Cloudcast { + audioQuality + } + + fragment ExclusiveCloudcastBadgeContainer_cloudcast on Cloudcast { + isExclusive + isExclusivePreviewOnly + slug + id + owner { + username + id + } + } + + fragment Hovercard_user on User { + id + } + + fragment QuantcastCloudcastTracking_cloudcast on Cloudcast { + owner { + quantcastTrackingPixel + id + } + } + + fragment UGCImage_picture on Picture { + urlRoot + primaryColor + } + + fragment UserBadge_user on User { + hasProFeatures + isStaff + hasPremiumFeatures + } + + fragment UserUploadsPage_user_3HcCKF on User { + id + displayName + username + uploads( + first: 10 + orderBy: $orderBy + audioTypes: $audioTypes + isPublic: true + ) { + edges { + node { + ...AudioCard_cloudcast + id + __typename + } + cursor + } + pageInfo { + endCursor + hasNextPage + } + } + } + + fragment UserUploadsPage_viewer on Viewer { + ...AudioCard_viewer + }`, + }, + favorites: { + query: `query UserFavoritesQuery($lookup: UserLookup!) { + user: userLookup(lookup: $lookup) { + username + hiddenFavorites: favorites { + isHidden + } + ...UserFavoritesPage_user + id + } + viewer { + ...UserFavoritesPage_viewer + id + } + } + + fragment AudioCardActions_cloudcast on Cloudcast { + id + isPublic + slug + isExclusive + isUnlisted + isScheduled + isDraft + audioType + isDisabledCopyright + owner { + id + username + isSubscribedTo + isViewer + } + ...AudioCardFavoriteButton_cloudcast + ...AudioCardRepostButton_cloudcast + ...AudioCardShareButton_cloudcast + ...AudioCardAddToButton_cloudcast + ...AudioCardHighlightButton_cloudcast + ...AudioCardBoostButton_cloudcast + ...AudioCardStats_cloudcast + } + + fragment AudioCardActions_viewer on Viewer { + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + ...AudioCardFavoriteButton_viewer + ...AudioCardRepostButton_viewer + ...AudioCardHighlightButton_viewer + } + + fragment AudioCardAddToButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + } + + fragment AudioCardBoostButton_cloudcast on Cloudcast { + id + isPublic + owner { + id + isViewer + } + } + + fragment AudioCardFavoriteButton_cloudcast on Cloudcast { + id + isFavorited + isPublic + hiddenStats + favorites { + totalCount + } + slug + owner { + id + isFollowing + username + isSelect + displayName + isViewer + } + } + + fragment AudioCardFavoriteButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardHighlightButton_cloudcast on Cloudcast { + id + isPublic + isHighlighted + owner { + isViewer + id + } + } + + fragment AudioCardHighlightButton_viewer on Viewer { + me { + id + hasProFeatures + highlighted { + totalCount + } + } + } + + fragment AudioCardPlayButton_cloudcast on Cloudcast { + id + restrictedReason + owner { + displayName + country + username + isSubscribedTo + isViewer + id + } + slug + isAwaitingAudio + isDraft + isPlayable + streamInfo { + hlsUrl + dashUrl + url + uuid + } + audioLength + currentPosition + proportionListened + repeatPlayAmount + hasPlayCompleted + seekRestriction + previewUrl + isExclusivePreviewOnly + isExclusive + isDisabledCopyright + } + + fragment AudioCardProgress_cloudcast on Cloudcast { + id + proportionListened + audioLength + } + + fragment AudioCardRepostButton_cloudcast on Cloudcast { + id + isReposted + isExclusive + isPublic + reposts { + totalCount + } + owner { + isViewer + isSubscribedTo + id + } + } + + fragment AudioCardRepostButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardShareButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + slug + description + picture { + urlRoot + } + owner { + displayName + isViewer + username + id + } + } + + fragment AudioCardStats_cloudcast on Cloudcast { + isExclusive + isDraft + hiddenStats + plays + publishDate + qualityScore + listenerMinutes + owner { + isSubscribedTo + id + } + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + ...AudioCardTags_cloudcast + } + + fragment AudioCardTags_cloudcast on Cloudcast { + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + } + + fragment AudioCardTitle_cloudcast on Cloudcast { + id + slug + name + audioType + isLiveRecording + isExclusive + owner { + id + displayName + username + ...Hovercard_user + ...UserBadge_user + } + ...AudioCardPlayButton_cloudcast + ...ExclusiveCloudcastBadgeContainer_cloudcast + ...CloudcastHQAudio_cloudcast + } + + fragment AudioCard_cloudcast on Cloudcast { + id + slug + name + audioType + isAwaitingAudio + isDraft + isScheduled + restrictedReason + publishDate + isLiveRecording + isDisabledCopyright + owner { + isViewer + username + id + } + picture { + ...UGCImage_picture + } + ...AudioCardTitle_cloudcast + ...AudioCardProgress_cloudcast + ...AudioCardActions_cloudcast + ...QuantcastCloudcastTracking_cloudcast + } + + fragment AudioCard_viewer on Viewer { + ...AudioCardActions_viewer + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + } + + fragment CloudcastHQAudio_cloudcast on Cloudcast { + audioQuality + } + + fragment ExclusiveCloudcastBadgeContainer_cloudcast on Cloudcast { + isExclusive + isExclusivePreviewOnly + slug + id + owner { + username + id + } + } + + fragment Hovercard_user on User { + id + } + + fragment QuantcastCloudcastTracking_cloudcast on Cloudcast { + owner { + quantcastTrackingPixel + id + } + } + + fragment UGCImage_picture on Picture { + urlRoot + primaryColor + } + + fragment UserBadge_user on User { + hasProFeatures + isStaff + hasPremiumFeatures + } + + fragment UserFavoritesPage_user on User { + id + displayName + username + isViewer + favorites(first: 10) { + edges { + node { + id + ...AudioCard_cloudcast + __typename + } + cursor + } + pageInfo { + endCursor + hasNextPage + } + } + } + + fragment UserFavoritesPage_viewer on Viewer { + me { + id + } + ...AudioCard_viewer + }`, + }, + listens: { + query: `query UserListensQuery($lookup: UserLookup!) { + user: userLookup(lookup: $lookup) { + username + hiddenListeningHistory: listeningHistory { + isHidden + } + ...UserListensPage_user + id + } + viewer { + ...UserListensPage_viewer + id + } + } + + fragment AudioCardActions_cloudcast on Cloudcast { + id + isPublic + slug + isExclusive + isUnlisted + isScheduled + isDraft + audioType + isDisabledCopyright + owner { + id + username + isSubscribedTo + isViewer + } + ...AudioCardFavoriteButton_cloudcast + ...AudioCardRepostButton_cloudcast + ...AudioCardShareButton_cloudcast + ...AudioCardAddToButton_cloudcast + ...AudioCardHighlightButton_cloudcast + ...AudioCardBoostButton_cloudcast + ...AudioCardStats_cloudcast + } + + fragment AudioCardActions_viewer on Viewer { + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + ...AudioCardFavoriteButton_viewer + ...AudioCardRepostButton_viewer + ...AudioCardHighlightButton_viewer + } + + fragment AudioCardAddToButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + } + + fragment AudioCardBoostButton_cloudcast on Cloudcast { + id + isPublic + owner { + id + isViewer + } + } + + fragment AudioCardFavoriteButton_cloudcast on Cloudcast { + id + isFavorited + isPublic + hiddenStats + favorites { + totalCount + } + slug + owner { + id + isFollowing + username + isSelect + displayName + isViewer + } + } + + fragment AudioCardFavoriteButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardHighlightButton_cloudcast on Cloudcast { + id + isPublic + isHighlighted + owner { + isViewer + id + } + } + + fragment AudioCardHighlightButton_viewer on Viewer { + me { + id + hasProFeatures + highlighted { + totalCount + } + } + } + + fragment AudioCardPlayButton_cloudcast on Cloudcast { + id + restrictedReason + owner { + displayName + country + username + isSubscribedTo + isViewer + id + } + slug + isAwaitingAudio + isDraft + isPlayable + streamInfo { + hlsUrl + dashUrl + url + uuid + } + audioLength + currentPosition + proportionListened + repeatPlayAmount + hasPlayCompleted + seekRestriction + previewUrl + isExclusivePreviewOnly + isExclusive + isDisabledCopyright + } + + fragment AudioCardProgress_cloudcast on Cloudcast { + id + proportionListened + audioLength + } + + fragment AudioCardRepostButton_cloudcast on Cloudcast { + id + isReposted + isExclusive + isPublic + reposts { + totalCount + } + owner { + isViewer + isSubscribedTo + id + } + } + + fragment AudioCardRepostButton_viewer on Viewer { + me { + id + } + } + + fragment AudioCardShareButton_cloudcast on Cloudcast { + id + isUnlisted + isPublic + slug + description + picture { + urlRoot + } + owner { + displayName + isViewer + username + id + } + } + + fragment AudioCardStats_cloudcast on Cloudcast { + isExclusive + isDraft + hiddenStats + plays + publishDate + qualityScore + listenerMinutes + owner { + isSubscribedTo + id + } + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + ...AudioCardTags_cloudcast + } + + fragment AudioCardTags_cloudcast on Cloudcast { + tags(country: "GLOBAL") { + tag { + name + slug + id + } + } + } + + fragment AudioCardTitle_cloudcast on Cloudcast { + id + slug + name + audioType + isLiveRecording + isExclusive + owner { + id + displayName + username + ...Hovercard_user + ...UserBadge_user + } + ...AudioCardPlayButton_cloudcast + ...ExclusiveCloudcastBadgeContainer_cloudcast + ...CloudcastHQAudio_cloudcast + } + + fragment AudioCard_cloudcast on Cloudcast { + id + slug + name + audioType + isAwaitingAudio + isDraft + isScheduled + restrictedReason + publishDate + isLiveRecording + isDisabledCopyright + owner { + isViewer + username + id + } + picture { + ...UGCImage_picture + } + ...AudioCardTitle_cloudcast + ...AudioCardProgress_cloudcast + ...AudioCardActions_cloudcast + ...QuantcastCloudcastTracking_cloudcast + } + + fragment AudioCard_viewer on Viewer { + ...AudioCardActions_viewer + me { + uploadLimits { + tracksPublishRemaining + showsPublishRemaining + } + id + } + } + + fragment CloudcastHQAudio_cloudcast on Cloudcast { + audioQuality + } + + fragment ExclusiveCloudcastBadgeContainer_cloudcast on Cloudcast { + isExclusive + isExclusivePreviewOnly + slug + id + owner { + username + id + } + } + + fragment Hovercard_user on User { + id + } + + fragment QuantcastCloudcastTracking_cloudcast on Cloudcast { + owner { + quantcastTrackingPixel + id + } + } + + fragment UGCImage_picture on Picture { + urlRoot + primaryColor + } + + fragment UserBadge_user on User { + hasProFeatures + isStaff + hasPremiumFeatures + } + + fragment UserListensPage_user on User { + id + isViewer + displayName + username + listeningHistory(first: 10) { + totalCount + edges { + cursor + node { + id + cloudcast { + ...AudioCard_cloudcast + id + } + __typename + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + + fragment UserListensPage_viewer on Viewer { + id + me { + id + } + ...AudioCard_viewer + }`, + }, + profile: { + query: `query UserProfileHeaderQuery($lookup: UserLookup!) { + user: userLookup(lookup: $lookup) { + id + displayName + username + isBranded + isStaff + isViewer + followers { + totalCount + } + hasCoverPicture + hasPremiumFeatures + hasProFeatures + picture { + primaryColor + ...UGCImage_picture + } + coverPicture { + urlRoot + } + ...ProfileNavigation_user + ...UserBadge_user + ...ShareUserButton_user + ...ProfileRegisterUpsellComponent_user + ...FollowButton_user + } + viewer { + ...ProfileRegisterUpsellComponent_viewer + ...FollowButton_viewer + id + } + } + + fragment FollowButton_user on User { + id + isFollowed + isFollowing + isViewer + followers { + totalCount + } + username + displayName + } + + fragment FollowButton_viewer on Viewer { + me { + id + } + } + + fragment ProfileNavigation_user on User { + id + username + stream { + totalCount + } + favorites { + totalCount + } + listeningHistory { + totalCount + } + uploads(audioTypes: [SHOW]) { + totalCount + } + tracks: uploads(audioTypes: [TRACK]) { + totalCount + } + posts { + totalCount + } + profileNavigation( + showsAudioTypes: [SHOW] + tracksAudioTypes: [TRACK] + streamAudioTypes: [SHOW, TRACK] + ) { + menuItems { + __typename + ... on NavigationItemInterface { + __isNavigationItemInterface: __typename + inDropdown + } + ... on HideableNavigationItemInterface { + __isHideableNavigationItemInterface: __typename + hidden + } + ... on PlaylistNavigationItem { + count + playlist { + id + name + slug + } + } + } + } + } + + fragment ProfileRegisterUpsellComponent_user on User { + id + displayName + followers { + totalCount + } + } + + fragment ProfileRegisterUpsellComponent_viewer on Viewer { + me { + id + } + } + + fragment ShareUserButton_user on User { + biog + username + displayName + id + isUploader + picture { + urlRoot + } + } + + fragment UGCImage_picture on Picture { + urlRoot + primaryColor + } + + fragment UserBadge_user on User { + hasProFeatures + isStaff + hasPremiumFeatures + }`, + }, +}; + +module.exports = { + queries, +}; diff --git a/lib/v2/mixcloud/radar.js b/lib/v2/mixcloud/radar.js new file mode 100644 index 0000000000..5e07f71f74 --- /dev/null +++ b/lib/v2/mixcloud/radar.js @@ -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`; + } + } + }, + }, + ], + }, +}; diff --git a/lib/v2/mixcloud/router.js b/lib/v2/mixcloud/router.js new file mode 100644 index 0000000000..5c7246a588 --- /dev/null +++ b/lib/v2/mixcloud/router.js @@ -0,0 +1,3 @@ +module.exports = function (router) { + router.get('/:username/:type?', require('./index')); +};