├── .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 |  
5 |
6 | It also includes settings to totally hide the volume percentage, and to show/hide decimals (2 points) of the percentage.
7 |
8 | 
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 `