async search(artists, track, album, duration) {
    [artists, track, album, duration] = _getSearchArgs(artists, track, album, duration);

    const results = await this.#search({query: [track, album, ...artists].join(' ')});
    const strippedMeta = textUtils.stripText([...track.split(' '), album, ...artists]);
    const validSections = [
      ...((results.top || {}).contents || []), // top recommended songs
      ...((results.songs || {}).contents || []), // song section
      ...((results.videos || {}).contents || []), // videos section
    ]
      .map(
        item =>
          item &&
          'title' in item &&
          ['song', 'video'].includes(item.type) && {
            ...item,
            weight: textUtils.getWeight(
              strippedMeta,
              textUtils.stripText([
                ...item.title.split(' '),
                ...(item.album?.name.split(' ') ?? []),
                ...item.artists.map(artist => artist.name),
              ]),
            ),
          },
      )
      .filter(Boolean);
    function calculateAccuracyFor(item, weight) {
      let accuracy = 0;
      // get weighted delta from expected duration
      accuracy += weight - (duration ? Math.abs(duration - item.duration_ms) / duration : 0.5) * 100;
      // if item is a song, bump remaining by 50%, if video, bump up by 25%, anything else - by 5%
      accuracy += (cur => ((item.type === 'song' ? 50 : item.type === 'video' ? 25 : 5) / 100) * cur)(100 - accuracy);
      // TODO: CALCULATE ACCURACY BY AUTHOR
      return accuracy;
    }
    const classified = Object.values(
      validSections.reduce((final, item) => {
        // prune duplicates
        if (item.weight > 65 && item && 'videoId' in item && !(item.videoId in final)) {
          let cleanItem = {
            title: item.title,
            type: item.type,
            author: item.artists,
            duration: item.duration,
            duration_ms: item.duration.split(':').reduce((acc, time) => 60 * acc + +time) * 1000,
            videoId: item.videoId,
            getFeeds: genAsyncGetFeedsFn(item.videoId),
          };
          if ((cleanItem.accuracy = calculateAccuracyFor(cleanItem, item.weight)) > 80) final[item.videoId] = cleanItem;
        }
        return final;
      }, {}),
      // sort descending by accuracy
    ).sort((a, b) => (a.accuracy > b.accuracy ? -1 : 1));
    return classified;
  }
Search the YouTube Music service for matches
@param {string|string[]} [artists] An artist or list of artists
@param {string} [track] Track name
@param {string} [album] Album name
@param {number} [duration] Duration in milliseconds

If `track` is a number, it becomes duration, leaving `track` undefined.
If `album` is a number, it becomes duration, leaving `album` undefined.
If `artists` is a string and `track` is undefined, it becomes `track`, leaving artists empty.
If `artists` is non-array but `track` is defined, artists becomes an item in the artists array.

@returns {YouTubeSearchResult} YouTubeMusicSearchResults