Artlist DL

Allows you to download artlist.io Music & SFX

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Artlist DL
// @namespace   http://tampermonkey.net/
// @description Allows you to download artlist.io Music & SFX
// @author      Mia @ github.com/xNasuni
// @match       *://*.artlist.io/*
// @grant       none
// @require     https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @version     2.6
// @run-at	    document-start
// @supportURL  https://github.com/xNasuni/artlist-downloader/issues
// ==/UserScript==

const LoadedMusicLists = []
const LoadedSfxLists = []
const LoadedSfxsList = []
const LoadedSongsList = []
const LoadedSstemsLists = []
const ModifiedMusicButtonColor = "#82ff59"
const ModifiedSfxButtonColor = "#ff90bf"
const ErrorButtonColor = "#ff3333"
const UNKNOWN_DATATYPE = "_unknown"
const SINGLE_SOUND_EFFECT_DATATYPE = "_ssfx"
const SINGLE_SONG_DATATYPE = "_ssong"
const MUSIC_ALBUM_PAGETYPE = "_amusic"
const SONGS_PAGETYPE = "_songs"
const MUSIC_PAGETYPE = "_music"
const SFXS_PAGETYPE = "_sfxs"
const SFX_PAGETYPE = "_sfx"
const SONG_STEMS_PAGETYPE = "_sstem"
const oldXMLHttpRequestOpen = window.XMLHttpRequest.prototype.open

var AudioTable
var TBody
var LastChangeObserver
var ActionContainer
var SongPage
var LastInterval = -1
var RequestsInterval = -1
var DontPoll = false
var SingleSoundEffectData = "none"
var SingleSongData = "none"

async function ShowSaveFilePickerForURL(url, filename) {
    let blobDataFromURL = await fetch(url).then((r) => r.blob());
    try {
        if (window.showSaveFilePicker) {
            const BlobData = new Blob([blobDataFromURL], {
                type: "audio/aac"
            });
            const Handle = await window.showSaveFilePicker({
                suggestedName: filename,
                types: [{
                    description: "AAC File (Compressed MP3)",
                    accept: {
                        "audio/aac": [".aac"]
                    },
                }, ],
            });
            const Writable = await Handle.createWritable();
            await Writable.write(BlobData);
            await Writable.close();
        } else {
            const blobURL = URL.createObjectURL(blobDataFromURL);
            const a = document.createElement("a");
            a.href = blobURL;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(blobURL);
        }
    } catch (e) {
        console.error("Error saving file:", e);
    }
}

async function ShowSaveFilePickerForURLsZipped(files, filename) {
    try {
        const zip = new JSZip();

        for (const file of files) {
            const blobDataFromURL = await fetch(file.URL).then((r) => r.blob());
            zip.file(file.Filename, blobDataFromURL);
        }

        const zipBlob = await zip.generateAsync({
            type: "blob"
        });
        if (window.showSaveFilePicker) {
            const handle = await window.showSaveFilePicker({
                suggestedName: filename,
                types: [{
                    description: "ZIP File",
                    accept: {
                        "application/zip": [".zip"]
                    },
                }, ],
            });

            const writable = await handle.createWritable();
            await writable.write(zipBlob);
            await writable.close();
        } else {
            const blobURL = URL.createObjectURL(zipBlob);
            const a = document.createElement("a");
            a.href = blobURL;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(blobURL);
        }
    } catch (e) {
        console.warn("Error saving zip:", e)
    }
}

function Until(testFunc) {
    // https://stackoverflow.com/a/52657929
    const poll = (resolve) => {
        if (DontPoll) {
            resolve()
        }
        if (testFunc()) {
            resolve();
        } else setTimeout((_) => poll(resolve), 100);
    };
    return new Promise(poll)
}

