Skip to content
This repository has been archived by the owner on Nov 13, 2022. It is now read-only.

Added spotify links to track object #106

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/structures/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class Player {
public bands = new Array<number>(15).fill(0.0);
/** The voice state object from Discord. */
public voiceState: VoiceState = Object.assign({});
public spotifyUri: string;
/** The Manager. */
public manager: Manager;
private static _manager: Manager;
Expand Down Expand Up @@ -146,13 +147,18 @@ export class Player {
*/
public setEQ(...bands: EqualizerBand[]): this {
// Hacky support for providing an array
if (Array.isArray(bands[0])) bands = bands[0] as unknown as EqualizerBand[]
if (Array.isArray(bands[0]))
bands = (bands[0] as unknown) as EqualizerBand[];

if (!bands.length || !bands.every(
if (
!bands.length ||
!bands.every(
(band) => JSON.stringify(Object.keys(band).sort()) === '["band","gain"]'
)
)
throw new TypeError("Bands must be a non-empty object array containing 'band' and 'gain' properties.");
throw new TypeError(
"Bands must be a non-empty object array containing 'band' and 'gain' properties."
);

for (const { band, gain } of bands) this.bands[band] = gain;

Expand Down Expand Up @@ -278,7 +284,10 @@ export class Player {
* @param track
* @param options
*/
public async play(track: Track | UnresolvedTrack, options: PlayOptions): Promise<void>;
public async play(
track: Track | UnresolvedTrack,
options: PlayOptions
): Promise<void>;
public async play(
optionsOrTrack?: PlayOptions | Track | UnresolvedTrack,
playOptions?: PlayOptions
Expand All @@ -303,7 +312,9 @@ export class Player {

if (TrackUtils.isUnresolvedTrack(this.queue.current)) {
try {
this.queue.current = await TrackUtils.getClosestTrack(this.queue.current as UnresolvedTrack);
this.queue.current = await TrackUtils.getClosestTrack(
this.queue.current as UnresolvedTrack
);
} catch (error) {
this.manager.emit("trackError", this, this.queue.current, error);
if (this.queue[0]) return this.play(this.queue[0]);
Expand Down Expand Up @@ -385,7 +396,8 @@ export class Player {
/** Stops the current track, optionally give an amount to skip to, e.g 5 would play the 5th song. */
public stop(amount?: number): this {
if (typeof amount === "number" && amount > 1) {
if (amount > this.queue.length) throw new RangeError("Cannot skip more than the queue length.");
if (amount > this.queue.length)
throw new RangeError("Cannot skip more than the queue length.");
this.queue.splice(0, amount - 1);
}

Expand Down Expand Up @@ -464,6 +476,8 @@ export interface PlayerOptions {

/** If track partials are set some of these will be `undefined` as they were removed. */
export interface Track {
/** T encoded track. */
spotifyUri: string | null;
/** The base64 encoded track. */
readonly track: string;
/** The title of the track. */
Expand Down
110 changes: 75 additions & 35 deletions src/structures/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const TRACK_SYMBOL = Symbol("track"),
];

/** @hidden */
const escapeRegExp = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const escapeRegExp = (str: string): string =>
str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

export abstract class TrackUtils {
static trackPartial: string[] | null = null;
Expand All @@ -32,8 +33,13 @@ export abstract class TrackUtils {
}

static setTrackPartial(partial: string[]): void {
if (!Array.isArray(partial) || !partial.every(str => typeof str === "string"))
throw new Error("Provided partial is not an array or not a string array.");
if (
!Array.isArray(partial) ||
!partial.every((str) => typeof str === "string")
)
throw new Error(
"Provided partial is not an array or not a string array."
);
if (!partial.includes("track")) partial.unshift("track");

this.trackPartial = partial;
Expand All @@ -49,15 +55,16 @@ export abstract class TrackUtils {

if (Array.isArray(trackOrTracks) && trackOrTracks.length) {
for (const track of trackOrTracks) {
if (!(track[TRACK_SYMBOL] || track[UNRESOLVED_TRACK_SYMBOL])) return false
if (!(track[TRACK_SYMBOL] || track[UNRESOLVED_TRACK_SYMBOL]))
return false;
}
return true;
}

return (
trackOrTracks[TRACK_SYMBOL] ||
trackOrTracks[UNRESOLVED_TRACK_SYMBOL]
) === true;
(trackOrTracks[TRACK_SYMBOL] ||
trackOrTracks[UNRESOLVED_TRACK_SYMBOL]) === true
);
}

/**
Expand Down Expand Up @@ -99,6 +106,7 @@ export abstract class TrackUtils {
isSeekable: data.info.isSeekable,
isStream: data.info.isStream,
uri: data.info.uri,
spotifyUri: null,
thumbnail: data.info.uri.includes("youtube")
? `https://img.youtube.com/vi/${data.info.identifier}/default.jpg`
: null,
Expand All @@ -122,12 +130,14 @@ export abstract class TrackUtils {

Object.defineProperty(track, TRACK_SYMBOL, {
configurable: true,
value: true
value: true,
});

return track;
} catch (error) {
throw new RangeError(`Argument "data" is not a valid track: ${error.message}`);
throw new RangeError(
`Argument "data" is not a valid track: ${error.message}`
);
}
}

Expand All @@ -136,25 +146,28 @@ export abstract class TrackUtils {
* @param query
* @param requester
*/
static buildUnresolved(query: string | UnresolvedQuery, requester?: unknown): UnresolvedTrack {
static buildUnresolved(
query: string | UnresolvedQuery,
requester?: unknown
): UnresolvedTrack {
if (typeof query === "undefined")
throw new RangeError('Argument "query" must be present.');

let unresolvedTrack: Partial<UnresolvedTrack> = {
requester,
async resolve(): Promise<void> {
const resolved = await TrackUtils.getClosestTrack(this)
Object.getOwnPropertyNames(this).forEach(prop => delete this[prop]);
const resolved = await TrackUtils.getClosestTrack(this);
Object.getOwnPropertyNames(this).forEach((prop) => delete this[prop]);
Object.assign(this, resolved);
}
},
};

if (typeof query === "string") unresolvedTrack.title = query;
else unresolvedTrack = { ...unresolvedTrack, ...query }
else unresolvedTrack = { ...unresolvedTrack, ...query };

Object.defineProperty(unresolvedTrack, UNRESOLVED_TRACK_SYMBOL, {
configurable: true,
value: true
value: true,
});

return unresolvedTrack as UnresolvedTrack;
Expand All @@ -163,41 +176,67 @@ export abstract class TrackUtils {
static async getClosestTrack(
unresolvedTrack: UnresolvedTrack
): Promise<Track> {
if (!TrackUtils.manager) throw new RangeError("Manager has not been initiated.");
if (!TrackUtils.manager)
throw new RangeError("Manager has not been initiated.");

if (!TrackUtils.isUnresolvedTrack(unresolvedTrack))
throw new RangeError("Provided track is not a UnresolvedTrack.");

const query = [unresolvedTrack.author, unresolvedTrack.title].filter(str => !!str).join(" - ");
const res = await TrackUtils.manager.search(query, unresolvedTrack.requester);

if (res.loadType !== "SEARCH_RESULT") throw res.exception ?? {
message: "No tracks found.",
severity: "COMMON",
};
const query = [unresolvedTrack.author, unresolvedTrack.title]
.filter((str) => !!str)
.join(" - ");
const res = await TrackUtils.manager.search(
query,
unresolvedTrack.requester
);

if (res.loadType !== "SEARCH_RESULT")
throw (
res.exception ?? {
message: "No tracks found.",
severity: "COMMON",
}
);

if (unresolvedTrack.author) {
const channelNames = [unresolvedTrack.author, `${unresolvedTrack.author} - Topic`];
const channelNames = [
unresolvedTrack.author,
`${unresolvedTrack.author} - Topic`,
];

const originalAudio = res.tracks.find(track => {
const originalAudio = res.tracks.find((track) => {
return (
channelNames.some(name => new RegExp(`^${escapeRegExp(name)}$`, "i").test(track.author)) ||
new RegExp(`^${escapeRegExp(unresolvedTrack.title)}$`, "i").test(track.title)
channelNames.some((name) =>
new RegExp(`^${escapeRegExp(name)}$`, "i").test(track.author)
) ||
new RegExp(`^${escapeRegExp(unresolvedTrack.title)}$`, "i").test(
track.title
)
);
});

if (originalAudio) return originalAudio;
if (originalAudio) {
if (unresolvedTrack.spotifyUri)
originalAudio.spotifyUri = unresolvedTrack.spotifyUri;
return originalAudio;
}
}

if (unresolvedTrack.duration) {
const sameDuration = res.tracks.find(track =>
(track.duration >= (unresolvedTrack.duration - 1500)) &&
(track.duration <= (unresolvedTrack.duration + 1500))
const sameDuration = res.tracks.find(
(track) =>
track.duration >= unresolvedTrack.duration - 1500 &&
track.duration <= unresolvedTrack.duration + 1500
);

if (sameDuration) return sameDuration;
if (sameDuration) {
if (unresolvedTrack.spotifyUri)
sameDuration.spotifyUri = unresolvedTrack.spotifyUri;
return sameDuration;
}
}

if (unresolvedTrack.spotifyUri)
res.tracks[0].spotifyUri = unresolvedTrack.spotifyUri;
return res.tracks[0];
}
}
Expand All @@ -213,7 +252,8 @@ export abstract class Structure {
name: K,
extender: (target: Extendable[K]) => T
): T {
if (!structures[name]) throw new TypeError(`"${name} is not a valid structure`);
if (!structures[name])
throw new TypeError(`"${name} is not a valid structure`);
const extended = extender(structures[name]);
structures[name] = extended;
return extended;
Expand Down Expand Up @@ -397,4 +437,4 @@ export interface PlayerUpdate {
time: number;
};
guildId: string;
}
}