mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +00:00
Supporting multiplatform track likes
This commit is contained in:
parent
fec281dece
commit
4add14652c
@ -1,7 +1,6 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
import seekToTimeLabel from "utils/seekToTimeLabel"
|
|
||||||
|
|
||||||
import { ImageViewer } from "components"
|
import { ImageViewer } from "components"
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
@ -13,6 +12,17 @@ import { Context as PlaylistContext } from "contexts/WithPlaylistContext"
|
|||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
"like": async (ctx, track) => {
|
||||||
|
app.cores.player.toggleCurrentTrackLike(true, track)
|
||||||
|
ctx.closeMenu()
|
||||||
|
},
|
||||||
|
"unlike": async (ctx, track) => {
|
||||||
|
app.cores.player.toggleCurrentTrackLike(false, track)
|
||||||
|
ctx.closeMenu()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const Track = (props) => {
|
const Track = (props) => {
|
||||||
const {
|
const {
|
||||||
track_manifest,
|
track_manifest,
|
||||||
@ -55,8 +65,19 @@ const Track = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMoreMenuItemClick = () => {
|
const handleMoreMenuItemClick = (e) => {
|
||||||
|
const { key } = e
|
||||||
|
|
||||||
|
if (typeof handlers[key] === "function") {
|
||||||
|
return handlers[key](
|
||||||
|
{
|
||||||
|
closeMenu: () => {
|
||||||
|
setMoreMenuOpened(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props.track
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const moreMenuItems = React.useMemo(() => {
|
const moreMenuItems = React.useMemo(() => {
|
||||||
@ -70,19 +91,30 @@ const Track = (props) => {
|
|||||||
key: "share",
|
key: "share",
|
||||||
icon: <Icons.MdShare />,
|
icon: <Icons.MdShare />,
|
||||||
label: "Share",
|
label: "Share",
|
||||||
|
disabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "add_to_playlist",
|
key: "add_to_playlist",
|
||||||
icon: <Icons.MdPlaylistAdd />,
|
icon: <Icons.MdPlaylistAdd />,
|
||||||
label: "Add to playlist",
|
label: "Add to playlist",
|
||||||
|
disabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "add_to_queue",
|
key: "add_to_queue",
|
||||||
icon: <Icons.MdQueueMusic />,
|
icon: <Icons.MdQueueMusic />,
|
||||||
label: "Add to queue",
|
label: "Add to queue",
|
||||||
|
disabled: true,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (props.track.liked) {
|
||||||
|
items[0] = {
|
||||||
|
key: "unlike",
|
||||||
|
icon: <Icons.MdFavorite />,
|
||||||
|
label: "Unlike",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (playlist_ctx) {
|
if (playlist_ctx) {
|
||||||
if (playlist_ctx.owning_playlist) {
|
if (playlist_ctx.owning_playlist) {
|
||||||
items.push({
|
items.push({
|
||||||
@ -98,7 +130,7 @@ const Track = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
}, [props.track])
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
id={props.track._id}
|
id={props.track._id}
|
||||||
|
@ -18,8 +18,8 @@ const EventsHandlers = {
|
|||||||
"playback": () => {
|
"playback": () => {
|
||||||
return app.cores.player.playback.toggle()
|
return app.cores.player.playback.toggle()
|
||||||
},
|
},
|
||||||
"like": () => {
|
"like": async (ctx) => {
|
||||||
|
await app.cores.player.toggleCurrentTrackLike(!ctx.track_manifest?.liked)
|
||||||
},
|
},
|
||||||
"previous": () => {
|
"previous": () => {
|
||||||
return app.cores.player.playback.previous()
|
return app.cores.player.playback.previous()
|
||||||
|
37
packages/app/src/components/Player/ExtraActions/index.jsx
Normal file
37
packages/app/src/components/Player/ExtraActions/index.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { Button } from "antd"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
|
||||||
|
import LikeButton from "components/LikeButton"
|
||||||
|
import { Context } from "contexts/WithPlayerContext"
|
||||||
|
|
||||||
|
const ExtraActions = (props) => {
|
||||||
|
const ctx = React.useContext(Context)
|
||||||
|
|
||||||
|
const handleClickLike = async () => {
|
||||||
|
await app.cores.player.toggleCurrentTrackLike(!ctx.track_manifest?.liked)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="extra_actions">
|
||||||
|
{
|
||||||
|
app.isMobile && <Button
|
||||||
|
type="ghost"
|
||||||
|
icon={<Icons.MdAbc />}
|
||||||
|
disabled={!ctx.track_manifest.lyricsEnabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!app.isMobile && <LikeButton
|
||||||
|
liked={ctx.track_manifest?.liked ?? false}
|
||||||
|
onClick={handleClickLike}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="ghost"
|
||||||
|
icon={<Icons.MdQueueMusic />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtraActions
|
@ -13,7 +13,7 @@ import Controls from "components/Player/Controls"
|
|||||||
|
|
||||||
import RGBStringToValues from "utils/rgbToValues"
|
import RGBStringToValues from "utils/rgbToValues"
|
||||||
|
|
||||||
import LikeButton from "components/LikeButton"
|
import ExtraActions from "../ExtraActions"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
@ -45,17 +45,6 @@ const ServiceIndicator = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExtraActions = (props) => {
|
|
||||||
return <div className="extra_actions">
|
|
||||||
<LikeButton />
|
|
||||||
|
|
||||||
<antd.Button
|
|
||||||
type="ghost"
|
|
||||||
icon={<Icons.MdQueueMusic />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const Player = (props) => {
|
const Player = (props) => {
|
||||||
const ctx = React.useContext(Context)
|
const ctx = React.useContext(Context)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import AudioPlayerStorage from "./player.storage"
|
|||||||
import defaultAudioProccessors from "./processors"
|
import defaultAudioProccessors from "./processors"
|
||||||
|
|
||||||
import MediaSession from "./mediaSession"
|
import MediaSession from "./mediaSession"
|
||||||
import servicesToManifestResolver from "./servicesToManifestResolver"
|
import ServicesHandlers from "./services"
|
||||||
|
|
||||||
export default class Player extends Core {
|
export default class Player extends Core {
|
||||||
static dependencies = [
|
static dependencies = [
|
||||||
@ -94,6 +94,7 @@ export default class Player extends Core {
|
|||||||
seek: this.seek.bind(this),
|
seek: this.seek.bind(this),
|
||||||
minimize: this.toggleMinimize.bind(this),
|
minimize: this.toggleMinimize.bind(this),
|
||||||
collapse: this.toggleCollapse.bind(this),
|
collapse: this.toggleCollapse.bind(this),
|
||||||
|
toggleCurrentTrackLike: this.toggleCurrentTrackLike.bind(this),
|
||||||
state: new Proxy(this.state, {
|
state: new Proxy(this.state, {
|
||||||
get: (target, prop) => {
|
get: (target, prop) => {
|
||||||
return target[prop]
|
return target[prop]
|
||||||
@ -127,6 +128,10 @@ export default class Player extends Core {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wsEvents = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async onInitialize() {
|
async onInitialize() {
|
||||||
this.native_controls.initialize()
|
this.native_controls.initialize()
|
||||||
|
|
||||||
@ -317,11 +322,16 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
// check if manifest has `manifest` property, if is and not inherit or missing source, resolve
|
// check if manifest has `manifest` property, if is and not inherit or missing source, resolve
|
||||||
if (manifest.service) {
|
if (manifest.service) {
|
||||||
|
if (!ServicesHandlers[manifest.service]) {
|
||||||
|
this.console.error(`Service ${manifest.service} is not supported`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (manifest.service !== "inherit" && !manifest.source) {
|
if (manifest.service !== "inherit" && !manifest.source) {
|
||||||
const resolver = servicesToManifestResolver[manifest.service]
|
const resolver = ServicesHandlers[manifest.service].resolve
|
||||||
|
|
||||||
if (!resolver) {
|
if (!resolver) {
|
||||||
this.console.error(`Service ${manifest.service} is not supported`)
|
this.console.error(`Resolving for service [${manifest.service}] is not supported`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,6 +546,8 @@ export default class Player extends Core {
|
|||||||
// play
|
// play
|
||||||
await this.track_instance.media.play()
|
await this.track_instance.media.play()
|
||||||
|
|
||||||
|
this.console.log(this.track_instance)
|
||||||
|
|
||||||
// update manifest
|
// update manifest
|
||||||
this.state.track_manifest = instance.manifest
|
this.state.track_manifest = instance.manifest
|
||||||
|
|
||||||
@ -960,4 +972,38 @@ export default class Player extends Core {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleCurrentTrackLike(to, manifest) {
|
||||||
|
let isCurrent = !!!manifest
|
||||||
|
|
||||||
|
if (typeof manifest === "undefined") {
|
||||||
|
manifest = this.track_instance.manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
this.console.error("Track instance or manifest not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof to !== "boolean") {
|
||||||
|
this.console.warn("Like must be a boolean")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = manifest.service ?? "default"
|
||||||
|
|
||||||
|
if (!ServicesHandlers[service].toggleLike) {
|
||||||
|
this.console.error(`Service [${service}] does not support like actions`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await ServicesHandlers[service].toggleLike(manifest, to)
|
||||||
|
|
||||||
|
if (isCurrent) {
|
||||||
|
this.track_instance.manifest.liked = to
|
||||||
|
this.state.track_manifest.liked = to
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
35
packages/app/src/cores/player/services.js
Normal file
35
packages/app/src/cores/player/services.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import SyncModel from "comty.js/models/sync"
|
||||||
|
import MusicModel from "comty.js/models/music"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
"default": {
|
||||||
|
resolve: () => { },
|
||||||
|
toggleLike: async (manifest, to) => {
|
||||||
|
return await MusicModel.toggleTrackLike(manifest, to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tidal": {
|
||||||
|
resolve: async (manifest) => {
|
||||||
|
const resolvedManifest = await SyncModel.tidalCore.getTrackManifest(manifest.id)
|
||||||
|
|
||||||
|
manifest.source = resolvedManifest.playback.url
|
||||||
|
|
||||||
|
if (!manifest.metadata) {
|
||||||
|
manifest.metadata = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.metadata.title = resolvedManifest.metadata.title
|
||||||
|
manifest.metadata.artist = resolvedManifest.metadata.artists.map(artist => artist.name).join(", ")
|
||||||
|
manifest.metadata.album = resolvedManifest.metadata.album.title
|
||||||
|
|
||||||
|
const coverUID = resolvedManifest.metadata.album.cover.replace(/-/g, "/")
|
||||||
|
|
||||||
|
manifest.metadata.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg`
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
},
|
||||||
|
toggleLike: async (manifest, to) => {
|
||||||
|
return await MusicModel.toggleTrackLike(manifest, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
import SyncModel from "comty.js/models/sync"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
"tidal": async (manifest) => {
|
|
||||||
const resolvedManifest = await SyncModel.tidalCore.getTrackManifest(manifest.id)
|
|
||||||
|
|
||||||
manifest.source = resolvedManifest.playback.url
|
|
||||||
|
|
||||||
if (!manifest.metadata) {
|
|
||||||
manifest.metadata = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.metadata.title = resolvedManifest.metadata.title
|
|
||||||
manifest.metadata.artist = resolvedManifest.metadata.artists.map(artist => artist.name).join(", ")
|
|
||||||
manifest.metadata.album = resolvedManifest.metadata.album.title
|
|
||||||
|
|
||||||
const coverUID = resolvedManifest.metadata.album.cover.replace(/-/g, "/")
|
|
||||||
|
|
||||||
manifest.metadata.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg`
|
|
||||||
|
|
||||||
return manifest
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,6 +8,8 @@ import SeekBar from "components/Player/SeekBar"
|
|||||||
import Controls from "components/Player/Controls"
|
import Controls from "components/Player/Controls"
|
||||||
import { WithPlayerContext, Context } from "contexts/WithPlayerContext"
|
import { WithPlayerContext, Context } from "contexts/WithPlayerContext"
|
||||||
|
|
||||||
|
import ExtraActions from "components/Player/ExtraActions"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const ServiceIndicator = (props) => {
|
const ServiceIndicator = (props) => {
|
||||||
@ -112,18 +114,7 @@ const AudioPlayerComponent = (props) => {
|
|||||||
disabled={ctx.control_locked}
|
disabled={ctx.control_locked}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="extra_actions">
|
<ExtraActions />
|
||||||
<Button
|
|
||||||
type="ghost"
|
|
||||||
icon={<Icons.MdLyrics />}
|
|
||||||
disabled={!lyricsEnabled}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="ghost"
|
|
||||||
icon={<Icons.MdQueueMusic />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -506,21 +506,41 @@ export default class MusicModel {
|
|||||||
/**
|
/**
|
||||||
* Toggles the like status of a track.
|
* Toggles the like status of a track.
|
||||||
*
|
*
|
||||||
* @param {number} track_id - The ID of the track.
|
* @param {Object} manifest - The manifest object containing track information.
|
||||||
* @throws {Error} If track_id is not provided.
|
* @param {boolean} to - The like status to toggle (true for like, false for unlike).
|
||||||
* @return {Promise<Object>} The response data.
|
* @throws {Error} Throws an error if the manifest is missing.
|
||||||
|
* @return {Object} The response data from the API.
|
||||||
*/
|
*/
|
||||||
static async toggleTrackLike(track_id) {
|
static async toggleTrackLike(manifest, to) {
|
||||||
if (!track_id) {
|
if (!manifest) {
|
||||||
throw new Error("Track ID is required")
|
throw new Error("Manifest is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Toggling track ${manifest._id} like status to ${to}`)
|
||||||
|
|
||||||
|
const track_id = manifest._id
|
||||||
|
|
||||||
|
switch (manifest.service) {
|
||||||
|
case "tidal": {
|
||||||
|
const response = await SyncModel.tidalCore.toggleTrackLike({
|
||||||
|
track_id,
|
||||||
|
to,
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
default: {
|
||||||
const response = await request({
|
const response = await request({
|
||||||
instance: MusicModel.api_instance,
|
instance: MusicModel.api_instance,
|
||||||
method: "POST",
|
method: to ? "POST" : "DELETE",
|
||||||
url: `/tracks/${track_id}/toggle-like`,
|
url: `/tracks/${track_id}/like`,
|
||||||
|
params: {
|
||||||
|
service: manifest.service
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -156,4 +156,17 @@ export default class TidalService {
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async toggleTrackLike({
|
||||||
|
track_id,
|
||||||
|
to,
|
||||||
|
}) {
|
||||||
|
const { data } = await request({
|
||||||
|
instance: TidalService.api_instance,
|
||||||
|
method: to ? "POST" : "DELETE",
|
||||||
|
url: `/services/tidal/track/${track_id}/like`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,27 +21,16 @@ export default async (req, res) => {
|
|||||||
user_id: req.session.user_id,
|
user_id: req.session.user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (like) {
|
|
||||||
await like.delete()
|
await like.delete()
|
||||||
like = null
|
|
||||||
} else {
|
|
||||||
like = new TrackLike({
|
|
||||||
track_id: track_id,
|
|
||||||
user_id: req.session.user_id,
|
|
||||||
created_at: new Date().getTime(),
|
|
||||||
})
|
|
||||||
|
|
||||||
await like.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
global.ws.io.emit("music:self:track:toggle:like", {
|
global.ws.io.emit("music:self:track:toggle:like", {
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
user_id: req.session.user_id,
|
user_id: req.session.user_id,
|
||||||
action: like ? "liked" : "unliked",
|
action: "unliked",
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: "ok",
|
message: "ok",
|
||||||
action: like ? "liked" : "unliked",
|
action: "unliked",
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { TrackLike, Track } from "@shared-classes/DbModels"
|
||||||
|
import { AuthorizationError, NotFoundError } from "@shared-classes/Errors"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
if (!req.session) {
|
||||||
|
return new AuthorizationError(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { track_id } = req.params
|
||||||
|
|
||||||
|
const track = await Track.findById(track_id).catch((err) => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!track) {
|
||||||
|
return new NotFoundError(req, res, "Track not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
let like = await TrackLike.findOne({
|
||||||
|
track_id: track_id,
|
||||||
|
user_id: req.session.user_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
like = new TrackLike({
|
||||||
|
track_id: track_id,
|
||||||
|
user_id: req.session.user_id,
|
||||||
|
created_at: new Date().getTime(),
|
||||||
|
})
|
||||||
|
|
||||||
|
await like.save()
|
||||||
|
|
||||||
|
global.ws.io.emit("music:self:track:toggle:like", {
|
||||||
|
track_id: track_id,
|
||||||
|
user_id: req.session.user_id,
|
||||||
|
action: "liked" ,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "ok",
|
||||||
|
action: "liked",
|
||||||
|
})
|
||||||
|
}
|
@ -49,6 +49,7 @@
|
|||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^8.0.0",
|
||||||
"p-map": "^6.0.0",
|
"p-map": "^6.0.0",
|
||||||
"p-queue": "^7.3.4",
|
"p-queue": "^7.3.4",
|
||||||
|
"qs": "^6.11.2",
|
||||||
"redis": "^4.6.6",
|
"redis": "^4.6.6",
|
||||||
"sharp": "^0.31.3",
|
"sharp": "^0.31.3",
|
||||||
"split-chunk-merge": "^1.0.0",
|
"split-chunk-merge": "^1.0.0",
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import SecureSyncEntry from "@shared-classes/SecureSyncEntry"
|
||||||
|
import { AuthorizationError, InternalServerError } from "@shared-classes/Errors"
|
||||||
|
|
||||||
|
import TidalAPI from "@shared-classes/TidalAPI"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
if (!req.session) {
|
||||||
|
return new AuthorizationError(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const access_token = await SecureSyncEntry.get(req.session.user_id.toString(), "tidal_access_token")
|
||||||
|
|
||||||
|
if (!access_token) {
|
||||||
|
return new AuthorizationError(req, res, "Its needed to link your TIDAL account to perform this action.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_data = await SecureSyncEntry.get(req.session.user_id.toString(), "tidal_user")
|
||||||
|
|
||||||
|
user_data = JSON.parse(user_data)
|
||||||
|
|
||||||
|
let response = await TidalAPI.toggleTrackLike({
|
||||||
|
trackId: req.params.track_id,
|
||||||
|
to: false,
|
||||||
|
user_id: user_data.id,
|
||||||
|
access_token,
|
||||||
|
country: user_data.country,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json(response)
|
||||||
|
} catch (error) {
|
||||||
|
return new InternalServerError(req, res, error)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import SecureSyncEntry from "@shared-classes/SecureSyncEntry"
|
||||||
|
import { AuthorizationError, InternalServerError } from "@shared-classes/Errors"
|
||||||
|
|
||||||
|
import TidalAPI from "@shared-classes/TidalAPI"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
if (!req.session) {
|
||||||
|
return new AuthorizationError(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const access_token = await SecureSyncEntry.get(req.session.user_id.toString(), "tidal_access_token")
|
||||||
|
|
||||||
|
if (!access_token) {
|
||||||
|
return new AuthorizationError(req, res, "Its needed to link your TIDAL account to perform this action.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_data = await SecureSyncEntry.get(req.session.user_id.toString(), "tidal_user")
|
||||||
|
|
||||||
|
user_data = JSON.parse(user_data)
|
||||||
|
|
||||||
|
let response = await TidalAPI.toggleTrackLike({
|
||||||
|
trackId: req.params.track_id,
|
||||||
|
to: true,
|
||||||
|
user_id: user_data.id,
|
||||||
|
access_token,
|
||||||
|
country: user_data.countryCode,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json(response)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return new InternalServerError(req, res, error)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
|
import FormData from "form-data"
|
||||||
|
import qs from "qs"
|
||||||
|
|
||||||
const TIDAL_CLIENT_ID = process.env.TIDAL_CLIENT_ID
|
const TIDAL_CLIENT_ID = process.env.TIDAL_CLIENT_ID
|
||||||
const TIDAL_CLIENT_SECRET = process.env.TIDAL_CLIENT_SECRET
|
const TIDAL_CLIENT_SECRET = process.env.TIDAL_CLIENT_SECRET
|
||||||
@ -256,7 +258,6 @@ export default class TidalAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves self favorite playlists based on specified parameters.
|
* Retrieves self favorite playlists based on specified parameters.
|
||||||
*
|
*
|
||||||
@ -423,8 +424,55 @@ export default class TidalAPI {
|
|||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
static async toggleTrackLike(track_id) {
|
/**
|
||||||
|
* Toggles the like status of a track.
|
||||||
|
*
|
||||||
|
* @param {Object} params - The parameters for toggling the track like.
|
||||||
|
* @param {string} params.trackId - The ID of the track to toggle the like status.
|
||||||
|
* @param {boolean} params.to - The new like status. True to like the track, false to unlike it.
|
||||||
|
* @param {string} params.user_id - The ID of the user performing the action.
|
||||||
|
* @param {string} params.access_token - The access token for authentication.
|
||||||
|
* @param {string} params.country - The country code.
|
||||||
|
* @return {Object} - The response data from the API.
|
||||||
|
*/
|
||||||
|
static async toggleTrackLike({
|
||||||
|
trackId,
|
||||||
|
to,
|
||||||
|
user_id,
|
||||||
|
|
||||||
|
access_token,
|
||||||
|
country,
|
||||||
|
}) {
|
||||||
|
let url = `${TidalAPI.API_V1}/users/${user_id}/favorites/tracks`
|
||||||
|
let payload = null
|
||||||
|
let headers = {
|
||||||
|
Origin: "http://listen.tidal.com",
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to) {
|
||||||
|
url = `${url}/${trackId}`
|
||||||
|
} else {
|
||||||
|
payload = qs.stringify({
|
||||||
|
trackIds: trackId,
|
||||||
|
onArtifactNotFound: "FAIL"
|
||||||
|
})
|
||||||
|
|
||||||
|
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await axios({
|
||||||
|
url: url,
|
||||||
|
method: to ? "POST" : "DELETE",
|
||||||
|
headers: headers,
|
||||||
|
params: {
|
||||||
|
countryCode: country,
|
||||||
|
deviceType: "BROWSER"
|
||||||
|
},
|
||||||
|
data: payload
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
static async togglePlaylistLike(playlist_id) {
|
static async togglePlaylistLike(playlist_id) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user