function GetPagetype() {
    const PathSplit = window.location.pathname.split('/')
    if (window.location.host === "artlist.io" && (PathSplit[1] === "royalty-free-music" && (PathSplit[2] === "song" || PathSplit[2] === "artist"))) {
        return SONGS_PAGETYPE
    }
    if (window.location.host === "artlist.io" && (PathSplit[1] === "royalty-free-music" && PathSplit[2] === "album")) {
        return MUSIC_ALBUM_PAGETYPE
    }
    if (window.location.host === "artlist.io" && (PathSplit[1] === "royalty-free-music")) {
        return MUSIC_PAGETYPE
    }
    if (window.location.host === "artlist.io" && (PathSplit[1] === "sfx" && PathSplit[2] === "track")) {
        return SFXS_PAGETYPE
    }
    if (window.location.host == "artlist.io" && (PathSplit[1] === "sfx" || (PathSplit[1] === "sfx" && (PathSplit[2] === "search" || PathSplit[2] === "pack")))) {
        return SFX_PAGETYPE
    }
    return UNKNOWN_DATATYPE
}

function GetDatatype(Data) {
    var Datatype = UNKNOWN_DATATYPE

    try {
        if (Data.data.sfxList != undefined && Data.data.sfxList.songs != undefined) {
            Datatype = SFX_PAGETYPE
        }
    } catch (e) {}
    try {
        if (Data.data.songList != undefined && Data.data.songList.songs != undefined) {
            Datatype = MUSIC_PAGETYPE
        }
    } catch (e) {}
    try {
        if (Data.data.sfxs != undefined && Data.data.sfxs.length === 1 && Data.data.sfxs[0].similarList != undefined) {
            Datatype = SFXS_PAGETYPE
        }
    } catch (e) {}
    try {
        if (Data.data.sfxs != undefined && Data.data.sfxs.length === 1 && Data.data.sfxs[0].songName != undefined) {
            Datatype = SINGLE_SOUND_EFFECT_DATATYPE
        }
    } catch (e) {}
    try {
        if (Data.data.songs != undefined && Data.data.songs.length === 1 && Data.data.songs[0].songName != undefined) {
            Datatype = SINGLE_SONG_DATATYPE
        }
    } catch (e) {}
    try {
        if (Data.data.songs != undefined && Data.data.songs.length === 1 && Data.data.songs[0].similarSongs != undefined) {
            Datatype = SONGS_PAGETYPE
        }
    } catch (e) {}

    try {
        if (Data.data.songs != undefined && Data.data.songs.length === 1 && Data.data.songs[0].stems != undefined) {
            Datatype = SONG_STEMS_PAGETYPE
        }
    } catch (e) {}

    return Datatype
}

function MatchURL(Url) {
    const Pagetype = GetPagetype()
    var URLObject
    try {
        URLObject = new URL(Url)
    } catch (e) {
        return false
    }
    if ((Pagetype === MUSIC_PAGETYPE || Pagetype === SFX_PAGETYPE || Pagetype === SONGS_PAGETYPE || Pagetype === SFXS_PAGETYPE || Pagetype == SONG_STEMS_PAGETYPE) && URLObject.host === "search-api.artlist.io" && (URLObject.pathname == "/v1/graphql" || URLObject.pathname == "/v2/graphql")) {
        return true
    }
    return false
}

async function GetSfxInfo(Id) {
    const Query = `query Sfxs($ids: [Int!]!) {
  sfxs(ids: $ids) {
    songId
    songName
    artistId
    artistName
    albumId
    albumName
    assetTypeId
    duration
    sitePlayableFilePath
  }
}
`
    const Variables = {
        ids: [Id]
    }

    const Payload = {
        query: Query,
        variables: Variables
    }

    const Response = await fetch("https://search-api.artlist.io/v1/graphql", {
        method: "POST",
        headers: {
            "content-type": "application/json"
        },
        body: JSON.stringify(Payload)
    })
    const JSONData = await Response.json()

    var Data

    try {
        Data = JSONData.data.sfxs[0]
    } catch (e) {}

    if (Data === undefined) {
        return false
    }

    return Data
}

