main 35f13148eeab cached
17 files
147.4 KB
32.9k tokens
49 symbols
1 requests
Download .txt
Repository: huhridge/huh-spicetify-extensions
Branch: main
Commit: 35f13148eeab
Files: 17
Total size: 147.4 KB

Directory structure:
gitextract_6oyyqymh/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug-report.md
│       └── feature-request.md
├── .gitignore
├── README.md
├── fullAlbumDate/
│   ├── README.md
│   └── fullAlbumDate.js
├── fullAppDisplayModified/
│   ├── README.md
│   └── fullAppDisplayMod.js
├── goToSong/
│   ├── README.md
│   └── goToSong.js
├── listPlaylistsWithSong/
│   ├── README.md
│   └── listPlaylistsWithSong.js
├── manifest.json
├── playlistIntersection/
│   ├── README.md
│   └── playlistIntersection.js
└── skipStats/
    ├── README.md
    └── skipStats.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug Report
about: Create a report to help me resolve issues
title: "[Extension Name Here][BUG]"
labels: bug
assignees: huhridge

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

**Expected behavior**
A clear and concise description of what you expected to happen.

**Console Errors**
To check the console, run `spicetify enable-devtools` in the terminal, and press <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>i</kbd> and check the console.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS:
 - Spotify Version:
 - Spicetify Version:

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature Request
about: Suggest an idea for this project
title: "[REQ]"
labels: enhancement
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is.

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================

.DS_Store
.DS_Store
.DS_Store


================================================
FILE: README.md
================================================
# huh-spicetify-extensions

Collection of my spicetify extensions

🌟 Like it? Gimme some love!  
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)

## Full App Display modified

Filename : `fullAppDisplayMod.js`

Minimal album cover art display with beautiful blur effect background. Activating button locates in top bar. While in display mode, double click anywhere to exit. Right click anywhere to open setting menu. Now also includes lyrics if `lyrics-plus` custom app installed.

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAppDisplayModified/previews/preview.gif)

<details>
 <summary>Screenshots</summary>
 
* Album Art
  <details>
  <summary></summary>

  <img width="1440" alt="Album Art Background" src="https://user-images.githubusercontent.com/67046436/146332984-d45231e2-f193-43cb-b1b0-2456edf9ce29.png">
  </details>
 
  
* Colorful Background(doesn't work offline)
  <details>
  <summary></summary>

  <img width="1440" alt="Colorful Background" src="https://user-images.githubusercontent.com/67046436/146333067-c6d6694e-82a7-4948-bd56-3b31eb50ac7d.png">
  </details>
 
</details>

For more information: [fullAppDisplayMod README](/fullAppDisplayModified/README.md)

## skipStats

Filename : `skipStats.js`

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/preview.jpg)

Extension to track your skips!

-   Tracks your skips when listening to playlists or albums!
-   Displays the data in a readable manner
-   Auto-skip songs over a certain value of skips

For more information: [skipStats README](/skipStats/README.md)

## List Playlists with Song

Filename : `listPlaylistsWithSong.js`

Adds context menu button to view playlists in your library that contain the selected song.

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview.gif)

### To use:

Right Click on selected song, and click "List playlists with this Song".

![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview1.jpg)

#### Note:

~~Currently, doesn't work on the currently playing song(like in the bottom bar pictured below), finding a workaround.~~
Now it works, fixed by yours truly!

## Go to Song

Filename : `goToSong.js`

