UNPKG

aqualink

Version:

An Lavalink/Nodelink client, focused in pure performance and features

159 lines (138 loc) 4.54 kB
const YT_ID_REGEX = /(?:[?&]v=|youtu\.be\/|\/embed\/|\/shorts\/)([A-Za-z0-9_-]{11})/ const _h = { str: (v, d = '') => (typeof v === 'string' ? v : d), num: (v, d = 0) => (Number.isFinite(v) ? v : d) } class Track { constructor(data = {}, requester = null, node = null) { const info = data.info || {} this.track = data.track || data.encoded || null this.identifier = _h.str(info.identifier) this.isSeekable = Boolean(info.isSeekable) this.author = _h.str(info.author) this.position = _h.num(info.position) this.duration = _h.num(info.length) this.isStream = Boolean(info.isStream) this.title = _h.str(info.title) this.uri = _h.str(info.uri) this.sourceName = _h.str(info.sourceName) this.artworkUrl = _h.str(info.artworkUrl) this.pluginInfo = info.pluginInfo || data.pluginInfo || {} this.playlist = data.playlist || null this.node = node || data.node || null this.nodes = data.nodes || null this.requester = requester || null this._infoCache = null this._artworkCache = undefined // undefined = not computed, null = computed but no artwork } get info() { if (this._infoCache) return this._infoCache this._infoCache = Object.freeze({ identifier: this.identifier, isSeekable: this.isSeekable, position: this.position, author: this.author, length: this.duration, isStream: this.isStream, title: this.title, uri: this.uri, sourceName: this.sourceName, artworkUrl: this.artworkUrl || this._computeArtwork() }) return this._infoCache } get length() { return this.duration } get thumbnail() { if (this.artworkUrl) return this.artworkUrl if (this._artworkCache !== undefined) return this._artworkCache this._artworkCache = this._computeArtwork() return this._artworkCache } async resolve(aqua, opts = {}) { if (typeof this.track === 'string' && this.track) return this if (!aqua?.resolve) return null const platform = opts.platform || aqua?.options?.defaultSearchPlatform || 'ytsearch' const node = opts.nodes || opts.node || this.node || this.nodes let query = this.uri if (!query) { if (this.title) { query = this.author ? `${this.author} - ${this.title}`.trim() : this.title.trim() } else if ( this.identifier && this.sourceName?.toLowerCase().includes('youtube') ) { query = this.identifier } } if (!query) return null const payload = { query, source: platform, requester: this.requester } if (node) payload.nodes = node let result try { result = await aqua.resolve(payload) } catch { return null } const found = result?.tracks?.[0] if (!found) return null const fi = found.info || {} this.track = typeof found.track === 'string' ? found.track : found.encoded || this.track this.identifier = fi.identifier ?? this.identifier this.title = fi.title ?? this.title this.author = fi.author ?? this.author this.uri = fi.uri ?? this.uri this.sourceName = fi.sourceName ?? this.sourceName this.artworkUrl = fi.artworkUrl ?? this.artworkUrl this.pluginInfo = fi.pluginInfo ?? found.pluginInfo ?? this.pluginInfo this.isSeekable = fi.isSeekable ?? this.isSeekable this.isStream = fi.isStream ?? this.isStream this.position = _h.num(fi.position, this.position) this.duration = _h.num(fi.length, this.duration) this.playlist = found.playlist ?? this.playlist this._infoCache = null return this } isValid() { return Boolean( (typeof this.track === 'string' && this.track) || (typeof this.uri === 'string' && this.uri) ) } dispose() { this._infoCache = this.requester = this.node = this.nodes = this.playlist = this.track = null this.identifier = this.author = this.title = this.uri = this.sourceName = this.artworkUrl = '' } _computeArtwork() { if (this.artworkUrl) return this.artworkUrl if (this._artworkCache !== undefined) return this._artworkCache const id = this.identifier || (this.uri && YT_ID_REGEX.exec(this.uri)?.[1]) if (id && this.sourceName?.includes('youtube')) { this._artworkCache = `https://i.ytimg.com/vi/${id}/hqdefault.jpg` return this._artworkCache } this._artworkCache = null return null } } module.exports = Track