async function GetSongInfo(Id) {
    const Query = `query Songs($ids: [String!]!) {
  songs(ids: $ids) {
    songId
    songName
    artistId
    artistName
    albumId
    albumName
    assetTypeId
    duration
    sitePlayableFilePath
  }
}
`
    const Variables = {
        ids: [Id.toString()]
    }

    const Payload = {
        query: Query,
        variables: Variables
    }

    const Response = await fetch("https://search-api.artlist.io/v1/graphql", {
        method: "POST",
        headers: {
            "content-type": "application/json"
        },
        body: JSON.stringify(Payload)
    })
    const JSONData = await Response.json()

    var Data

    try {
        Data = JSONData.data.songs[0]
    } catch (e) {}

    if (Data === undefined) {
        return false
    }

    return Data
}

async function LoadAssetInfo(Id) {
    const Pagetype = GetPagetype()
    if (Pagetype === SFXS_PAGETYPE) {
        SingleSoundEffectData = await GetSfxInfo(Id)
        return true
    }
    if (Pagetype === SONGS_PAGETYPE) {
        SingleSongData = await GetSongInfo(Id)
        return true
    }
    return false
}

function GetAudioTable() {
    return window.document.querySelector("table.w-full.table-fixed[data-testid=AudioTable]")
}

function GetSongPage() {
    return window.document.querySelector("div[data-testid=SongPage]")
}

function GetBanner(SongPage) {
    return SongPage.querySelector("div.relative.min-h-95.w-full")
}

function GetActionRow(SongPage) {
    if (window.innerWidth >= 1024) { // page layout changes depending on viewport size
        return SongPage.querySelector("div.hidden")
    }
    return SongPage.querySelector("div.block.py-4.px-6")
}

function GetTBody() {
    return window.document.querySelector("div.w-full[data-testid=ComposableAudioList]") || window.document.querySelector("table[data-testid=AudioTable]>tbody")
}

function GetTBodyEdgeCase() {
    const TBody = window.document.querySelector("div[data-testid=Wrapper]") || window.document.querySelector("div[data-testid=ArtistContent]")
    if (TBody === null) {
        return
    }
    if (TBody.parentNode.classList.contains("hidden")) {
        return
    }
    return TBody
}

function GetAudioRowData(AudioRow, Pagetype) {
    var Data = {
        AudioTitle: "none",
        RawTitle: "None",
        Artists: [],
        Button: null,
        Pagetype: Pagetype
    }
    var AlbumsAndArtists = AudioRow.querySelector("td[data-testid=AlbumsAndArtists]")
    var DataAndActions = AudioRow.querySelector("td[data-testid=DataAndActions]")

    if (Pagetype === SONGS_PAGETYPE) {
        AlbumsAndArtists = AudioRow.querySelector("div[data-testid=AudioDetails]")
        DataAndActions = AudioRow.querySelector("div[data-testid=AnimatedToggleContainer]")
    }

    if (Pagetype == MUSIC_PAGETYPE) {
        AlbumsAndArtists = AudioRow.querySelector("div.flex[data-testid=AudioDetails]")
        DataAndActions = AudioRow.querySelector("div[data-testid=AnimatedToggleContainer]")
    }

    if (DataAndActions == null && AudioRow.querySelector("span[data-testid=stems-player-stem-name]") && AudioRow.parentNode.getAttribute("data-testid") != "ComposableAudioList") { // most likely a song stem, so default to audio row
        const StemContainer = AudioRow.parentNode.parentNode
        const Title = StemContainer.querySelector("span[data-testid=stems-player-song-name]")
        const Artists = StemContainer.querySelectorAll("span[data-testid=stems-player-song-artist]")

        Data.Pagetype = SONG_STEMS_PAGETYPE
        Data.AudioTitle = `${AudioRow.querySelector("span[data-testid=stems-player-stem-name]").innerText} of ${Title.innerText}`
        Data.RawTitle = AudioRow.querySelector("span[data-testid=stems-player-stem-name]").innerText

        for (const Artist of Artists) {
            Data.Artists.push(Artist.innerText)
        }

        DataAndActions = AudioRow
    }

    if (!DataAndActions) {
        console.warn("DataAndActions not found in", Pagetype, AudioRow)
    }

    var Button = DataAndActions.querySelector("button[aria-label='download']") || DataAndActions.querySelector("button[aria-label='Download']")

    if (Button) {
        Data.Button = Button
    }

    if (AlbumsAndArtists == null || DataAndActions == null) {
        return Data
    }

    const AudioTitle = AlbumsAndArtists.querySelector("a.truncate[data-testid=Link]")
    const Artists = AlbumsAndArtists.querySelectorAll("a.truncate.text-gray-200[data-testid=Link]")

    if (AudioTitle) {
        Data.AudioTitle = AudioTitle.childNodes[0].textContent.trim()
        Data.RawTitle = Data.AudioTitle
    }
    if (Artists) {
        for (const Artist of Artists) {
            Data.Artists.push(Artist.textContent.replaceAll(",", "").trim())
        }
    }

    if (Data.AudioTitle === "none" && Data.Artists.length === 0 && Data.Button == null) {
        return false
    }
    if ((Data.AudioTitle === "none" || Data.Artists.length === 0) && Data.Button !== null) {
        Data.Button.style.color = ErrorButtonColor
    }

    return Data
}

