├── .gitattributes ├── .gitignore ├── @Resources ├── 7z.dll ├── 7z.exe ├── AutoRestart.ahk ├── AutoRestart.exe ├── Fonts │ ├── Font Awesome 5 Free-Solid-900.otf │ ├── bahnschrift._semibold.ttf │ ├── bahnschrift_bold.ttf │ ├── bahnschrift_light.ttf │ ├── bahnschrift_reg.ttf │ └── fa-regular-400.ttf ├── JavascriptInject │ ├── jquery-3.3.1.min.js │ └── spicetifyWrapper.js ├── RainRGB4.exe ├── Script.lua └── shadow.png ├── Apps └── reddit │ ├── bundle.js │ ├── css │ └── style.css │ ├── index.html │ └── manifest.json ├── Extensions ├── Autoskipvideo.js ├── DJMode.js ├── QueueAll.js ├── Shuffle+.js ├── Trashbin.js ├── autoSkipExplicit.js └── wnpPlugin.js ├── LICENSE ├── LOGO.svg ├── README.md ├── Spicetify.ini ├── Themes └── SpicetifyDefault │ ├── color.inc │ └── user.css ├── clean.ps1 └── globals.d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.spa 2 | 3 | console.log 4 | @Resources/Backup 5 | @Resources/Extracted 6 | @Resources/robocopy_transfer_log.txt 7 | @Resources/robocopy_transfer_raw_log.txt 8 | @Resources/robocopy_copyapp_log.txt 9 | 10 | node_modules 11 | yarn-error.log 12 | yarn.lock 13 | -------------------------------------------------------------------------------- /@Resources/7z.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/7z.dll -------------------------------------------------------------------------------- /@Resources/7z.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/7z.exe -------------------------------------------------------------------------------- /@Resources/AutoRestart.ahk: -------------------------------------------------------------------------------- 1 | #NoEnv 2 | #NoTrayIcon 3 | #SingleInstance, force 4 | ; #Warn ; Enable warnings to assist with detecting common errors. 5 | 6 | WinGet, hwnd, PID, ahk_exe Spotify.exe 7 | Process, Close, %hwnd% 8 | WinWaitClose, ahk_exe Spotify.exe 9 | Run, %A_AppData%\Spotify\Spotify.exe -------------------------------------------------------------------------------- /@Resources/AutoRestart.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/AutoRestart.exe -------------------------------------------------------------------------------- /@Resources/Fonts/Font Awesome 5 Free-Solid-900.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/Fonts/Font Awesome 5 Free-Solid-900.otf -------------------------------------------------------------------------------- /@Resources/Fonts/bahnschrift._semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/Fonts/bahnschrift._semibold.ttf -------------------------------------------------------------------------------- /@Resources/Fonts/bahnschrift_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/Fonts/bahnschrift_bold.ttf -------------------------------------------------------------------------------- /@Resources/Fonts/bahnschrift_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/Fonts/bahnschrift_light.ttf -------------------------------------------------------------------------------- /@Resources/Fonts/bahnschrift_reg.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/Fonts/bahnschrift_reg.ttf -------------------------------------------------------------------------------- /@Resources/Fonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/Fonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /@Resources/RainRGB4.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/RainRGB4.exe -------------------------------------------------------------------------------- /@Resources/shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khanhas/Spicetify/dc93c277c03ff0ddebd4653952aa760fd5cd3140/@Resources/shadow.png -------------------------------------------------------------------------------- /Apps/reddit/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | GLUE Tokens - DLS 4 | 5 | The contents of this file is owned and generated by the DLS team. 6 | If you require changes making to the values stored here please reach out 7 | to @dls on Slack. 8 | 9 | Links: 10 | 11 | - Source: https://ghe.spotify.net/design/glue-tokens 12 | - Documentation: https://ghe.spotify.net/design/glue-tokens 13 | 14 | Tack! 15 | 16 | The DLS Team 17 | 18 | */ 19 | /** 20 | * Spoticons 21 | * 22 | * Variable Set: Spoticons 23 | * 24 | * Styleguide 1.2.1 25 | */ 26 | /** 27 | * Common color usages 28 | * 29 | * These colors are provided because they might be needed in many places where 30 | * they should be synced up. 31 | */ 32 | /** 33 | * Vertical Grid System 34 | * -------------------- 35 | * 36 | * We try to adhere to a baseline grid, which is a vertical grid with lines 37 | * every X pixels. The baseline of text should always fall on this line. 38 | */ 39 | /** 40 | * Horizontal Grid System 41 | * ---------------------- 42 | * 43 | * This grid system is responsive in four sizes, where all sizes use a 12 column 44 | * grid. 45 | * 46 | * Since the values are calculated, you need to compile the Less code to see the 47 | * values. From glue, run `node tools/output-grid-values.js` to see 48 | * the values. 49 | * 50 | * Here's a reference for what to use the variables for: 51 | * 52 | * Min Width (including scroll bar) 53 | * -------------------------------- 54 | * Including scroll bar: $glue-screen-min 55 | * Excluding scroll bar: $glue-body-width-min 56 | * 57 | * Max Width (of content, space on the sides) 58 | * ------------------------------------------ 59 | * Including scroll bar: $glue-screen-max 60 | * Excluding scroll bar: $glue-body-width-max 61 | * 62 | * Page Gutter (gutter on the sides of the page) 63 | * --------------------------------------------- 64 | * Size: $glue-grid-page-gutter 65 | * 66 | * Extra Small (xs) 67 | * ---------------- 68 | * Gutter size: $glue-grid-column-gutter-xs 69 | * View width range: $glue-screen-min to $glue-screen-xs-max 70 | * 71 | * Small (sm) 72 | * ---------- 73 | * Gutter size: $glue-grid-column-gutter-sm 74 | * View width range: $glue-screen-sm-min to $glue-screen-sm-max 75 | * 76 | * Medium (md) 77 | * ----------- 78 | * Gutter size: $glue-grid-column-gutter-md 79 | * View width range: $glue-screen-md-min to $glue-screen-md-max 80 | * 81 | * Large (lg) 82 | * ---------- 83 | * Gutter size: $glue-grid-column-gutter-lg 84 | * View width range: $glue-screen-lg-min to $glue-screen-lg-max 85 | */ 86 | .context-menu-container { 87 | position: absolute; 88 | width: 100%; 89 | height: 100%; 90 | top: 0; 91 | left: 0; 92 | } 93 | 94 | /** 95 | * 1. This creates a stacking context, which forces the scroll bar to be 96 | * rendered on top of any content (the scroll bar is added through the JS 97 | * by adding `overflow-y: scroll` when items don't fit). 98 | */ 99 | .context-menu { 100 | padding: 8px 0; 101 | position: absolute; 102 | display: none; 103 | width: auto; 104 | min-width: 160px; 105 | max-width: 350px; 106 | color: var(--modspotify_secondary_fg); 107 | background-color: var(--modspotify_main_bg); 108 | box-shadow: 0 4px 12px 4px rgba(var(--modspotify_rgb_cover_overlay_and_shadow),0.5); 109 | border-radius: 8px; 110 | isolation: isolate; 111 | /* [1] */ 112 | } 113 | 114 | .scrollbar-style-visible-mac .context-menu::-webkit-scrollbar-track, 115 | .scrollbar-style-visible-windows .context-menu::-webkit-scrollbar-track { 116 | background-color: var(--modspotify_main_bg); 117 | } 118 | 119 | .scrollbar-style-visible-mac .context-menu::-webkit-scrollbar-button, 120 | .scrollbar-style-visible-windows .context-menu::-webkit-scrollbar-button { 121 | background-color: var(--modspotify_main_bg); 122 | } 123 | 124 | .body-container--windows .context-menu, 125 | .body-container--linux .context-menu { 126 | border-radius: 0; 127 | } 128 | 129 | .context-menu .item { 130 | padding: 7px 34px; 131 | position: relative; 132 | white-space: nowrap; 133 | overflow: hidden; 134 | text-overflow: ellipsis; 135 | cursor: default; 136 | line-height: 18px; 137 | } 138 | 139 | .context-menu .sep { 140 | display: block; 141 | margin: 4px 0; 142 | height: 1px; 143 | background-color: var(--modspotify_slider_bg); 144 | } 145 | 146 | .context-menu .item.disabled { 147 | opacity: 0.4; 148 | } 149 | 150 | .context-menu .filter-search { 151 | position: fixed; 152 | left: 0; 153 | right: 0; 154 | height: 0; 155 | width: 0; 156 | opacity: 0; 157 | } 158 | 159 | .context-menu .filter-search input:focus { 160 | outline: 0; 161 | } 162 | 163 | .context-menu .item.with-icon { 164 | padding-left: 12px; 165 | } 166 | 167 | .context-menu .item.with-icon::before { 168 | padding-right: 6px; 169 | } 170 | 171 | .context-menu .item.with-icon-svg::before { 172 | content: ''; 173 | background-image: var(--svg-icon); 174 | background-repeat: no-repeat; 175 | background-size: contain; 176 | display: inline-block; 177 | width: 16px; 178 | height: 16px; 179 | vertical-align: top; 180 | position: relative; 181 | top: 1px; 182 | } 183 | 184 | .context-menu .item span { 185 | pointer-events: none; 186 | } 187 | 188 | .context-menu .item.secondary { 189 | display: flex; 190 | flex-direction: row; 191 | justify-content: space-between; 192 | padding-right: 10px; 193 | } 194 | 195 | .context-menu .item.secondary .text { 196 | overflow: hidden; 197 | text-overflow: ellipsis; 198 | } 199 | 200 | .context-menu .item.secondary .secondary { 201 | margin-left: 20px; 202 | letter-spacing: 1px; 203 | flex-shrink: 0; 204 | } 205 | 206 | .context-menu .filtered { 207 | display: none; 208 | } 209 | 210 | .context-menu .item .highlight { 211 | background-color: var(--modspotify_miscellaneous_bg); 212 | color: var(--modspotify_main_fg); 213 | padding: 0 3px; 214 | border-radius: 2px; 215 | } 216 | 217 | .context-menu .item.parent::after { 218 | font-family: "glue-spoticon"; 219 | font-style: normal; 220 | font-weight: normal; 221 | -webkit-font-smoothing: antialiased; 222 | display: inline-block; 223 | line-height: inherit; 224 | vertical-align: bottom; 225 | content: "\f110"; 226 | display: block; 227 | position: absolute; 228 | right: 24px; 229 | top: 8px; 230 | width: 0; 231 | height: 0; 232 | } 233 | 234 | .context-menu .item.hover:not(.disabled) { 235 | background-color: var(--modspotify_slider_bg); 236 | color: var(--modspotify_main_fg); 237 | cursor: default; 238 | } 239 | 240 | .context-menu .item.active { 241 | background-color: var(--modspotify_slider_bg); 242 | color: var(--modspotify_main_fg); 243 | } 244 | 245 | .context-menu .item:last-child { 246 | border: 0; 247 | } 248 | 249 | .app-header { 250 | position: relative; 251 | z-index: 2; 252 | } 253 | 254 | .app-content { 255 | position: relative; 256 | z-index: 1; 257 | will-change: transform; 258 | } 259 | 260 | .profile-section { 261 | contain: paint; 262 | } 263 | 264 | .profile { 265 | margin: 0 auto; 266 | } 267 | 268 | .profile > .error { 269 | margin-top: 40px; 270 | } 271 | 272 | .profile > .playlists, 273 | .profile > .recent-artists, 274 | .profile > .followers, 275 | .profile > .following { 276 | display: none; 277 | } 278 | 279 | .media-info.has-meta.has-subtitle { 280 | min-height: 9em; 281 | } 282 | 283 | .no-section-divider { 284 | padding-top: 32px; 285 | } 286 | 287 | .header-media { 288 | position: relative; 289 | } 290 | 291 | .media-image-edit:hover { 292 | opacity: 1; 293 | } 294 | 295 | .overlay { 296 | display: none; 297 | } 298 | 299 | .overlay.show { 300 | display: block; 301 | position: fixed; 302 | top: 0; 303 | bottom: 0; 304 | left: 0; 305 | right: 0; 306 | z-index: 99999; 307 | background-color: rgba(var(--modspotify_rgb_cover_overlay_and_shadow),0.7); 308 | } 309 | 310 | .section-throbber { 311 | position: absolute; 312 | top: 50%; 313 | left: 50%; 314 | margin-top: -20px; 315 | margin-left: -20px; 316 | } 317 | 318 | .loading-playlists { 319 | margin-bottom: 20px; 320 | } 321 | 322 | .legal-permission { 323 | font-size: 11px; 324 | line-height: 16px; 325 | letter-spacing: 0.015em; 326 | display: inline-flex; 327 | width: 80%; 328 | height: 32px; 329 | align-items: center; 330 | } 331 | 332 | .profile-controllers { 333 | display: flex; 334 | } 335 | 336 | .following .list-group-item { 337 | border-top: 0; 338 | } 339 | 340 | .followers .list-group-item { 341 | border-top: 0; 342 | } 343 | 344 | .loading-playlists-throbber { 345 | margin: 0 auto; 346 | } 347 | 348 | .empty-state { 349 | margin-top: 30px; 350 | } 351 | 352 | .empty-state i { 353 | color: var(--modspotify_secondary_fg); 354 | line-height: 68px; 355 | } 356 | 357 | .empty-state i::before { 358 | font-family: "glue-spoticon"; 359 | font-style: normal; 360 | font-weight: normal; 361 | -webkit-font-smoothing: antialiased; 362 | display: inline-block; 363 | line-height: inherit; 364 | vertical-align: bottom; 365 | content: "\F28A"; 366 | font-size: 64px; 367 | line-height: inherit; 368 | } 369 | 370 | .empty-state .message { 371 | text-align: center; 372 | padding: 50px 0; 373 | } 374 | 375 | /* Investigate: It's a bit mysterious that we need to declare this, because 376 | this is what is declared in the context menu default CSS. */ 377 | #context-menu-container { 378 | z-index: 2147483647; 379 | } 380 | 381 | .error-and-offline { 382 | display: block; 383 | } 384 | 385 | .error-and-offline .error-content, 386 | .error-and-offline .offline-content { 387 | display: block; 388 | } 389 | 390 | .glue-page-header__content .glue-page-header__image:hover .background-stripe { 391 | opacity: 0.7; 392 | } 393 | 394 | .glue-page-header__content .glue-page-header__image:hover .add-image-label { 395 | color: var(--modspotify_main_fg); 396 | } 397 | 398 | .glue-page-header__content .glue-page-header__image:hover .display-hover { 399 | opacity: 1; 400 | } 401 | 402 | .glue-page-header .card-type-user { 403 | -webkit-clip-path: circle(50% at center); 404 | } 405 | 406 | .header-verified-check { 407 | display: inline-block; 408 | width: 20px; 409 | height: 20px; 410 | line-height: 20px; 411 | text-align: center; 412 | margin-left: 8px; 413 | border-radius: 100px; 414 | background-color: var(--modspotify_miscellaneous_bg); 415 | color: var(--modspotify_main_fg); 416 | } 417 | 418 | .header-verified-check::before { 419 | font-family: "glue-spoticon"; 420 | font-style: normal; 421 | font-weight: normal; 422 | -webkit-font-smoothing: antialiased; 423 | display: inline-block; 424 | line-height: inherit; 425 | vertical-align: bottom; 426 | content: "\f10a"; 427 | font-size: 12px; 428 | line-height: inherit; 429 | } 430 | 431 | /* 432 | REDDIT 433 | */ 434 | 435 | #reddit-option { 436 | height: 290px; 437 | } 438 | 439 | #reddit-subreddit { 440 | height: 290px; 441 | } 442 | 443 | #reddit-subreddit-list { 444 | margin-top: 10px; 445 | height: 270px; 446 | overflow: scroll; 447 | } 448 | 449 | .popover-footer { 450 | padding: 9px 14px; 451 | } 452 | 453 | .setting-row::after { 454 | content: ""; 455 | display: table; 456 | clear: both; 457 | } 458 | 459 | .setting-row .col { 460 | padding: 10px 0; 461 | vertical-align: middle; 462 | } 463 | 464 | .setting-row .col.description { 465 | float: left; 466 | padding-right: 15px; 467 | cursor: default; 468 | } 469 | 470 | .setting-row .col.action { 471 | float: right; 472 | text-align: right; 473 | } 474 | 475 | h4 { 476 | padding-bottom: 5px; 477 | } 478 | 479 | #youtube-container { 480 | display: none; 481 | position: fixed; 482 | left: 50%; 483 | top: 50%; 484 | z-index: 1040; 485 | right: auto; 486 | bottom: auto; 487 | margin-left: -300px; 488 | margin-top: -169px; 489 | width: 600px; 490 | height: 338px; 491 | overflow: hidden; 492 | border-radius: 8px !important; 493 | box-shadow: 0 10px 40px rgba(var(--modspotify_rgb_cover_overlay_and_shadow), 0.5); 494 | } 495 | 496 | .form-control { 497 | border-radius: 4px !important; 498 | } 499 | 500 | #autocomplete { 501 | opacity: 0; 502 | position: absolute; 503 | background-color: var(--modspotify_main_fg); 504 | color: var(--modspotify_main_bg); 505 | z-index: 1; 506 | width: 90%; 507 | border-radius: 4px; 508 | margin-top: 10px; 509 | box-shadow: 0 5px 20px rgba(var(--modspotify_rgb_cover_overlay_and_shadow), 0.5); 510 | transition: opacity 200ms cubic-bezier(0.3, 0, 0.7, 1); 511 | } 512 | 513 | #autocomplete div { 514 | padding: 0 10px; 515 | text-overflow: ellipsis; 516 | overflow: hidden; 517 | font-size: 16px; 518 | font-weight: 400; 519 | vertical-align: middle; 520 | line-height: 30px; 521 | } 522 | 523 | #autocomplete div.active { 524 | background-color: var(--modspotify_main_bg); 525 | color: var(--modspotify_main_fg); 526 | } -------------------------------------------------------------------------------- /Apps/reddit/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 52 |
53 |
54 |
55 | 56 |
57 |
58 | 77 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Apps/reddit/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "BundleIdentifier": "reddit", 3 | "BundleType": "Application", 4 | "BundleVersion": "1.0.77", 5 | "VendorIdentifier": "com.spotify", 6 | "UserInstallable": false, 7 | "AppDescription": { 8 | "en": "", 9 | "cs": "", 10 | "de": "", 11 | "el": "", 12 | "es": "", 13 | "es-419": "", 14 | "fi": "", 15 | "fr": "", 16 | "fr-CA": "", 17 | "hu": "", 18 | "id": "", 19 | "it": "", 20 | "ja": "", 21 | "nl": "", 22 | "pl": "", 23 | "pt-BR": "", 24 | "sv": "", 25 | "th": "", 26 | "tr": "", 27 | "vi": "", 28 | "zh-Hant": "", 29 | "zsm": "" 30 | }, 31 | "AppName": { 32 | "en": "Reddit", 33 | "cs": "Reddit", 34 | "de": "Reddit", 35 | "el": "Reddit", 36 | "es": "Reddit", 37 | "es-419": "Reddit", 38 | "fi": "Reddit", 39 | "fr": "Reddit", 40 | "fr-CA": "Reddit", 41 | "hu": "Reddit", 42 | "id": "Reddit", 43 | "it": "Reddit", 44 | "ja": "Reddit", 45 | "nl": "Reddit", 46 | "pl": "Reddit", 47 | "pt-BR": "Reddit", 48 | "sv": "Reddit", 49 | "th": "Reddit", 50 | "tr": "Reddit", 51 | "vi": "Reddit", 52 | "zh-Hant": "Reddit", 53 | "zsm": "Reddit" 54 | }, 55 | "SupportedLanguages": [ 56 | "cs", 57 | "de", 58 | "el", 59 | "en", 60 | "es", 61 | "es-419", 62 | "fi", 63 | "fr", 64 | "fr-CA", 65 | "hu", 66 | "id", 67 | "it", 68 | "ja", 69 | "nl", 70 | "pl", 71 | "pt-BR", 72 | "sv", 73 | "th", 74 | "tr", 75 | "vi", 76 | "zh-Hant", 77 | "zsm" 78 | ], 79 | "SupportedDeviceClasses": [ 80 | "Desktop" 81 | ], 82 | "ProvidedFeatures": { 83 | "clientStorage": true 84 | }, 85 | "BridgeDependencies": { 86 | "bridge-desktop": "0.25.0", 87 | "bridge-web": "2.50.0", 88 | "bridge-zelda": "1.0.0" 89 | }, 90 | "Dependencies": { 91 | "api": "1.0.0" 92 | }, 93 | "InjectScripts": false, 94 | "InjectStylesheets": false, 95 | "SkipLanguageValidation": true, 96 | "SkipUnrequireValidation": true, 97 | "SpmApp": true, 98 | "GitRevision": "758ebd7" 99 | } -------------------------------------------------------------------------------- /Extensions/Autoskipvideo.js: -------------------------------------------------------------------------------- 1 | // START METADATA 2 | // NAME: Auto Skip Video 3 | // AUTHOR: khanhas 4 | // DESCRIPTION: Auto skip video 5 | // END METADATA 6 | 7 | /// 8 | 9 | (function SkipVideo() { 10 | if (!Spicetify.Player.data) { 11 | setTimeout(SkipVideo, 2000); 12 | return; 13 | } 14 | Spicetify.Player.addEventListener("songchange", () => { 15 | //Ads are also video media type so I need to exclude them out. 16 | var isVideo = 17 | Spicetify.Player.data.track.metadata["media.type"] === "video" && 18 | !(Spicetify.Player.data.track.metadata.is_advertisement === "true"); 19 | if (isVideo) { 20 | Spicetify.Player.next(); 21 | } 22 | }); 23 | })(); 24 | -------------------------------------------------------------------------------- /Extensions/DJMode.js: -------------------------------------------------------------------------------- 1 | // START METADATA 2 | // NAME: DJ Mode 3 | // AUTHOR: khanhas 4 | // DESCRIPTION: Queue only mode, Hide all controls. Toggles in Profile menu. 5 | // END METADATA 6 | 7 | /// 8 | 9 | (function DJMode() { 10 | if (!Spicetify.LocalStorage || !Spicetify.addToQueue || !Spicetify.LibURI) { 11 | setTimeout(DJMode, 200); 12 | return; 13 | } 14 | 15 | let DJSetting = JSON.parse(Spicetify.LocalStorage.get("DJMode")); 16 | if (!DJSetting || typeof DJSetting !== "object") { 17 | DJSetting = { 18 | enabled: false, 19 | hideControls: false, 20 | }; 21 | Spicetify.LocalStorage.set("DJMode", JSON.stringify(DJSetting)); 22 | } 23 | 24 | var menuEl = $("#PopoverMenu-container"); 25 | 26 | // Observing profile menu 27 | var menuObserver = new MutationObserver(() => { 28 | const innerMenu = menuEl.find(".Menu__root-items"); 29 | innerMenu.prepend( 30 | `` 71 | ); 72 | const menu = $("#DJModeMenu"); 73 | menu.on("mouseover", () => { 74 | $("#DJModeSubMenu").addClass("open"); 75 | $(".MenuItem").removeClass("selected"); 76 | menu.addClass("selected"); 77 | }); 78 | menu.on("mouseleave", () => { 79 | $("#DJModeSubMenu").removeClass("open"); 80 | menu.removeClass("selected"); 81 | }); 82 | 83 | $("#DJModeToggle").on("click", () => { 84 | DJSetting.enabled = !DJSetting.enabled; 85 | Spicetify.LocalStorage.set("DJMode", JSON.stringify(DJSetting)); 86 | document.location.reload(); 87 | }); 88 | $("#DJModeToggleControl").on("click", () => { 89 | DJSetting.hideControls = !DJSetting.hideControls; 90 | showHideControl(DJSetting.hideControls); 91 | Spicetify.LocalStorage.set("DJMode", JSON.stringify(DJSetting)); 92 | if (DJSetting.hideControls) { 93 | $("#DJModeToggleControl").addClass( 94 | "MenuItemToggle--checked MenuItem--is-active" 95 | ); 96 | } else { 97 | $("#DJModeToggleControl").removeClass( 98 | "MenuItemToggle--checked MenuItem--is-active" 99 | ); 100 | } 101 | }); 102 | }); 103 | 104 | menuObserver.observe(menuEl[0], { childList: true }); 105 | 106 | if (!DJSetting.enabled) { 107 | // Do nothing when DJ Mode is off 108 | return; 109 | } 110 | 111 | const playerControl = $(".player-controls-container"); 112 | const extraControl = $(".extra-controls-container"); 113 | const nowPlayingAddButton = $(".view-player .nowplaying-add-button"); 114 | 115 | const IFRAME_HIDE_ELEMENT_LIST = 116 | [ 117 | '[data-ta-id="card-button-play"]', 118 | '[data-ta-id="card-button-add"]', 119 | '[data-ta-id="card-button-context-menu"]', 120 | "div.glue-page-header__buttons", 121 | "th.tl-more", 122 | ".tl-cell.tl-more", 123 | "th.tl-save", 124 | ".tl-cell.tl-save", 125 | "th.tl-feedback", 126 | ".tl-cell.tl-feedback", 127 | "th.tl-more", 128 | ".tl-cell.tl-more", 129 | ].join(",") + "{display: none !important}"; 130 | 131 | const EMBEDDED_HIDE_ELEMENT_LIST = 132 | [ 133 | "div.Header__buttons", 134 | '[data-ta-id="play-button"]', 135 | '[data-ta-id="card-button-add"]', 136 | '[data-ta-id="card-button-context-menu"]', 137 | '[data-ta-id="ta-table-cell-add"]', 138 | '[data-ta-id="ta-table-cell-more"]', 139 | 'th[aria-label=""]', 140 | ].join(",") + "{display: none !important}"; 141 | 142 | function showHideControl(hide) { 143 | if (hide) { 144 | playerControl.hide(); 145 | extraControl.hide(); 146 | nowPlayingAddButton.hide(); 147 | } else { 148 | playerControl.show(); 149 | extraControl.show(); 150 | nowPlayingAddButton.show(); 151 | } 152 | } 153 | 154 | showHideControl(DJSetting.hideControls); 155 | 156 | function isValidURI(uri) { 157 | const uriType = Spicetify.LibURI.from(uri).type; 158 | if ( 159 | !uri && 160 | uriType !== "album" && 161 | uriType !== "track" && 162 | uriType !== "episode" 163 | ) { 164 | return false; 165 | } 166 | return true; 167 | } 168 | 169 | function addClickToQueue(button, uri) { 170 | button.on("click", function() { 171 | Spicetify.addToQueue(uri, () => { 172 | Spicetify.BridgeAPI.request("track_metadata", [uri], (e, p) => { 173 | Spicetify.showNotification( 174 | `${p.name} - ${p.artists[0].name} added to queue` 175 | ); 176 | }); 177 | }); 178 | }); 179 | } 180 | 181 | function findActiveIframeAndChangeButtonIntent() { 182 | const activeIframe = $("iframe.active"); 183 | if (activeIframe.length > 0) { 184 | var doc = activeIframe.contents(); 185 | 186 | doc.find( 187 | ".tl-cell.tl-play, .tl-cell.tl-number, .tl-cell.tl-type" 188 | ).each(function() { 189 | var playButton = $(this).find(".button"); 190 | if (playButton.attr("djmode-injected") === "true") { 191 | return; 192 | } 193 | 194 | var songURI = $(this) 195 | .parent() 196 | .attr("data-uri"); 197 | if (!isValidURI(songURI)) { 198 | return; 199 | } 200 | 201 | // Remove all default interaction intent 202 | playButton.attr("data-button", ""); 203 | playButton.attr("data-ta-id", ""); 204 | playButton.attr("data-interaction-target", ""); 205 | playButton.attr("data-interaction-intent", ""); 206 | playButton.attr("data-log-click", ""); 207 | 208 | playButton.attr("djmode-injected", "true"); 209 | addClickToQueue(playButton, songURI); 210 | }); 211 | 212 | if (DJSetting.hideControls) { 213 | addCSS( 214 | doc, 215 | "IframeDJModeHideControl", 216 | IFRAME_HIDE_ELEMENT_LIST 217 | ); 218 | } else { 219 | removeCSS(doc, "IframeDJModeHideControl"); 220 | } 221 | } 222 | 223 | var embeddedApp = $(".embedded-app.active"); 224 | if (embeddedApp.length > 0) { 225 | embeddedApp.find(".TableCellTrackNumber").each(function() { 226 | var songURI = $(this) 227 | .parent() 228 | .attr("data-ta-uri"); 229 | 230 | if (!isValidURI(songURI)) { 231 | return; 232 | } 233 | 234 | $(this).on("mouseover", function() { 235 | var playButton = $(this).find( 236 | ".TableCellTrackNumber__button-wrapper" 237 | ); 238 | if (playButton.attr("djmode-injected") === "true") { 239 | return; 240 | } 241 | playButton.html( 242 | `` 246 | ); 247 | playButton.attr("djmode-injected", "true"); 248 | 249 | var newButton = $(this).find(".button"); 250 | addClickToQueue(newButton, songURI); 251 | }); 252 | }); 253 | 254 | if (DJSetting.hideControls) { 255 | addCSS( 256 | $(document), 257 | "EmbeddedDJModeHideControl", 258 | EMBEDDED_HIDE_ELEMENT_LIST 259 | ); 260 | } else { 261 | removeCSS($(document), "EmbeddedDJModeHideControl"); 262 | } 263 | } 264 | } 265 | 266 | function addCSS(doc, id, text) { 267 | if (doc.find("head #" + id).length == 0) { 268 | const style = $(`