Go to the currrently playing song in a playlist **/or/** currently playing playlist.

### To use:

-   Currently playing playlist: Go to Profile > GoToSong > Choose "Go To Song in current Playlist"
-   ![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview1.jpg)

-   Any Playlist: Right Click on the Playlist, and choose "Go to Currently Playing Song"
-   ![Preview2](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview2.jpg)

### Note:

You may need to adjust your delay if it's giving an error, follow the instructions in the popup.

![Adjust](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/adjust.jpg)

## playlistIntersection

Filename : `playlistIntersection.js`

Adds context menu buttons to see

-   songs in common between two playlists
-   songs only present in one playlist

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/both.jpg)

### To use:

Check the full readme: [playlistIntersection readme](/playlistIntersection/README.md)

## Display full Album date

Filename : `fullAlbumDate.js`

Display full album date instead of just year

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAlbumDate/preview.jpg)


================================================
FILE: fullAlbumDate/README.md
================================================
# Display full Album date
Filename : `fullAlbumDate.js`
Display full album date instead of just year

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAlbumDate/preview.jpg)

## More
🌟 Like it? Gimme some love!    
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)


================================================
FILE: fullAlbumDate/fullAlbumDate.js
================================================
(async function fullAlbumDate() {
    if (!Spicetify.Platform?.History || !Spicetify.CosmosAsync || !Spicetify.Locale) {
        setTimeout(fullAlbumDate, 300);
        return;
    }

    const { CosmosAsync, Locale } = Spicetify;
    const { History } = Spicetify.Platform;

    async function getAlbumDate(uri) {
        const albumInfo = await CosmosAsync.get(`https://api.spotify.com/v1/albums/${uri}`);
        const albumDate = new Date(albumInfo.release_date);
        return Locale.formatRelativeTime( albumDate );
    }

    function replaceDate(newDate) {
        const dateElement =
            document.querySelector(".main-entityHeader-divider.main-type-mesto") ??
            document.querySelector(".main-entityHeader-metaData span:nth-last-child(2)");
        if (!dateElement) {
            setTimeout(replaceDate, 100, newDate);
            return;
        }
        dateElement.textContent = newDate;
    }

    async function setDate() {
        const { pathname } = History.location;
        if (pathname.startsWith("/album/")) {
            const uri = pathname.split("/")[2];
            const newDate = await getAlbumDate(uri);
            replaceDate(newDate);
        }
    }

    setDate();
    History.listen(setDate);
})();


================================================
FILE: fullAppDisplayModified/README.md
================================================
# Full App Display modified
Filename : `fullAppDisplayMod.js`
Minimal album cover art display with beautiful blur effect background. Activating button locates in top bar. While in display mode, double click anywhere to exit. Right click anywhere to open setting menu. Now also includes lyrics if `lyrics-plus` custom app installed.

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAppDisplayModified/previews/preview.gif)

Screenshots:
* Album Art
 <img width="1440" alt="Album Art Background" src="https://user-images.githubusercontent.com/67046436/146332984-d45231e2-f193-43cb-b1b0-2456edf9ce29.png">
* Colorful Background(doesn't work offline)
 <img width="1440" alt="Colorful Background" src="https://user-images.githubusercontent.com/67046436/146333067-c6d6694e-82a7-4948-bd56-3b31eb50ac7d.png">
Colorful background changes text color if the color is too light,
<img width="1440" alt="Colorful Background(dynamic text)" src="https://user-images.githubusercontent.com/67046436/146333271-9cd687e6-cb87-4c89-ba02-a21654596514.png">

Settings:
(May look a bit different depending on your theme)
<img width="1440" alt="Screenshot 2021-12-16 at 14 03 04" src="https://user-images.githubusercontent.com/67046436/146336169-7aa6ecba-e475-49fa-a111-b3407268d1ce.png">

## More
🌟 Like it? Gimme some love!    
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)


================================================
FILE: fullAppDisplayModified/fullAppDisplayMod.js
================================================
// @ts-check
// NAME: Full App Display
// AUTHOR: khanhas
// VERSION: 1.2
// DESCRIPTION: Fancy artwork and track status display.

/// <reference path="../globals.d.ts" />

(function FullAppDisplay() {
    if (!Spicetify.Keyboard || !Spicetify.React || !Spicetify.ReactDOM || !Spicetify.Platform) {
        setTimeout(FullAppDisplay, 200);
        return;
    }

    const { React: react, ReactDOM: reactDOM } = Spicetify;
    const { useState, useEffect } = react;

    const CONFIG = getConfig();

    if (
        !CONFIG["colorChoice"] ||
        CONFIG["colorChoice"] == "colorDark" ||
        CONFIG["colorChoice"] == "colorLight" ||
        CONFIG["colorChoice"] == "colorRaw"
    ) {
        CONFIG["colorChoice"] = "LIGHT_VIBRANT";
        saveConfig();
    }

    if (!CONFIG["version"]) {
        CONFIG["version"] = "1.2";
        saveConfig();
    }

    let updateVisual;
    let nextUri, prevColor, nextColor, finImage;
    let isHidden = false;
    let time;

    const style = document.createElement("style");
    const styleBase = `
#full-app-display {
    display: none;
    position: fixed;
    width: 100%;
    height: 100%;
    cursor: default;
    left: 0;
    top: 0;
}
#fad-header {
    position: fixed;
    width: 100%;
    height: 80px;
    -webkit-app-region: drag;
}
#fad-body {
    height: 100vh;
}
#fad-foreground {
    position: relative;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: scale(var(--fad-scale));
    transition: all 1s ease;
}
#fad-art-image {
    position: relative;
    width: 100%;
    height: 100%;
    padding-bottom: 100%;
    border-radius: 15px;
    background-size: cover;
}
#fad-art-inner {
    position: absolute;
    left: 3%;
    bottom: 0;
    width: 94%;
    height: 94%;
    z-index: -1;
    backface-visibility: hidden;
    transform: translateZ(0);
    filter: blur(6px);
    backdrop-filter: blur(6px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important;
}
#fad-progress-container {
    width: 100%;
    display: flex;
    align-items: center;
}
#fad-progress {
    width: 100%;
    height: 6px;
    margin: 6px 0 6px;
    border-radius: 6px;
    background-color: #ffffff50;
    overflow: hidden;
}
#fad-progress-inner {
    height: 100%;
    transition: width 100ms ease;
    border-radius: 6px;
    background-color: #ffffff;
    box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8) !important;
}
#fad-volume {
    width: 4rem;
    height: 16rem;
    position: fixed;
    left: 1rem;
    transition: opacity ease 350ms;
}
#fad-volume:hover {
    opacity: 1 !important;
}
#fad-volicon {
    height: fit-content;
    display: grid;
    justify-content: start;
}
#fad-volbar {
    display: flex;
    height: 200px;
    width: 8px;
    border-radius: 6px;
    overflow: hidden;
    background-color: #ffffff50;
    justify-content: center;
    padding-top: 10px;
    align-items: end;
    margin-left: 27.5px;
    margin-top: 10px;
    position: absolute;
}
#fad-volbar-inner {
    width: 8px;
    border-radius: 6px;
    background-color: rgb(255, 255, 255);
    transition: height 0.5s ease 0s;
}
#fad-duration {
    margin-left: 10px;
}
#fad-background {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: -2;
}
body.fad-activated #full-app-display {
    display: block
}
.fad-background-fade {
    transition: background-image 1s linear;
}
body.video-full-screen.video-full-screen--hide-ui {
    cursor: auto;
}
#fad-controls button, #fad-extracontrols button {
    background-color: transparent;
    border: 0;
    color: currentColor;
    padding: 0 5px;
    pointer-events: auto;
}
#fad-controls button:hover, #fad-extracontrols button:hover, #fad-volicon button:hover{
    transform: scale(1.1);
}
#fad-artist svg, #fad-album svg {
    display: inline-block;
}
#fad-upnext {
    position: absolute;
    width: 399px;
    height: 90px;
    display: flex;
    border-radius: 10px;
    animation: textchange 0.5s forwards;
    transition: clip-path 0.4s;
}
#fad-upnext-image {
    width: 64px;
    align-self: center;
    margin-left: 13px;
    height: 64px;
    background-size: cover;
    background-position: center;
    border-radius: 5px;
    box-shadow: 0 4px 8px rgb(0 0 0 / 30%) !important;
    z-index: 0;
}
#fad-upnext-blur {
    position: absolute;
    backdrop-filter: blur(15px) brightness(0.6);
    background-color: rgb(255,255,255,0.1);
    width: 100%;
    height: 100%;
    border-radius: 10px;
}
#fad-upnext-details{
    margin-left: 15px;
    align-self: center;
    font-size: 14.5px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    color: #ffffff;
    overflow: hidden;
    white-space: nowrap;
}
#scroll-queue{
    position: absolute;
    width: 399px;
    height: 90px;
    color: transparent;
    z-index: 2;
}
.dont-scale{
    transform: scale(calc(1/(var(--fad-scale))));
}
.dot-after:after{
    background-color: currentColor;
    border-radius: 50%;
    bottom: 3px;
    content: "";
    display: block;
    height: 4px;
    left: 50%;
    position: relative;
    transform: translateX(-50%);
    width: 4px;
}
.crossed-out:after{
    background-color: currentColor;
    bottom: 24px;
    transform: rotate(45deg);
    transform-origin: 0 0;
    content: "";
    display: block;
    height: 27px;
    left: 18px;
    position: relative;
    width: 0.5px;
}
::-webkit-scrollbar {
    width: 8px;
}

.fad-grad-image{
    position: absolute;
    filter: blur(40px) brightness(0.60);
    border-radius: 100em;
    animation: rotategrad 50s linear infinite 1s;
}

@keyframes textchange {
    0%{
        opacity: 0;
    }   
    30%{
        opacity: 0.3;
    }
    60%{
        opacity: 0.6;
    }
    90%{
        opacity: 0.9;
    }
  }

@keyframes rotategrad {
    0% {
        transform: rotate(18deg);
    }
    100% {
        transform: rotate(378deg);
    }
}
`;

    const styleChoices = [
        `
#fad-foreground {
    flex-direction: row;
    text-align: left;
}
#fad-art {
    width: calc(100vw - 840px);
    min-width: 200px;
    max-width: 340px;
}
#fad-details {
    padding-left: 40px;
    line-height: initial;
    max-width: 70%;
    color: #FFFFFF;
    filter: invert(0);
}
#fad-title {
    font-size: 87px;
    font-weight: 900;
}
#fad-artist, #fad-album {
    font-size: 54px;
    font-weight: 400;
}
#fad-artist svg, #fad-album svg {
    margin-right: 5px;
}
#fad-status {
    display: flex;
    min-width: 400px;
    max-width: 400px;
    align-items: center;
}
#fad-status.active {
    margin-top: 20px;
}
#fad-controls {
    display: flex;
    margin-right: 10px;
    z-index: 0;
}
#fad-extracontrols {
    height: 28px;
    display: flex;
}
#fad-elapsed {
    min-width: 52px;
}`,
        `
#fad-art {
    width: calc(100vh - 400px);
    max-width: 340px;
}
#fad-foreground {
    flex-direction: column;
    text-align: center;
}
#fad-details {
    padding-top: 40px;
    line-height: initial;
    max-width: 70%;
    color: #FFFFFF;
    filter: invert(0);
}
#fad-title {
    line-height: 1;
    font-size: 54px;
    font-weight: 900;
}
#fad-artist, #fad-album {
    font-size: 33px;
    font-weight: 400;
}
#fad-artist svg, #fad-album svg {
    width: 25px;
    height: 25px;
    margin-right: 5px;
}
#fad-status {
    display: flex;
    min-width: 400px;
    max-width: 400px;
    align-items: center;
    flex-direction: column;
}
#fad-status.active {
    margin: 20px auto 0;
}
#fad-controls {
    margin-top: 20px;
    order: 2;
    z-index: 0;
}
#fad-extracontrols {
    order: 3;
    width: 100%;
    height: 28px;
    display: flex;
}
#fad-elapsed {
    min-width: 56px;
    margin-right: 10px;
    text-align: right;
}`,
    ];

    const lyricsPlusBase = `
#fad-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
}
#fad-foreground {
    padding: 0 50px 0 100px;
    width: 50vw;
}
#fad-lyrics-plus-container {
    position: relative;
    width: 50vw;
}
.lyrics-lyricsContainer-LyricsContainer.fad-enabled .lyrics-config-button-container {
    display: none;
}
`;
    const lyricsPlusStyleChoices = [
        `
#fad-title {
    font-size: 4vw;
}
#fad-artist, #fad-album {
    font-size: 2.5vw;
    font-weight: 400;
}
#fad-art {
    max-width: 210px;
    margin-left: 50px;
}`,
        `
#fad-title {
    font-size: 3.9vw;
}
#fad-artist, #fad-album {
    font-size: 2.5vw;
    font-weight: 400;
}
        `,
    ];

    const verticalMonitorStyle = [
        `
#fad-body {
    grid-template-columns: none;
}
#fad-foreground, #fad-lyrics-plus-container {
    width: 100%;
    height: 50vh;
}
#fad-foreground {
    padding: 0 50px 0;
}
.lyrics-lyricsContainer-LyricsContainer.fad-enabled {
    height: 50vh;
    --lyrics-align-text: center !important;
}
#fad-volume {
    top: 15vh;
}
        `,
    ];
    updateStyle();

    function displayUpdate() {
        let updateText = react.createElement(
            "p",
            {
                className: "fad-update",
            },
            `
             This update brings:
             `,
            react.createElement("li", {}, "Added seekable progress bar: Now you can seek songs from FAD itself, click on it to seek!"),
            react.createElement("li", {}, "Added show only on hover mode for volume bar (change in settings)"),
            react.createElement("li", {}, "Bug Fixes: Reworked the upnext and queue function, to account for the scale.")
        );
        Spicetify.PopupModal.display({
            title: "What's New with FullAppDisplayMod",
            content: updateText,
            isLarge: true,
        });
    }

    if (CONFIG["version"] == "1.1") {
        displayUpdate();
        CONFIG["version"] = "1.2";
        saveConfig();
    }

    async function fetchColors(uri) {
        let colors = {};

        try {
            const body = await Spicetify.CosmosAsync.get(`https://spclient.wg.spotify.com/colorextractor/v1/extract-presets?uri=${uri}&format=json`);
            for (const color of body.entries[0].color_swatches) {
                colors[color.preset] = `#${color.color.toString(16).padStart(6, "0")}`;
            }
        } catch {
            colors = {
                DARK_VIBRANT: "#000000",
                DESATURATED: "#000000",
                LIGHT_VIBRANT: "#000000",
                PROMINENT: "#000000",
                VIBRANT: "#000000",
                VIBRANT_NON_ALARMING: "#000000",
            };
        }
        return colors;
    }

    function lightnessColor(hex) {
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        let r = parseInt(result[1], 16);
        let g = parseInt(result[2], 16);
        let b = parseInt(result[3], 16);
        return (Math.max(r, g, b) + Math.min(r, g, b)) / 2;
    }

    const DisplayIcon = ({ icon, size }) => {
        return react.createElement("svg", {
            width: size,
            height: size,
            viewBox: "0 0 16 16",
            fill: "currentColor",
            dangerouslySetInnerHTML: {
                __html: icon,
            },
        });
    };

    const SubInfo = ({ text, id, icon }) => {
        return react.createElement(
            "div",
            {
                id,
            },
            CONFIG.icons && react.createElement(DisplayIcon, { icon, size: 35 }),
            react.createElement("span", null, text)
        );
    };

    const ButtonIcon = ({ icon, onClick, className = null, style = null, onMouseEnter = null, onMouseLeave = null }) => {
        return react.createElement(
            "button",
            {
                className,
                style,
                onClick,
                onMouseEnter,
                onMouseLeave,
            },
            react.createElement(DisplayIcon, { icon, size: 20 })
        );
    };

    const ProgressBar = () => {
        const [value, setValue] = useState(Spicetify.Player.getProgress());
        useEffect(() => {
            const update = ({ data }) => {
                setValue(data);
            };
            Spicetify.Player.addEventListener("onprogress", update);
            // @ts-ignore
            return () => Spicetify.Player.removeEventListener("onprogress", update);
        });
        const duration = Spicetify.Platform.PlayerAPI._state.duration;
        return react.createElement(
            "div",
            { id: "fad-progress-container" },
            react.createElement("span", { id: "fad-elapsed" }, Spicetify.Player.formatTime(value)),
            react.createElement(
                "div",
                {
                    id: "fad-progress",
                    onClick: (e) => {
                        e.persist();
                        console.log(e);
                        let coors = document.querySelector("#fad-progress").getBoundingClientRect();
                        let temp = (e.pageX - coors.x) / coors.width;
                        console.log(temp);
                        Spicetify.Player.seek(temp);
                        setTimeout(console.log(Spicetify.Player.getProgressPercent()), 200);
                        document.querySelector("#fad-progress-inner").style.width = `${temp}%`;
                    },
                },
                react.createElement("div", {
                    id: "fad-progress-inner",
                    style: {
                        width: (value / duration) * 100 + "%",
                    },
                })
            ),
            react.createElement("span", { id: "fad-duration" }, Spicetify.Player.formatTime(duration))
        );
    };

    // @ts-ignore
    const VolumeBar = () => {
        // @ts-ignore
        const [value, setValue] = useState(Spicetify.Platform.PlaybackAPI._volume);
        let isHover = false;
        if (CONFIG["volumeBar"] == "onlyHover") {
            isHover = true;
        }
        useEffect(() => {
            const update = ({ data }) => {
                setValue(data.volume);
            };
            Spicetify.Platform.PlaybackAPI._events.addListener("volume", update);
            return () => Spicetify.Platform.PlaybackAPI._events.removeListener("volume", update);
        });
        return react.createElement(
            "div",
            {
                id: "fad-volume",
                style: {
                    top: `${(window.innerHeight - 256) / 2}px`,
                    opacity: isHover ? 0 : 1,
                },
                onWheel: (event) => {
                    let dir = event.deltaY < 0 ? 1 : -1;
                    let temp = parseInt(document.querySelector("#fad-volbar-inner").style.height) / 2 + dir * 1;
                    if (temp < 0) {
                        temp = 0;
                    } else if (temp > 100) {
                        temp = 100;
                    }
                    Spicetify.Player.setVolume(temp / 100);
                    document.querySelector("#fad-volbar-inner").style.height = `${2 * temp}px`;
                },
            },
            react.createElement(
                "div",
                { id: "fad-volicon" },
                react.createElement(ButtonIcon, {
                    style: {
                        marginLeft: "18px",
                        backgroundColor: "transparent",
                        border: "0",
                        color: "white",
                        padding: "0 5px",
                        pointerEvents: "auto",
                    },
                    // @ts-ignore
                    icon: Spicetify.Player.getMute() ? Spicetify.SVGIcons["volume-off"] : Spicetify.SVGIcons["volume"],
                    onClick: () => {
                        if (!Spicetify.Player.getMute()) {
                            document.querySelector("#fad-volicon svg").innerHTML = Spicetify.SVGIcons["volume-off"];
                            document.querySelector("#fad-volbar-inner").style.height = `0px`;
                            Spicetify.Player.toggleMute();
                        } else {
                            Spicetify.Player.toggleMute();
                            document.querySelector("#fad-volicon svg").innerHTML = Spicetify.SVGIcons["volume"];
                            setTimeout(() => {
                                document.querySelector("#fad-volbar-inner").style.height = `${Spicetify.Player.getVolume() * 200}px`;
                            }, 200);
                        }
                    },
                })
            ),
            react.createElement(
                "div",
                {
                    id: "fad-volbar",
                    onClick: (e) => {
                        let temp = 200 - e.nativeEvent.layerY;
                        if (temp < 0) {
                            temp = 0;
                        }
                        Spicetify.Player.setVolume(temp / 200);
                        document.querySelector("#fad-volbar-inner").style.height = `${temp}px`;
                    },
                },
                react.createElement("div", {
                    id: "fad-volbar-inner",
                    style: {
                        height: `${value * 200}px`,
                    },
                })
            )
        );
    };

    const upNext = async ({ index, queue }) => {
        let meta,
            uri,
            bottom = -100,
            right = 0,
            color,
            context,
            invertDetails = false,
            invertWhole = false,
            isContext = false,
            isColor = false;

        let deets = document.querySelector("#fad-details");
        const coor = deets.getBoundingClientRect();
        let scale = CONFIG["scale"];
        if (coor.bottom + scale * 90 + 10 > window.innerHeight) {
            bottom = bottom + (coor.bottom + 90 * scale + 10 - window.innerHeight) / scale + 10;
            right = -409;
        }

        if (Spicetify.Player.getRepeat() == 2) {
            uri = Spicetify.Player.data.item.uri;
            meta = Spicetify.Player.data.item.metadata;
        } else {
            // @ts-ignore
            uri = Spicetify.Queue.nextTracks[index].contextTrack.uri;
            // @ts-ignore
            meta = Spicetify.Queue.nextTracks[index].contextTrack.metadata;
        }

        let artistNames = Object.keys(meta)
            .filter((key) => key.startsWith("artist_name"))
            .sort()
            .map((key) => meta[key])
            .join(" • ");
        //@ts-ignore
        if (Spicetify.Queue.nextTracks[index].provider == "context") {
            isContext = true;
            context = Spicetify.Player.data.context.metadata.context_description;
            if (!context) {
                const uriObj = Spicetify.URI.fromString(Spicetify.Player.data.context.uri);
                switch (uriObj.type) {
                    case Spicetify.URI.Type.SEARCH:
                        context = `Search`;
                        break;
                    case Spicetify.URI.Type.COLLECTION:
                        context = "Liked Songs";
                        break;
                    case Spicetify.URI.Type.STATION:
                    case Spicetify.URI.Type.RADIO:
                        // @ts-ignore
                        const rType = uriObj.args[0];
                        context = `${rType} radio`;
                        break;
                    case Spicetify.URI.Type.FOLDER:
                        context = "Playlist Folder";
                        break;
                    default:
                        context = "unknown";
                }
            }
        }

        if (CONFIG["optionBackground"] === "colorText") {
            isColor = true;
            color = await fetchColors(uri);
            color = color[CONFIG["colorChoice"]];
            const luma =
                parseInt(color.substring(1, 3), 16) * 0.2126 +
                parseInt(color.substring(3, 5), 16) * 0.7152 +
                parseInt(color.substring(5, 7), 16) * 0.0722;
            if (luma > 180) {
                invertDetails = true;
            }
            if (deets.style.filter == "invert(1)") {
                invertWhole = true;
            }
        }

        return react.createElement(
            "div",
            {
                id: "fad-upnext",
                style: {
                    bottom: queue ? "" : `${bottom}px`,
                    right: queue ? "" : `${right}px`,
                    backgroundColor: isColor ? color : "",
                    backgroundImage: isColor ? "" : `url(${meta.image_url})`,
                    backgroundPosition: isColor ? "" : "center",
                    backgroundRepeat: isColor ? "" : "no-repeat",
                    backgroundSize: isColor ? "" : "cover",
                    border: isColor ? "" : "2px solid",
                    borderColor: isColor ? "" : "white",
                    clipPath: queue ? (index == 0 ? "inset(0px 0px 0px)" : "inset(90px 0px 0px)") : "",
                    filter: invertWhole ? "invert(1)" : "invert(0)",
                },
                ref: (el) => !queue && el && el.style.setProperty("box-shadow", "0 0 8px rgb(0 0 0 / 30%)", "important"),
            },
            !isColor &&
                react.createElement("div", {
                    id: "fad-upnext-blur",
                }),
            react.createElement("div", {
                id: "fad-upnext-image",
                style: {
                    backgroundImage: `url(${meta.image_url})`,
                },
            }),
            react.createElement(
                "div",
                {
                    id: "fad-upnext-details",
                    style: { filter: invertDetails ? "invert(1)" : "invert(0)" },
                },
                react.createElement(
                    "p",
                    {
                        id: "fad-upnext-provider",
                        style: {
                            fontWeight: "700",
                            overflow: "hidden",
                            textOverflow: "ellipsis",
                            maxWidth: "300px",
                        },
                    },
                    queue
                        ? isContext
                            ? `Track No.${index + 1} in Queue from ${context}`
                            : `Track No.${index + 1} from Queue`
                        : isContext
                        ? `Next From ${context}:`
                        : "Next in Queue:"
                    // isContext && react.createElement("em", {}, `${context}:`)
                ),
                react.createElement(
                    "div",
                    {
                        id: "fad-upnext-title",
                        style: {
                            fontSize: "18px",
                            fontWeight: "900",
                            overflow: "hidden",
                            textOverflow: "ellipsis",
                            maxWidth: "300px",
                        },
                    },
                    meta.title
                ),
                react.createElement(
                    "div",
                    {
                        id: "fad-upnext-artist",
                        style: {
                            fontWeight: "500",
                            overflow: "hidden",
                            textOverflow: "ellipsis",
                            maxWidth: "300px",
                        },
                    },
                    artistNames
                )
            )
        );
    };

    const PlayerControls = () => {
        const [value, setValue] = useState(Spicetify.Player.isPlaying());
        let timer;
        useEffect(() => {
            const update = () => setValue(Spicetify.Player.isPlaying());
            Spicetify.Player.addEventListener("onplaypause", update);
            // @ts-ignore
            return () => Spicetify.Player.removeEventListener("onplaypause", update);
        });
        return react.createElement(
            "div",
            { id: "fad-controls" },
            react.createElement(ButtonIcon, {
                // @ts-ignore
                icon: Spicetify.SVGIcons["skip-back"],
                onClick: Spicetify.Player.back,
            }),
            react.createElement(ButtonIcon, {
                // @ts-ignore
                icon: Spicetify.SVGIcons[value ? "pause" : "play"],
                onClick: Spicetify.Player.togglePlay,
            }),
            react.createElement(ButtonIcon, {
                // @ts-ignore
                icon: Spicetify.SVGIcons["skip-forward"],
                onClick: Spicetify.Player.next,
                onMouseEnter: async () => {
                    timer = setTimeout(async () => {
                        let cont = document.createElement("div");
                        cont.id = "fad-upnext-container";
                        let fore = document.querySelector("#fad-details");
                        fore.append(cont);
                        reactDOM.render(await upNext({ index: 0, queue: false }), cont);
                    }, 450);
                },
                onMouseLeave: () => {
                    let cont = document.querySelectorAll("#fad-upnext-container");
                    for (const con of cont) {
                        con.remove();
                    }
                    clearTimeout(timer);
                },
            })
        );
    };

    const ExtraPlayerControls = () => {
        const [isShuffle, setShuffle] = useState(Spicetify.Player.getShuffle());
        const [isRepeat, setRepeat] = useState(Spicetify.Player.getRepeat());
        const [isHeart, setHeart] = useState(Spicetify.Player.getHeart());
        const [isPodcast, setPodcast] = useState(Spicetify.URI.isEpisode(Spicetify.Player.data.item.uri));
        useEffect(() => {
            const update = ({ data }) => {
                data.item.metadata["collection.in_collection"] == "true" ? setHeart(true) : setHeart(false);

                setPodcast(Spicetify.URI.isEpisode(Spicetify.Player.data.item.uri));
                // @ts-ignore
                const state = Spicetify.Player.origin._state;
                if (!state.restrictions?.canToggleShuffle) {
                    setShuffle(undefined);
                }
                if (!state.restrictions?.canToggleRepeatContext || !state.restrictions?.canToggleRepeatTrack) {
                    setRepeat(undefined);
                }
            };
            Spicetify.Player.addEventListener("songchange", update);
            // @ts-ignore
            return () => Spicetify.Player.removeEventListener("songchange", update);
        });
        return react.createElement(
            "div",
            {
                id: "fad-extracontrols",
                style: {
                    marginTop: CONFIG.vertical ? (CONFIG.enableControl ? "-25px" : "10px") : CONFIG.enableControl ? "-25px" : "",
                    width: CONFIG.vertical ? (CONFIG.enableControl ? "100%" : "") : CONFIG.enableControl ? "360px" : "",
                    alignSelf: !CONFIG.vertical && CONFIG.enableControl ? "baseline" : "",
                },
            },
            react.createElement(ButtonIcon, {
                // @ts-ignore
                className: isShuffle
                    ? "dot-after"
                    : // @ts-ignore
                    !Spicetify.Player.origin._state.restrictions?.canToggleShuffle || isShuffle == undefined
                    ? "crossed-out"
                    : "",
                style: {
                    marginLeft: CONFIG.vertical ? "18px" : "",
                },
                // @ts-ignore
                icon: Spicetify.SVGIcons["shuffle"],
                onClick: () => {
                    Spicetify.Player.toggleShuffle();
                    setShuffle(!isShuffle);
                },
            }),
            react.createElement(ButtonIcon, {
                // @ts-ignore
                className: isRepeat
                    ? "dot-after"
                    : // @ts-ignore
                    !Spicetify.Player.origin._state.restrictions?.canToggleRepeatContext ||
                      // @ts-ignore
                      !Spicetify.Player.origin._state.restrictions?.canToggleRepeatTrack ||
                      isRepeat == undefined
                    ? "crossed-out"
                    : "",
                // @ts-ignore
                icon: Spicetify.SVGIcons[isRepeat == 2 ? "repeat-once" : "repeat"],
                onClick: () => {
                    Spicetify.Player.toggleRepeat();
                    setRepeat((isRepeat + 1) % 3);
                },
            }),
            react.createElement(ButtonIcon, {
                // @ts-ignore
                icon: isPodcast
                    ? Spicetify.SVGIcons[isHeart ? "check-alt-fill" : "plus-alt"]
                    : Spicetify.SVGIcons[isHeart ? "heart-active" : "heart"],
                style: {
                    marginLeft: CONFIG.vertical || CONFIG.enableControl ? "auto" : "",
                    marginRight: !CONFIG.vertical && !CONFIG.enableControl ? "10px" : "",
                },
                onClick: () => {
                    Spicetify.Player.toggleHeart();
                    setHeart(!isHeart);
                },
            }),
            CONFIG.enableControl &&
                react.createElement(ButtonIcon, {
                    // @ts-ignore
                    icon: '<path d="M2 2v5l4.33-2.5L2 2zm0 12h14v-1H2v1zm0-4h14V9H2v1zm7-5v1h7V5H9z"></path>',
                    className: "fad-queue-button",
                    // @ts-ignore
                    // @ts-ignore
                    onClick: async (e) => {
                        let ele = document.querySelector(".fad-queue-button");
                        if (ele.classList.contains("dot-after")) {
                            let cont = document.querySelector("#fad-queue-container");
                            cont.remove();
                            ele.classList.remove("dot-after");
                            return;
                        }
                        ele.classList.add("dot-after");

                        let body = document.querySelector("#fad-body");

                        let noticont = document.createElement("div");
                        noticont.className = "main-notificationBubbleContainer-NotificationBubbleContainer";
                        let notitext = document.createElement("div");
                        notitext.className = "main-notificationBubble-NotificationBubble main-notificationBubble-isNotice";
                        notitext.innerText = "Generating queue...";
                        noticont.append(notitext);
                        body.append(noticont);
                        setTimeout(function () {
                            noticont.remove();
                        }, 1000);

                        const next = await upNext({ index: 0, queue: false });
                        const bottom = next.props.style.bottom;
                        const right = next.props.style.right;

                        CONFIG["viewing"] = 0;

                        let tracks = [];
                        for (var i = 0; i < 10; i++) {
                            try {
                                tracks.push(await upNext({ index: i, queue: true }));
                            } catch {
                                break;
                            }
                        }

                        let scroll = react.createElement(
                            "div",
                            {
                                id: "scroll-queue",
                                style: {
                                    bottom: bottom,
                                    right: right,
                                    borderRadius: "10px",
                                    boxShadow: "0 0 8px rgb(0 0 0 / 30%)",
                                },
                                onWheel: (e) => {
                                    var now = Date.now();
                                    if (time !== -1 && now - time < 1000) return;
                                    time = now;

                                    if (e.deltaY > 0) {
                                        if (CONFIG["viewing"] == tracks.length - 1) {
                                            return;
                                        }
                                        CONFIG["viewing"] += 1;
                                        var item = document.querySelector("#scroll-queue").childNodes[CONFIG["viewing"]];
                                        // @ts-ignore
                                        item.style.clipPath = "inset(0px 0px 0px)";
                                    } else {
                                        if (CONFIG["viewing"] == 0) {
                                            return;
                                        }
                                        var item = document.querySelector("#scroll-queue").childNodes[CONFIG["viewing"]];
                                        // @ts-ignore
                                        item.style.clipPath = "inset(90px 0px 0px)";
                                        CONFIG["viewing"] -= 1;
                                    }
                                },
                            },
                            tracks
                        );
                        let fore = document.querySelector("#fad-details");
                        let wrapper = document.createElement("div");
                        wrapper.id = "fad-queue-container";
                        fore.append(wrapper);
                        reactDOM.render(scroll, wrapper);
                    },
                })
        );
    };

    class FAD extends react.Component {
        constructor(props) {
            super(props);

            this.state = {
                title: "",
                artist: "",
                album: "",
                cover: "",
            };
            this.currTrackImg = new Image();
            this.nextTrackImg = new Image();
            this.mousetrap = new Spicetify.Mousetrap();
        }

        async getAlbumDate(uri) {
            const id = uri.replace("spotify:album:", "");
            // const albumInfo = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${id}/desktop`);

            // const albumDate = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0);
            // hermes protocol deprecated 1.1.81 onwards
            const albumInfo = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${id}`);
            const albumDate = new Date(albumInfo.release_date);
            const recentDate = new Date();
            recentDate.setMonth(recentDate.getMonth() - 6);
            return albumDate.toLocaleString("default", albumDate > recentDate ? { year: "numeric", month: "short" } : { year: "numeric" });
        }

        async fetchInfo() {
            const meta = Spicetify.Player.data.item.metadata;
            const prevUri = nextUri;
            nextUri = Spicetify.Player.data.item.uri;
            const uriFinal = nextUri.split(":")[2];
            let isLocalOrEpisode =
                Spicetify.URI.isLocalTrack(Spicetify.Player.data.item.uri) || Spicetify.URI.isEpisode(Spicetify.Player.data.item.uri);

            if (!isLocalOrEpisode) {
                const ximage = await Spicetify.CosmosAsync.get("https://api.spotify.com/v1/tracks/" + uriFinal);
                let images = ximage.album.images;
                for (const image of images) {
                    if (image.height == 640) {
                        finImage = image.url;
                    }
                }
                updateStyle();
            } else {
                finImage = meta.image_xlarge_url;
                style.innerHTML =
                    styleBase +
                    styleChoices[CONFIG.vertical ? 1 : 0] +
                    (window.innerHeight > window.innerWidth && CONFIG.verticalMonitor ? verticalMonitorStyle : "");
            }

            // prepare title
            let rawTitle = meta.title;
            if (CONFIG["trimTitle"] === "justFeat") {
                rawTitle = rawTitle
                    .replace(/-\s+(feat|with|ft).*/i, "")
                    .replace(/(\(|\[)(feat|with|ft)\.?\s+.*(\)|\])/i, "")
                    .trim();
            } else if (CONFIG["trimTitle"] === "trimEvery") {
                rawTitle = rawTitle
                    .replace(/\(.+?\)/g, "")
                    .replace(/\[.+?\]/g, "")
                    .replace(/\s\-\s.+?$/, "")
                    .replace(/,.+?$/, "")
                    .trim();
            }

            // prepare artist
            let artistName;
            if (CONFIG.showAllArtists) {
                artistName = Object.keys(meta)
                    .filter((key) => key.startsWith("artist_name"))
                    .sort()
                    .map((key) => meta[key])
                    .join(", ");
            } else {
                artistName = meta.artist_name;
            }

            // prepare album
            let albumText = meta.album_title || "";
            if (CONFIG.showAlbum) {
                const albumURI = meta.album_uri;
                if (albumURI?.startsWith("spotify:album:")) {
                    albumText += " • " + (await this.getAlbumDate(albumURI));
                }
            }

            //          if (meta.image_xlarge_url === this.currTrackImg.src) {
            if (finImage === this.currTrackImg.src) {
                this.setState({
                    title: rawTitle || "",
                    artist: artistName || "",
                    album: albumText || "",
                });
                if (CONFIG["optionBackground"] === "colorText" && !isLocalOrEpisode) {
                    this.animateCanvasColor(prevUri, prevUri);
                } else if (CONFIG["optionBackground"] === "static" && !isLocalOrEpisode) {
                    this.animateCanvasColor(prevUri, prevUri, true);
                } else if (CONFIG["optionBackground"] === "albumart") {
                    this.animateCanvas(this.currTrackImg, this.currTrackImg);
                }
                return;
            }

            if (isHidden) {
                isHidden = false;
                updateStyle();
            }

            // TODO: Pre-load next track
            // Wait until next track image is downloaded then update UI text and images
            const previousImg = this.currTrackImg.cloneNode();
            this.currTrackImg.src = finImage;
            this.currTrackImg.onload = () => {
                const bgImage = this.currTrackImg.src;
                if (CONFIG["optionBackground"] === "colorText" && !isLocalOrEpisode) {
                    this.animateCanvasColor(prevUri, nextUri);
                } else if (CONFIG["optionBackground"] === "static" && !isLocalOrEpisode) {
                    this.animateCanvasColor(prevUri, nextUri, true);
                } else if (CONFIG["optionBackground"] === "albumart") {
                    this.animateCanvas(previousImg, this.currTrackImg);
                }
                if (CONFIG.enableFade) {
                    this.deets.style.animation = "";
                    void this.deets.offsetWidth;
                    this.deets.style.animation = "textchange 1s forwards";
                }
                this.setState({
                    title: rawTitle || "",
                    artist: artistName || "",
                    album: albumText || "",
                    cover: bgImage,
                });

                if (CONFIG.lyricsPlus) {
                    autoHideLyrics();
                }
            };
            this.currTrackImg.onerror = () => {
                // Placeholder
                this.currTrackImg.src =
                    "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCI+CiAgPHJlY3Qgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiB4PSIwIiB5PSIwIiAvPgogIDxwYXRoIGZpbGw9IiNCM0IzQjMiIGQ9Ik0yNi4yNSAxNi4xNjJMMjEuMDA1IDEzLjEzNEwyMS4wMTIgMjIuNTA2QzIwLjU5NCAyMi4xOTIgMjAuMDgxIDIxLjk5OSAxOS41MTkgMjEuOTk5QzE4LjE0MSAyMS45OTkgMTcuMDE5IDIzLjEyMSAxNy4wMTkgMjQuNDk5QzE3LjAxOSAyNS44NzggMTguMTQxIDI2Ljk5OSAxOS41MTkgMjYuOTk5QzIwLjg5NyAyNi45OTkgMjIuMDE5IDI1Ljg3OCAyMi4wMTkgMjQuNDk5QzIyLjAxOSAyNC40MjIgMjIuMDA2IDE0Ljg2NyAyMi4wMDYgMTQuODY3TDI1Ljc1IDE3LjAyOUwyNi4yNSAxNi4xNjJaTTE5LjUxOSAyNS45OThDMTguNjkyIDI1Ljk5OCAxOC4wMTkgMjUuMzI1IDE4LjAxOSAyNC40OThDMTguMDE5IDIzLjY3MSAxOC42OTIgMjIuOTk4IDE5LjUxOSAyMi45OThDMjAuMzQ2IDIyLjk5OCAyMS4wMTkgMjMuNjcxIDIxLjAxOSAyNC40OThDMjEuMDE5IDI1LjMyNSAyMC4zNDYgMjUuOTk4IDE5LjUxOSAyNS45OThaIi8+Cjwvc3ZnPgo=";
            };
        }

        animateCanvas(prevImg, nextImg) {
            const { innerWidth: width, innerHeight: height } = window;
            this.back.width = width;
            this.back.height = height;
            const dim = width > height ? width : height;

            this.deets.style.filter = "invert(0)";
            if (!(CONFIG["volumeBar"] === "disable")) {
                document.querySelector("#fad-volume").style.filter = "invert(0)";
            }

            if (CONFIG.lyricsPlus) {
                this.lyrics.style.setProperty("--lyrics-color-active", "#ffffff");
                this.lyrics.style.setProperty("--lyrics-color-inactive", "#ffffff50");
            }

            const ctx = this.back.getContext("2d");
            ctx.imageSmoothingEnabled = false;
            ctx.filter = `blur(30px) brightness(0.6)`;
            const blur = 30;

            if (!CONFIG.enableFade) {
                ctx.globalAlpha = 1;
                width > height
                    ? ctx.drawImage(nextImg, -blur * 2, -blur * 2 - (width - height) / 2, dim + 4 * blur, dim + 4 * blur)
                    : ctx.drawImage(nextImg, -blur * 2 - (height - width) / 2, -blur * 2, dim + 4 * blur, dim + 4 * blur);
                return;
            }

            let factor = 0.0;
            const animate = () => {
                ctx.globalAlpha = 1;
                width > height
                    ? ctx.drawImage(prevImg, -blur * 2, -blur * 2 - (width - height) / 2, dim + 4 * blur, dim + 4 * blur)
                    : ctx.drawImage(prevImg, -blur * 2 - (height - width) / 2, -blur * 2, dim + 4 * blur, dim + 4 * blur);
                ctx.globalAlpha = Math.sin((Math.PI / 2) * factor);
                width > height
                    ? ctx.drawImage(nextImg, -blur * 2, -blur * 2 - (width - height) / 2, dim + 4 * blur, dim + 4 * blur)
                    : ctx.drawImage(nextImg, -blur * 2 - (height - width) / 2, -blur * 2, dim + 4 * blur, dim + 4 * blur);

                if (factor < 1.0) {
                    factor += 0.016;
                    requestAnimationFrame(animate);
                }
            };

            requestAnimationFrame(animate);
        }

        async animateCanvasColor(prevUri, nextUri, isStatic = false) {
            const { innerWidth: width, innerHeight: height } = window;
            const ctx = this.back.getContext("2d");

            if (isStatic) {
                if (ctx.fillStyle == CONFIG["staticColor"]) {
                    return;
                } else {
                    ctx.filter = "brightness(1)";
                    ctx.imageSmoothingEnabled = false;
                    ctx.globalAlpha = 1;
                    ctx.fillStyle = CONFIG["staticColor"];
                    ctx.fillRect(0, 0, width, height);
                    return;
                }
            }

            prevColor = await fetchColors(prevUri);
            nextColor = await fetchColors(nextUri);

            this.back.width = width;
            this.back.height = height;

            CONFIG["color"] = nextColor;
            saveConfig();

            this.deets.style.filter = "invert(0)";

            if (!(CONFIG["volumeBar"] === "disable")) {
                document.querySelector("#fad-volume").style.filter = "invert(0)";
            }

            if (CONFIG.lyricsPlus) {
                this.lyrics.style.setProperty("--lyrics-color-active", "#ffffff");
                this.lyrics.style.setProperty("--lyrics-color-inactive", "#ffffff50");
            }

            prevColor = prevColor[CONFIG["colorChoice"]];
            nextColor = nextColor[CONFIG["colorChoice"]];
            const luma =
                parseInt(nextColor.substring(1, 3), 16) * 0.2126 +
                parseInt(nextColor.substring(3, 5), 16) * 0.7152 +
                parseInt(nextColor.substring(5, 7), 16) * 0.0722;

            console.log(nextColor);
            if (luma > 180) {
                this.deets.style.filter = "invert(1)";

                if (!(CONFIG["volumeBar"] === "disable")) {
                    document.querySelector("#fad-volume").style.filter = "invert(1)";
                }
                if (CONFIG.lyricsPlus) {
                    this.lyrics.style.setProperty("--lyrics-color-active", "#000000");
                    this.lyrics.style.setProperty("--lyrics-color-inactive", "#00000050");
                }
            }

            if (!CONFIG.enableFade) {
                ctx.globalAlpha = 1;
                ctx.fillStyle = nextColor;
                ctx.fillRect(0, 0, width, height);
                return;
            }

            let factor = 0.0;
            const animate = () => {
                ctx.globalAlpha = 1;
                ctx.fillStyle = prevColor;
                ctx.fillRect(0, 0, width, height);
                ctx.globalAlpha = Math.sin((Math.PI / 2) * factor);
                ctx.fillStyle = nextColor;
                ctx.fillRect(0, 0, width, height);

                if (factor < 1.0) {
                    factor += 0.016;
                    requestAnimationFrame(animate);
                }
            };
            requestAnimationFrame(animate);
        }

        componentDidMount() {
            this.updateInfo = this.fetchInfo.bind(this);
            Spicetify.Player.addEventListener("songchange", this.updateInfo);
            this.updateInfo();

            updateVisual = () => {
                updateStyle();
                this.fetchInfo();
            };

            this.onQueueChange = async (queue) => {
                queue = queue.data;
                let nextTrack;
                if (queue.queued.length) {
                    nextTrack = queue.queued[0];
                } else {
                    nextTrack = queue.nextUp[0];
                }
                this.nextTrackImg.src = nextTrack.metadata.image_xlarge_url;
            };

            const scaleLimit = { min: 0.1, max: 4, step: 0.05 };
            this.onScaleChange = (event) => {
                if (!event.ctrlKey) return;
                let dir = event.deltaY < 0 ? 1 : -1;
                let temp = (CONFIG["scale"] || 1) + dir * scaleLimit.step;
                if (temp < scaleLimit.min) {
                    temp = scaleLimit.min;
                } else if (temp > scaleLimit.max) {
                    temp = scaleLimit.max;
                }
                CONFIG["scale"] = temp;
                saveConfig();
                updateVisual();
            };

            Spicetify.Platform.PlayerAPI._events.addListener("queue_update", this.onQueueChange);
            this.mousetrap.bind("esc", deactivate);
            window.dispatchEvent(new Event("fad-request"));
        }

        componentWillUnmount() {
            Spicetify.Player.removeEventListener("songchange", this.updateInfo);
            Spicetify.Platform.PlayerAPI._events.removeListener("queue_update", this.onQueueChange);
            this.mousetrap.unbind("esc");
        }

        render() {
            return react.createElement(
                "div",
                {
                    id: "full-app-display",
                    className: "Video VideoPlayer--fullscreen VideoPlayer--landscape",
                    onDoubleClick: deactivate,
                    onContextMenu: openConfig,
                },
                !(CONFIG["optionBackground"] === "grad") &&
                    react.createElement("canvas", {
                        id: "fad-background",
                        ref: (el) => (this.back = el),
                    }),
                CONFIG["optionBackground"] === "grad" &&
                    react.createElement(
                        "div",
                        { id: "fad-gradient-background" },
                        react.createElement("img", {
                            src: this.state.cover,
                            className: "fad-grad-image",
                            style: {
                                right: "-15%",
                                top: "-20%",
                                zIndex: 10,
                                transform: "scale(2)",
                            },
                        }),
                        react.createElement("img", {
                            src: this.state.cover,
                            className: "fad-grad-image",
                            style: {
                                left: "-5%",
                                bottom: "-10%",
                                transform: "scale(1.5)",
                                zIndex: 1,
                                animationDirection: "reverse",
                            },
                        }),
                        react.createElement("img", {
                            src: this.state.cover,
                            className: "fad-grad-image",
                            style: {
                                width: "200%",
                                right: "-50%",
                                top: "-33%",
                                filter: "blur(69px) brightness(0.6)",
                                zIndex: 0,
                                animationDirection: "reverse",
                            },
                        })
                    ),
                react.createElement("div", { id: "fad-header" }),
                react.createElement(
                    "div",
                    { id: "fad-body" },
                    react.createElement(
                        "div",
                        {
                            id: "fad-foreground",
                            style: {
                                "--fad-scale": CONFIG["scale"] || 1,
                                zIndex: 20,
                            },
                            ref: (el) => {
                                if (!el) return;
                                el.onmousewheel = this.onScaleChange;
                            },
                        },
                        react.createElement(
                            "div",
                            { id: "fad-art" },
                            react.createElement(
                                "div",
                                {
                                    id: "fad-art-image",
                                    className: CONFIG.enableFade && "fad-background-fade",
                                    style: {
                                        backgroundImage: `url("${this.state.cover}")`,
                                    },
                                },
                                react.createElement("div", { id: "fad-art-inner" })
                            )
                        ),
                        react.createElement(
                            "div",
                            { id: "fad-details", ref: (el) => (this.deets = el) },
                            react.createElement("div", { id: "fad-title" }, this.state.title),
                            react.createElement(SubInfo, {
                                id: "fad-artist",
                                text: this.state.artist,
                                // @ts-ignore
                                icon: Spicetify.SVGIcons.artist,
                            }),
                            CONFIG.showAlbum &&
                                react.createElement(SubInfo, {
                                    id: "fad-album",
                                    text: this.state.album,
                                    // @ts-ignore
                                    icon: Spicetify.SVGIcons.album,
                                }),
                            react.createElement(
                                "div",
                                {
                                    id: "fad-status",
                                    className: (CONFIG.enableControl || CONFIG.enableProgress) && "active",
                                    style: {
                                        flexDirection: !CONFIG.vertical && CONFIG.enableControl && CONFIG.enableExtraControl ? "column" : "",
                                    },
                                },
                                CONFIG.enableControl && react.createElement(PlayerControls),
                                CONFIG.enableExtraControl && react.createElement(ExtraPlayerControls),
                                CONFIG.enableProgress && react.createElement(ProgressBar)
                            )
                        )
                    ),
                    // @ts-ignore
                    !(CONFIG["volumeBar"] === "disable") && react.createElement(VolumeBar),
                    CONFIG.lyricsPlus &&
                        react.createElement("div", {
                            id: "fad-lyrics-plus-container",
                            style: {
                                "--lyrics-color-active": "#ffffff",
                                "--lyrics-color-inactive": "#ffffff50",
                            },
                            ref: (el) => (this.lyrics = el),
                        })
                )
            );
        }
    }

    const classes = ["video", "video-full-screen", "video-full-window", "video-full-screen--hide-ui", "fad-activated"];

    const container = document.createElement("div");
    container.id = "fad-main";
    let lastApp;

    async function toggleFullscreen() {
        if (CONFIG.enableFullscreen) {
            await document.documentElement.requestFullscreen();
            // @ts-ignore
        } else if (document.webkitIsFullScreen) {
            await document.exitFullscreen();
        }
    }

    async function activate() {
        await toggleFullscreen();

        document.body.classList.add(...classes);
        document.body.append(style, container);
        reactDOM.render(react.createElement(FAD), container);

        requestLyricsPlus();
    }

    function deactivate() {
        // @ts-ignore
        if (CONFIG.enableFullscreen || document.webkitIsFullScreen) {
            document.exitFullscreen();
        }
        document.body.classList.remove(...classes);
        reactDOM.unmountComponentAtNode(container);
        style.remove();
        container.remove();
        window.dispatchEvent(new Event("fad-request"));

        if (lastApp && lastApp !== "/lyrics-plus") {
            Spicetify.Platform.History.push(lastApp);
        }
    }

    function toggleFad() {
        if (document.body.classList.contains("fad-activated")) {
            deactivate();
        } else {
            activate();
        }
    }

    function updateStyle() {
        style.innerHTML =
            styleBase +
            styleChoices[CONFIG.vertical ? 1 : 0] +
            (checkLyricsPlus() && CONFIG.lyricsPlus && !isHidden
                ? lyricsPlusBase +
                  lyricsPlusStyleChoices[CONFIG.vertical ? 1 : 0] +
                  (window.innerHeight > window.innerWidth && CONFIG.verticalMonitor ? verticalMonitorStyle : "")
                : "");
    }

    function checkLyricsPlus() {
        return Spicetify.Config?.custom_apps?.includes("lyrics-plus") || !!document.querySelector("a[href='/lyrics-plus']");
    }

    function autoHideLyrics() {
        // @ts-ignore
        if (!document.querySelector("#fad-lyrics-plus-container").innerText) {
            setTimeout(autoHideLyrics, 100);
        } else {
            // @ts-ignore
            if (document.querySelector("#fad-lyrics-plus-container").innerText == "(• _ • )") {
                isHidden = true;
                updateStyle();
            }
        }
    }

    function requestLyricsPlus() {
        if (CONFIG.lyricsPlus && checkLyricsPlus()) {
            lastApp = Spicetify.Platform.History.location.pathname;
            if (lastApp !== "/lyrics-plus") {
                Spicetify.Platform.History.push("/lyrics-plus");
            }
        }
        window.dispatchEvent(new Event("fad-request"));
        autoHideLyrics();
    }

    function getConfig() {
        try {
            const parsed = JSON.parse(Spicetify.LocalStorage.get("full-app-display-config") || "{}");
            if (parsed && typeof parsed === "object") {
                return parsed;
            }
            throw "";
        } catch {
            Spicetify.LocalStorage.set("full-app-display-config", "{}");
            return {};
        }
    }

    function saveConfig() {
        Spicetify.LocalStorage.set("full-app-display-config", JSON.stringify(CONFIG));
    }

    const ConfigItem = ({ name, field, func, disabled = false }) => {
        const [value, setValue] = useState(CONFIG[field]);
        return react.createElement(
            "div",
            { className: "setting-row" },
            react.createElement("label", { className: "col description" }, name),
            react.createElement(
                "div",
                { className: "col action" },
                react.createElement(
                    "button",
                    {
                        className: "switch" + (value ? "" : " disabled"),
                        disabled,
                        onClick: () => {
                            const state = !value;
                            CONFIG[field] = state;
                            setValue(state);
                            saveConfig();
                            func();
                        },
                    },
                    // @ts-ignore
                    react.createElement(DisplayIcon, { icon: Spicetify.SVGIcons.check, size: 16 })
                )
            )
        );
    };

    const ConfigSelection = ({ name, field, options, def, func }) => {
        const [value, setValue] = useState(CONFIG[field] ?? def);
        return react.createElement(
            "div",
            { className: "setting-row" },
            react.createElement("label", { className: "col description" }, name),
            react.createElement(
                "div",
                { className: "col action" },
                react.createElement(
                    "select",
                    {
                        value,
                        onChange: (e) => {
                            setValue(e.target.value);
                            CONFIG[field] = e.target.value;
                            saveConfig();
                            func();
                        },
                    },
                    Object.keys(options).map((item) =>
                        react.createElement(
                            "option",
                            {
                                value: item,
                            },
                            options[item]
                        )
                    )
                )
            )
        );
    };

    const ConfigInput = ({ name, field, func, isColor = false }) => {
        const [value, setValue] = useState(CONFIG[field]);
        return react.createElement(
            "div",
            { className: "setting-row" },
            react.createElement("label", { className: "col description" }, name),
            react.createElement(
                "div",
                { className: "col action" },
                react.createElement("input", {
                    type: isColor ? "color" : "",
                    value,
                    className: "input",
                    onChange: (e) => {
                        setValue(e.target.value);
                        CONFIG[field] = e.target.value;
                        saveConfig();
                        func();
                    },
                })
            )
        );
    };

    const ConfigHotkey = ({ name, field, def, onChange = () => {} }) => {
        const [value, setValue] = useState(CONFIG[field] ?? def);
        const [trap] = useState(new Spicetify.Mousetrap());

        function record() {
            trap.handleKey = (character, modifiers, e) => {
                if (e.type == "keydown") {
                    const sequence = [...new Set([...modifiers, character])];
                    if (sequence.length === 1 && sequence[0] === "esc") {
                        onChange("");
                        setValue("");
                        return;
                    }
                    setValue(sequence.join("+"));
                }
            };
        }

        function finishRecord() {
            trap.handleKey = () => {};
            onChange(value);
        }

        return react.createElement(
            "div",
            {
                className: "setting-row",
            },
            react.createElement(
                "label",
                {
                    className: "col description",
                },
                name
            ),
            react.createElement(
                "div",
                {
                    className: "col action",
                },
                react.createElement("input", {
                    value,
                    onFocus: record,
                    onBlur: finishRecord,
                })
            )
        );
    };

    const colorRow = ({ name, color }) => {
        let originalColor;
        const modal = document.getElementsByTagName("generic-modal");
        return react.createElement(
            "div",
            { className: "color-row" },
            react.createElement("label", { className: "col description" }, name),
            react.createElement(
                "div",
                { className: "col action" },
                react.createElement("div", {
                    className: "col color",
                    style: {
                        height: "20px",
                        width: "20px",
                        border: "2px solid black",
                        clear: "both",
                        backgroundColor: CONFIG["color"][color],
                    },
                    // @ts-ignore
                    // @ts-ignore
                    onMouseEnter: (e) => {
                        originalColor = CONFIG["colorChoice"];
                        CONFIG["colorChoice"] = color;
                        // @ts-ignore
                        modal[0].style.opacity = 0.37;
                        updateVisual();
                    },
                    // @ts-ignore
                    // @ts-ignore
                    onMouseLeave: (e) => {
                        CONFIG["colorChoice"] = originalColor;
                        // @ts-ignore
                        modal[0].style.opacity = 1;
                        updateVisual();
                    },
                    // @ts-ignore
                    // @ts-ignore
                    onClick: (e) => {
                        CONFIG["colorChoice"] = color;
                        updateVisual();
                        // @ts-ignore
                        modal[0].style.opacity = 1;
                        Spicetify.PopupModal.hide();
                    },
                })
            )
        );
    };

    function openColor(event) {
        event.preventDefault();
        const style = react.createElement("style", {
            dangerouslySetInnerHTML: {
                __html: `
.color-row::after {
    content: "";
    display: table;
    clear: both;
}
.color-row .col {
    display: flex;
    padding: 10px 0;
    align-items: center;
}
.color-row .col.description {
    float: left;
    padding-right: 15px;
}
.color-row .col.action {
    float: right;
    text-align: right;
}
`,
            },
        });
        let colorContainer = react.createElement(
            "div",
            null,
            style,
            react.createElement(colorRow, { name: "Dark Vibrant", color: "DARK_VIBRANT" }),
            react.createElement(colorRow, { name: "Desaturated", color: "DESATURATED" }),
            react.createElement(colorRow, { name: "Light Vibrant", color: "LIGHT_VIBRANT" }),
            react.createElement(colorRow, { name: "Vibrant", color: "VIBRANT" }),
            react.createElement(colorRow, { name: "Vibrant(NA)", color: "VIBRANT_NON_ALARMING" })
        );
        Spicetify.PopupModal.display({
            title: "Color Display (Hover to preview)",
            content: colorContainer,
        });
    }

    function openConfig(event) {
        try {
            event.preventDefault();
        } catch {}
        const style = react.createElement("style", {
            dangerouslySetInnerHTML: {
                __html: `
.setting-row::after {
    content: "";
    display: table;
    clear: both;
}
.setting-row .col {
    display: flex;
    padding: 10px 0;
    align-items: center;
}
.setting-row .col.description {
    float: left;
    padding-right: 15px;
}
.setting-row .col.action {
    float: right;
    text-align: right;
}
.setting-row .col.action input {
    padding-left: 10px;
}
button.switch {
    align-items: center;
    border: 0px;
    border-radius: 50%;
    background-color: rgba(var(--spice-rgb-shadow), .7);
    color: var(--spice-text);
    cursor: pointer;
    display: flex;
    margin-inline-start: 12px;
    padding: 8px;
}
button.switch.disabled,
button.switch[disabled] {
    color: rgba(var(--spice-rgb-text), .3);
}
select {
    color: var(--spice-text);
    background: rgba(var(--spice-rgb-shadow), .7);
    border: 0;
    height: 32px;
}
`,
            },
        });
        let configContainer = react.createElement(
            "div",
            null,
            style,
            react.createElement(ConfigItem, {
                name: checkLyricsPlus() ? "Enable Lyrics Plus integration" : "Unable to find Lyrics Plus",
                field: "lyricsPlus",
                func: () => {
                    updateVisual();
                    requestLyricsPlus();
                    openConfig();
                },
                disabled: !checkLyricsPlus(),
            }),
            react.createElement(ConfigSelection, {
                name: "Background",
                field: "optionBackground",
                options: {
                    albumart: "Album Art",
                    colorText: "Colorful background",
                    static: "Static Color",
                    // grad: "Gradient",
                },
                func: updateVisual,
            }),
            CONFIG["optionBackground"] == "static" &&
                react.createElement(ConfigInput, {
                    name: "Select static color:",
                    field: "staticColor",
                    func: () => {
                        const ctx = document.getElementById("fad-background")?.getContext("2d");
                        ctx.filter = "brightness(1)";
                        ctx.imageSmoothingEnabled = false;
                        ctx.globalAlpha = 1;
                        ctx.fillStyle = CONFIG["staticColor"];
                        ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
                    },
                    isColor: true,
                }),
            react.createElement(ConfigItem, { name: "Enable progress bar", field: "enableProgress", func: updateVisual }),
            react.createElement(ConfigSelection, {
                name: "Enable volume bar",
                field: "volumeBar",
                options: {
                    disable: "Disable",
                    onlyHover: "Show on hover only",
                    alwaysOn: "Show always",
                },
                func: updateVisual,
            }),
            react.createElement(ConfigItem, { name: "Enable controls", field: "enableControl", func: updateVisual }),
            react.createElement(ConfigItem, { name: "Enable extra controls", field: "enableExtraControl", func: updateVisual }),
            react.createElement(ConfigSelection, {
                name: "Trim title",
                field: "trimTitle",
                options: {
                    dontTrim: "Don't trim title",
                    justFeat: "Trim just feat. and with ",
                    trimEvery: "Trim Everything",
                },
                func: updateVisual,
            }),
            react.createElement(ConfigItem, { name: "Show album", field: "showAlbum", func: updateVisual }),
            react.createElement(ConfigItem, { name: "Show all artists", field: "showAllArtists", func: updateVisual }),
            react.createElement(ConfigItem, { name: "Show icons", field: "icons", func: updateVisual }),
            react.createElement(ConfigItem, { name: "Vertical mode", field: "vertical", func: updateVisual }),
            CONFIG.lyricsPlus &&
                window.innerHeight > window.innerWidth &&
                react.createElement(ConfigItem, { name: "Vertical Monitor Mode", field: "verticalMonitor", func: updateStyle }),
            react.createElement(ConfigItem, { name: "Enable fullscreen", field: "enableFullscreen", func: toggleFullscreen }),
            react.createElement(ConfigItem, { name: "Enable song change animation", field: "enableFade", func: updateVisual }),
            react.createElement(ConfigHotkey, {
                name: "FAD hotkey: ",
                field: "hotkey",
                def: "alt+f",
                onChange: (key) => {
                    CONFIG["hotkey"] = key;
                    saveConfig();
                    Spicetify.Mousetrap.bind(key, toggleFad);
                },
            }),
            react.createElement(ConfigItem, { name: "Enable development features", field: "enableDev", func: openConfig }),

            CONFIG.enableDev &&
                react.createElement(ConfigSelection, {
                    name: "Color Choice (Press F6 for colors)",
                    field: "colorChoice",
                    options: {
                        DARK_VIBRANT: "Dark Vibrant",
                        DESATURATED: "Desaturated",
                        LIGHT_VIBRANT: "Light Vibrant",
                        VIBRANT: "Vibrant",
                        VIBRANT_NON_ALARMING: "Vibrant(NA)",
                    },
                    def: "LIGHT_VIBRANT",
                    func: updateVisual,
                })
        );
        Spicetify.PopupModal.display({
            title: "Full App Display",
            content: configContainer,
        });
    }

    // Add activator on top bar
    new Spicetify.Topbar.Button(
        "Full App Display",
        // @ts-ignore
        `<svg role="img" height="16" width="16" viewBox="0 0 16 16" fill="currentColor">${Spicetify.SVGIcons.projector}</svg>`,
        activate
    );

    Spicetify.Mousetrap.bind(CONFIG["hotkey"] ?? "alt+f", toggleFad);
    Spicetify.Mousetrap.bind("f6", openColor);
})();