function GetBannerData(SongPage, Pagetype) {
    const Data = {
        AudioTitle: "none",
        RawTitle: "none",
        Artists: [],
        Button: null,
        Pagetype: Pagetype
    }

    const Banner = GetBanner(SongPage)
    const ActionRow = GetActionRow(SongPage)

    if (Banner === null || ActionRow === null) {
        return false
    }

    const Title = Banner.querySelector("h1[data-testid=Heading]")
    const Artists = Banner.querySelectorAll("a[data-testid=Link]")
    const Button = ActionRow.querySelector("button[aria-label='direct download']")

    if (Title == null || Artists.length <= 0 || Button == null) {
        return Data
    }

    Data.AudioTitle = Title.textContent
    Data.RawTitle = Data.AudioTitle
    Data.Button = Button

    for (const Artist of Artists) {
        Data.Artists.push(Artist.textContent.replaceAll(",", "").trim())
    }

    if (Data.AudioTitle === "none" && Data.Artists.length == 0 && Data.Button == null) {
        return false
    }
    if ((Data.AudioTitle === "none" || Data.Artists.length == 0) && Data.Button != null) {
        Data.Button.style.color = ErrorButtonColor
        Data.Button.style.borderColor = ErrorButtonColor
    }

    return Data
}

function MakeFilename(AssetData, Pagetype) {
    const NoAlbum = AssetData.albumId === undefined
    return `${(Pagetype === MUSIC_PAGETYPE || Pagetype === SONGS_PAGETYPE || Pagetype === SONG_STEMS_PAGETYPE) ? (Pagetype == SONG_STEMS_PAGETYPE ? "Music Stem" : "Music") : "Sfx"} ${AssetData.artistName} - ${AssetData.songName} ${AssetData.songName != AssetData.albumName ? `on ${AssetData.albumName} ` : ''}(${AssetData.artistId}.${NoAlbum ? '' : AssetData.albumId + '.'}${AssetData.songId})`
}

function WriteAudio(RowData, AudioData) {
    const Pagetype = RowData.Pagetype
    const ChosenColor = (Pagetype === MUSIC_PAGETYPE || Pagetype === SONGS_PAGETYPE || Pagetype == SONG_STEMS_PAGETYPE) ? ModifiedMusicButtonColor : ModifiedSfxButtonColor
    const FileName = MakeFilename(AudioData, Pagetype)
    RowData.Button.setAttribute("artlist-dl-processed", "true")
    RowData.Button.style.color = ChosenColor
    RowData.Button.addEventListener("click", function(event) {
        event.stopImmediatePropagation() // prevent premium popup upsell
        ShowSaveFilePickerForURL((AudioData.sitePlayableFilePath || AudioData.playableFileUrl), FileName + ".aac");
    }, true)
}

