aqualink
Version:
An Lavalink/Nodelink client, focused in pure performance and features
159 lines (138 loc) • 4.54 kB
JavaScript
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