├── .gitattributes
├── resource
└── preview.png
├── src
├── settings.json
├── types
│ ├── css-modules.d.ts
│ └── spicetify.d.ts
├── components
│ ├── extractLyrics.js
│ └── Lyrics.jsx
├── css
│ ├── app.module.scss
│ └── icon.svg
└── app.tsx
├── manifest.json
├── tsconfig.json
├── dist
├── style.css
└── manifest.json
├── package.json
├── install.sh
├── README.md
├── LICENSE
├── install.ps1
├── .gitignore
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | dist/* linguist-vendored
--------------------------------------------------------------------------------
/resource/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nuzair46/Lyrixed/HEAD/resource/preview.png
--------------------------------------------------------------------------------
/src/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "displayName": "Lyrixed",
3 | "nameId": "lyrixed",
4 | "icon": "css/icon.svg",
5 | "activeIcon": "css/icon.svg"
6 | }
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Lyrixed",
3 | "description": "Bringing back lyrics to Spotify free version with Genius.",
4 | "preview": "resource/preview.png",
5 | "readme": "README.md",
6 | "tags": ["lyrics"]
7 | }
--------------------------------------------------------------------------------
/src/types/css-modules.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css' {
2 | const classes: { [key: string]: string };
3 | export default classes;
4 | }
5 |
6 | declare module '*.module.scss' {
7 | const classes: { [key: string]: string };
8 | export default classes;
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "jsx": "react",
5 | "module": "commonjs",
6 | "resolveJsonModule": true,
7 | "outDir": "dist",
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "strict": true,
11 | "skipLibCheck": true
12 | },
13 | "include": [
14 | "./src/**/*.*"
15 | ]
16 | }
--------------------------------------------------------------------------------
/dist/style.css:
--------------------------------------------------------------------------------
1 | .app-module__container___thHYo_lyrixed{display:flex;justify-content:center;align-items:center;flex-direction:column;background-color:gray}.app-module__container___thHYo_lyrixed>*{padding:40px 0}.app-module__lyrics_container___BnyjF_lyrixed{top:0;justify-content:center;align-items:center;flex-direction:column;border:1px;border-radius:10px;width:100%;position:absolute;overflow:hidden}.app-module__lyrics___xFuUo_lyrixed{position:relative;color:#f0f8ff;padding:25px 0 0 25px;font-size:48px;line-height:1.5;overflow:scroll!important}
--------------------------------------------------------------------------------
/src/components/extractLyrics.js:
--------------------------------------------------------------------------------
1 | import cheerio from "cheerio-without-node-native";
2 | import axios from "axios";
3 |
4 | const proxyUrl = "https://api.allorigins.win/raw?url=";
5 |
6 | module.exports = async function (url) {
7 | try {
8 | let { data } = await axios.get(`${proxyUrl}${url}`);
9 | const $ = cheerio.load(data);
10 | let lyrics = $("[data-lyrics-container='true']")
11 | .find("br")
12 | .replaceWith("\n")
13 | .end()
14 | .text();
15 |
16 | if (!lyrics) return null;
17 | return lyrics;
18 | } catch (e) {
19 | throw e;
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lyrixed",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "spicetify-creator",
7 | "build-local": "spicetify-creator --out=dist --minify",
8 | "watch": "spicetify-creator --watch"
9 | },
10 | "license": "MIT",
11 | "devDependencies": {
12 | "@types/react": "^18.2.28",
13 | "@types/react-dom": "^18.2.13",
14 | "spicetify-creator": "^1.0.15"
15 | },
16 | "dependencies": {
17 | "axios": "^1.5.1",
18 | "cheerio-without-node-native": "0.20.2",
19 | "express": "^4.18.2",
20 | "genius-lyrics-api": "^3.2.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/css/app.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | flex-direction: column;
6 |
7 | > * {
8 | padding: 40px 0;
9 | }
10 | background-color: gray;
11 | }
12 |
13 | .lyrics_container {
14 | top: 0;
15 | justify-content: center;
16 | align-items: center;
17 | flex-direction: column;
18 | border: 1px;
19 | border-radius: 10px;
20 | width: 100%;
21 | position: absolute;
22 | overflow: hidden;
23 | }
24 |
25 | .lyrics {
26 | position: relative;
27 | color: aliceblue;
28 | padding: 25px 0px 0px 25px;
29 | font-size: 48px;
30 | line-height: 1.5;
31 | overflow: scroll !important;
32 | }
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import styles from './css/app.module.scss'
2 | import Lyrics from './components/Lyrics';
3 | const react = Spicetify.React;
4 |
5 | function render() {
6 | return react.createElement(App, { title: "Lyrixed" });
7 | }
8 |
9 | class App extends react.Component {
10 | render() {
11 | return react.createElement(
12 | 'div',
13 | { className: styles.container },
14 | react.createElement(
15 | 'div',
16 | { className: styles.lyrics_container },
17 | react.createElement(
18 | 'div',
19 | { className: styles.lyrics },
20 | react.createElement(Lyrics, null)
21 | )
22 | )
23 | );
24 | }
25 | }
26 |
27 | export default App;
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if command -v spicetify &> /dev/null
4 | then
5 | echo "Spicetify is already installed. Skipping Spicetify installation..."
6 | else
7 | echo "Spicetify not found. Installing Spicetify..."
8 | curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh | sh
9 | curl -fsSL https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.sh | sh
10 | fi
11 |
12 | lyrixedUrl="https://github.com/Nuzair46/Lyrixed/releases/latest/download/lyrixed.zip"
13 | targetDir="$HOME/.spicetify/CustomApps/lyrixed"
14 |
15 | mkdir -p "$targetDir"
16 |
17 | zipFile="$targetDir/lyrixed.zip"
18 | curl -L "$lyrixedUrl" -o "$zipFile"
19 |
20 | unzip -o "$zipFile" -d "$targetDir"
21 | rm "$zipFile"
22 |
23 | echo "Lyrixed has been successfully downloaded and extracted to $targetDir"
24 |
25 | echo "Applying Lyrixed with Spicetify..."
26 | spicetify config custom_apps lyrixed
27 | spicetify apply
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lyrixed
2 |
3 | [](https://discord.gg/eYudMwgYtY)
4 |
5 | 
6 |
7 | Lyrixed is a Spicetify custom app to bring back the lyrics feature to Spotify desktop app for freemium users.
8 |
9 | ## Installation
10 |
11 | Windows:
12 | - In your Powershell, Run:
13 | ```powershell
14 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-Expression "& { $(Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/Nuzair46/Lyrixed/main/install.ps1') }"
15 | ```
16 | MacOS/Linux:
17 | - In your terminal, Run:
18 | ```bash
19 | curl -fsSL https://raw.githubusercontent.com/Nuzair46/Lyrixed/main/install.sh | sh
20 | ```
21 |
22 | ## Uninstallation
23 | - In your terminal, Run:
24 | ```
25 | spicetify config custom_apps lyrixed-
26 | spicetify apply
27 | ```
28 | ## Note
29 |
30 | - Feel free to open issue for fixes and features or contact on Discord.
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Nuzair
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/install.ps1:
--------------------------------------------------------------------------------
1 | try {
2 | Get-Command spicetify -ErrorAction Stop
3 | Write-Output "Spicetify is already installed. Skipping Spicetify installation..."
4 | } catch {
5 | Write-Output "Spicetify not found. Installing Spicetify..."
6 | iwr -useb https://raw.githubusercontent.com/spicetify/cli/main/install.ps1 | iex
7 | iwr -useb https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.ps1 | iex
8 | }
9 |
10 | $lyrixedUrl = "https://github.com/Nuzair46/Lyrixed/releases/latest/download/lyrixed.zip"
11 | $targetDir = Join-Path -Path $env:APPDATA -ChildPath "spicetify\CustomApps\lyrixed"
12 |
13 | if (-not (Test-Path -Path $targetDir)) {
14 | New-Item -ItemType Directory -Path $targetDir | Out-Null
15 | }
16 |
17 | $zipFile = Join-Path -Path $targetDir -ChildPath "lyrixed.zip"
18 |
19 | Invoke-WebRequest -Uri $lyrixedUrl -OutFile $zipFile
20 |
21 | Expand-Archive -Path $zipFile -DestinationPath $targetDir -Force
22 |
23 | Remove-Item -Path $zipFile
24 |
25 | Write-Output "Lyrixed has been successfully downloaded and extracted to $targetDir"
26 |
27 | Write-Output "Applying Lyrixed with Spicetify..."
28 | spicetify config custom_apps lyrixed
29 | spicetify apply
30 |
--------------------------------------------------------------------------------
/src/components/Lyrics.jsx:
--------------------------------------------------------------------------------
1 | const react = Spicetify.React;
2 | const { useState, useEffect, useCallback } = Spicetify.React;
3 |
4 | import axios from "axios";
5 | import extractLyrics from "./extractLyrics";
6 |
7 | const searchUrl = "https://genius.com/api/search/song?";
8 | const proxyUrl = "https://api.allorigins.win/raw?url=";
9 |
10 | class Lyrics extends react.Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | currentTrack: {},
15 | lyricsData: "",
16 | isLoading: true,
17 | };
18 | }
19 |
20 | getTitle(title, artist) {
21 | return `${title} ${artist}`
22 | .toLowerCase()
23 | .replace(/ *\([^)]*\) */g, "")
24 | .replace(/ *\[[^\]]*]/, "")
25 | .replace(/feat.|ft./g, "")
26 | .replace(/\s+/g, " ")
27 | .trim();
28 | }
29 |
30 | async fetchTrackLyrics() {
31 | try {
32 | this.setState({ isLoading: true });
33 | const track = Spicetify.Player.data.item.metadata;
34 | const { title, artist_name } = track;
35 | const query = new URLSearchParams({
36 | per_page: 20,
37 | q: `${title} ${artist_name}`,
38 | });
39 | const url = encodeURIComponent(`${searchUrl}${query}`);
40 | const reqUrl = `${proxyUrl}${url}`;
41 | const trackResponse = await axios.get(reqUrl);
42 |
43 | if (trackResponse.status !== 200) {
44 | return null;
45 | }
46 | const result = trackResponse.data.response.sections[0].hits[0].result;
47 |
48 | if (!result) {
49 | return null;
50 | }
51 |
52 | const lyrics = await extractLyrics(result.url);
53 |
54 | this.setState({ lyricsData: lyrics });
55 | } catch (e) {
56 | console.error(e);
57 | } finally {
58 | this.setState({ isLoading: false });
59 | }
60 | }
61 |
62 | componentDidMount() {
63 | Spicetify.Player.addEventListener("songchange", async (e) => {
64 | await this.fetchTrackLyrics();
65 | });
66 | this.fetchTrackLyrics();
67 | }
68 |
69 | render() {
70 | const { isLoading, lyricsData } = this.state;
71 |
72 | if (isLoading) {
73 | return react.createElement(
74 | "div",
75 | null,
76 | "Lyrixed is finding the lyrics. Please wait..."
77 | );
78 | }
79 |
80 | return lyricsData
81 | ? react.createElement(
82 | "div",
83 | null,
84 | lyricsData
85 | .split("\n")
86 | .map((str) => react.createElement("p", null, str))
87 | )
88 | : react.createElement("div", null, "Couldn't find lyrics for this one.");
89 | }
90 | }
91 |
92 | export default Lyrics;
93 |
--------------------------------------------------------------------------------
/src/css/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/node
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
4 |
5 | ### Node ###
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 | .pnpm-debug.log*
14 |
15 | # Diagnostic reports (https://nodejs.org/api/report.html)
16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17 |
18 | # Runtime data
19 | pids
20 | *.pid
21 | *.seed
22 | *.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 | lib-cov
26 |
27 | # Coverage directory used by tools like istanbul
28 | coverage
29 | *.lcov
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # Snowpack dependency directory (https://snowpack.dev/)
51 | web_modules/
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Optional stylelint cache
63 | .stylelintcache
64 |
65 | # Microbundle cache
66 | .rpt2_cache/
67 | .rts2_cache_cjs/
68 | .rts2_cache_es/
69 | .rts2_cache_umd/
70 |
71 | # Optional REPL history
72 | .node_repl_history
73 |
74 | # Output of 'npm pack'
75 | *.tgz
76 |
77 | # Yarn Integrity file
78 | .yarn-integrity
79 |
80 | # dotenv environment variable files
81 | .env
82 | .env.development.local
83 | .env.test.local
84 | .env.production.local
85 | .env.local
86 |
87 | # parcel-bundler cache (https://parceljs.org/)
88 | .cache
89 | .parcel-cache
90 |
91 | # Next.js build output
92 | .next
93 | out
94 |
95 | # Nuxt.js build / generate output
96 | .nuxt
97 |
98 | # Gatsby files
99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 |
110 | # Docusaurus cache and generated files
111 | .docusaurus
112 |
113 | # Serverless directories
114 | .serverless/
115 |
116 | # FuseBox cache
117 | .fusebox/
118 |
119 | # DynamoDB Local files
120 | .dynamodb/
121 |
122 | # TernJS port file
123 | .tern-port
124 |
125 | # Stores VSCode versions used for testing VSCode extensions
126 | .vscode-test
127 |
128 | # yarn v2
129 | .yarn/cache
130 | .yarn/unplugged
131 | .yarn/build-state.yml
132 | .yarn/install-state.gz
133 | .pnp.*
134 |
135 | ### Node Patch ###
136 | # Serverless Webpack directories
137 | .webpack/
138 |
139 | # Optional stylelint cache
140 |
141 | # SvelteKit build / generate output
142 | .svelte-kit
143 |
144 | # End of https://www.toptal.com/developers/gitignore/api/node
145 |
--------------------------------------------------------------------------------
/dist/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Lyrixed",
3 | "icon": "",
4 | "active-icon": "",
5 | "subfiles": [],
6 | "subfiles_extension": []
7 | }
--------------------------------------------------------------------------------
/src/types/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 `