function WriteBanner(BannerData, AudioData) {
    const Pagetype = BannerData.Pagetype
    const ChosenColor = (Pagetype === MUSIC_PAGETYPE || Pagetype === SONGS_PAGETYPE || Pagetype == SONG_STEMS_PAGETYPE) ? ModifiedMusicButtonColor : ModifiedSfxButtonColor
    const FileName = MakeFilename(AudioData, Pagetype)
    BannerData.Button.setAttribute("artlist-dl-processed", "true")
    BannerData.Button.style.color = ChosenColor
    BannerData.Button.style.borderColor = ChosenColor
    BannerData.Button.addEventListener("click", function(event) {
        event.stopImmediatePropagation() // prevent premium popup upsell
        ShowSaveFilePickerForURL((AudioData.sitePlayableFilePath || AudioData.playableFileUrl), FileName + ".aac");
    }, true)
}

var changeBackTimeout = -1
function WriteDownloadAllStems(StemsContainer, DownloadButton) {
    const Pagetype = GetPagetype()
    const ChosenColor = ModifiedMusicButtonColor
    DownloadButton.setAttribute("artlist-dl-processed", "true")
    DownloadButton.style.backgroundColor = ChosenColor
    DownloadButton.style.borderColor = ChosenColor
    DownloadButton.style.color = "black"
    DownloadButton.addEventListener("click", async function(event) {
        event.stopImmediatePropagation() // prevent dropdown
        clearInterval(changeBackTimeout)
        changeBackTimeout = setTimeout(() => {
          DownloadButton.querySelector("span.whitespace-nowrap").innerText = "Download All Stems"
          DownloadButton.disabled = false
        }, 10000)
        DownloadButton.querySelector("span.whitespace-nowrap").innerText = "Please Wait..."
        DownloadButton.disabled = true
        const dataList = []
        var firstData = null
        for (const Stem of StemsContainer.querySelectorAll("div[data-testid=StemRow]")) {
            try {
                const AudioData = GetAudioDataFromRowData(GetAudioRowData(Stem, SONG_STEMS_PAGETYPE))
                if (!firstData) {
                    firstData = AudioData
                }

                dataList.push({
                    URL: (AudioData.sitePlayableFilePath || AudioData.playableFileUrl),
                    Filename: MakeFilename(AudioData, SONG_STEMS_PAGETYPE) + ".aac"
                })
            } catch (e) {
                console.warn("Exception while handling stem rows:", e)
            }
        }
        const fileName = `Music Stems ${firstData.artistName} - ${firstData._songName}${firstData._songName == firstData._albumName ? "" : ` on ${firstData._albumName}`}.zip`
        await ShowSaveFilePickerForURLsZipped(dataList, fileName)
        clearInterval(changeBackTimeout)
        DownloadButton.querySelector("span.whitespace-nowrap").innerText = "Download All Stems"
        DownloadButton.disabled = false
    })
}

function MatchAudioToRow(AudioData, RowData, SkipArtistCheck) {
    return (AudioData.songName || AudioData.name).trim() === RowData.RawTitle.trim() && (SkipArtistCheck || RowData.Artists.indexOf(AudioData.artistName.trim()) != -1)
}

function OnRowAdded(AudioRow, RowData, AudioData) {
    AudioRow.setAttribute("artlist-dl-state", "modified")
    if (AudioData !== undefined) {
        WriteAudio(RowData, AudioData)
    } else {
        console.warn("No data given for row", RowData)
        if (RowData.Button !== null) {
            RowData.Button.style.color = ErrorButtonColor
        }
    }
}

function cloneref(object) {
    return {
        ...object
    }
}