================================================
FILE: goToSong/README.md
================================================
# Go to Song
Filename : `goToSong.js`

Go to the currrently playing song in a playlist **/or/** currently playing playlist.

## To use:

* Currently playing playlist: Go to Profile > GoToSong > Choose "Go To Song in current Playlist" 

![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview1.jpg)

* Any Playlist: Right Click on the Playlist, and choose "Go to Currently Playing Song"

![Preview2](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview2.jpg)

## Note:
You may need to adjust your delay if it's giving an error, follow the instructions in the popup.

![Adjust](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/adjust.jpg)

## More
🌟 Like it? Gimme some love!    
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)



================================================
FILE: goToSong/goToSong.js
================================================
// @ts-check
// NAME: goToSong
// AUTHOR: huhridge
// DESCRIPTION: Go to currently playing song in playlists.
/// <reference path="../globals.d.ts" />

(function goToSong(){
    const { Player, Menu, LocalStorage, Platform, ContextMenu, URI, React: react, ReactDOM: reactDOM} = Spicetify
    let tracks, index, playlisturi, curruri;

    if (!(Player && Menu && LocalStorage && Platform)) {
        setTimeout(goToSong, 1000)
        return
    }

    function delay(delayInms) {
        return new Promise(resolve => {
        setTimeout(() => {
            resolve(2);
        }, delayInms);
        });
    }

    if (!Spicetify.LocalStorage.get("goToDelay")){
        Spicetify.LocalStorage.set("goToDelay", '200')
    }

    const ConfigInput = ({ name, lkey}) => {
        const [value, setValue] = react.useState(Spicetify.LocalStorage.get("goToDelay"));

        const setValueCallback = react.useCallback(
            (event) => {
                const value = event.target.value;
                setValue(value);
                Spicetify.LocalStorage.set(lkey, value)
            },
            [value]
        );
    
        return react.createElement(
            "div",
            {
                className: "setting-row",
            },
            react.createElement(
                "label",
                {
                    className: "col description",
                },
                name
            ),
            react.createElement(
                "div",
                {
                    className: "col action",
                },
                react.createElement("input", {
                    type: "number",
                    value,
                    onChange: setValueCallback,
                })
            )
        );
    };

    function shouldDisplayGoTo(uris) {
        if (uris.length > 1) {
            return false;
        }

        const uri = uris[0];
        const uriObj = Spicetify.URI.fromString(uri);
        if (uriObj.type == Spicetify.URI.Type.PLAYLIST || uriObj.type == Spicetify.URI.Type.PLAYLIST_V2) {
            return true;
        }
        return false;
    }

    async function scrollSong(playlisturi) {
        tracks = await Spicetify.Platform.PlaylistAPI.getContents(playlisturi)
        curruri = Spicetify.Player.data.item.uri
        for (var i=0; i < tracks.items.length; i++){
            if (tracks.items[i].uri == curruri){
                break;
            }
        }
        if (i == tracks.items.length){
            Spicetify.showNotification("Song not in Playlist.")
            return;
        }
        const playlisturl = '/playlist/' + playlisturi.split(':')[2]
        if (!(Spicetify.Platform.History.location.pathname == playlisturl)){
            Spicetify.Platform.History.push(playlisturl)
            await delay(1000)
        }
        if ((i+2)<58 && i == (tracks.items.length-1)){
            document.querySelector(`[aria-rowindex="${i+1}"]`).click()
            await delay(1)
            document.querySelector(`[aria-rowindex="${i+2}"]`).click()
            return;
        }
        if ((i+2)<58){
            document.querySelector(`[aria-rowindex="${i+2}"]`).click()
        }
        else{
            try {
                for (var j=57; j < tracks.items.length; j += 28){
                    document.querySelector(`[aria-rowindex="${j}"]`).click()
                    const delayms = Spicetify.LocalStorage.get('goToDelay')
                    await delay(Number(delayms))
                    if (Math.abs(j-(i+2)) < 28){
                        break
                    }
                }
                document.querySelector(`[aria-rowindex="${i+2}"]`).click()
            }
            catch(err){
                Spicetify.showNotification(err + '    Try to Adjust your delay in Profile > GoToSong > Set Delay')
            }

        }
    }

    async function gotoCurrPlay() { 
        if (Spicetify.Player.data.context_uri.startsWith('spotify:playlist:')){
            playlisturi = Spicetify.Player.data.context_uri
            await scrollSong(playlisturi)
        }
        else {
            Spicetify.showNotification('The song currently played is not part of a playlist.')            
        }
    }

    async function gotoselectedPlay(uris){    
        await scrollSong(uris[0])
    }

    let configContent = react.createElement(ConfigInput, {name: "Set Delay(in ms) (200 default)", lkey: "goToDelay"})


    const goTocurrPlay = new Spicetify.Menu.Item(
        "Go To Song in Playlist",
        false,
        gotoCurrPlay,
    )

    const goToConfig = new Spicetify.Menu.Item("Set Delay", false, () =>{
        Spicetify.PopupModal.display({
            title: "Set Delay",
            content: configContent,
        })
    })

    new Spicetify.Menu.SubMenu("GoToSong", [goTocurrPlay,goToConfig]).register();
    
    new Spicetify.ContextMenu.Item("Go To Currently Playing Song", gotoselectedPlay, shouldDisplayGoTo).register();

})();



================================================
FILE: listPlaylistsWithSong/README.md
================================================
# List Playlists with Song
Filename : `listPlaylistsWithSong.js`

Adds context menu button to view playlists in your library that contain the selected song.

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview.gif)

## To use:

Right Click on selected song, and click "List playlists with this Song".

![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview1.jpg)


### Note:
~~Currently, doesn't work on the currently playing song(like in the bottom bar), finding a workaround.~~
Now it shows up, fixed by your truly!


## More
🌟 Like it? Gimme some love!    
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)


================================================
FILE: listPlaylistsWithSong/listPlaylistsWithSong.js
================================================
//@ts-check
// NAME: ListPlaylistsWithSong
// AUTHOR: huhridge (based on elijaholmos's version)
// DESCRIPTION: Adds context menu button to view playlists in your library that contain the selected song
/// <reference path="../globals.d.ts" />

(async function listPlaylistsWithSong() {
    const { Player, Menu, LocalStorage, Platform, React: react, ReactDOM: reactDOM } = Spicetify;

    if (!(Player && Menu && LocalStorage && Platform)) {
        setTimeout(listPlaylistsWithSong, 1000);
        return;
    }

    function delay(delayInms) {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(2);
            }, delayInms);
        });
    }

    // const user = await Spicetify.Platform.UserAPI.getUser()

    async function recursivePlaylistFolder(folder) {
        //to get every playlist no matter how deep, thanks to elijaholmos for reminding me, else i would have forgotten it.
        let playlists = [];
        for (const playlist of folder) {
            if (playlist.type == "playlist") {
                if ((playlist.isCollaborative || playlist.isOwnedBySelf || playlist.canAdd) && playlist.totalLength > 0) {
                    let image;
                    try {
                        image = !playlist.images[0]
                            ? (await Spicetify.Platform.PlaylistAPI.getMetadata(playlist.uri)).images[0].url
                            : playlist.images[0].url;
                    } catch {
                        image = "";
                    }
                    playlists.push({
                        uri: playlist.uri,
                        title: playlist.name,
                        desc: playlist.description,
                        isCollab: playlist.isCollaborative || playlist.canAdd,
                        noOfSongs: playlist.totalLength,
                        created: playlist.addedAt.toLocaleString("default", { year: "numeric", month: "short", day: "numeric" }),
                        image: image,
                    });
                }
            } else if (playlist.type == "folder") {
                playlists.push(...(await recursivePlaylistFolder(playlist.items)));
            }
        }
        return playlists;
    }

    async function getUserLibrary() {
        let playlistsToCheck = Array();
        const userContents = await Spicetify.Platform.RootlistAPI.getContents();
        for (const playlist of userContents.items) {
            if (playlist.type == "playlist") {
                if ((playlist.isCollaborative || playlist.isOwnedBySelf || playlist.canAdd) && playlist.totalLength > 0) {
                    let image;
                    try {
                        image = !playlist.images[0]
                            ? (await Spicetify.Platform.PlaylistAPI.getMetadata(playlist.uri)).images[0].url
                            : playlist.images[0].url;
                    } catch {
                        image = "";
                    }
                    playlistsToCheck.push({
                        uri: playlist.uri,
                        title: playlist.name,
                        desc: playlist.description,
                        isCollab: playlist.isCollaborative || playlist.canAdd,
                        noOfSongs: playlist.totalLength,
                        created: playlist.addedAt.toLocaleString("default", { year: "numeric", month: "short", day: "numeric" }),
                        image: image,
                    });
                }
            } else if (playlist.type == "folder") {
                playlistsToCheck.push(...(await recursivePlaylistFolder(playlist.items)));
            }
        }
        return playlistsToCheck;
    }

    async function checkPlaylist(playlist, songUri) {
        var songFound = false;
        let addedAtDate;
        const tracks = await Spicetify.Platform.PlaylistAPI.getContents(playlist.uri);
        for (var i = 0; i < tracks.items.length; i++) {
            if (tracks.items[i].uri == songUri) {
                songFound = true;
                addedAtDate = new Date(tracks.items[i].addedAt).toLocaleString("default", { year: "numeric", month: "short", day: "numeric" });
                break;
            }
        }
        if (songFound) {
            playlist.index = i + 1;
            playlist.songAddedAt = addedAtDate;
            return playlist;
        } else {
            return false;
        }
    }

    const playlistCard = ({ playlist }) => {
        let isDesc = false;
        if (playlist.desc) {
            isDesc = true;
        }
        return react.createElement(
            "div",
            {
                className: "contentSpacing main-entityHeader-container main-entityHeader-nonWrapped main-trackList-trackListHeaderRow",
                style: {
                    minHeight: "280px",
                    marginLeft: "2%",
                    marginRight: "2%",
                    justifyContent: "left",
                },
            },
            react.createElement(
                "div",
                { className: "main-entityHeader-imageContainer" },
                react.createElement("img", {
                    className: "main-image-image",
                    src: playlist.image,
                    style: {
                        height: "inherit",
                    },
                })
            ),
            react.createElement(
                "div",
                { className: "main-entityHeader-headerText" },
                react.createElement(
                    "h2",
                    { className: "main-entityHeader-subtitle main-entityHeader-small main-entityHeader-uppercase main-entityHeader-bold" },
                    playlist.isCollab ? "Collaborative Playlist" : "Playlist"
                ),
                react.createElement(
                    "h1",
                    {
                        className: "main-entityHeader-title main-type-bass",
                        style: {
                            padding: "0.08em 0px",
                            visibility: "visible",
                            width: "100%",
                            fontSize: "9vmin",
                            lineHeight: "9vmin",
                        },
                    },
                    react.createElement(
                        "a",
                        {
                            href: playlist.uri,
                            draggable: "false",
                        },
                        playlist.title
                    )
                ),
                isDesc &&
                    react.createElement("h2", { className: "main-entityHeader-subtitle main-entityHeader-gray main-type-viola" }, playlist.desc),
                react.createElement(
                    "span",
                    { className: "main-entityHeader-metaData main-type-mesto" },
                    `${playlist.created} • ${playlist.noOfSongs} songs`
                )
            )
        );
    };

    async function listPlaylists(uris) {
        // getting playlists to display
        const allPlaylists = await getUserLibrary();
        const playlistsFound = [];
        for (var playlist of allPlaylists) {
            const playlistRes = await checkPlaylist(playlist, uris[0]);
            if (playlistRes) {
                playlistsFound.push(playlistRes);
            }
        }
        if (playlistsFound.length == 0) {
            Spicetify.showNotification("Song is not in any of your playlists.");
            return;
        }

        // getting song data to prepare elements
        const songmeta = await Spicetify.CosmosAsync.get("https://api.spotify.com/v1/tracks/" + uris[0].split(":")[2]);
        Spicetify.Platform.History.push(`/album/${songmeta.album.uri.split(":")[2]}?highlight=${uris[0]}`);
        await delay(2000); // waiting to load.

        // modifying album page and saving info card and song row
        let songRow = document.querySelector(`[aria-selected="true"]`);
        let section = document.querySelector(`[data-testid="album-page"]`);

        // if (songmeta.album.total_tracks == 1) {
        //     songRow = document.querySelector(`[aria-rowindex="2"]`).cloneNode(true);
        // } else {
        //     songRow = document.querySelector(`[aria-rowindex="3"]`).cloneNode(true);
        //     songRow.childNodes[0].childNodes[1].childNodes[0].childNodes[0].innerText = songmeta.name;
        //     if (songmeta.artists.length > 1) {
        //         let artists = "";
        //         for (const artist of songmeta.artists) {
        //             artists = artists.concat(artist.name, ", ");
        //         }
        //         artists = artists.slice(0, -2);
        //         if (songmeta.explicit) {
        //             songRow.childNodes[0].childNodes[1].childNodes[0].childNodes[2].innerHTML = artists;
        //         } else {
        //             songRow.childNodes[0].childNodes[1].childNodes[0].childNodes[1].innerHTML = artists;
        //         }
        //     }
        // }
        let info = document.querySelector(`[data-testid="album-page"] > div`).cloneNode(true);
        info.classList.add("main-trackList-trackListHeaderRow");
        section.innerHTML = ""; //wiping all other elements

        await delay(200); // waiting for the topbar text to appear, to remove it
        document
            .querySelector(".main-topBar-topbarContent.main-entityHeader-topbarContent.main-entityHeader-topbarContentFadeIn")
            .classList.remove("main-entityHeader-topbarContentFadeIn");

        section.appendChild(info);
        // creating the heading
        let appearsIn = document.createElement("h1");
        appearsIn.className = "main-type-bass main-trackList-trackListHeaderRow";
        appearsIn.style.fontSize = "48px";
        appearsIn.style.lineHeight = "60px";
        appearsIn.style.paddingLeft = "10px";
        appearsIn.style.height = "auto";
        appearsIn.innerText = `Appears In ${playlistsFound.length}/${allPlaylists.length} of your playlists:`;
        section.appendChild(appearsIn); //adding it
        // modifying info card
        let infoText = section.childNodes[0].childNodes[5];
        infoText.childNodes[0].innerText = "SONG";
        infoText.childNodes[1].childNodes[0].style.fontSize = "8vmin";
        infoText.childNodes[1].childNodes[0].style.lineHeight = "8vmin";
        infoText.childNodes[1].childNodes[0].innerText = songmeta.name;
        if (songmeta.album.total_tracks > 1) {
            let albumText = infoText.childNodes[0].cloneNode();
            albumText.classList.remove("main-entityHeader-uppercase", "main-entityHeader-small");
            albumText.style = "margin-top: 0px; margin-bottom: 8px";
            albumText.innerText = `Track ${songmeta.track_number} / ${songmeta.album.total_tracks} • ${songmeta.album.name}`;
            infoText.childNodes[1].appendChild(albumText);
            infoText.childNodes[2].childNodes[2].innerText = `1 song, ${parseInt(songmeta.duration_ms / 1000 / 60)} min ${parseInt(
                (songmeta.duration_ms / 1000) % 60
            )} sec`;
        }

        // preparing song row element
        let songImage = document.createElement("img"); //getting small album art and adding it
        songImage.className = "main-image-image main-trackList-rowImage";
        songImage.src = songmeta.album.images.at(-1).url;
        songImage.width = 40;
        songImage.height = 40;

        songRow.childNodes[0].classList.remove("main-trackList-selected");
        songRow.childNodes[0].style = "grid-template-columns: [index] 30px [first] 4fr [var1] 3fr [last] minmax(240px,2fr);";
        songRow.childNodes[0].childNodes[1].insertBefore(songImage, songRow.childNodes[0].childNodes[1].firstChild);
        songRow.childNodes[0].childNodes[2].childNodes[0].style.width = "fit-content";
        songRow.childNodes[0].childNodes[2].childNodes[0].innerText = songmeta.album.name;
        songRow.childNodes[0].childNodes[3].childNodes[1].classList.remove("main-trackList-rowDuration");
        if (songRow.childNodes[0].classList.contains("main-trackList-active")) {
            let spanIndex = document.createElement("span"); //adding index in playlist to song row
            spanIndex.classList.add("main-trackList-number", "main-type-ballad");

            songRow.childNodes[0].childNodes[0].childNodes[0].childNodes[0].remove();
            songRow.childNodes[0].childNodes[0].childNodes[0].appendChild(spanIndex);
        }

        // finally rendering the playlists
        for (const playlist of playlistsFound) {
            let preElement = document.createElement("div");
            preElement.classList.add("main-trackList-trackListHeaderRow");
            preElement.style.height = "auto";
            section.append(preElement);

            const playlist_card = react.createElement(playlistCard, { playlist: playlist });
            reactDOM.render(playlist_card, preElement);

            songRow.childNodes[0].childNodes[0].childNodes[0].childNodes[0].innerText = playlist.index;
            songRow.childNodes[0].childNodes[3].childNodes[1].innerText = playlist.songAddedAt;
            // finally adding song row
            preElement.appendChild(songRow.cloneNode(true));
        }
    }

    new Spicetify.ContextMenu.Item(
        "List playlists with this Song",
        listPlaylists,
        (uris) => {
            if (uris.length != 1) return false;
            return Spicetify.URI.fromString(uris[0]).type == Spicetify.URI.Type.TRACK;
        },
        "search"
    ).register();
})();


================================================
FILE: manifest.json
================================================
[
    {
        "name": "Full App Display Modified",
        "description": "View your music at a glance.",
        "preview": "fullAppDisplayModified/previews/preview.gif",
        "main": "fullAppDisplayModified/fullAppDisplayMod.js",
        "readme": "fullAppDisplayModified/README.md"
    },
    {
        "name" : "SkipStats",
        "description" : "See your skipping stats in playlists and albums!",
        "preview" : "skipStats/preview.jpg",
        "main" : "skipStats/skipStats.js",
        "readme" : "skipStats/README.md"
    },
    {
        "name": "listPlaylistsWithSong",
        "description": "Adds context menu button to view playlists in your library that contain the selected song",
        "preview": "listPlaylistsWithSong/preview1.jpg",
        "main": "listPlaylistsWithSong/listPlaylistsWithSong.js",
        "readme": "listPlaylistsWithSong/README.md"
    },
    {
        "name": "GoToSong",
        "description": "Go to currently playing song in playlist",
        "preview": "goToSong/preview1.jpg",
        "main": "goToSong/goToSong.js",
        "readme": "goToSong/README.md"
    },
    {
        "name": "playlistIntersection",
        "description": "Adds context menu buttons to see songs in common between two playlists.",
        "preview": "playlistIntersection/preview.jpg",
        "main": "playlistIntersection/playlistIntersection.js",
        "readme": "playlistIntersection/README.md"
    },
    {
        "name": "Display full Album date",
        "description": "Display the full album date instead of just year",
        "preview": "fullAlbumDate/preview.jpg",
        "main": "fullAlbumDate/fullAlbumDate.js",
        "readme": "fullAlbumDate/README.md"
    }
]


================================================
FILE: playlistIntersection/README.md
================================================
# playlistIntersection
Filename : `playlistIntersection.js`

Adds context menu buttons to see 
* songs in common between two playlists
* songs only present in one playlist

and convert them to a playlist.

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/both.jpg)

## To use:

* Right Click on desired playlist, and click "Select for Intersection".

![Step 1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/preview.jpg)

* Right Click on a second playlist, and click "Compare with Selected Playlist".

![Step 2](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/compare.jpg)

* If you want to clear your first selection, Go to profile, and click "Clear Selection from Intersection".

![Clear](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/clear.jpg)

### Note: 
After comparing, please wait for all the songs to load, and then change the mode or it **will** malfunction.

## Convert to Playlist:
![Playlist](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/convert.jpg)

Clicking this button will turn the displayed tracks into a playlist.

## Changing Modes:

![Mode Selection](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/modeselection.jpg)

Clicking this button, will cycle through the following modes:
* Intersection: This mode displays the song common in both the playlists. This is the default mode and will always open.

![Intersection](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/modeinter.jpg)
![Intersection Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/both.jpg)
* Songs only in Playlist 1: This displays the song present only in the first playlist.

![First Only](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/firstonly.jpg)
![First Only Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/first.jpg)
* Songs only in Playlist 2: This displays the song present only in the second playlist.

![Second Only](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/secondonly.jpg)
![Second Only Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/second.jpg)


## More
🌟 Like it? Gimme some love!    
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)


================================================
FILE: playlistIntersection/playlistIntersection.js
================================================
//@ts-check
// NAME: playlistIntersection
// AUTHOR: huhridge
// DESCRIPTION: Adds context menu buttons to see songs in common between two playlists.
/// <reference types="react" />
/// <reference path="../globals.d.ts" />

(async function playlistIntersecter() {
    const { Player, Menu, LocalStorage, Platform, ReactDOM: reactDOM } = Spicetify;
    let play1, play2, name1, name2, commonTracks;
    let renderedTracks;

    /** @type {React} */
    const react = Spicetify.React;

    if (!(Player && Menu && LocalStorage && Platform)) {
        setTimeout(playlistIntersecter, 1000);
        return;
    }

    function delay(delayInms) {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(2);
            }, delayInms);
        });
    }

    function trackIntersection(track1, track2) {
        var inter = new Array();
        var uri1 = new Array();

        for (i = 0; i < track1.length; i++) {
            uri1.push(track1[i].uri);
        }

        for (var i = 0; i < track2.length; i++) {
            var index1 = uri1.indexOf(track2[i].uri);
            if (index1 >= 0) {
                track2[i].index1 = index1;
                track2[i].index2 = i;
                inter.push(track2[i]);
            }
        }
        return inter;
    }

    const playlistInfo = (playlist, isLeft) => {
        let isDesc = false;
        if (playlist.description) {
            isDesc = true;
        }
        return react.createElement(
            "div",
            {
                className: "contentSpacing main-entityHeader-container main-entityHeader-nonWrapped",
                style: {
                    marginTop: "1.5%",
                    marginBottom: "1.5%",
                    justifyContent: isLeft ? "left" : "start",
                    paddingLeft: isLeft ? "32px" : "16px",
                    paddingRight: isLeft ? "16px" : "32px",
                    borderRight: isLeft ? "1px solid rgba(255,255,255,.1)" : "",
                },
            },
            react.createElement(
                "div",
                {
                    draggable: false,
                    style: {
                        alignSelf: "center",
                        position: "relative",
                        height: "232px",
                        minWidth: "232px",
                        width: "232px",
                        marginInlineEnd: "24px",
                    },
                },
                react.createElement("img", {
                    className: "main-image-image main-entityHeader-shadow",
                    style: {
                        height: "100%",
                        width: "100%",
                    },
                    src: playlist.images[0].url,
                })
            ),
            react.createElement(
                "div",
                {
                    className: "main-entityHeader-headerText",
                    style: {
                        justifyContent: "center",
                    },
                },
                react.createElement(
                    "h2",
                    {
                        className: "main-entityHeader-subtitle main-entityHeader-small main-entityHeader-uppercase main-entityHeader-bold",
                    },
                    playlist.isCollaborative ? "Collaborative Playlist" : "Playlist"
                ),
                react.createElement(
                    "span",
                    {
                        className: "main-entityHeader-title",
                    },
                    react.createElement(
                        "h1",
                        {
                            dir: "auto",
                            className: "main-type-bass",
                            style: {
                                padding: "0.08em 0px",
                                visibility: "visible",
                                width: "100%",
                                lineHeight: "3vw",
                                letterSpacing: "-0.04em",
                                fontWeight: "900",
                            },
                            ref: (el) => el && el.style.setProperty("font-size", "2.5vw", "important"),
                        },
                        react.createElement(
                            "a",
                            {
                                href: playlist.uri,
                                draggable: "false",
                            },
                            playlist.name
                        )
                    )
                ),
                isDesc &&
                    react.createElement(
                        "h2",
                        { className: "main-entityHeader-subtitle main-entityHeader-gray main-type-viola" },
                        playlist.description
                    ),
                react.createElement(
                    "div",
                    {
                        className: "main-entityHeader-metaData",
                    },
                    react.createElement(
                        "span",
                        {
                            className: "main-type-mesto",
                        },
                        react.createElement(
                            "a",
                            {
                                href: playlist.owner.uri,
                            },
                            playlist.owner.displayName
                        ),
                        ` • ${playlist.totalLength} songs`
                    )
                )
            )
        );
    };

    const songRowHeader = () => {
        return react.createElement(
            "div",
            {
                className: "main-trackList-trackListRowGrid",
                role: "row",
                "aria-rowindex": "1",
                style: {
                    marginTop: "10px",
                    marginBottom: "8px",
                },
                ref: (el) =>
                    el && el.style.setProperty("grid-template-columns", "[index] 1fr [first] 6fr [var1] 6fr [var2] 1fr [last] 1fr", "important"),
            },
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionIndex",
                    role: "columnheader",
                    "aria-colindex": "1",
                },
                "#"
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionStart",
                    role: "columnheader",
                    "aria-colindex": "2",
                },
                react.createElement(
                    "span",
                    {
                        className: "standalone-ellipsis-one-line main-type-minuet",
                    },
                    "title"
                )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionVariable",
                    role: "columnheader",
                    "aria-colindex": "3",
                },
                react.createElement(
                    "span",
                    {
                        className: "standalone-ellipsis-one-line main-type-minuet",
                    },
                    "album"
                )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionVariable",
                    role: "columnheader",
                    "aria-colindex": "4",
                },
                react.createElement(
                    "svg",
                    {
                        role: "img",
                        height: "16",
                        width: "16",
                        fill: "var(--spice-subtext)",
                        viewBox: "0 0 16 16",
                    },
                    react.createElement("path", {
                        d: "M7.999 3h-1v5h3V7h-2V3zM7.5 0a7.5 7.5 0 100 15 7.5 7.5 0 000-15zm0 14C3.916 14 1 11.084 1 7.5S3.916 1 7.5 1 14 3.916 14 7.5 11.084 14 7.5 14z",
                    }),
                    react.createElement("path", {
                        fill: "none",
                        d: "M16 0v16H0V0z",
                    })
                )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionIndex",
                    role: "columnheader",
                    "aria-colindex": "5",
                },
                "#"
            )
        );
    };

    const songRow = (song, i, display = "both") => {
        let artists = "";
        for (const artist of song.artists) {
            artists = artists.concat(artist.name, ", ");
        }
        let displayOne = true;
        let displayTwo = true;
        artists = artists.slice(0, -2);
        if (display == "1") {
            displayTwo = false;
        } else if (display == "2") {
            displayOne = false;
        }
        return react.createElement(
            "div",
            {
                className: "main-trackList-trackListRow main-trackList-trackListRowGrid",
                draggable: true,
                role: "presentation",
                "aria-rowindex": `${i}`,
                ref: (el) =>
                    el && el.style.setProperty("grid-template-columns", "[index] 1fr [first] 6fr [var1] 6fr [var2] 1fr [last] 1fr", "important"),
                onDoubleClick: () => {
                    Spicetify.Player.playUri(song.uri);
                },
            },
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionStart",
                    role: "gridcell",
                    style: {
                        justifySelf: "end",
                    },
                    "aria-colindex": "1",
                },
                displayOne &&
                    react.createElement(
                        "div",
                        {
                            className: "main-trackList-rowMarker",
                        },
                        react.createElement(
                            "span",
                            {
                                className: "main-trackList-number main-type-ballad",
                            },
                            song.index1 + 1
                        )
                    )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionStart",
                    role: "gridcell",
                    "aria-colindex": "2",
                },
                react.createElement("img", {
                    className: "main-image-image main-trackList-rowImage",
                    draggable: false,
                    width: "40",
                    height: "40",
                    src: song.album.images[1].url,
                }),
                react.createElement(
                    "div",
                    {
                        className: "main-trackList-rowMainContent",
                    },
                    react.createElement(
                        "div",
                        {
                            className: "main-trackList-rowTitle standalone-ellipsis-one-line main-type-ballad",
                            dir: "auto",
                        },
                        song.name
                    ),
                    song.isExplicit &&
                        react.createElement(
                            "span",
                            {
                                className: "main-trackList-rowBadges main-type-ballad",
                                style: {
                                    color: "var(--spice-subtext)",
                                },
                            },
                            react.createElement(
                                "span",
                                {
                                    className: "main-tag-container",
                                    title: "Explicit",
                                },
                                "E"
                            )
                        ),
                    react.createElement(
                        "span",
                        {
                            className: "main-trackList-rowSubTitle standalone-ellipsis-one-line main-type-mesto",
                            style: {
                                color: "var(--spice-subtext)",
                            },
                        },
                        artists
                    )
                )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionVariable",
                    role: "gridcell",
                    "aria-colindex": "3",
                },
                react.createElement(
                    "a",
                    {
                        draggable: true,
                        className: "standalone-ellipsis-one-line main-type-mesto",
                        href: song.album.uri,
                    },
                    song.album.name
                )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionVariable",
                    role: "gridcell",
                    "aria-colindex": "4",
                },
                react.createElement(
                    "span",
                    {
                        className: "main-type-mesto",
                        style: {
                            color: "var(--spice-subtext)",
                        },
                    },
                    `${parseInt(song.duration.milliseconds / 1000 / 60)}:${parseInt((song.duration.milliseconds / 1000) % 60).toLocaleString(
                        undefined,
                        {
                            minimumIntegerDigits: 2,
                            useGrouping: false,
                        }
                    )}`
                )
            ),
            react.createElement(
                "div",
                {
                    className: "main-trackList-rowSectionEnd",
                    role: "gridcell",
                    "aria-colindex": "5",
                    style: {
                        justifyContent: "end",
                    },
                },
                displayTwo &&
                    react.createElement(
                        "div",
                        {
                            className: "main-trackList-rowMarker",
                        },
                        react.createElement(
                            "span",
                            {
                                className: "main-trackList-number main-type-ballad",
                            },
                            song.index2 + 1
                        )
                    )
            )
        );
    };

    const interHeading = () => {
        return react.createElement(
            "div",
            {
                className: "interHeading",
                style: {
                    display: "flex",
                    justifyContent: "flex-start",
                    borderBottom: "1px solid rgba(255,255,255,.1)",
                },
            },
            react.createElement("h1", {
                className: "main-type-bass main-trackList-trackListHeaderRow",
                style: {
                    fontSize: "30px",
                    height: "100%",
                    lineHeight: "60px",
                    paddingLeft: "10px",
                    justifySelf: "flex-start",
                    border: "none",
                },
            }),
            react.createElement(
                "button",
                {
                    className: "changeMode main-topBar-button",
                    title: "Change Mode",
                    style: {
                        display: "inline-flex",
                        alignSelf: "center",
                        width: "32px",
                        height: "32px",
                        marginLeft: "auto",
                        marginRight: "16px",
                        borderWidth: "0px",
                        borderRadius: "5px",
                        alignItems: "center",
                        justifyContent: "center",
                    },
                    ref: (el) => el && el.style.setProperty("background-color", "var(--spice-button)", "important"),
                    onClick: () => {
                        let ele = document.querySelector(".changeMode.main-topBar-button");
                        if (LocalStorage.get("spicetify-intermode") == "Intersect" && Spicetify.LocalStorage.get("spicetify-interorder") == "0") {
                            LocalStorage.set("spicetify-intermode", "exceptIntersect");
                            Spicetify.LocalStorage.set("spicetify-interorder", "1");
                            ele.style.backgroundColor = "var(--spice-button-active)";
                            let d = ele.firstChild.firstChild.getAttribute("d");
                            d = d + "M4.318 10.836A.5.5 0 006.089 11.548.5.5 0 004.318 10.836Z";
                            ele.firstChild.firstChild.setAttribute("d", d);
                            exceptIntersect();
                        } else if (
                            LocalStorage.get("spicetify-intermode") == "exceptIntersect" &&
                            Spicetify.LocalStorage.get("spicetify-interorder") == "1"
                        ) {
                            Spicetify.LocalStorage.set("spicetify-interorder", "2");
                            ele.firstChild.innerHTML = Spicetify.SVGIcons.copy;
                            let d = ele.firstChild.firstChild.getAttribute("d");
                            d = d + "M10.423 3.806A.5.5 0 0011.411 5.234.5.5 0 0010.423 3.806Z";
                            ele.firstChild.firstChild.setAttribute("d", d);
                            exceptIntersect();
                        } else {
                            LocalStorage.set("spicetify-intermode", "Intersect");
                            Spicetify.LocalStorage.set("spicetify-interorder", "0");
                            ele.firstChild.innerHTML = Spicetify.SVGIcons.copy;
                            ele.style.backgroundColor = "var(--spice-button)";
                            renderIntersect();
                        }
                    },
                },
                react.createElement("svg", {
                    role: "img",
                    width: "16",
                    height: "16",
                    fill: "black",
                    viewBox: "0 0 16 16",
                    class: "modeSVG",
                    dangerouslySetInnerHTML: {
                        __html: Spicetify.SVGIcons.copy,
                    },
                })
            ),
            react.createElement(
                "button",
                {
                    className: "convertPlaylist main-topBar-button",
                    title: "Convert to Playlist",
                    style: {
                        display: "inline-flex",
                        alignSelf: "center",
                        width: "32px",
                        height: "32px",
                        marginRight: "16px",
                        borderWidth: "0px",
                        borderRadius: "5px",
                        alignItems: "center",
                        justifyContent: "center",
                    },
                    ref: (el) => el && el.style.setProperty("background-color", "var(--spice-button)", "important"),
                    onClick: () => {
                        convertToPlaylist();
                    },
                },
                react.createElement("svg", {
                    role: "img",
                    width: "16",
                    height: "16",
                    fill: "black",
                    viewBox: "0 0 16 16",
                    class: "playlistSVG",
                    dangerouslySetInnerHTML: {
                        __html: Spicetify.SVGIcons.playlist,
                    },
                })
            )
        );
    };

    async function intersect() {
        play1 = LocalStorage.get("spicetify-interplaylist1");
        play2 = LocalStorage.get("spicetify-interplaylist2");
        LocalStorage.set("spicetify-intermode", "Intersect");
        LocalStorage.set("spicetify-interorder", "0");
        LocalStorage.remove("spicetify-interplaylist1");
        LocalStorage.remove("spicetify-interplaylist2");

        const tracks1 = (await Spicetify.Platform.PlaylistAPI.getContents(play1)).items;
        const tracks2 = (await Spicetify.Platform.PlaylistAPI.getContents(play2)).items;

        commonTracks = trackIntersection(tracks1, tracks2);

        if (commonTracks.length == 0) {
            Spicetify.showNotification("No common tracks between the playlists.");
            return;
        }

        const meta1 = await Spicetify.Platform.PlaylistAPI.getMetadata(play1);
        name1 = meta1.name;
        const meta2 = await Spicetify.Platform.PlaylistAPI.getMetadata(play2);
        name2 = meta2.name;

        Spicetify.Platform.History.push(`/playlist/${meta1.uri.split(":")[2]}`);
        await delay(1000);

        let section = document.querySelector(`[data-testid="playlist-page"]`);
        section.innerHTML = "";

        await delay(200); // waiting for the topbar text to appear, to remove it
        document
            .querySelector(".main-topBar-topbarContent.main-entityHeader-topbarContent.main-entityHeader-topbarContentFadeIn")
            .classList.remove("main-entityHeader-topbarContentFadeIn");
        let style = document.createElement("style");
        style.innerHTML = `
.main-type-mesto {
    font-size: 14px;
    font-weight: 400;
    letter-spacing: normal;
    line-height: 16px;
    text-transform: none
}
.main-type-bass {
    font-size: 96px;
    font-weight: 900;
    letter-spacing: -.04em;
    line-height: 96px;
    text-transform: none
}
.main-type-minuet {
    font-size: 12px;
    font-weight: 400;
    letter-spacing: .1em;
    line-height: 16px;
    text-transform: uppercase
}
.main-type-ballad {
    font-size: 16px;
    font-weight: 400;
    letter-spacing: normal;
    line-height: 24px;
    text-transform: none
}
        `;
        section.append(style);
        let playContainer = document.createElement("div");
        playContainer.classList.add("main-trackList-trackListHeaderRow");
        playContainer.style.display = "flex";
        playContainer.style.borderBottom = "1px solid rgba(255,255,255,.1)";
        playContainer.style.height = "100%"
        section.append(playContainer);

        const playele1 = playlistInfo(meta1, true);
        const playele2 = playlistInfo(meta2, false);

        let container = document.querySelector(".main-trackList-trackListHeaderRow");
        let preElement1 = document.createElement("div");
        preElement1.style.width = "50%";
        container.append(preElement1);
        reactDOM.render(playele1, preElement1);
        let preElement2 = document.createElement("div");
        preElement2.style.width = "50%";
        container.append(preElement2);
        reactDOM.render(playele2, preElement2);

        let headingWrapper = document.createElement("div");
        section.append(headingWrapper);
        let heading = interHeading();
        reactDOM.render(heading, headingWrapper);

        let songHeader = document.createElement("div");
        songHeader.className = "main-trackList-trackListHeaderRow";
        songHeader.style.height = "100%"
        songHeader.style.background = "var(--spice-main)";
        songHeader.style.borderBottom = "1px solid rgba(255,255,255,.1)";
        songHeader.style.marginBottom = "8px";
        songHeader.style.position = "sticky";
        songHeader.style.top = "64px";
        songHeader.style.zIndex = "2";
        section.append(songHeader);
        const trackRowHeader = await songRowHeader();
        reactDOM.render(trackRowHeader, songHeader);
        renderIntersect();
    }

    async function renderIntersect() {
        let section = document.querySelector(`[data-testid="playlist-page"]`);
        let songContainer;
        try {
            songContainer = document.querySelector(".interSongContainer");
            songContainer.innerHTML = "";
        } catch {
            songContainer = document.createElement("div");
            songContainer.className = "interSongContainer";
            section.append(songContainer);
        }
        let heading1 = document.querySelector(".main-type-bass.main-trackList-trackListHeaderRow");
        if (commonTracks.length == 1) {
            heading1.innerText = `${commonTracks.length} song appears in both of the playlists: `;
        } else {
            heading1.innerText = `${commonTracks.length} songs appear in both of the playlists: `;
        }
        //rendering songs
        let i = 2;
        renderedTracks = commonTracks;
        for (const track of commonTracks) {
            let preElement = document.createElement("div");
            songContainer.append(preElement);
            const trackRow = songRow(track, i);
            reactDOM.render(trackRow, preElement);
            i += 1;
        }
    }

    async function exceptIntersect() {
        const toDisplay = LocalStorage.get("spicetify-interorder");
        let tracks = [];
        let isOne, meta;
        if (toDisplay == "1") {
            tracks = (await Spicetify.Platform.PlaylistAPI.getContents(play1)).items;
            meta = await Spicetify.Platform.PlaylistAPI.getMetadata(play1);
            for (var i = 0; i < tracks.length; i++) {
                tracks[i].index1 = i;
            }
            isOne = true;
        } else {
            tracks = (await Spicetify.Platform.PlaylistAPI.getContents(play2)).items;
            meta = await Spicetify.Platform.PlaylistAPI.getMetadata(play2);
            for (var i = 0; i < tracks.length; i++) {
                tracks[i].index2 = i;
            }
            isOne = false;
        }
        for (const track of commonTracks) {
            isOne ? delete tracks[track.index1] : delete tracks[track.index2];
        }
        let heading = document.querySelector(".main-type-bass.main-trackList-trackListHeaderRow");
        if (tracks.length == 1) {
            heading.innerText = `${tracks.length} song exists only in ${meta.name}`;
        } else {
            heading.innerText = `${tracks.length - commonTracks.length} songs exist only in ${meta.name}`;
        }

        let container = document.querySelector(".interSongContainer");
        container.innerHTML = "";

        renderedTracks = tracks;
        let j = 2;
        for (const track of tracks) {
            if (!track) {
                continue;
            }
            let preElement = document.createElement("div");
            container.append(preElement);
            const trackRow = songRow(track, j, toDisplay);
            reactDOM.render(trackRow, preElement);
            j += 1;
        }
    }

    async function convertToPlaylist() {
        let songUris = renderedTracks
            .filter(track => track) // Ensure no null tracks
            .map(track => track.uri);

        let playname;
        if (LocalStorage.get("spicetify-intermode") == "Intersect") {
            playname = `${name1} ∩ ${name2}`;
        } else {
            if (LocalStorage.get("spicetify-interorder") == "1") {
                playname = `${name1} ∅ ${name2}`;
            } else {
                playname = `${name2} ∅ ${name1}`;
            }
        }

        const response = await Spicetify.CosmosAsync.post(
            "https://api.spotify.com/v1/me/playlists", {
                name: playname,
                public: false
            }
        );

        if (!response || !response.id) {
            Spicetify.showNotification("Failed to create playlist.");
            return;
        }
        const uri = response.id
        await delay(100);
        while (songUris.length) {
            const b = songUris.splice(0, 100);
            Spicetify.CosmosAsync.post("https://api.spotify.com/v1/playlists/" + uri + "/tracks", {
                uris: b,
            });
        }
        Spicetify.showNotification(`Succesfully created playlist ${playname}`);
    }

    const clearSelection = new Spicetify.Menu.Item("Clear Selection from Intersection", false, (self) => {
        LocalStorage.remove("spicetify-interplaylist1");
        self.deregister();
    });

    new Spicetify.ContextMenu.Item(
        "Select for Intersection",
        (uris) => {
            LocalStorage.set("spicetify-interplaylist1", uris[0]);
            clearSelection.register();
        },
        (uris) => {
            if (uris.length > 1) {
                return false;
            }
            if (LocalStorage.get("spicetify-interplaylist1")) {
                return false;
            }
            const uriObj = Spicetify.URI.fromString(uris[0]);
            if (uriObj.type == Spicetify.URI.Type.PLAYLIST || uriObj.type == Spicetify.URI.Type.PLAYLIST_V2) {
                return true;
            }
            return false;
        },
        "copy"
    ).register();

    new Spicetify.ContextMenu.Item(
        "Compare with Selected Playlist",
        (uris) => {
            LocalStorage.set("spicetify-interplaylist2", uris[0]);
            clearSelection.deregister();
            intersect();
        },
        (uris) => {
            if (uris.length > 1) {
                return false;
            }
            if (!LocalStorage.get("spicetify-interplaylist1")) {
                return false;
            }
            if (uris[0] == LocalStorage.get("spicetify-interplaylist1")) {
                return false;
            }
            const uriObj = Spicetify.URI.fromString(uris[0]);
            if (uriObj.type == Spicetify.URI.Type.PLAYLIST || uriObj.type == Spicetify.URI.Type.PLAYLIST_V2) {
                return true;
            }
            return false;
        },
        "copy"
    ).register();
})();


