├── .gitignore ├── img ├── volume-percentage.png ├── volume-percentage-edit-mode.png └── volume-percentage-settings.png ├── manifest.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── volumePercentage.js └── spicetify.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /img/volume-percentage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroentvb/spicetify-volume-percentage/HEAD/img/volume-percentage.png -------------------------------------------------------------------------------- /img/volume-percentage-edit-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroentvb/spicetify-volume-percentage/HEAD/img/volume-percentage-edit-mode.png -------------------------------------------------------------------------------- /img/volume-percentage-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroentvb/spicetify-volume-percentage/HEAD/img/volume-percentage-settings.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Volume percentage and edit", 3 | "description": "Show an editable volume percentage next to the volume bar, allowing percise volume adjustments.", 4 | "preview": "img/volume-percentage.png", 5 | "main": "volumePercentage.js", 6 | "readme": "README.md", 7 | "authors": [{ "name": "jeroentvb", "url": "https://github.com/jeroentvb" }] 8 | } 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.1.0 2 | * Wait for volume bar wrapper to exist preventing a breaking error. 3 | * Add notification to let the user know if there was an error. 4 | * Fix reading and updating volume percentage 5 | 6 | # 2.0.0 7 | * Update and refactor for spicetify 2 8 | * Add settings menu to show/hide percentage and/or the whole volume percentage element 9 | * Improve volume percentage edit UX 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2021 James Chen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spicetify volume percentage 2 | This extension adds the percentage your volume is set at to the right of the volume bar, as seen below. It also has an edit mode, so you can precisely adjust your volume. 3 | 4 | ![volume percentage next to volume bar](img/volume-percentage.png) ![volume percentage in edit mode](img/volume-percentage-edit-mode.png) 5 | 6 | It also includes settings to totally hide the volume percentage, and to show/hide decimals (2 points) of the percentage. 7 | 8 | ![volume percentage settings](img/volume-percentage-settings.png) 9 | 10 | ## Installation 11 | Install from the [marketplace](https://github.com/spicetify/spicetify-marketplace). 12 | 13 | Or install manually: 14 | 15 | Copy `volumePercentage.js` to the spicetify extenstions folder 16 | | **Platform** | **Path** | 17 | |-----------------|----------------------------------------| 18 | | **MacOs/Linux** | `~/.config/spicetify/Extensions` | 19 | | **Windows** | `%appdata%\spicetify\Extensions` | 20 | 21 | Run the following commands 22 | ```sh 23 | spicetify config extensions volumePercentage.js 24 | spicetify apply 25 | ``` 26 | 27 | ## Credits 28 | This repo was originally forked from [jamesrchen/Spicetify-volumePercent](https://github.com/jamesrchen/Spicetify-volumePercent). 29 | -------------------------------------------------------------------------------- /volumePercentage.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | // NAME: Volume Percent 4 | // VERSION: 2.1.1 5 | // DESCRIPTION: Add volume percentage to the right of the volume bar, and an entry in the settings menu to enable/dsable decimals and volume percentage. 6 | // AUTHOR: jeroentvb (https://github.com/jeroentvb) 7 | // CREDITS: based on original version by unknownguy2002 -- Improved by p0rtL (https://github.com/p0rtL6) 8 | 9 | /// 10 | 11 | (async function volumePercent(){ 12 | const NOW_PLAYING_CONTROLS_WRAPPER = '.main-nowPlayingBar-extraControls' 13 | const VOLUME_BAR_WRAPPER_CLASS = '.main-nowPlayingBar-volumeBar'; 14 | 15 | // Check if Spicetify has loaded 16 | const { Platform, Menu, LocalStorage } = Spicetify; 17 | if (!Platform || !Menu || !LocalStorage) { 18 | setTimeout(volumePercent, 300); 19 | return; 20 | } 21 | 22 | try { 23 | /** 24 | * Volume percent extension settings 25 | */ 26 | /** @type {boolean} */ 27 | let showVolumePercent = JSON.parse(LocalStorage.get('showVolumePercent')) ?? true; 28 | /** @type {boolean} */ 29 | let showVolumePercentDecimals = JSON.parse(LocalStorage.get('showVolumePercentDecimals')) ?? false; 30 | 31 | /** 32 | * Get reference to volume bar wrapper element and add back original width if volume percentage is hidden 33 | */ 34 | const volumeBarWrapper = await getVolumeBarWrapper(); 35 | if (!showVolumePercent) volumeBarWrapper.classList.add('inherit-flex'); 36 | 37 | /** 38 | * Create and render volume percentage element 39 | */ 40 | const volumePercentageElement = await createVolumePercentageElement(); 41 | volumeBarWrapper.append(volumePercentageElement); 42 | 43 | /** 44 | * Create and render volume percentage edit element 45 | */ 46 | const volumePercentageEditElement = createVolumePercentageEditElement(); 47 | volumeBarWrapper.append(volumePercentageEditElement); 48 | 49 | /** 50 | * Watch volumebar changes and update volumePercentageElement percentage 51 | */ 52 | const volumeBar = document.querySelector(`${VOLUME_BAR_WRAPPER_CLASS} [data-testid="progress-bar"]`); 53 | const observer = new MutationObserver(() => updateDisplayPercentage()); 54 | observer.observe(volumeBar, { 55 | attributes: true, 56 | childList: true, 57 | subtree: true 58 | }); 59 | 60 | /** 61 | * Add styles for the extension ui 62 | */ 63 | addStylesheet(); 64 | 65 | /** 66 | * Add settings menu 67 | */ 68 | const menuItems = [ 69 | new Menu.Item('Show volume percentage', showVolumePercent, (menuItem) => { 70 | showVolumePercent = !showVolumePercent; 71 | 72 | LocalStorage.set('showVolumePercent', JSON.stringify(showVolumePercent)); 73 | menuItem.setState(showVolumePercent); 74 | 75 | volumePercentageElement.classList.toggle('hide'); 76 | volumeBarWrapper.classList.toggle('inherit-flex'); 77 | }), 78 | 79 | new Menu.Item('Show decimals', showVolumePercentDecimals, (menuItem) => { 80 | showVolumePercentDecimals = !showVolumePercentDecimals; 81 | 82 | LocalStorage.set('showVolumePercentDecimals', JSON.stringify(showVolumePercentDecimals)); 83 | menuItem.setState(showVolumePercentDecimals); 84 | 85 | updateDisplayPercentage(); 86 | }), 87 | ] 88 | 89 | new Menu.SubMenu('Volume percentage', menuItems).register(); 90 | 91 | /** 92 | * Wait for the volume bar wrapper to exist before returning it 93 | * @returns {Promise} 94 | */ 95 | function getVolumeBarWrapper() { 96 | return new Promise(resolve => { 97 | const elementExists = setInterval(() => { 98 | // main-nowPlayingBar-extraControls 99 | const volumeBarWrapper = document.querySelector(`${NOW_PLAYING_CONTROLS_WRAPPER} ${VOLUME_BAR_WRAPPER_CLASS}`); 100 | 101 | if (volumeBarWrapper) { 102 | clearInterval(elementExists); 103 | resolve(volumeBarWrapper); 104 | } 105 | }, 300); 106 | }) 107 | } 108 | 109 | /** 110 | * Parses the passed in or current spotify volume to a human readable percentage 111 | * @param {number} [volume] 112 | * @returns {Promise} 113 | */ 114 | async function getDisplayVolumePercentage(volume) { 115 | if (!volume) volume = await Platform.PlaybackAPI.getVolume(); 116 | 117 | const volumePercentage = volume * 100; 118 | const roundedPercentage = showVolumePercentDecimals 119 | ? Math.round(volumePercentage * 100) / 100 // Rounds to 2 decimals 120 | : Math.round(volumePercentage); // Round without decimals 121 | 122 | return `${roundedPercentage}%`; 123 | } 124 | 125 | async function updateDisplayPercentage() { 126 | volumePercentageElement.textContent = await getDisplayVolumePercentage(); 127 | } 128 | 129 | /** 130 | * Create the paragraph that will show the volume percentage 131 | * @returns {Promise} 132 | */ 133 | async function createVolumePercentageElement() { 134 | // Create button 135 | const p = document.createElement('button'); 136 | 137 | p.textContent = await getDisplayVolumePercentage(); 138 | p.setAttribute('data-tooltip', 'volume'); 139 | p.setAttribute('id', 'volume-percentage'); 140 | // button.setAttribute('contentEditable', 'true'); 141 | 142 | // Hide element if settings say so 143 | if (!showVolumePercent) p.classList.add('hide'); 144 | 145 | // Handle click event 146 | p.addEventListener('click', onVolumePercentageClick); 147 | 148 | return p; 149 | } 150 | 151 | /** 152 | * Create the input that will be used to edit the volume percentage 153 | * @returns {HTMLInputElement} 154 | */ 155 | function createVolumePercentageEditElement() { 156 | // Create input 157 | const input = document.createElement('input'); 158 | 159 | input.setAttribute('type', 'number'); 160 | input.setAttribute('id', 'volume-percentage-edit'); 161 | input.setAttribute('min', '0'); 162 | input.setAttribute('max', '100'); 163 | input.classList.add('generic-hidden'); 164 | input.value = ''; 165 | 166 | // Handle events 167 | input.addEventListener('change', onVolumeChange); 168 | input.addEventListener('focusout', toggleEditMode); 169 | input.addEventListener('keydown', (e) => { 170 | // Toggling display and edit elements is done by focusout event which is triggered by the blur function. 171 | if (e.key === 'Enter') input.blur(); 172 | }) 173 | 174 | return input; 175 | } 176 | 177 | function toggleEditMode() { 178 | volumePercentageElement.classList.toggle('generic-hidden'); 179 | volumePercentageEditElement.classList.toggle('generic-hidden'); 180 | } 181 | 182 | /** 183 | * Set edit value. Show and focus on edit element 184 | */ 185 | async function onVolumePercentageClick() { 186 | const volume = await Platform.PlaybackAPI.getVolume(); 187 | const roundedVolume = Math.round(volume * 100); 188 | 189 | volumePercentageEditElement.value = roundedVolume.toString(); 190 | toggleEditMode(); 191 | volumePercentageEditElement.focus(); 192 | } 193 | 194 | /** 195 | * Update volume based on edit value 196 | */ 197 | function onVolumeChange() { 198 | const percentage = parseFloat(volumePercentageEditElement.value.replace(/[^0-9]/g, '')); 199 | const volume = percentage / 100; 200 | 201 | if (isNaN(volume)){ 202 | Spicetify.showNotification('Invalid input, only integers or floats accepted'); 203 | return 204 | } 205 | 206 | Platform.PlaybackAPI.setVolume(volume); 207 | } 208 | 209 | function addStylesheet() { 210 | const style = document.createElement('style'); 211 | style.textContent = ` 212 | #volume-percentage { 213 | background-color: transparent; 214 | border: none; 215 | flex: 0 1 100px; 216 | margin-left: 1em; 217 | font-size: 14px; 218 | } 219 | 220 | #volume-percentage:hover { 221 | color: #fff; 222 | } 223 | 224 | #volume-percentage.hide { 225 | display: none; 226 | } 227 | 228 | 229 | #volume-percentage-edit { 230 | color: #fff; 231 | flex: 0 1 75px; 232 | margin: 0 12px; 233 | background-color: transparent; 234 | border: none; 235 | border-bottom: 1px solid #fff; 236 | text-align: center; 237 | font-size: 14px; 238 | } 239 | 240 | 241 | ${NOW_PLAYING_CONTROLS_WRAPPER} ${VOLUME_BAR_WRAPPER_CLASS} { 242 | flex-basis: 200px; 243 | } 244 | 245 | ${NOW_PLAYING_CONTROLS_WRAPPER} ${VOLUME_BAR_WRAPPER_CLASS}.inherit-flex { 246 | flex-basis: 125px; 247 | } 248 | 249 | 250 | .generic-hidden { 251 | display: none; 252 | } 253 | 254 | input::-webkit-outer-spin-button, 255 | input::-webkit-inner-spin-button { 256 | -webkit-appearance: none; 257 | margin: 0; 258 | } 259 | 260 | input[type=number] { 261 | -moz-appearance:textfield; /* Firefox */ 262 | } 263 | `; 264 | 265 | document.body.appendChild(style); 266 | } 267 | } catch (err) { 268 | Spicetify.showNotification('An error ocurred while loading volume percentage. Check the console for more info'); 269 | 270 | console.error(err); 271 | } 272 | })() 273 | -------------------------------------------------------------------------------- /spicetify.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Spicetify { 2 | type Icon = 3 | | "album" 4 | | "artist" 5 | | "block" 6 | | "brightness" 7 | | "car" 8 | | "chart-down" 9 | | "chart-up" 10 | | "check" 11 | | "check-alt-fill" 12 | | "chevron-left" 13 | | "chevron-right" 14 | | "chromecast-disconnected" 15 | | "clock" 16 | | "collaborative" 17 | | "computer" 18 | | "copy" 19 | | "download" 20 | | "downloaded" 21 | | "edit" 22 | | "enhance" 23 | | "exclamation-circle" 24 | | "external-link" 25 | | "facebook" 26 | | "follow" 27 | | "fullscreen" 28 | | "gamepad" 29 | | "grid-view" 30 | | "heart" 31 | | "heart-active" 32 | | "instagram" 33 | | "laptop" 34 | | "library" 35 | | "list-view" 36 | | "location" 37 | | "locked" 38 | | "locked-active" 39 | | "lyrics" 40 | | "menu" 41 | | "minimize" 42 | | "minus" 43 | | "more" 44 | | "new-spotify-connect" 45 | | "offline" 46 | | "pause" 47 | | "phone" 48 | | "play" 49 | | "playlist" 50 | | "playlist-folder" 51 | | "plus-alt" 52 | | "plus2px" 53 | | "podcasts" 54 | | "projector" 55 | | "queue" 56 | | "repeat" 57 | | "repeat-once" 58 | | "search" 59 | | "search-active" 60 | | "shuffle" 61 | | "skip-back" 62 | | "skip-back15" 63 | | "skip-forward" 64 | | "skip-forward15" 65 | | "soundbetter" 66 | | "speaker" 67 | | "spotify" 68 | | "subtitles" 69 | | "tablet" 70 | | "ticket" 71 | | "twitter" 72 | | "visualizer" 73 | | "voice" 74 | | "volume" 75 | | "volume-off" 76 | | "volume-one-wave" 77 | | "volume-two-wave" 78 | | "watch" 79 | | "x"; 80 | type Variant = 81 | | "bass" 82 | | "forte" 83 | | "brio" 84 | | "altoBrio" 85 | | "alto" 86 | | "canon" 87 | | "celloCanon" 88 | | "cello" 89 | | "ballad" 90 | | "balladBold" 91 | | "viola" 92 | | "violaBold" 93 | | "mesto" 94 | | "mestoBold" 95 | | "metronome" 96 | | "finale" 97 | | "finaleBold" 98 | | "minuet" 99 | | "minuetBold"; 100 | type SemanticColor = 101 | | "textBase" 102 | | "textSubdued" 103 | | "textBrightAccent" 104 | | "textNegative" 105 | | "textWarning" 106 | | "textPositive" 107 | | "textAnnouncement" 108 | | "essentialBase" 109 | | "essentialSubdued" 110 | | "essentialBrightAccent" 111 | | "essentialNegative" 112 | | "essentialWarning" 113 | | "essentialPositive" 114 | | "essentialAnnouncement" 115 | | "decorativeBase" 116 | | "decorativeSubdued" 117 | | "backgroundBase" 118 | | "backgroundHighlight" 119 | | "backgroundPress" 120 | | "backgroundElevatedBase" 121 | | "backgroundElevatedHighlight" 122 | | "backgroundElevatedPress" 123 | | "backgroundTintedBase" 124 | | "backgroundTintedHighlight" 125 | | "backgroundTintedPress" 126 | | "backgroundUnsafeForSmallTextBase" 127 | | "backgroundUnsafeForSmallTextHighlight" 128 | | "backgroundUnsafeForSmallTextPress"; 129 | type ColorSet = 130 | | "base" 131 | | "brightAccent" 132 | | "negative" 133 | | "warning" 134 | | "positive" 135 | | "announcement" 136 | | "invertedDark" 137 | | "invertedLight" 138 | | "mutedAccent" 139 | | "overMedia"; 140 | type ColorSetBackgroundColors = { 141 | base: string; 142 | highlight: string; 143 | press: string; 144 | }; 145 | type ColorSetNamespaceColors = { 146 | announcement: string; 147 | base: string; 148 | brightAccent: string; 149 | negative: string; 150 | positive: string; 151 | subdued: string; 152 | warning: string; 153 | }; 154 | type ColorSetBody = { 155 | background: ColorSetBackgroundColors & { 156 | elevated: ColorSetBackgroundColors; 157 | tinted: ColorSetBackgroundColors; 158 | unsafeForSmallText: ColorSetBackgroundColors; 159 | }; 160 | decorative: { 161 | base: string; 162 | subdued: string; 163 | }; 164 | essential: ColorSetNamespaceColors; 165 | text: ColorSetNamespaceColors; 166 | }; 167 | type Metadata = Partial>; 168 | type ContextTrack = { 169 | uri: string; 170 | uid?: string; 171 | metadata?: Metadata; 172 | }; 173 | type PlayerState = { 174 | timestamp: number; 175 | context: PlayerContext; 176 | index: PlayerIndex; 177 | item: PlayerTrack; 178 | shuffle: boolean; 179 | repeat: number; 180 | speed: number; 181 | positionAsOfTimestamp: number; 182 | duration: number; 183 | hasContext: boolean; 184 | isPaused: boolean; 185 | isBuffering: boolean; 186 | restrictions: Restrictions; 187 | previousItems?: PlayerTrack[]; 188 | nextItems?: PlayerTrack[]; 189 | playbackQuality: PlaybackQuality; 190 | playbackId: string; 191 | sessionId: string; 192 | signals?: any[]; 193 | /** 194 | * @deprecated Use `item` instead. This will be removed in the future. 195 | */ 196 | track: PlayerTrack; 197 | }; 198 | type PlayerContext = { 199 | uri: string; 200 | url: string; 201 | metadata: { 202 | "player.arch": string; 203 | }; 204 | }; 205 | type PlayerIndex = { 206 | pageURI?: string | null; 207 | pageIndex: number; 208 | itemIndex: number; 209 | }; 210 | type PlayerTrack = { 211 | type: string; 212 | uri: string; 213 | uid: string; 214 | name: string; 215 | mediaType: string; 216 | duration: { 217 | milliseconds: number; 218 | }; 219 | album: Album; 220 | artists?: ArtistsEntity[]; 221 | isLocal: boolean; 222 | isExplicit: boolean; 223 | is19PlusOnly: boolean; 224 | provider: string; 225 | metadata: TrackMetadata; 226 | images?: ImagesEntity[]; 227 | }; 228 | type TrackMetadata = { 229 | artist_uri: string; 230 | entity_uri: string; 231 | iteration: string; 232 | title: string; 233 | "collection.is_banned": string; 234 | "artist_uri:1": string; 235 | "collection.in_collection": string; 236 | image_small_url: string; 237 | "collection.can_ban": string; 238 | is_explicit: string; 239 | album_disc_number: string; 240 | album_disc_count: string; 241 | track_player: string; 242 | album_title: string; 243 | "collection.can_add": string; 244 | image_large_url: string; 245 | "actions.skipping_prev_past_track": string; 246 | page_instance_id: string; 247 | image_xlarge_url: string; 248 | marked_for_download: string; 249 | "actions.skipping_next_past_track": string; 250 | context_uri: string; 251 | "artist_name:1": string; 252 | has_lyrics: string; 253 | interaction_id: string; 254 | image_url: string; 255 | album_uri: string; 256 | album_artist_name: string; 257 | album_track_number: string; 258 | artist_name: string; 259 | duration: string; 260 | album_track_count: string; 261 | popularity: string; 262 | }; 263 | type Album = { 264 | type: string; 265 | uri: string; 266 | name: string; 267 | images?: ImagesEntity[]; 268 | }; 269 | type ImagesEntity = { 270 | url: string; 271 | label: string; 272 | }; 273 | type ArtistsEntity = { 274 | type: string; 275 | uri: string; 276 | name: string; 277 | }; 278 | type Restrictions = { 279 | canPause: boolean; 280 | canResume: boolean; 281 | canSeek: boolean; 282 | canSkipPrevious: boolean; 283 | canSkipNext: boolean; 284 | canToggleRepeatContext: boolean; 285 | canToggleRepeatTrack: boolean; 286 | canToggleShuffle: boolean; 287 | disallowPausingReasons?: string[]; 288 | disallowResumingReasons?: string[]; 289 | disallowSeekingReasons?: string[]; 290 | disallowSkippingPreviousReasons?: string[]; 291 | disallowSkippingNextReasons?: string[]; 292 | disallowTogglingRepeatContextReasons?: string[]; 293 | disallowTogglingRepeatTrackReasons?: string[]; 294 | disallowTogglingShuffleReasons?: string[]; 295 | disallowTransferringPlaybackReasons?: string[]; 296 | }; 297 | type PlaybackQuality = { 298 | bitrateLevel: number; 299 | strategy: number; 300 | targetBitrateLevel: number; 301 | targetBitrateAvailable: boolean; 302 | hifiStatus: number; 303 | }; 304 | namespace Player { 305 | /** 306 | * Register a listener `type` on Spicetify.Player. 307 | * 308 | * On default, `Spicetify.Player` always dispatch: 309 | * - `songchange` type when player changes track. 310 | * - `onplaypause` type when player plays or pauses. 311 | * - `onprogress` type when track progress changes. 312 | * - `appchange` type when user changes page. 313 | */ 314 | function addEventListener(type: string, callback: (event?: Event) => void): void; 315 | function addEventListener(type: "songchange", callback: (event?: Event & { data: PlayerState }) => void): void; 316 | function addEventListener(type: "onplaypause", callback: (event?: Event & { data: PlayerState }) => void): void; 317 | function addEventListener(type: "onprogress", callback: (event?: Event & { data: number }) => void): void; 318 | function addEventListener( 319 | type: "appchange", 320 | callback: ( 321 | event?: Event & { 322 | data: { 323 | /** 324 | * App href path 325 | */ 326 | path: string; 327 | /** 328 | * App container 329 | */ 330 | container: HTMLElement; 331 | }; 332 | } 333 | ) => void 334 | ): void; 335 | /** 336 | * Skip to previous track. 337 | */ 338 | function back(): void; 339 | /** 340 | * An object contains all information about current track and player. 341 | */ 342 | const data: PlayerState; 343 | /** 344 | * Decrease a small amount of volume. 345 | */ 346 | function decreaseVolume(): void; 347 | /** 348 | * Dispatches an event at `Spicetify.Player`. 349 | * 350 | * On default, `Spicetify.Player` always dispatch 351 | * - `songchange` type when player changes track. 352 | * - `onplaypause` type when player plays or pauses. 353 | * - `onprogress` type when track progress changes. 354 | * - `appchange` type when user changes page. 355 | */ 356 | function dispatchEvent(event: Event): void; 357 | const eventListeners: { 358 | [key: string]: Array<(event?: Event) => void>; 359 | }; 360 | /** 361 | * Convert milisecond to `mm:ss` format 362 | * @param milisecond 363 | */ 364 | function formatTime(milisecond: number): string; 365 | /** 366 | * Return song total duration in milisecond. 367 | */ 368 | function getDuration(): number; 369 | /** 370 | * Return mute state 371 | */ 372 | function getMute(): boolean; 373 | /** 374 | * Return elapsed duration in milisecond. 375 | */ 376 | function getProgress(): number; 377 | /** 378 | * Return elapsed duration in percentage (0 to 1). 379 | */ 380 | function getProgressPercent(): number; 381 | /** 382 | * Return current Repeat state (No repeat = 0/Repeat all = 1/Repeat one = 2). 383 | */ 384 | function getRepeat(): number; 385 | /** 386 | * Return current shuffle state. 387 | */ 388 | function getShuffle(): boolean; 389 | /** 390 | * Return track heart state. 391 | */ 392 | function getHeart(): boolean; 393 | /** 394 | * Return current volume level (0 to 1). 395 | */ 396 | function getVolume(): number; 397 | /** 398 | * Increase a small amount of volume. 399 | */ 400 | function increaseVolume(): void; 401 | /** 402 | * Return a boolean whether player is playing. 403 | */ 404 | function isPlaying(): boolean; 405 | /** 406 | * Skip to next track. 407 | */ 408 | function next(): void; 409 | /** 410 | * Pause track. 411 | */ 412 | function pause(): void; 413 | /** 414 | * Resume track. 415 | */ 416 | function play(): void; 417 | /** 418 | * Play a track, playlist, album, etc. immediately 419 | * @param uri Spotify URI 420 | * @param context 421 | * @param options 422 | */ 423 | function playUri(uri: string, context?: any, options?: any): Promise; 424 | /** 425 | * Unregister added event listener `type`. 426 | * @param type 427 | * @param callback 428 | */ 429 | function removeEventListener(type: string, callback: (event?: Event) => void): void; 430 | /** 431 | * Seek track to position. 432 | * @param position can be in percentage (0 to 1) or in milisecond. 433 | */ 434 | function seek(position: number): void; 435 | /** 436 | * Turn mute on/off 437 | * @param state 438 | */ 439 | function setMute(state: boolean): void; 440 | /** 441 | * Change Repeat mode 442 | * @param mode `0` No repeat. `1` Repeat all. `2` Repeat one track. 443 | */ 444 | function setRepeat(mode: number): void; 445 | /** 446 | * Turn shuffle on/off. 447 | * @param state 448 | */ 449 | function setShuffle(state: boolean): void; 450 | /** 451 | * Set volume level 452 | * @param level 0 to 1 453 | */ 454 | function setVolume(level: number): void; 455 | /** 456 | * Seek to previous `amount` of milisecond 457 | * @param amount in milisecond. Default: 15000. 458 | */ 459 | function skipBack(amount?: number): void; 460 | /** 461 | * Seek to next `amount` of milisecond 462 | * @param amount in milisecond. Default: 15000. 463 | */ 464 | function skipForward(amount?: number): void; 465 | /** 466 | * Toggle Heart (Favourite) track state. 467 | */ 468 | function toggleHeart(): void; 469 | /** 470 | * Toggle Mute/No mute. 471 | */ 472 | function toggleMute(): void; 473 | /** 474 | * Toggle Play/Pause. 475 | */ 476 | function togglePlay(): void; 477 | /** 478 | * Toggle No repeat/Repeat all/Repeat one. 479 | */ 480 | function toggleRepeat(): void; 481 | /** 482 | * Toggle Shuffle/No shuffle. 483 | */ 484 | function toggleShuffle(): void; 485 | } 486 | /** 487 | * Adds a track or array of tracks to prioritized queue. 488 | */ 489 | function addToQueue(uri: ContextTrack[]): Promise; 490 | /** 491 | * @deprecated 492 | */ 493 | const BridgeAPI: any; 494 | /** 495 | * @deprecated 496 | */ 497 | const CosmosAPI: any; 498 | /** 499 | * Async wrappers of CosmosAPI 500 | */ 501 | namespace CosmosAsync { 502 | type Method = "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "SUB"; 503 | interface Error { 504 | code: number; 505 | error: string; 506 | message: string; 507 | stack?: string; 508 | } 509 | 510 | type Headers = Record; 511 | type Body = Record; 512 | 513 | interface Response { 514 | body: any; 515 | headers: Headers; 516 | status: number; 517 | uri?: string; 518 | } 519 | 520 | function head(url: string, headers?: Headers): Promise; 521 | function get(url: string, body?: Body, headers?: Headers): Promise; 522 | function post(url: string, body?: Body, headers?: Headers): Promise; 523 | function put(url: string, body?: Body, headers?: Headers): Promise; 524 | function del(url: string, body?: Body, headers?: Headers): Promise; 525 | function patch(url: string, body?: Body, headers?: Headers): Promise; 526 | function sub( 527 | url: string, 528 | callback: (b: Response["body"]) => void, 529 | onError?: (e: Error) => void, 530 | body?: Body, 531 | headers?: Headers 532 | ): Promise; 533 | function postSub( 534 | url: string, 535 | body: Body | null, 536 | callback: (b: Response["body"]) => void, 537 | onError?: (e: Error) => void 538 | ): Promise; 539 | function request(method: Method, url: string, body?: Body, headers?: Headers): Promise; 540 | function resolve(method: Method, url: string, body?: Body, headers?: Headers): Promise; 541 | } 542 | /** 543 | * Fetch interesting colors from URI. 544 | * @param uri Any type of URI that has artwork (playlist, track, album, artist, show, ...) 545 | */ 546 | function colorExtractor(uri: string): Promise<{ 547 | DESATURATED: string; 548 | LIGHT_VIBRANT: string; 549 | PROMINENT: string; 550 | VIBRANT: string; 551 | VIBRANT_NON_ALARMING: string; 552 | }>; 553 | /** 554 | * @deprecated 555 | */ 556 | function getAblumArtColors(): any; 557 | /** 558 | * Fetch track analyzed audio data. 559 | * Beware, not all tracks have audio data. 560 | * @param uri is optional. Leave it blank to get current track 561 | * or specify another track uri. 562 | */ 563 | function getAudioData(uri?: string): Promise; 564 | /** 565 | * Set of APIs method to register, deregister hotkeys/shortcuts 566 | */ 567 | namespace Keyboard { 568 | type ValidKey = 569 | | "BACKSPACE" 570 | | "TAB" 571 | | "ENTER" 572 | | "SHIFT" 573 | | "CTRL" 574 | | "ALT" 575 | | "CAPS" 576 | | "ESCAPE" 577 | | "SPACE" 578 | | "PAGE_UP" 579 | | "PAGE_DOWN" 580 | | "END" 581 | | "HOME" 582 | | "ARROW_LEFT" 583 | | "ARROW_UP" 584 | | "ARROW_RIGHT" 585 | | "ARROW_DOWN" 586 | | "INSERT" 587 | | "DELETE" 588 | | "A" 589 | | "B" 590 | | "C" 591 | | "D" 592 | | "E" 593 | | "F" 594 | | "G" 595 | | "H" 596 | | "I" 597 | | "J" 598 | | "K" 599 | | "L" 600 | | "M" 601 | | "N" 602 | | "O" 603 | | "P" 604 | | "Q" 605 | | "R" 606 | | "S" 607 | | "T" 608 | | "U" 609 | | "V" 610 | | "W" 611 | | "X" 612 | | "Y" 613 | | "Z" 614 | | "WINDOW_LEFT" 615 | | "WINDOW_RIGHT" 616 | | "SELECT" 617 | | "NUMPAD_0" 618 | | "NUMPAD_1" 619 | | "NUMPAD_2" 620 | | "NUMPAD_3" 621 | | "NUMPAD_4" 622 | | "NUMPAD_5" 623 | | "NUMPAD_6" 624 | | "NUMPAD_7" 625 | | "NUMPAD_8" 626 | | "NUMPAD_9" 627 | | "MULTIPLY" 628 | | "ADD" 629 | | "SUBTRACT" 630 | | "DECIMAL_POINT" 631 | | "DIVIDE" 632 | | "F1" 633 | | "F2" 634 | | "F3" 635 | | "F4" 636 | | "F5" 637 | | "F6" 638 | | "F7" 639 | | "F8" 640 | | "F9" 641 | | "F10" 642 | | "F11" 643 | | "F12" 644 | | ";" 645 | | "=" 646 | | " | " 647 | | "-" 648 | | "." 649 | | "/" 650 | | "`" 651 | | "[" 652 | | "\\" 653 | | "]" 654 | | '"' 655 | | "~" 656 | | "!" 657 | | "@" 658 | | "#" 659 | | "$" 660 | | "%" 661 | | "^" 662 | | "&" 663 | | "*" 664 | | "(" 665 | | ")" 666 | | "_" 667 | | "+" 668 | | ":" 669 | | "<" 670 | | ">" 671 | | "?" 672 | | "|"; 673 | type KeysDefine = 674 | | string 675 | | { 676 | key: string; 677 | ctrl?: boolean; 678 | shift?: boolean; 679 | alt?: boolean; 680 | meta?: boolean; 681 | }; 682 | const KEYS: Record; 683 | function registerShortcut(keys: KeysDefine, callback: (event: KeyboardEvent) => void): void; 684 | function registerIsolatedShortcut(keys: KeysDefine, callback: (event: KeyboardEvent) => void): void; 685 | function registerImportantShortcut(keys: KeysDefine, callback: (event: KeyboardEvent) => void): void; 686 | function _deregisterShortcut(keys: KeysDefine): void; 687 | function deregisterImportantShortcut(keys: KeysDefine): void; 688 | function changeShortcut(keys: KeysDefine, newKeys: KeysDefine): void; 689 | } 690 | 691 | /** 692 | * @deprecated 693 | */ 694 | const LiveAPI: any; 695 | 696 | namespace LocalStorage { 697 | /** 698 | * Empties the list associated with the object of all key/value pairs, if there are any. 699 | */ 700 | function clear(): void; 701 | /** 702 | * Get key value 703 | */ 704 | function get(key: string): string | null; 705 | /** 706 | * Delete key 707 | */ 708 | function remove(key: string): void; 709 | /** 710 | * Set new value for key 711 | */ 712 | function set(key: string, value: string): void; 713 | } 714 | /** 715 | * To create and prepend custom menu item in profile menu. 716 | */ 717 | namespace Menu { 718 | /** 719 | * Create a single toggle. 720 | */ 721 | class Item { 722 | constructor(name: string, isEnabled: boolean, onClick: (self: Item) => void, icon?: Icon | string); 723 | name: string; 724 | isEnabled: boolean; 725 | /** 726 | * Change item name 727 | */ 728 | setName(name: string): void; 729 | /** 730 | * Change item enabled state. 731 | * Visually, item would has a tick next to it if its state is enabled. 732 | */ 733 | setState(isEnabled: boolean): void; 734 | /** 735 | * Change icon 736 | */ 737 | setIcon(icon: Icon | string): void; 738 | /** 739 | * Item is only available in Profile menu when method "register" is called. 740 | */ 741 | register(): void; 742 | /** 743 | * Stop item to be prepended into Profile menu. 744 | */ 745 | deregister(): void; 746 | } 747 | 748 | /** 749 | * Create a sub menu to contain Item toggles. 750 | * `Item`s in `subItems` array shouldn't be registered. 751 | */ 752 | class SubMenu { 753 | constructor(name: string, subItems: Item[]); 754 | name: string; 755 | /** 756 | * Change SubMenu name 757 | */ 758 | setName(name: string): void; 759 | /** 760 | * Add an item to sub items list 761 | */ 762 | addItem(item: Item); 763 | /** 764 | * Remove an item from sub items list 765 | */ 766 | removeItem(item: Item); 767 | /** 768 | * SubMenu is only available in Profile menu when method "register" is called. 769 | */ 770 | register(): void; 771 | /** 772 | * Stop SubMenu to be prepended into Profile menu. 773 | */ 774 | deregister(): void; 775 | } 776 | } 777 | 778 | /** 779 | * Keyboard shortcut library 780 | * 781 | * Documentation: https://craig.is/killing/mice v1.6.5 782 | * 783 | * Spicetify.Keyboard is wrapper of this library to be compatible with legacy Spotify, 784 | * so new extension should use this library instead. 785 | */ 786 | function Mousetrap(element?: any): void; 787 | 788 | /** 789 | * Contains vast array of internal APIs. 790 | * Please explore in Devtool Console. 791 | */ 792 | const Platform: any; 793 | /** 794 | * Queue object contains list of queuing tracks, 795 | * history of played tracks and current track metadata. 796 | */ 797 | const Queue: { 798 | nextTracks: any[]; 799 | prevTracks: any[]; 800 | queueRevision: string; 801 | track: any; 802 | }; 803 | /** 804 | * Remove a track or array of tracks from current queue. 805 | */ 806 | function removeFromQueue(uri: ContextTrack[]): Promise; 807 | /** 808 | * Display a bubble of notification. Useful for a visual feedback. 809 | * @param message Message to display. Can use inline HTML for styling. 810 | * @param isError If true, bubble will be red. Defaults to false. 811 | * @param msTimeout Time in milliseconds to display the bubble. Defaults to Spotify's value. 812 | */ 813 | function showNotification(message: React.ReactNode, isError?: boolean, msTimeout?: number): void; 814 | /** 815 | * Set of APIs method to parse and validate URIs. 816 | */ 817 | class URI { 818 | constructor(type: string, props: any); 819 | public type: string; 820 | public hasBase62Id: boolean; 821 | 822 | public id?: string; 823 | public disc?: any; 824 | public args?: any; 825 | public category?: string; 826 | public username?: string; 827 | public track?: string; 828 | public artist?: string; 829 | public album?: string; 830 | public duration?: number; 831 | public query?: string; 832 | public country?: string; 833 | public global?: boolean; 834 | public context?: string | typeof URI | null; 835 | public anchor?: string; 836 | public play?: any; 837 | public toplist?: any; 838 | 839 | /** 840 | * 841 | * @return The URI representation of this uri. 842 | */ 843 | toURI(): string; 844 | 845 | /** 846 | * 847 | * @return The URI representation of this uri. 848 | */ 849 | toString(): string; 850 | 851 | /** 852 | * Get the URL path of this uri. 853 | * 854 | * @param opt_leadingSlash True if a leading slash should be prepended. 855 | * @return The path of this uri. 856 | */ 857 | toURLPath(opt_leadingSlash: boolean): string; 858 | 859 | /** 860 | * 861 | * @param origin The origin to use for the URL. 862 | * @return The URL string for the uri. 863 | */ 864 | toURL(origin?: string): string; 865 | 866 | /** 867 | * Clones a given SpotifyURI instance. 868 | * 869 | * @return An instance of URI. 870 | */ 871 | clone(): URI | null; 872 | 873 | /** 874 | * Gets the path of the URI object by removing all hash and query parameters. 875 | * 876 | * @return The path of the URI object. 877 | */ 878 | getPath(): string; 879 | 880 | /** 881 | * The various URI Types. 882 | * 883 | * Note that some of the types in this enum are not real URI types, but are 884 | * actually URI particles. They are marked so. 885 | * 886 | */ 887 | static Type: { 888 | AD: string; 889 | ALBUM: string; 890 | GENRE: string; 891 | QUEUE: string; 892 | APPLICATION: string; 893 | ARTIST: string; 894 | ARTIST_TOPLIST: string; 895 | ARTIST_CONCERTS: string; 896 | AUDIO_FILE: string; 897 | COLLECTION: string; 898 | COLLECTION_ALBUM: string; 899 | COLLECTION_ARTIST: string; 900 | COLLECTION_MISSING_ALBUM: string; 901 | COLLECTION_TRACK_LIST: string; 902 | CONCERT: string; 903 | CONTEXT_GROUP: string; 904 | DAILY_MIX: string; 905 | EMPTY: string; 906 | EPISODE: string; 907 | /** URI particle; not an actual URI. */ 908 | FACEBOOK: string; 909 | FOLDER: string; 910 | FOLLOWERS: string; 911 | FOLLOWING: string; 912 | IMAGE: string; 913 | INBOX: string; 914 | INTERRUPTION: string; 915 | LIBRARY: string; 916 | LIVE: string; 917 | ROOM: string; 918 | EXPRESSION: string; 919 | LOCAL: string; 920 | LOCAL_TRACK: string; 921 | LOCAL_ALBUM: string; 922 | LOCAL_ARTIST: string; 923 | MERCH: string; 924 | MOSAIC: string; 925 | PLAYLIST: string; 926 | PLAYLIST_V2: string; 927 | PRERELEASE: string; 928 | PROFILE: string; 929 | PUBLISHED_ROOTLIST: string; 930 | RADIO: string; 931 | ROOTLIST: string; 932 | SEARCH: string; 933 | SHOW: string; 934 | SOCIAL_SESSION: string; 935 | SPECIAL: string; 936 | STARRED: string; 937 | STATION: string; 938 | TEMP_PLAYLIST: string; 939 | TOPLIST: string; 940 | TRACK: string; 941 | TRACKSET: string; 942 | USER_TOPLIST: string; 943 | USER_TOP_TRACKS: string; 944 | UNKNOWN: string; 945 | MEDIA: string; 946 | QUESTION: string; 947 | POLL: string; 948 | }; 949 | 950 | /** 951 | * Creates a new URI object from a parsed string argument. 952 | * 953 | * @param str The string that will be parsed into a URI object. 954 | * @throws TypeError If the string argument is not a valid URI, a TypeError will 955 | * be thrown. 956 | * @return The parsed URI object. 957 | */ 958 | static fromString(str: string): URI; 959 | 960 | /** 961 | * Parses a given object into a URI instance. 962 | * 963 | * Unlike URI.fromString, this function could receive any kind of value. If 964 | * the value is already a URI instance, it is simply returned. 965 | * Otherwise the value will be stringified before parsing. 966 | * 967 | * This function also does not throw an error like URI.fromString, but 968 | * instead simply returns null if it can't parse the value. 969 | * 970 | * @param value The value to parse. 971 | * @return The corresponding URI instance, or null if the 972 | * passed value is not a valid value. 973 | */ 974 | static from(value: any): URI | null; 975 | 976 | /** 977 | * Checks whether two URI:s refer to the same thing even though they might 978 | * not necessarily be equal. 979 | * 980 | * These two Playlist URIs, for example, refer to the same playlist: 981 | * 982 | * spotify:user:napstersean:playlist:3vxotOnOGDlZXyzJPLFnm2 983 | * spotify:playlist:3vxotOnOGDlZXyzJPLFnm2 984 | * 985 | * @param baseUri The first URI to compare. 986 | * @param refUri The second URI to compare. 987 | * @return Whether they shared idenitity 988 | */ 989 | static isSameIdentity(baseUri: URI | string, refUri: URI | string): boolean; 990 | 991 | /** 992 | * Returns the hex representation of a Base62 encoded id. 993 | * 994 | * @param id The base62 encoded id. 995 | * @return The hex representation of the base62 id. 996 | */ 997 | static idToHex(id: string): string; 998 | 999 | /** 1000 | * Returns the base62 representation of a hex encoded id. 1001 | * 1002 | * @param hex The hex encoded id. 1003 | * @return The base62 representation of the id. 1004 | */ 1005 | static hexToId(hex: string): string; 1006 | 1007 | /** 1008 | * Creates a new 'album' type URI. 1009 | * 1010 | * @param id The id of the album. 1011 | * @param disc The disc number of the album. 1012 | * @return The album URI. 1013 | */ 1014 | static albumURI(id: string, disc: number): URI; 1015 | 1016 | /** 1017 | * Creates a new 'application' type URI. 1018 | * 1019 | * @param id The id of the application. 1020 | * @param args An array containing the arguments to the app. 1021 | * @return The application URI. 1022 | */ 1023 | static applicationURI(id: string, args: string[]): URI; 1024 | 1025 | /** 1026 | * Creates a new 'artist' type URI. 1027 | * 1028 | * @param id The id of the artist. 1029 | * @return The artist URI. 1030 | */ 1031 | static artistURI(id: string): URI; 1032 | 1033 | /** 1034 | * Creates a new 'collection' type URI. 1035 | * 1036 | * @param username The non-canonical username of the rootlist owner. 1037 | * @param category The category of the collection. 1038 | * @return The collection URI. 1039 | */ 1040 | static collectionURI(username: string, category: string): URI; 1041 | 1042 | /** 1043 | * Creates a new 'collection-album' type URI. 1044 | * 1045 | * @param username The non-canonical username of the rootlist owner. 1046 | * @param id The id of the album. 1047 | * @return The collection album URI. 1048 | */ 1049 | static collectionAlbumURI(username: string, id: string): URI; 1050 | 1051 | /** 1052 | * Creates a new 'collection-artist' type URI. 1053 | * 1054 | * @param username The non-canonical username of the rootlist owner. 1055 | * @param id The id of the artist. 1056 | * @return The collection artist URI. 1057 | */ 1058 | static collectionAlbumURI(username: string, id: string): URI; 1059 | 1060 | /** 1061 | * Creates a new 'concert' type URI. 1062 | * 1063 | * @param id The id of the concert. 1064 | * @return The concert URI. 1065 | */ 1066 | static concertURI(id: string): URI; 1067 | 1068 | /** 1069 | * Creates a new 'episode' type URI. 1070 | * 1071 | * @param id The id of the episode. 1072 | * @return The episode URI. 1073 | */ 1074 | static episodeURI(id: string): URI; 1075 | 1076 | /** 1077 | * Creates a new 'folder' type URI. 1078 | * 1079 | * @param id The id of the folder. 1080 | * @return The folder URI. 1081 | */ 1082 | static folderURI(id: string): URI; 1083 | 1084 | /** 1085 | * Creates a new 'local-album' type URI. 1086 | * 1087 | * @param artist The artist of the album. 1088 | * @param album The name of the album. 1089 | * @return The local album URI. 1090 | */ 1091 | static localAlbumURI(artist: string, album: string): URI; 1092 | 1093 | /** 1094 | * Creates a new 'local-artist' type URI. 1095 | * 1096 | * @param artist The name of the artist. 1097 | * @return The local artist URI. 1098 | */ 1099 | static localArtistURI(artist: string): URI; 1100 | 1101 | /** 1102 | * Creates a new 'playlist-v2' type URI. 1103 | * 1104 | * @param id The id of the playlist. 1105 | * @return The playlist URI. 1106 | */ 1107 | static playlistV2URI(id: string): URI; 1108 | 1109 | /** 1110 | * Creates a new 'prerelease' type URI. 1111 | * 1112 | * @param id The id of the prerelease. 1113 | * @return The prerelease URI. 1114 | */ 1115 | static prereleaseURI(id: string): URI; 1116 | 1117 | /** 1118 | * Creates a new 'profile' type URI. 1119 | * 1120 | * @param username The non-canonical username of the rootlist owner. 1121 | * @param args A list of arguments. 1122 | * @return The profile URI. 1123 | */ 1124 | static profileURI(username: string, args: string[]): URI; 1125 | 1126 | /** 1127 | * Creates a new 'search' type URI. 1128 | * 1129 | * @param query The unencoded search query. 1130 | * @return The search URI 1131 | */ 1132 | static searchURI(query: string): URI; 1133 | 1134 | /** 1135 | * Creates a new 'show' type URI. 1136 | * 1137 | * @param id The id of the show. 1138 | * @return The show URI. 1139 | */ 1140 | static showURI(id: string): URI; 1141 | 1142 | /** 1143 | * Creates a new 'station' type URI. 1144 | * 1145 | * @param args An array of arguments for the station. 1146 | * @return The station URI. 1147 | */ 1148 | static stationURI(args: string[]): URI; 1149 | 1150 | /** 1151 | * Creates a new 'track' type URI. 1152 | * 1153 | * @param id The id of the track. 1154 | * @param anchor The point in the track formatted as mm:ss 1155 | * @param context An optional context URI 1156 | * @param play Toggles autoplay 1157 | * @return The track URI. 1158 | */ 1159 | static trackURI(id: string, anchor: string, context?: string, play?: boolean): URI; 1160 | 1161 | /** 1162 | * Creates a new 'user-toplist' type URI. 1163 | * 1164 | * @param username The non-canonical username of the toplist owner. 1165 | * @param toplist The toplist type. 1166 | * @return The user-toplist URI. 1167 | */ 1168 | static userToplistURI(username: string, toplist: string): URI; 1169 | 1170 | static isAd(uri: URI | string): boolean; 1171 | static isAlbum(uri: URI | string): boolean; 1172 | static isGenre(uri: URI | string): boolean; 1173 | static isQueue(uri: URI | string): boolean; 1174 | static isApplication(uri: URI | string): boolean; 1175 | static isArtist(uri: URI | string): boolean; 1176 | static isArtistToplist(uri: URI | string): boolean; 1177 | static isArtistConcerts(uri: URI | string): boolean; 1178 | static isAudioFile(uri: URI | string): boolean; 1179 | static isCollection(uri: URI | string): boolean; 1180 | static isCollectionAlbum(uri: URI | string): boolean; 1181 | static isCollectionArtist(uri: URI | string): boolean; 1182 | static isCollectionMissingAlbum(uri: URI | string): boolean; 1183 | static isCollectionTrackList(uri: URI | string): boolean; 1184 | static isConcert(uri: URI | string): boolean; 1185 | static isContextGroup(uri: URI | string): boolean; 1186 | static isDailyMix(uri: URI | string): boolean; 1187 | static isEmpty(uri: URI | string): boolean; 1188 | static isEpisode(uri: URI | string): boolean; 1189 | static isFacebook(uri: URI | string): boolean; 1190 | static isFolder(uri: URI | string): boolean; 1191 | static isFollowers(uri: URI | string): boolean; 1192 | static isFollowing(uri: URI | string): boolean; 1193 | static isImage(uri: URI | string): boolean; 1194 | static isInbox(uri: URI | string): boolean; 1195 | static isInterruption(uri: URI | string): boolean; 1196 | static isLibrary(uri: URI | string): boolean; 1197 | static isLive(uri: URI | string): boolean; 1198 | static isRoom(uri: URI | string): boolean; 1199 | static isExpression(uri: URI | string): boolean; 1200 | static isLocal(uri: URI | string): boolean; 1201 | static isLocalTrack(uri: URI | string): boolean; 1202 | static isLocalAlbum(uri: URI | string): boolean; 1203 | static isLocalArtist(uri: URI | string): boolean; 1204 | static isMerch(uri: URI | string): boolean; 1205 | static isMosaic(uri: URI | string): boolean; 1206 | static isPlaylist(uri: URI | string): boolean; 1207 | static isPlaylistV2(uri: URI | string): boolean; 1208 | static isPrerelease(uri: URI | string): boolean; 1209 | static isProfile(uri: URI | string): boolean; 1210 | static isPublishedRootlist(uri: URI | string): boolean; 1211 | static isRadio(uri: URI | string): boolean; 1212 | static isRootlist(uri: URI | string): boolean; 1213 | static isSearch(uri: URI | string): boolean; 1214 | static isShow(uri: URI | string): boolean; 1215 | static isSocialSession(uri: URI | string): boolean; 1216 | static isSpecial(uri: URI | string): boolean; 1217 | static isStarred(uri: URI | string): boolean; 1218 | static isStation(uri: URI | string): boolean; 1219 | static isTempPlaylist(uri: URI | string): boolean; 1220 | static isToplist(uri: URI | string): boolean; 1221 | static isTrack(uri: URI | string): boolean; 1222 | static isTrackset(uri: URI | string): boolean; 1223 | static isUserToplist(uri: URI | string): boolean; 1224 | static isUserTopTracks(uri: URI | string): boolean; 1225 | static isUnknown(uri: URI | string): boolean; 1226 | static isMedia(uri: URI | string): boolean; 1227 | static isQuestion(uri: URI | string): boolean; 1228 | static isPoll(uri: URI | string): boolean; 1229 | static isPlaylistV1OrV2(uri: URI | string): boolean; 1230 | } 1231 | 1232 | /** 1233 | * Create custom menu item and prepend to right click context menu 1234 | */ 1235 | namespace ContextMenu { 1236 | type OnClickCallback = (uris: string[], uids?: string[], contextUri?: string) => void; 1237 | type ShouldAddCallback = (uris: string[], uids?: string[], contextUri?: string) => boolean; 1238 | 1239 | // Single context menu item 1240 | class Item { 1241 | /** 1242 | * List of valid icons to use. 1243 | */ 1244 | static readonly iconList: Icon[]; 1245 | constructor(name: string, onClick: OnClickCallback, shouldAdd?: ShouldAddCallback, icon?: Icon, disabled?: boolean); 1246 | name: string; 1247 | icon: Icon | string; 1248 | disabled: boolean; 1249 | /** 1250 | * A function returning boolean determines whether item should be prepended. 1251 | */ 1252 | shouldAdd: ShouldAddCallback; 1253 | /** 1254 | * A function to call when item is clicked 1255 | */ 1256 | onClick: OnClickCallback; 1257 | /** 1258 | * Item is only available in Context Menu when method "register" is called. 1259 | */ 1260 | register: () => void; 1261 | /** 1262 | * Stop Item to be prepended into Context Menu. 1263 | */ 1264 | deregister: () => void; 1265 | } 1266 | 1267 | /** 1268 | * Create a sub menu to contain `Item`s. 1269 | * `Item`s in `subItems` array shouldn't be registered. 1270 | */ 1271 | class SubMenu { 1272 | constructor(name: string, subItems: Iterable, shouldAdd?: ShouldAddCallback, disabled?: boolean); 1273 | name: string; 1274 | disabled: boolean; 1275 | /** 1276 | * A function returning boolean determines whether item should be prepended. 1277 | */ 1278 | shouldAdd: ShouldAddCallback; 1279 | addItem: (item: Item) => void; 1280 | removeItem: (item: Item) => void; 1281 | /** 1282 | * SubMenu is only available in Context Menu when method "register" is called. 1283 | */ 1284 | register: () => void; 1285 | /** 1286 | * Stop SubMenu to be prepended into Context Menu. 1287 | */ 1288 | deregister: () => void; 1289 | } 1290 | } 1291 | 1292 | /** 1293 | * Popup Modal 1294 | */ 1295 | namespace PopupModal { 1296 | interface Content { 1297 | title: string; 1298 | /** 1299 | * You can specify a string for simple text display 1300 | * or a HTML element for interactive config/setting menu 1301 | */ 1302 | content: string | Element; 1303 | /** 1304 | * Bigger window 1305 | */ 1306 | isLarge?: boolean; 1307 | } 1308 | 1309 | function display(e: Content): void; 1310 | function hide(): void; 1311 | } 1312 | 1313 | /** React instance to create components */ 1314 | const React: any; 1315 | /** React DOM instance to render and mount components */ 1316 | const ReactDOM: any; 1317 | /** React DOM Server instance to render components to string */ 1318 | const ReactDOMServer: any; 1319 | 1320 | /** Stock React components exposed from Spotify library */ 1321 | namespace ReactComponent { 1322 | type ContextMenuProps = { 1323 | /** 1324 | * Decide whether to use the global singleton context menu (rendered in ) 1325 | * or a new inline context menu (rendered in a sibling 1326 | * element to `children`) 1327 | */ 1328 | renderInline?: boolean; 1329 | /** 1330 | * Determins what will trigger the context menu. For example, a click, or a right-click 1331 | */ 1332 | trigger?: "click" | "right-click"; 1333 | /** 1334 | * Determins is the context menu should open or toggle when triggered 1335 | */ 1336 | action?: "toggle" | "open"; 1337 | /** 1338 | * The preferred placement of the context menu when it opens. 1339 | * Relative to trigger element. 1340 | */ 1341 | placement?: 1342 | | "top" 1343 | | "top-start" 1344 | | "top-end" 1345 | | "right" 1346 | | "right-start" 1347 | | "right-end" 1348 | | "bottom" 1349 | | "bottom-start" 1350 | | "bottom-end" 1351 | | "left" 1352 | | "left-start" 1353 | | "left-end"; 1354 | /** 1355 | * The x and y offset distances at which the context menu should open. 1356 | * Relative to trigger element and `position`. 1357 | */ 1358 | offset?: [number, number]; 1359 | /** 1360 | * Will stop the client from scrolling while the context menu is open 1361 | */ 1362 | preventScrollingWhileOpen?: boolean; 1363 | /** 1364 | * The menu UI to render inside of the context menu. 1365 | */ 1366 | menu: 1367 | | typeof Spicetify.ReactComponent.Menu 1368 | | typeof Spicetify.ReactComponent.AlbumMenu 1369 | | typeof Spicetify.ReactComponent.PodcastShowMenu 1370 | | typeof Spicetify.ReactComponent.ArtistMenu 1371 | | typeof Spicetify.ReactComponent.PlaylistMenu; 1372 | /** 1373 | * A child of the context menu. Should be `