function GetAudioDataFromRowData(RowData) {
    if (RowData.Pagetype === SFX_PAGETYPE) {
        if (LoadedSfxLists.length <= 0) {
            console.warn("No loaded sound effects to loop through.");
            return
        }
        for (const SfxList of LoadedSfxLists) {
            for (const SfxData of SfxList) {
                if (MatchAudioToRow(SfxData, RowData)) {
                    return SfxData
                }
            }
        }
    }
    if (RowData.Pagetype === MUSIC_PAGETYPE) {
        if (LoadedMusicLists.length <= 0) {
            console.warn("No loaded songs to loop through.");
            return
        }
        for (const MusicList of LoadedMusicLists) {
            for (const SongData of MusicList) {
                if (MatchAudioToRow(SongData, RowData)) {
                    return SongData
                }
            }
        }
    }
    if (RowData.Pagetype === SFXS_PAGETYPE) {
        if (LoadedSfxsList.length <= 0) {
            console.warn("No loaded similar sfxs to loop through.");
            return
        }
        for (const SfxsList of LoadedSfxsList) {
            for (const SfxData of SfxsList) {
                if (MatchAudioToRow(SfxData, RowData)) {
                    return SfxData
                }
            }
        }
    }
    if (RowData.Pagetype === SONGS_PAGETYPE) {
        if (LoadedSongsList.length <= 0) {
            console.warn("No loaded similar songs to loop through.");
            return
        }
        for (const SongsList of LoadedSongsList) {
            for (const SongData of SongsList) {
                if (MatchAudioToRow(SongData, RowData)) {
                    return SongData
                }
            }
        }
    }
    if (RowData.Pagetype === SONG_STEMS_PAGETYPE) {
        if (LoadedSstemsLists.length <= 0) {
            console.warn("No loaded song stems to loop through.");
            return
        }
        for (const StemData of LoadedSstemsLists) {
            for (const Stem of StemData.stems) {
                if (MatchAudioToRow(Stem, RowData, true)) {
                    const clonedData = cloneref(StemData)
                    clonedData.sitePlayableFilePath = Stem.playableFileUrl
                    clonedData._songName = clonedData.songName
                    clonedData._albumName = clonedData.albumName
                    clonedData.songName = `${Stem.name} of ${StemData.songName}`
                    clonedData.albumName = `${Stem.name} of ${StemData.albumName}`
                    return clonedData
                }
            }
        }
    }

    console.warn("Couldn't handle data:", RowData)
}

function ApplyXHR(XHR) {
    const Pagetype = GetPagetype()

    if (Pagetype !== UNKNOWN_DATATYPE) {
        XHR.addEventListener("readystatechange", function() {
            if (XHR.readyState == XMLHttpRequest.DONE) {
                var JSONData
                try {
                    JSONData = JSON.parse(XHR.responseText)
                } catch (e) {
                    console.warn(`Couldn't parse as json: ${XHR.responseText}`)
                    return
                }
                HandleJSONData(JSONData)
            }
        })
    }
}

function HandleJSONData(Data) {
    console.log(Data)
    const Datatype = GetDatatype(Data)
    if (Datatype === SONGS_PAGETYPE) {
        LoadedSongsList.push(Data.data.songs[0].similarSongs)
    }
    if (Datatype === MUSIC_PAGETYPE) {
        LoadedMusicLists.push(Data.data.songList.songs)
    }
    if (Datatype === SFXS_PAGETYPE) {
        LoadedSfxsList.push(Data.data.sfxs[0].similarList)
    }
    if (Datatype === SFX_PAGETYPE) {
        LoadedSfxLists.push(Data.data.sfxList.songs)
    }
    if ((Datatype === SONG_STEMS_PAGETYPE && (Data.data.songs && Data.data.songs[0] && Data.data.songs[0].stems))) {
        LoadedSstemsLists.push(Data.data.songs[0])
    }
}