================================================
FILE: skipStats/README.md
================================================
# skipStats

Filename : `skipStats.js`

![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/preview.jpg)

Extension to track your skips!

-   Tracks your skips when listening to playlists or albums!
-   Displays the data in a readable manner
-   Auto-skip songs over a certain value of skips

## To use:

-   To see the skips in current playlist/album: Click on the profile and select "See Skips for current playlist/album".

![profilemenu](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/skip_profilemenu.jpg)

-   Right Click on a playlist or album, and click "See Skip Stats".

![playlist](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/skip_playlist.jpg)

## Config

-   Set the auto skip limit: Go to profile menu and click on "Auto-Skip".

![autoskip](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/autoskip.jpg)

-   Reset Stats: Go to profile menu and click on the option accordingly.

## Install

Copy `skipStats.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
| **Platform** | **Path** |
|------------|-----------------------------------------------------------------------------------|
| **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
| **MacOS** | `~/.config/spicetify/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
| **Windows** | `%userprofile%\.spicetify\Extensions\` |

After putting the extension file into the correct folder, run the following command to install the extension:

```
spicetify config extensions skipStats.js
spicetify apply
```

Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.

Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
Example:

```ini
[AdditionalOptions]
...
extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|skipStats.js
```

🌟 Like it? Gimme some love!  
[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)


================================================
FILE: skipStats/skipStats.js
================================================
// @ts-check
// NAME: Skip Stats
// AUTHOR: huhridge
// DESCRIPTION: Tracks skipping stats in playlists and albums.
//TODO: Add liked songs.
/// <reference path="../globals.d.ts" />

(function skipStats() {
    if (!Spicetify.React || !Spicetify.ReactDOM || !Spicetify.Platform) {
        setTimeout(skipStats, 200);
        return;
    }

    const { LocalStorage, Platform, ReactDOM: reactDOM, React: react } = Spicetify;

    let progress;

    if (!Spicetify.LocalStorage.get("autoSkipThreshold")) {
        Spicetify.LocalStorage.set("autoSkipThreshold", "0");
    }

    if (!LocalStorage.get("skipData")) {
        LocalStorage.set("skipData", JSON.stringify({}));
    }

    setInterval(() => {
        progress = Spicetify.Player.getProgressPercent();
    }, 500);

    Spicetify.Player.addEventListener("songchange", trackSkips);

    async function trackSkips() {
        if (progress < 0.95) {
            await delay(200);
            //@ts-ignore
            let song_key = Spicetify.Queue.prevTracks.slice(-1)[0].contextTrack.uri;
            let skipData = JSON.parse(LocalStorage.get("skipData"));
            if (!skipData[song_key]) {
                skipData[song_key] = 1;
            } else {
                skipData[song_key] += 1;
            }
            LocalStorage.set("skipData", JSON.stringify(skipData));
        }
        //auto skip
        let skipData = JSON.parse(LocalStorage.get("skipData"));
        let thresh = Number(Spicetify.LocalStorage.get("autoSkipThreshold"));
        if (thresh > 0 && skipData[Spicetify.Player.data.item.uri] >= thresh) {
            Spicetify.Player.next();
            Spicetify.showNotification("The track was auto-skipped due to being skipped too many times.");
        }
        LocalStorage.set("skipData", JSON.stringify(skipData));
    }

    function resetSkips(mode, uri = "") {
        if (mode === "all") {
            LocalStorage.set("skipData", JSON.stringify({}));
            Spicetify.showNotification("Resetted all skip data!");
        } else if (mode === "current") {
            let skipData = JSON.parse(LocalStorage.get("skipData"));
            skipData[Spicetify.Player.data.item.uri] = 0;
            LocalStorage.set("skipData", JSON.stringify(skipData));
            Spicetify.showNotification("Resetted skip data for current track!");
        } else if (mode === "context") {
            let skipData = JSON.parse(LocalStorage.get("skipData"));
            skipData[uri] = 0;
            LocalStorage.set("skipData", JSON.stringify(skipData));
            Spicetify.showNotification("Resetted skip data for selected track!");
        }
    }

    async function seeStats(uri) {
        const uriObj = Spicetify.URI.fromString(uri);
        let tracks;
        switch (uriObj.type) {
            case Spicetify.URI.Type.PLAYLIST:
            case Spicetify.URI.Type.PLAYLIST_V2:
                tracks = await fetchPlaylist(uri);
                break;
            case Spicetify.URI.Type.ALBUM:
                tracks = await fetchAlbum(uri);
                break;
            case Spicetify.URI.Type.COLLECTION:
                tracks = await fetchCollection();
                break;
        }
        let skipData = JSON.parse(LocalStorage.get("skipData"));
        tracks = tracks.filter((item) => Boolean(skipData[item.uri]));
        if (tracks.length == 0) {
            Spicetify.showNotification("No Skipping Data found!");
            return;
        }
        tracks.forEach((item) => {
            item.skips = skipData[item.uri];
        });
        tracks.sort((a, b) => b.skips - a.skips);
        const skipTable = statTable({ tracks: tracks });
        // @ts-ignore
        Spicetify.PopupModal.display({ title: "SkipStats", content: skipTable, isLarge: true });
    }

    function delay(delayInms) {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(2);
            }, delayInms);
        });
    }

    const fetchPlaylist = async (uri) => {
        const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}/rows`);
        return res.rows.map((item, index) => ({
            uri: item.link,
            title: item.name,
            index: index + 1,
            album: item.album.name,
            artists: item.artists.map((item) => item.name).join(", "),
        }));
    };

    const fetchCollection = async () => {
        const res = await Spicetify.CosmosAsync.get("sp://core-collection/unstable/@/list/tracks/all?responseFormat=protobufJson");
        return res.item.map((item) => ({
            uri: item.trackMetadata.link,
            title: item.trackMetadata.name,
            index: item.index + 1,
            album: item.trackMetadata.album.name,
            artists: item.trackMetadata.artist.map((item) => item.name).join(", "),
        }));
    };

    const fetchAlbum = async (uri) => {
        const arg = uri.split(":")[2];
        const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${arg}`);
        return res.tracks.items.map((item) => ({
            uri: item.uri,
            title: item.name,
            index: item.track_number,
            album: res.name,
            artists: item.artists.map((item) => item.name).join(", "),
        }));
    };

    const statTable = ({ tracks }) => {
        const headers = ["Title", "Album", "Artists", "Skips"];
        let thresh = Number(Spicetify.LocalStorage.get("autoSkipThreshold"));
        let isThresh = Boolean(thresh);
        const style = react.createElement("style", {
            dangerouslySetInnerHTML: {
                __html: `
div[aria-label="SkipStats"] > div {
    min-width: max-content;
}
.styled-table {
    margin: 25px 0;
    font-size: 0.9em;
    font-family: sans-serif;
    min-width: 400px;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
    overflow: hidden;
    border-radius: 8px;
}
.styled-table thead tr {
    background-color: var(--spice-sidebar);
    color: var(--spice-sidebar-text);
    text-align: left;
    border-bottom: thin solid #dddddd;
}
tbody td:nth-child(4) {
    text-align: center;
}
.styled-table th,
.styled-table td {
    padding: 12px 15px;
    width: 1%;
    white-space: nowrap;
}
.styled-table tbody tr {
    border-bottom: thin solid #dddddd;
}
td.auto-skip {
    color: var(--spice-button-active);
}
                `,
            },
        });
        return react.createElement(
            "table",
            { className: "styled-table" },
            style,
            react.createElement(
                "thead",
                null,
                react.createElement(
                    "tr",
                    null,
                    headers.map((item) => react.createElement("th", null, item))
                )
            ),
            react.createElement(
                "tbody",
                null,
                tracks.map((track) =>
                    react.createElement(
                        "tr",
                        {
                            "data-id": track.uri,
                        },
                        // react.createElement("td", null, track.index),
                        react.createElement("td", null, track.title),
                        react.createElement("td", null, track.album),
                        react.createElement("td", null, track.artists),
                        react.createElement(
                            "td",
                            {
                                className: isThresh && track.skips >= thresh ? "auto-skip" : "",
                            },
                            track.skips
                        )
                    )
                )
            )
        );
    };

    const Config = ({ name, lkey }) => {
        const [value, setValue] = react.useState(Spicetify.LocalStorage.get(lkey));

        const setValueCallback = react.useCallback(
            (event) => {
                const value = event.target.value;
                setValue(value);
                Spicetify.LocalStorage.set(lkey, value);
            },
            [value]
        );

        const style = react.createElement("style", {
            dangerouslySetInnerHTML: {
                __html: `
.setting-row::after {
    content: "";
    display: table;
    clear: both;
}
.setting-row .col {
    padding: 16px 0 4px;
    align-items: center;
}
.setting-row .col.description {
    float: left;
    padding-right: 15px;
    cursor: default;
}
.setting-row .col.action {
    float: right;
    display: flex;
    justify-content: flex-end;
    align-items: center;
}
.col.action input {
    width: 100%;
    margin-top: 10px;
    padding: 0 5px;
    height: 32px;
    border: 0;
    color: var(--spice-text);
    background-color: initial;
    border-bottom: 1px solid var(--spice-text);
}
`,
            },
        });

        return react.createElement(
            "div",
            {
                className: "skip-stats-config-container",
            },
            style,
            react.createElement(
                "div",
                {
                    className: "setting-row",
                },
                react.createElement(
                    "label",
                    {
                        className: "col description",
                    },
                    name
                ),
                react.createElement(
                    "div",
                    {
                        className: "col action",
                    },
                    react.createElement("input", {
                        type: "number",
                        value,
                        onChange: setValueCallback,
                    })
                )
            )
        );
    };

    const currentSkips = new Spicetify.Menu.Item("See Skips for current playlist/album", false, async () => {
        const uriObj = Spicetify.URI.fromString(Spicetify.Player.data.context_uri);
        switch (uriObj.type) {
            case Spicetify.URI.Type.PLAYLIST:
            case Spicetify.URI.Type.PLAYLIST_V2:
            case Spicetify.URI.Type.ALBUM:
            case Spicetify.URI.Type.COLLECTION:
                await seeStats(Spicetify.Player.data.context_uri);
                break;
            default:
                throw Spicetify.showNotification("Unsupported context type! Please use for a playlist or an album only");
        }
    });

    const autoSkip = new Spicetify.Menu.Item("Auto-Skip", false, () => {
        Spicetify.PopupModal.display({
            title: "Auto-Skip Threshold",
            content: react.createElement(Config, { name: "Auto-Skip after this many skips (0 for off)", lkey: "autoSkipThreshold" }),
        });
    });

    const resetStatsAll = new Spicetify.Menu.Item("Reset all stats", false, () => {
        resetSkips("all");
    });

    const resetStatsCurrent = new Spicetify.Menu.Item("Reset stats for current track", false, () => {
        resetSkips("current");
    });

    new Spicetify.Menu.SubMenu("skipStats", [currentSkips, autoSkip, resetStatsAll, resetStatsCurrent]).register();

    new Spicetify.ContextMenu.Item(
        "See Skip Stats",
        async (uris) => {
            await seeStats(uris[0]);
        },
        (uris) => {
            if (uris.length > 1) {
                return false;
            }
            const uriObj = Spicetify.URI.fromString(uris[0]);
            switch (uriObj.type) {
                case Spicetify.URI.Type.PLAYLIST:
                case Spicetify.URI.Type.PLAYLIST_V2:
                case Spicetify.URI.Type.ALBUM:
                    // case Spicetify.URI.Type.COLLECTION:
                    return true;
            }
            return false;
        },
        "skip-forward"
    ).register();

    new Spicetify.ContextMenu.Item(
        "Reset Skips for this track",
        (uris) => {
            resetSkips("context", uris[0]);
        },
        (uris) => {
            if (uris.length != 1) return false;
            return Spicetify.URI.fromString(uris[0]).type == Spicetify.URI.Type.TRACK;
        }
    ).register();
})();
Download .txt
gitextract_6oyyqymh/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug-report.md
│       └── feature-request.md
├── .gitignore
├── README.md
├── fullAlbumDate/
│   ├── README.md
│   └── fullAlbumDate.js
├── fullAppDisplayModified/
│   ├── README.md
│   └── fullAppDisplayMod.js
├── goToSong/
│   ├── README.md
│   └── goToSong.js
├── listPlaylistsWithSong/
│   ├── README.md
│   └── listPlaylistsWithSong.js
├── manifest.json
├── playlistIntersection/
│   ├── README.md
│   └── playlistIntersection.js
└── skipStats/
    ├── README.md
    └── skipStats.js
Download .txt
SYMBOL INDEX (49 symbols across 6 files)

FILE: fullAlbumDate/fullAlbumDate.js
  function getAlbumDate (line 10) | async function getAlbumDate(uri) {
  function replaceDate (line 16) | function replaceDate(newDate) {
  function setDate (line 27) | async function setDate() {

FILE: fullAppDisplayModified/fullAppDisplayMod.js
  function displayUpdate (line 462) | function displayUpdate() {
  function fetchColors (line 488) | async function fetchColors(uri) {
  function lightnessColor (line 509) | function lightnessColor(hex) {
  class FAD (line 1067) | class FAD extends react.Component {
    method constructor (line 1068) | constructor(props) {
    method getAlbumDate (line 1082) | async getAlbumDate(uri) {
    method fetchInfo (line 1095) | async fetchInfo() {
    method animateCanvas (line 1215) | animateCanvas(prevImg, nextImg) {
    method animateCanvasColor (line 1264) | async animateCanvasColor(prevUri, nextUri, isStatic = false) {
    method componentDidMount (line 1345) | componentDidMount() {
    method componentWillUnmount (line 1386) | componentWillUnmount() {
    method render (line 1392) | render() {
  function toggleFullscreen (line 1530) | async function toggleFullscreen() {
  function activate (line 1539) | async function activate() {
  function deactivate (line 1549) | function deactivate() {
  function toggleFad (line 1565) | function toggleFad() {
  function updateStyle (line 1573) | function updateStyle() {
  function checkLyricsPlus (line 1584) | function checkLyricsPlus() {
  function autoHideLyrics (line 1588) | function autoHideLyrics() {
  function requestLyricsPlus (line 1601) | function requestLyricsPlus() {
  function getConfig (line 1612) | function getConfig() {
  function saveConfig (line 1625) | function saveConfig() {
  function record (line 1720) | function record() {
  function finishRecord (line 1734) | function finishRecord() {
  function openColor (line 1815) | function openColor(event) {
  function openConfig (line 1857) | function openConfig(event) {

FILE: goToSong/goToSong.js
  function delay (line 16) | function delay(delayInms) {
  function shouldDisplayGoTo (line 66) | function shouldDisplayGoTo(uris) {
  function scrollSong (line 79) | async function scrollSong(playlisturi) {
  function gotoCurrPlay (line 124) | async function gotoCurrPlay() {
  function gotoselectedPlay (line 134) | async function gotoselectedPlay(uris){

FILE: listPlaylistsWithSong/listPlaylistsWithSong.js
  function delay (line 15) | function delay(delayInms) {
  function recursivePlaylistFolder (line 25) | async function recursivePlaylistFolder(folder) {
  function getUserLibrary (line 56) | async function getUserLibrary() {
  function checkPlaylist (line 87) | async function checkPlaylist(playlist, songUri) {
  function listPlaylists (line 174) | async function listPlaylists(uris) {

FILE: playlistIntersection/playlistIntersection.js
  function delay (line 21) | function delay(delayInms) {
  function trackIntersection (line 29) | function trackIntersection(track1, track2) {
  function intersect (line 547) | async function intersect() {
  function renderIntersect (line 652) | async function renderIntersect() {
  function exceptIntersect (line 681) | async function exceptIntersect() {
  function convertToPlaylist (line 727) | async function convertToPlaylist() {

FILE: skipStats/skipStats.js
  function trackSkips (line 32) | async function trackSkips() {
  function resetSkips (line 55) | function resetSkips(mode, uri = "") {
  function seeStats (line 72) | async function seeStats(uri) {
  function delay (line 102) | function delay(delayInms) {
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (159K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "chars": 795,
    "preview": "---\nname: Bug Report\nabout: Create a report to help me resolve issues\ntitle: \"[Extension Name Here][BUG]\"\nlabels: bug\nas"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "chars": 572,
    "preview": "---\nname: Feature Request\nabout: Suggest an idea for this project\ntitle: \"[REQ]\"\nlabels: enhancement\nassignees: ''\n\n---\n"
  },
  {
    "path": ".gitignore",
    "chars": 31,
    "preview": "\n.DS_Store\n.DS_Store\n.DS_Store\n"
  },
  {
    "path": "README.md",
    "chars": 3787,
    "preview": "# huh-spicetify-extensions\n\nCollection of my spicetify extensions\n\n🌟 Like it? Gimme some love!  \n[![Github Stars badge]("
  },
  {
    "path": "fullAlbumDate/README.md",
    "chars": 429,
    "preview": "# Display full Album date\nFilename : `fullAlbumDate.js`\nDisplay full album date instead of just year\n\n![Preview](https:/"
  },
  {
    "path": "fullAlbumDate/fullAlbumDate.js",
    "chars": 1253,
    "preview": "(async function fullAlbumDate() {\n    if (!Spicetify.Platform?.History || !Spicetify.CosmosAsync || !Spicetify.Locale) {"
  },
  {
    "path": "fullAppDisplayModified/README.md",
    "chars": 1519,
    "preview": "# Full App Display modified\nFilename : `fullAppDisplayMod.js`\nMinimal album cover art display with beautiful blur effect"
  },
  {
    "path": "fullAppDisplayModified/fullAppDisplayMod.js",
    "chars": 72039,
    "preview": "// @ts-check\n// NAME: Full App Display\n// AUTHOR: khanhas\n// VERSION: 1.2\n// DESCRIPTION: Fancy artwork and track status"
  },
  {
    "path": "goToSong/README.md",
    "chars": 970,
    "preview": "# Go to Song\nFilename : `goToSong.js`\n\nGo to the currrently playing song in a playlist **/or/** currently playing playli"
  },
  {
    "path": "goToSong/goToSong.js",
    "chars": 4996,
    "preview": "// @ts-check\n// NAME: goToSong\n// AUTHOR: huhridge\n// DESCRIPTION: Go to currently playing song in playlists.\n/// <refer"
  },
  {
    "path": "listPlaylistsWithSong/README.md",
    "chars": 857,
    "preview": "# List Playlists with Song\nFilename : `listPlaylistsWithSong.js`\n\nAdds context menu button to view playlists in your lib"
  },
  {
    "path": "listPlaylistsWithSong/listPlaylistsWithSong.js",
    "chars": 13976,
    "preview": "//@ts-check\r\n// NAME: ListPlaylistsWithSong\r\n// AUTHOR: huhridge (based on elijaholmos's version)\r\n// DESCRIPTION: Adds "
  },
  {
    "path": "manifest.json",
    "chars": 1716,
    "preview": "[\n    {\n        \"name\": \"Full App Display Modified\",\n        \"description\": \"View your music at a glance.\",\n        \"pre"
  },
  {
    "path": "playlistIntersection/README.md",
    "chars": 2769,
    "preview": "# playlistIntersection\nFilename : `playlistIntersection.js`\n\nAdds context menu buttons to see \n* songs in common between"
  },
  {
    "path": "playlistIntersection/playlistIntersection.js",
    "chars": 30740,
    "preview": "//@ts-check\n// NAME: playlistIntersection\n// AUTHOR: huhridge\n// DESCRIPTION: Adds context menu buttons to see songs in "
  },
  {
    "path": "skipStats/README.md",
    "chars": 2341,
    "preview": "# skipStats\n\nFilename : `skipStats.js`\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/m"
  },
  {
    "path": "skipStats/skipStats.js",
    "chars": 12162,
    "preview": "// @ts-check\n// NAME: Skip Stats\n// AUTHOR: huhridge\n// DESCRIPTION: Tracks skipping stats in playlists and albums.\n//TO"
  }
]

About this extraction

This page contains the full source code of the huhridge/huh-spicetify-extensions GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (147.4 KB), approximately 32.9k tokens, and a symbol index with 49 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!