function HookRequests() {
    var handler = function() {
        const Method = (arguments)[0]
        const URL = (arguments)[1]

        if (MatchURL(URL)) {
            ApplyXHR(this)
        }

        return oldXMLHttpRequestOpen.apply(this, arguments)
    }

    if (RequestsInterval != -1) {
        clearInterval(RequestsInterval)
    }
    RequestsInterval = setInterval(() => {
        window.XMLHttpRequest.prototype.open = handler
    })
}

// this makes the user-script support the [←] Back and [→] Right navigations
// aswell as switching pages because artlist doesn't navigate, but instead
// changes their HTML dynamically so that the end-user does not have to
// reload the entire page.

// by polling the changes in an albeit bad way, we can detect when this
// occurs, and as far as i know there's no other better way to do it.
// please make an issue on github and educate me if there is.

async function Initialize() {
    DontPoll = false

    const Pagetype = GetPagetype()

    console.log("searching for table...")

    if (Pagetype === SONGS_PAGETYPE || Pagetype === SFXS_PAGETYPE) {
        const Id = location.pathname.split("/")[4]
        const NumId = new Number(Id)
        if (NumId.toString() !== "NaN") {
            LoadAssetInfo(NumId)
        }
        SongPage = GetSongPage()
        await Until(() => {
            const Data = GetBannerData(SongPage, Pagetype)
            return Data != false && Data.Button != null
        })
        const RowData = GetBannerData(SongPage, Pagetype)
        await Until(() => {
            return Pagetype === SONGS_PAGETYPE ? SingleSongData != "none" : SingleSoundEffectData != "none"
        })
        const AudioData = Pagetype === SONGS_PAGETYPE ? SingleSongData : SingleSoundEffectData
        if (RowData.AudioTitle && RowData.Artists.length >= 1) {
            WriteBanner(RowData, AudioData)
        }
    }

    if (Pagetype === SONGS_PAGETYPE) {
        await Until(() => {
            return GetTBodyEdgeCase() != undefined
        })
        TBody = GetTBodyEdgeCase()
    } else {
        await Until(() => {
            return GetTBody() != undefined
        })
        TBody = GetTBody()
        console.log("tbody", TBody)
    }

    function OnAudioRowAdded(AudioRow) {
        if (AudioRow.getAttribute("artlist-dl-processed") === "true") {
            return
        }
        if (AudioRow.classList.contains("hidden")) {
            return
        }
        const RowData = GetAudioRowData(AudioRow, GetPagetype())
        const AudioData = GetAudioDataFromRowData(RowData)

        OnRowAdded(AudioRow, RowData, AudioData)
    }

    LastInterval = setInterval(() => {
        for (const AudioRow of TBody.childNodes) {
            if (!AudioRow.hasAttribute("artlist-dl-processed")) {
                try {
                    OnAudioRowAdded(AudioRow)
                    AudioRow.setAttribute("artlist-dl-processed", "true")
                } catch (e) {
                    console.warn(e)
                }
            }
        }

        for (const modal of document.querySelectorAll(".ReactModal__Content")) {
            for (const AllStemsDownload of modal.querySelectorAll("button[data-testid=renderButton]")) {
                if (!AllStemsDownload.hasAttribute("artlist-dl-processed") && AllStemsDownload.parentNode.getAttribute("data-testid") == "download-all-stems-dropdown") {
                    WriteDownloadAllStems(modal, AllStemsDownload)
                }
            }
            for (const Stem of modal.querySelectorAll("div[data-testid=StemRow]")) {
                if (!Stem.hasAttribute("artlist-dl-processed") && document.contains(Stem)) {
                    try {
                        OnAudioRowAdded(Stem)
                        Stem.setAttribute("artlist-dl-processed", "true")
                    } catch (e) {} // will fail on false positives so just hide errors
                }
            }
        }
    }, 500)
}

HookRequests()
document.addEventListener("DOMContentLoaded", () => {
    Initialize()
    setInterval(() => {
        if (TBody != null && !document.contains(TBody)) {
            console.log("Re-updating...")
            DontPoll = true
            TBody = null
            AudioTable = null
            SongPage = null
            Initialize()
        }
    }, 1000)
})