├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── adblock-bug.md
│ ├── bug_report.md
│ ├── feature_request.md
│ ├── featureshuffle-bug.md
│ ├── fixenhance-bug.md
│ ├── formatcolors-bug.md
│ ├── phrasetoplaylist-bug.md
│ ├── songstats-bug.md
│ └── wikify-bug.md
├── .gitignore
├── LICENSE
├── README.md
├── adblock
├── README.md
├── adblock.js
└── adblock.png
├── featureshuffle
├── .DS_Store
├── README.md
├── featureshuffle.js
└── featureshuffle.png
├── formatColors
├── README.md
├── formatColors.js
└── formatColors.png
├── manifest.json
├── old-sidebar
├── README.md
└── oldSidebar.js
├── phraseToPlaylist
├── README.md
├── phraseToPlaylist.js
└── phraseToPlaylist.png
├── songstats
├── README.md
├── songstats.js
└── songstats.png
├── wikify
├── README.md
├── wikify.js
└── wikify.png
└── writeify
├── README.md
├── writeify.js
└── writeify.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/adblock-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Adblock Bug
3 | about: Bugs related to Adblock.
4 | title: "[Adblock BUG] Describe bug."
5 | labels: adblock, bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/featureshuffle-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: FeatureShuffle Bug
3 | about: Bugs related to FeatureShuffle
4 | title: "[FeatureShuffle BUG] Describe bug here."
5 | labels: bug, featureshuffle
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/fixenhance-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: FixEnhance Bug
3 | about: Bugs related to FixEnhance
4 | title: "[FixEnhance Bug] Describe bug here."
5 | labels: bug, fixEnhance
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/formatcolors-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: FormatColors Bug
3 | about: Bugs related to FormatColors
4 | title: "[FormatColors BUG] Describe bug here."
5 | labels: bug, formatColors
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/phrasetoplaylist-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: PhraseToPlaylist Bug
3 | about: Bugs related to PhraseToPlaylist
4 | title: "[Phrase2Playlist BUG] Describe bug here."
5 | labels: bug, phrase2playlist
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/songstats-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: SongStats Bug
3 | about: Bugs related to SongStats
4 | title: "[SongStats BUG] Describe bug here"
5 | labels: bug, songstats
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/wikify-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Wikify Bug
3 | about: Bugs related to Wikify.
4 | title: "[Wikify BUG] Describe bug"
5 | labels: bug, wikify
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe bug
11 |
12 | ## Steps to replicate
13 |
14 | ## Necessary screenshots
15 |
16 | ## Spicetify and Spotify Version
17 |
18 |
19 | ## Config file:
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 CharlieS1103
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spicetify-extensions
2 | Project archived as I have moved on from maintaining these extensions.
3 | Can be found at - https://github.com/rxri/spicetify-extensions
4 |
5 |
--------------------------------------------------------------------------------
/adblock/README.md:
--------------------------------------------------------------------------------
1 | # Adblock
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to block all audio ads and UI ads
4 |
5 | * Simply run the extension and it will work!
6 |
7 | ## Install
8 |
9 | Copy `adblock.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions adblock.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|adblock.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/adblock/adblock.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | // NAME: adblock
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: Block all audio and UI ads on Spotify
6 |
7 | ///
8 |
9 | (function adblock() {
10 | const { Platform } = Spicetify;
11 | if (!Platform) {
12 | setTimeout(adblock, 300)
13 | return
14 | }
15 |
16 | const styleSheet = document.createElement("style")
17 |
18 | styleSheet.innerHTML =
19 | `
20 | .MnW5SczTcbdFHxLZ_Z8j, .WiPggcPDzbwGxoxwLWFf, .ReyA3uE3K7oEz7PTTnAn, .main-leaderboardComponent-container, .sponsor-container, a.link-subtle.main-navBar-navBarLink.GKnnhbExo0U9l7Jz2rdc, button[title="Upgrade to Premium"], button[aria-label="Upgrade to Premium"], .main-topBar-UpgradeButton, .main-contextMenu-menuItem a[href^="https://www.spotify.com/premium/"] {
21 | display: none !important;
22 | }
23 | `
24 | document.body.appendChild(styleSheet);
25 |
26 | delayAds();
27 |
28 | const billboard = Spicetify.Platform.AdManagers.billboard.displayBillboard;
29 |
30 | Spicetify.Platform.AdManagers.billboard.displayBillboard = function (arguments) {
31 | Spicetify.Platform.AdManagers.billboard.finish()
32 | // hook before call
33 | var ret = billboard.apply(this, arguments);
34 | // hook after call
35 | Spicetify.Platform.AdManagers.billboard.finish()
36 | const observer = new MutationObserver((mutations, obs) => {
37 | const billboardAd = document.getElementById('view-billboard-ad');
38 | if (billboardAd) {
39 | Spicetify.Platform.AdManagers.billboard.finish()
40 | obs.disconnect();
41 | return;
42 | }
43 | });
44 |
45 | observer.observe(document, {
46 | childList: true,
47 | subtree: true
48 | });
49 | return ret;
50 | };
51 |
52 | async function delayAds() {
53 | if (!Spicetify.Platform?.UserAPI) {
54 | setTimeout(delayAds, 300);
55 | return;
56 | }
57 | const productState = Spicetify.Platform.UserAPI._product_state || Spicetify.Platform.UserAPI._product_state_service;
58 |
59 | await productState.putOverridesValues({ pairs: { ads: "0", catalogue: "premium", product: "premium", type: "premium" } });
60 | Spicetify.Platform.AdManagers.audio.audioApi.cosmosConnector.increaseStreamTime(-100000000000);
61 | Spicetify.Platform.AdManagers.billboard.billboardApi.cosmosConnector.increaseStreamTime(-100000000000);
62 | await Spicetify.Platform.AdManagers.audio.disable();
63 | await Spicetify.Platform.AdManagers.billboard.disable();
64 | await Spicetify.Platform.AdManagers.leaderboard.disableLeaderboard();
65 | await Spicetify.Platform.AdManagers.sponsoredPlaylist.disable();
66 |
67 | console.log("[Adblock] Ads disabled", Spicetify.Platform.AdManagers);
68 | };
69 |
70 | setInterval(delayAds, 30 * 10000);
71 |
72 | (async function disableEsperantoAds() {
73 | if (!Spicetify.Platform?.UserAPI) {
74 | setTimeout(disableEsperantoAds, 300);
75 | return;
76 | }
77 | try{
78 | const productState = Spicetify.Platform.UserAPI._product_state || Spicetify.Platform.UserAPI._product_state_service;
79 |
80 | await productState.putOverridesValues({ pairs: { ads: "0", catalogue: "premium", product: "premium", type: "premium" } });
81 | productState.subValues({ keys: ["ads"] }, () => {
82 | delayAds();
83 | });
84 | }catch(e){
85 | console.log("[Adblock] Product State does not exist", e);
86 | }
87 |
88 | })();
89 | })()
90 |
91 |
--------------------------------------------------------------------------------
/adblock/adblock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/adblock/adblock.png
--------------------------------------------------------------------------------
/featureshuffle/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/featureshuffle/.DS_Store
--------------------------------------------------------------------------------
/featureshuffle/README.md:
--------------------------------------------------------------------------------
1 | # FeatureShuffle
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to create playlists based off of another playlists audio features..
4 |
5 | * Right click a playlist and click "Create Feature Based Playlist" (This process may take up to 3 minutes.)
6 |
7 | ## Install
8 |
9 | Copy `featureshuffle.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions FeatureShuffle.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|FeatureShuffle.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | [](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/FeatureShuffle/FeatureShuffle.png)
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 |
50 | [](https://github.com/CharlieS1103/spicetify-extensions/)
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/featureshuffle/featureshuffle.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | // NAME: Feature Shuffle
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: Create a playlist based on the average features of a playlist
6 |
7 | ///
8 |
9 | (function songstats() {
10 |
11 | const {
12 | CosmosAsync,
13 |
14 | URI
15 | } = Spicetify;
16 | if (!(CosmosAsync && URI)) {
17 | setTimeout(songstats, 300)
18 | return
19 | }
20 |
21 |
22 | const buttontxt = "Create Feature Based Playlist"
23 |
24 | const average = (array) => array.reduce((a, b) => a + b) / array.length;
25 |
26 | async function makePlaylist(uris) {
27 | const uri = uris[0];
28 | const uriFinal = uri.split(":")[2]
29 |
30 | const user = await CosmosAsync.get('https://api.spotify.com/v1/me')
31 |
32 | const playlistitems = (await CosmosAsync.get('https://api.spotify.com/v1/playlists/' + uriFinal + '/tracks')).items.map(i => i.track.href);
33 |
34 | const avrDanceability= [], avrTempo= [], avrEnergy= [], avrInstrumentalness= [], avrSpeechiness= [], avrLiveness = [];
35 |
36 | var avr2Dance, avr2Tempo,avr2Energy,avr2Intrumentalness
37 |
38 |
39 | var avr2Liveness
40 | for (i = 0; i < playlistitems.length; i++) {
41 | var songuri = playlistitems[i].split("/")[5]
42 | var res;
43 | try {
44 | res = await CosmosAsync.get('https://api.spotify.com/v1/audio-features/' + songuri);
45 | } catch (error) {
46 | //e
47 | }
48 |
49 | avrDanceability.push(Math.round(100 * res.danceability) / 100);
50 |
51 | avrEnergy.push(Math.round(100 * res.energy) / 100);
52 |
53 | avrInstrumentalness.push(Math.round(100 * res.instrumentalness) / 100);
54 |
55 | avrSpeechiness.push(Math.round(100 * res.speechiness) / 100);
56 |
57 | avrTempo.push(Math.round(100 * res.tempo) / 100);
58 |
59 | avrLiveness.push(Math.round(100 * res.liveness) / 100);
60 | }
61 |
62 |
63 | avr2Dance = average(avrDanceability);
64 |
65 | avr2Tempo = average(avrTempo)
66 |
67 | avr2Energy = average(avrEnergy)
68 |
69 | avr2Intrumentalness = average(avrInstrumentalness)
70 |
71 | avr2Liveness = average(avrLiveness)
72 |
73 |
74 | function randAlph(rndInt, ) {
75 |
76 | const alphabet = "abcdefghijklmnopqrstuvwxyz"
77 | const letters = []
78 |
79 | for (var i = 0; i < rndInt; i++) {
80 | const randomletter = alphabet[Math.floor(Math.random() * alphabet.length)]
81 | letters.push(randomletter)
82 | }
83 | const string = letters.join("")
84 | return (string)
85 | }
86 |
87 |
88 | const randomSongrequest = [];
89 |
90 | for (var i = 0; i < 21; i++) {
91 |
92 | const getRandomSongsArray = ['%25-%25', '-%25', '%25-%25', '-%25', '%25-%25', '-%25', '%25-%25', '-%25'];
93 | const rndInt = Math.floor(Math.random() * 3) + 1
94 |
95 | var ranSong = getRandomSongsArray[Math.floor(Math.random() * getRandomSongsArray.length)];
96 |
97 |
98 |
99 |
100 | const ranString = randAlph(rndInt)
101 |
102 | const getRandomSongs = ranSong.replace("-", ranString)
103 |
104 | const getRandomOffset = Math.floor(Math.random() * (500 - 1 + 1) + 1)
105 |
106 | const url = "https://api.spotify.com/v1/search?q=" + getRandomSongs + '&offset=' + getRandomOffset + "&type=track&limit=1&market=US";
107 |
108 | const randomSongrequestToAppend = (await CosmosAsync.get(url)).tracks.items.map(track => track.uri);
109 | console.log(randomSongrequestToAppend)
110 |
111 |
112 |
113 |
114 | let res2
115 | function validateSong(res2){
116 | if (Math.round(100 * res2.liveness) / 100 >= avr2Liveness - 2 && Math.round(100 * res2.liveness) / 100 <= avr2Liveness + 2) {}else{return false}
117 | if (res2.tempo >= avr2Tempo - 5 && res2.tempo <= avr2Tempo + 5) {}else{return false}
118 | if (Math.round(100 * res2.instrumentalness) / 100 >= avr2Intrumentalness - 2 && Math.round(100 * res2.instrumentalness) / 100 <= avr2Intrumentalness + 2){}else{return false}
119 | if (Math.round(100 * res2.energy) / 100 >= avr2Energy - 2 && Math.round(100 * res2.energy) / 100 <= avr2Energy + 2) {}else{return false}
120 | if (Math.round(100 * res2.danceability) / 100 >= avr2Dance - 2 && Math.round(100 * res2.danceability) / 100 <= avr2Dance + 2){}else{return false}
121 | return true
122 | }
123 |
124 |
125 | if (randomSongrequestToAppend[0] != undefined) {
126 | try {
127 | res2 = await CosmosAsync.get('https://api.spotify.com/v1/audio-features/' + randomSongrequestToAppend[0].split(":")[2]);
128 | if(validateSong(res2)){
129 | randomSongrequest.push(randomSongrequestToAppend[0])
130 | console.log("Song passed")
131 | }else{
132 | i--
133 | }
134 | } catch (error) {
135 | console.warn(error)
136 | }
137 |
138 |
139 |
140 |
141 | }
142 |
143 |
144 |
145 | }
146 | const newplaylist = await CosmosAsync.post('https://api.spotify.com/v1/users/' + user.id + '/playlists', {
147 | name: 'New Playlist'
148 | });
149 |
150 | const playlisturi = newplaylist.uri.split(":")[2]
151 |
152 | CosmosAsync.post('https://api.spotify.com/v1/playlists/' + playlisturi + '/tracks', {
153 | uris: randomSongrequest
154 | });
155 |
156 |
157 | }
158 |
159 | function shouldDisplayContextMenu(uris) {
160 |
161 | if (uris.length > 1) {
162 | return false;
163 | }
164 |
165 | const uri = uris[0];
166 | const uriObj = Spicetify.URI.fromString(uri);
167 |
168 |
169 | if (uriObj.type === Spicetify.URI.Type.PLAYLIST_V2) {
170 | return true;
171 | }
172 |
173 | return false;
174 | }
175 |
176 |
177 | const cntxMenu = new Spicetify.ContextMenu.Item(
178 |
179 | buttontxt,
180 | makePlaylist,
181 | shouldDisplayContextMenu,
182 |
183 | );
184 |
185 | cntxMenu.register();
186 | })();
--------------------------------------------------------------------------------
/featureshuffle/featureshuffle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/featureshuffle/featureshuffle.png
--------------------------------------------------------------------------------
/formatColors/README.md:
--------------------------------------------------------------------------------
1 | # FormatColors
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to convert the current colors defined in root to color.ini format
4 |
5 | * For use by theme developers, click the button and it will open a popup model. Click on any of the text to select the full text, then right click and hit copy.
6 |
7 | ## Install
8 |
9 | Copy `formatColors.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions formatColors.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|formatColors.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/formatColors/formatColors.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | // NAME: formatColors
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: Copies colors in colors.ini format from the CSS (For developers)
6 |
7 | ///
8 |
9 | (function formatColors() {
10 | const { Platform } = Spicetify;
11 | if (!(Platform)) {
12 | setTimeout(formatColors, 300)
13 | return
14 | }
15 |
16 | const buttontxt = "Format Colors"
17 | const BUTTON_ICON = `
18 |
19 |
20 |
37 | `
38 |
39 | new Spicetify.Topbar.Button(
40 | "Format Colors",
41 | BUTTON_ICON,
42 | convertColorSheet,
43 | false
44 | );
45 |
46 |
47 | })()
48 |
49 |
50 | function convertColorSheet(){
51 | for (const sheet of document.styleSheets) {
52 | // TODO: Fix the wall of ignores
53 | // @ts-ignore
54 | if (sheet.href == "https://xpui.app.spotify.com/colors.css" || sheet.ownerNode.classList[1] == "marketplaceScheme") {
55 | let cssText = sheet.rules[0].cssText
56 | cssText = cssText.replaceAll(":root {", "");
57 | cssText = cssText.replaceAll("{", "");
58 | cssText = cssText.replaceAll(":", " =")
59 | cssText = cssText.replaceAll("--spice-", "")
60 | cssText = cssText.replaceAll("#", "")
61 | cssText = cssText.replaceAll("}", "")
62 | cssText = cssText.replaceAll("!important", "")
63 | Spicetify.Platform.ClipboardAPI.copy(cssText)
64 | cssText = cssText.replaceAll(";", `\\n`)
65 | const regex = /\\n|\\r\\n|\\n\\r|\\r/g;
66 | const reg = /rgb.*?\\n/gm;
67 | cssText = cssText.replace(reg, '')
68 |
69 | const htmlElement = `${cssText.replace(regex, '
')}`
70 | Spicetify.PopupModal.display({
71 | title: "Formatted Colors",
72 | content: htmlElement,
73 | });
74 |
75 | Spicetify.showNotification("Copied to clipboard")
76 | break;
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/formatColors/formatColors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/formatColors/formatColors.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Feature Shuffle",
4 | "description": "Spicetify extension to make a playlist based on another playlist's audio features",
5 | "preview": "featureshuffle/featureshuffle.png",
6 | "main": "featureshuffle/featureshuffle.js",
7 | "readme": "featureshuffle/README.md"
8 | },
9 | {
10 | "name": "Song Stats",
11 | "description": "Spicetify extension to display a song's audio features",
12 | "preview": "songstats/songstats.png",
13 | "main": "songstats/songstats.js",
14 | "readme": "songstats/README.md"
15 | },
16 | {
17 | "name": "WikiFy",
18 | "description": "Shows an Artists wikipedia page to learn more about them",
19 | "preview": "wikify/wikify.png",
20 | "main": "wikify/wikify.js",
21 | "readme": "wikify/README.md"
22 | },
23 | {
24 | "name": "Adblock",
25 | "description": "Block all audio ads and UI ads!",
26 | "preview": "adblock/adblock.png",
27 | "main": "adblock/adblock.js",
28 | "readme": "adblock/README.md"
29 |
30 | },
31 | {
32 | "name": "Format colors",
33 | "description": "Convert the current colors defined in root to color.ini format, for theme developers",
34 | "preview": "formatColors/formatColors.png",
35 | "main": "formatColors/formatColors.js",
36 | "readme": "formatColors/README.md"
37 | },
38 | {
39 | "name": "Phrase to Playlist",
40 | "description": "Convert a phrase into a playlist with songs arraged to make that phrase",
41 | "preview": "phraseToPlaylist/phraseToPlaylist.png",
42 | "main": "phraseToPlaylist/phraseToPlaylist.js",
43 | "readme": "phraseToPlaylist/README.md",
44 | "authors": [
45 | { "name": "CharlieS1103", "url": "https://github.com/CharlieS1103" },
46 | { "name": "MalTeeez", "url": "https://github.com/MalTeeez" }
47 | ]
48 | },
49 | {
50 | "name": "Writeify",
51 | "description": "Take notes on songs, albums, and artists",
52 | "preview": "writeify/writeify.png",
53 | "main": "writeify/writeify.js",
54 | "readme": "writeify/README.md"
55 | }
56 | ]
57 |
--------------------------------------------------------------------------------
/old-sidebar/README.md:
--------------------------------------------------------------------------------
1 | # OldSidebar
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) No more libraryx, go back to the old sidebar
4 |
5 | * To use, simply install the extension, I may add a toggle for it in the future.
6 |
7 | ## Install
8 |
9 | Copy `oldSidebar.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions oldSidebar.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|oldSidebar.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/old-sidebar/oldSidebar.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | // NAME: oldSidebar
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: Restore the old spotify sidebar
6 |
7 | ///
8 |
9 | (async function oldSidebar() {
10 | const { Platform } = Spicetify;
11 | // Also wait till the sidebar is loaded
12 | if (!(Platform) || !document.querySelector('.Root__nav-bar') || document.querySelector('.main-rootlist-topSentinel')) {
13 | setTimeout(oldSidebar, 10)
14 | console.log("Waiting for sidebar to load")
15 | return
16 | }
17 | // Sleep using await and a promise
18 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
19 | // Wait for the sidebar to load a little more
20 | await sleep(3000);
21 | let oldHTML = `
22 |
23 |
62 |
63 |
64 |
65 |
75 |
76 |
89 |
104 |
108 |
109 |
110 |
113 |
116 |
117 |
137 |
142 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | `
156 |
157 | const customAppHTML = createCustomAppHtml(document.getElementsByClassName("Root__nav-bar")[0]);
158 | const playlistHTML = createPlaylistHtml(document.getElementsByClassName("Root__nav-bar")[0]);
159 | if(customAppHTML == undefined || playlistHTML == undefined){
160 | console.log("Error")
161 | return;
162 | }
163 |
164 | oldHTML = oldHTML.replace(`**INJECTION**`, customAppHTML);
165 | oldHTML = oldHTML.replace(`**SECONDINJECTION**`, playlistHTML);
166 |
167 | // Replace the contents of of the element with the class "Root__nav-bar" with the old HTML
168 | document.getElementsByClassName("Root__nav-bar")[0].innerHTML = oldHTML;
169 |
170 | // For every "main-navBar-navBarItem" element in the oldHTML, add an event Listener for the click event, when it is clicked, run Spicetify.Platform.History.push() and push the href of the element to the history, also override the default behaviour of the click event
171 | let elements = document.getElementsByClassName("main-navBar-navBarItem")
172 | for (let i = 0; i < elements.length; i++) {
173 | elements[i].addEventListener("click", function (e) {
174 | const href = elements[i].getElementsByTagName("a")[0].href
175 | // Strip "https://xpui.app.spotify.com" from the href
176 | Spicetify.Platform.History.push(href.replace("https://xpui.app.spotify.com", ""));
177 | e.preventDefault();
178 | });
179 | }
180 | // For every "main-rootlist-rootlistItem" element in the oldHTML, add an event Listener for the click event, when it is clicked, run Spicetify.Platform.History.push() and push the href of the element to the history, also override the default behaviour of the click event
181 | elements = document.getElementsByClassName("main-rootlist-rootlistItem")
182 | for (let i = 0; i < elements.length; i++) {
183 | elements[i].addEventListener("click", function (e) {
184 | const href = elements[i].getElementsByTagName("a")[0].href
185 | // Strip "https://xpui.app.spotify.com" from the href
186 | Spicetify.Platform.History.push(href.replace("https://xpui.app.spotify.com", ""));
187 | e.preventDefault();
188 | });
189 | }
190 | })();
191 |
192 |
193 |
194 |
195 | function extractCustomAppsFromSidebarHtml(sidebarHtml) {
196 | const customAppsData = []
197 | const customAppElements = sidebarHtml.querySelectorAll(".main-topBar-navLink")
198 | customAppElements.forEach((customAppElement) => {
199 | const href = customAppElement.getAttribute("href")
200 | const icon = customAppElement.getElementsByClassName("home-icon")[0].innerHTML
201 | const activeIcon = customAppElement.getElementsByClassName("home-active-icon")[0].innerHTML
202 | // Name is the href with hypens replaced with spaces, the first / removed, and with the first letter of each word capitalized
203 | const name = href.replace(/-/g, " ").replace("/", "").replace(/\b\w/g, l => l.toUpperCase())
204 | customAppsData.push({ href, icon, activeIcon, name })
205 |
206 | })
207 | return customAppsData
208 | }
209 | function extractPlaylistsFromHtml(sidebarHtml){
210 | const playlists = []
211 | const playlistElements = sidebarHtml.querySelectorAll("li[role='listitem']");
212 | console.log(playlistElements)
213 |
214 | playlistElements.forEach((playlistElement) => {
215 | const nameElement = playlistElement.querySelector("a[class='yOc9v5MGaitl1r9UpHKg'] span");
216 | console.log(nameElement)
217 | const hrefElement = playlistElement.querySelector("a[class='yOc9v5MGaitl1r9UpHKg']");
218 | console.log(hrefElement)
219 | if(nameElement == null || hrefElement == null) return;
220 | playlists.push({
221 | name: nameElement.textContent,
222 | href: hrefElement.getAttribute("href")
223 | });
224 | });
225 | console.log(playlists)
226 | return playlists;
227 | }
228 | function createCustomAppHtml(newSidebarHtml) {
229 | if (!newSidebarHtml == null) return;
230 | const customAppsData = extractCustomAppsFromSidebarHtml(newSidebarHtml)
231 | let customAppHtmls = []
232 | customAppsData.forEach((customAppData) => {
233 | const customAppHtml = `
234 |
235 |
236 | ${customAppData.icon}
237 |
238 |
239 | ${customAppData.activeIcon}
240 |
241 | ${customAppData.name}
242 |
243 | `
244 | customAppHtmls.push(customAppHtml)
245 | })
246 | return customAppHtmls.join("")
247 | }
248 | function createPlaylistHtml(newPlaylistHtml) {
249 | if (!newPlaylistHtml == null) return;
250 | const playlistData = extractPlaylistsFromHtml(newPlaylistHtml)
251 | let playlistHtmls = []
252 | playlistData.forEach((playlistData) => {
253 | const playlistHtml = ``
255 | playlistHtmls.push(playlistHtml)
256 | })
257 | console.log(playlistHtmls)
258 | return playlistHtmls.join("")
259 | }
--------------------------------------------------------------------------------
/phraseToPlaylist/README.md:
--------------------------------------------------------------------------------
1 | # PhraseToPlaylist
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to convert a phrase into a playlist with songs arraged to make that phrase
4 |
5 | * Click the button on the topbar, input your phrase and click submit. If a song can't be found for a word, it will be filled with undefined(I can remove this if requested, however thought it might be funny ;) )
6 |
7 | ## Install
8 |
9 | Copy `phraseToPlaylist.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions phraseToPlaylist.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|phraseToPlaylist.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/phraseToPlaylist/phraseToPlaylist.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | // NAME: phraseToPlaylist
4 | // AUTHOR: CharlieS1103, MalTeeez
5 | // DESCRIPTION: Convert a phrase into a playlist with songs arraged to make that phrase.
6 |
7 | ///
8 |
9 | (function phraseToPlaylist() {
10 | const { Platform, CosmosAsync } = Spicetify;
11 | if (!(Platform && CosmosAsync)) {
12 | setTimeout(phraseToPlaylist, 300)
13 | return
14 | }
15 | const CONVERT_ICON = `
16 | `
17 |
18 | new Spicetify.Topbar.Button(
19 | "Phrase2Playlist",
20 | CONVERT_ICON,
21 | displayPhraseInput,
22 | false
23 | );
24 |
25 |
26 | })()
27 | function displayPhraseInput(){
28 | Spicetify.PopupModal.display({
29 | title: "Input phrase",
30 | content: createTextArea(),
31 | });
32 | addCustomCssListeners();
33 | }
34 |
35 | var offset = "";
36 | var offsetnum = 0;
37 | function createTextArea(){
38 | const container = document.createElement("div");
39 | container.innerHTML = `
40 |
41 |
42 | 0 / 0
43 |
44 | `;
45 | return container;
46 | }
47 |
48 | async function addCustomCssListeners() {
49 | console.log("[P2P]: Adding listeners");
50 | const textarea = document.querySelector("#playlist-phrase-box");
51 |
52 | const submit = document.querySelector("#playlist-phrase-submit");
53 | if (submit)
54 | submit.addEventListener("click", function (event) {
55 | // @ts-ignore
56 | generatePlaylist(textarea.value)
57 | event.preventDefault();
58 | }, false);
59 | }
60 |
61 | async function generatePlaylist(phrase){
62 | phrase = phrase.replace(/[^\w\s\-]|_/g, "").replace(/\s+/g, ' ')
63 | phrase = phrase.replace(/^\ |\ $/g, "").split(" "); //replace spaces at start and end of string so those don't become their own substrings
64 | const songArr = [],
65 | songMapCache = {};
66 | offset = "";
67 | for (var i = 0; i < phrase.length; i++) {
68 | var spanProgress = document.querySelector("#phrase-loading-indicator"),
69 | progressText = document.createTextNode('' + i + " / " + phrase.length),
70 | textarea = document.querySelector("#playlist-phrase-box"),
71 | currentText = "";
72 | if(spanProgress == null || progressText == null || textarea == null || currentText == null) {
73 | console.log("[P2P]: (Error) Error while generating playlist, aborting.");
74 | return;
75 | }
76 | spanProgress.innerHTML = '';
77 | textarea.innerHTML = ''; // clear existing
78 | for (var o = 0; o < i+1; o++) {
79 | var prefix = o == i ? "> " : "✓ ";
80 | currentText += prefix + phrase[o] + "\n";
81 | }
82 | textarea.scrollTop = textarea.scrollHeight; //scroll to bottom of now longer text box
83 | spanProgress.appendChild(progressText);
84 | // @ts-ignore
85 | textarea.value = currentText;
86 | if (phrase[i] in songMap || phrase[i].toUpperCase() in songMap) { //maybe one of the values in the song map (i.e. the alphabet) is only there in uppercase.
87 | phrase[i] in songMap ? songArr[i] = "spotify:track:" + songMap[phrase[i]] : songArr[i] = "spotify:track:" + songMap[phrase[i].toUpperCase()];
88 | //console.log("[P2P]: (Cache) Found cached song for word:\t\t " + phrase[i] + ",\t with track:\t" + songMap[phrase[i]]);
89 | } else if (phrase[i] in songMapCache) {
90 | //console.log("[P2P]: (Cache) Found repeating song for word:\t " + phrase[i] + ",\t with track:\t" + songMapCache[phrase[i]]);
91 | songArr[i] = "spotify:track:" + songMapCache[phrase[i]];
92 | } else {
93 | const songJson = await searchSong(phrase[i]);
94 | songArr[i] = "spotify:track:" + songJson;
95 | songMapCache[phrase[i]] = songJson;
96 | }
97 | }
98 | //console.log("song array: " + JSON.stringify(songArr));
99 | createPlaylist(songArr)
100 | }
101 |
102 | /**
103 | * @param {string} songToSearch
104 | * @param {string} jsonSong
105 | */
106 | async function isSameSong(songToSearch, jsonSong) {
107 | return (songToSearch === jsonSong
108 | || songToSearch === (jsonSong.charAt(0).toUpperCase() + jsonSong.slice(1))
109 | || songToSearch === jsonSong.toLowerCase()
110 | || songToSearch === jsonSong.toUpperCase());
111 | }
112 |
113 | async function searchSong(songName) {
114 | let songFound = false;
115 | //NORMAL SEARCH - Better for english queries (TODO: maybe fix with user country (maybe toggleable)), very good with shorter songs, faster in general
116 | try {
117 | const songJSON = await Spicetify.CosmosAsync.get('https://api.spotify.com/v1/search?q=' + songName + '&type=track&market=US&limit=50&offset=51');
118 | for (var i = 0; i < songJSON.tracks.items.length; i++) {
119 | //console.log("JSON Comparison for " + songName + " === " + songJSON.tracks.items[i].name);
120 | if (await isSameSong(songName, songJSON.tracks.items[i].name)) {
121 | //console.log("[P2P]: (Search) Found API song for word:\t\t\t " + songName + ",\twith track:\t" + songJSON.tracks.items[i].id);
122 | songFound = true;
123 | return songJSON.tracks.items[i].id;
124 | }
125 | }
126 | } catch (err) {
127 | //console.log("[P2P]: (API Error) Error while searching for word:\t\t\t " + songName + ",\tusing Error Track");
128 | return "ht4un5PoFxGjGFpERh7kkq0a"; //TRACK: "This Doesn't Work"
129 | }
130 | //FALLBACK SEARCH - Better for longer song names and more rare ones
131 | var offsetCounter = 0;
132 | while (!songFound && offsetCounter < 1000) {
133 | var fallbackSongJSON = await Spicetify.CosmosAsync.get('https://api.spotify.com/v1/search?q=' + songName + '&type=track&limit=50&offset='+offsetCounter)
134 | for (var i = 0; i < fallbackSongJSON.tracks.items.length; i++) {
135 | //console.log("(Fallback) JSON Comparison for " + songName + " === " + fallbackSongJSON.tracks.items[i].name);
136 | if (await isSameSong(songName, fallbackSongJSON.tracks.items[i].name) && !songFound) {
137 | //console.log("[P2P]: (Fallback) Found API song for word:\t\t\t " + songName + ",\twith track:\t" + fallbackSongJSON.tracks.items[i].id);
138 | return fallbackSongJSON.tracks.items[i].id;
139 | }
140 | }
141 | offsetCounter += 50;
142 | }
143 | //console.log("[P2P]: (Not Found) Couldn't find track for word:\t\t\t " + songName + ",\tusing Not Found Track");
144 | return "1qcn9qzMCyBDnYy0dYN824"; //TRACK: "This Song Doesn't Exist Because I Don't Like Effort"
145 | }
146 |
147 | async function createPlaylist(songArr){
148 | const randomName = await Spicetify.CosmosAsync.get('https://random-word-api.herokuapp.com/word?number=1')
149 | const user = await Spicetify.CosmosAsync.get('https://api.spotify.com/v1/me')
150 | const newplaylist = await Spicetify.CosmosAsync.post('https://api.spotify.com/v1/users/' + user.id + '/playlists', {
151 | name: randomName[0]
152 | });
153 |
154 | const playlisturi = newplaylist.uri.split(":")[2]
155 | while(songArr.length){
156 | const b = songArr.splice(0, 100)
157 | var status = "";
158 | try {
159 | status = await Spicetify.CosmosAsync.post('https://api.spotify.com/v1/playlists/' + playlisturi + '/tracks', {
160 | uris: b
161 | });
162 | } catch (err) {
163 | console.log("Playlist creation batch failed, unshifting. Error Status: " + status);
164 | songArr.splice(0, 0, ...b);
165 | //console.log("Unshifted Array: " + JSON.stringify(songArr));
166 | }
167 | }
168 | const span = document.querySelector("#phrase-loading-indicator")
169 | if(span){
170 | span.innerHTML = '';
171 | span.appendChild(document.createTextNode("Done! "));
172 | }
173 | }
174 |
175 | const songMap = {
176 | "that": "3mJCHAKdmZDINjCEEYMEkq",
177 | "THAT": "6hbfVquDat90Nv09n05ZnN",
178 | "That": "5XZTPT1jb4fEfmluLKmm4B",
179 | "you": "5Wdl4yFoXOX1xmA53udLyZ",
180 | "YOU": "6cVNYlO75XZ3UZnglTF6WI",
181 | "You": "6lbme14HiDWYmGiw1I2Dv6",
182 | "our": "5JTjuEFoIfQgP90nvOCWEj",
183 | "OUR": "5YhTy3qCTc2RELqbHKv94A",
184 | "Our": "4WLnE7W9K41HdRz1rHpz5T",
185 | "it": "6eG4JMN3f4WLgj1ElfuMUV",
186 | "for": "3beItkavCW1qXszPbFbijD",
187 | "is": "1epDL4xhczbpzkXIeGXZzb",
188 | "are": "0L6WRMANsYoX1mIe25zwbe",
189 | "i": "7wdzLe2Gsx1RGqbvYZHASz",
190 | "a": "6WH0LHM2vFBLpmU5RFdDh2",
191 | "the": "72FW5JjmSwgHNopNSLRocy",
192 | "have": "0DX4IC8aMjisNh7LHDyo6J",
193 | "he": "1bc28ebMDp7ym6rHfqFfj0",
194 | "we": "0BSI1Epu3YeVwXF1bvL8oH",
195 | "to": "4n3lfhTDOaFe9a1c4FPPSB",
196 | "and": "2YsrYsusAKqYD74ipCRxvz",
197 | "0": "3GzRIROhugr0XHjrOvyDRP",
198 | "1": "76nlq5gomu49Yn5dfmtv0C",
199 | "2": "62CprXvSWsKBvYu3Yba55A",
200 | "3": "6ECxq5Sh1ogq6oHDRUVmV2",
201 | "4": "6XvzSF3NDwOKP6RF0YmXEU",
202 | "5": "15UttZPJXWsb1fSLwNSfov",
203 | "6": "5os4iDInR4chqaCdXi895k",
204 | "7": "7zbFh74zImpQho3btxuANN",
205 | "8": "1lSnBlAErRss6asu9Y5HuA",
206 | "9": "3HGKzDBC6MfnJtcCRi7xB3",
207 | "A": "5uYalrRxXbbK7N8vYlXWFO",
208 | "B": "4oViUMnlTQhI9gJwEhUgv5",
209 | "C": "3Cv7jBCoHsV6ZnajqZk02J",
210 | "D": "6E1ejRJAfE8BC4T1Dc8DNo",
211 | "E": "1XBGTp6OqwYaYhemH3aMKT",
212 | "F": "6gpAgc7TPrcyJcjvjNVLFo",
213 | "G": "2ZUTVMR0zjB8ixh1ZhcbvL",
214 | "H": "7Eaoln7EVETUXJ1rotxHWo",
215 | "I": "0hJZZMFlSVmtQjOYGKnFng",
216 | "J": "4czA2rv6Hz8cgsiomaisAO",
217 | "K": "0SMxKocTYMTE8C4dktdlRX",
218 | "L": "6brTu7TkwXtFMjQgcxkMA4",
219 | "M": "1WWWfx7SyPJEbLCJKt2mpa",
220 | "N": "6cyYD58m4zLDzEWld7sxHC",
221 | "O": "77yuzxCS3csrgTPSW0pvyk",
222 | "P": "58xd48kdUNy6GtJ4j0qENM",
223 | "Q": "51CXAV2GNNL3deCtcXpCeu",
224 | "R": "0xHghnAskte2ZiCqA0YsPV",
225 | "S": "336eHf6SexQkX3MZDykFC7",
226 | "T": "4ghtfPjftCLrsqEeH83Q0x",
227 | "U": "1K8NZfZN8bh0ApIPYJplVB",
228 | "V": "4JnBvAHV8obYVyHVehuOiM",
229 | "W": "3hx0gIgOea9IsOpVjJejR1",
230 | "X": "62ssdaS7RIUmGROgns2TG1",
231 | "Y": "5yKZg1KWvtvWBmHIoU9tzs",
232 | "Z": "53CSKhZZKCvldFSyV2CMMX",
233 | "in": "2vec1SirAf9NVU5YFpYKWo",
234 | "as": "4Q72edajhMV46lHlQQ43Tp",
235 | "of": "0m8KR8qryLSgMUp88xYfiE",
236 | "It": "1HrOS7MY0qh7k7oF9UgvsJ",
237 | "by": "3HLSjmkYgx9jSlY6K7ide0",
238 | "on": "7HXs149WscOjEmnnfk0NSY",
239 | "so": "3lZgcpwEIunsw3xauzu62W",
240 | "be": "5GOZS9DCxvijXL2wLIddlH",
241 | "us": "0HBs0pHrnSP8DEAPM1JX5M",
242 | "no": "0V3K2DEX1fh7nmOLoN8Lla",
243 | "or": "0EQeU8sdVB1yGoY4LItykb",
244 | "an": "2yxqUsz8BNuhzj8JpdDpdW",
245 | "if": "0l8OTE4sFHWrjVUSUmcu2P",
246 | "my": "1AToLbGgM9GhCqc5CnmZ3R",
247 | "up": "06mEvkWtMBTiZkEzNehpxe",
248 | "Go": "6U6UC3Xg5ukTBQIy245bAo",
249 | "go": "7EpP2BHNXHPc7OWlpg8mxj",
250 | "In": "2vec1SirAf9NVU5YFpYKWo",
251 | "As": "13toFl1UwJPsRxDiD9jgtn",
252 | "Of": "313l4VILjTvipoamGptl5h",
253 | "By": "5C4sp6JprCFTO9ZQcg4qXs",
254 | "On": "167c1Blr84k9YpSCHLNh9m",
255 | "So": "7GlurUXL0ZsZYq1YMimC5u",
256 | "Be": "53OQwlz2w9TQDQXVAI5R0H",
257 | "Us": "5f4l3uDDTNNGEtWaXHOIB9",
258 | "No": "4HrI0DIPyvRF1cxUUAGQJc",
259 | "Or": "3OR1FPc6xWGlO13WP3LbvY",
260 | "An": "4jc4q9D2GhKxU8ID2hSVj1",
261 | "If": "40W8Mm9t3ZO1iNQlls35lL",
262 | "My": "0w5lktecJcEEjI9KBu0Dl9",
263 | "Up": "2wzNQYX6veNM0AJncAV75v",
264 | "Am": "5SkShE3Vc3iIDM9GOlboRd",
265 | "am": "5kYZKCHnBomMkRSu3Xij2V",
266 | "at": "23lpXblF7QUq7iRA5s4NRO",
267 | "At": "1AhAdO7zzrW4fQXXOoPyOG"
268 | }
269 |
--------------------------------------------------------------------------------
/phraseToPlaylist/phraseToPlaylist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/phraseToPlaylist/phraseToPlaylist.png
--------------------------------------------------------------------------------
/songstats/README.md:
--------------------------------------------------------------------------------
1 | # songstats
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to see the audio features of the music you like!
4 |
5 | * Right click a song and click "View Song Stats" to see it's audio features.
6 |
7 | ## Install
8 |
9 | Copy `songstats.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions songstats.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|songstats.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/songstats/songstats.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | // NAME: SongStats
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: View a songs stats, such as danceability and acousticness.
6 |
7 | ///
8 |
9 | (function songstats() {
10 | const { CosmosAsync, ContextMenu, URI } = Spicetify;
11 | if (!(CosmosAsync && URI)) {
12 | setTimeout(songstats, 300);
13 | return;
14 | }
15 | // @ts-ignore
16 | var local_language = Spicetify.Locale._locale;
17 | const translation = {
18 | en: {
19 | titletxt: "Song Stats",
20 | buttontxt: "View Song Stats",
21 | danceability: "Danceability",
22 | energy: "Energy",
23 | key: "Key",
24 | loudness: "Loudness",
25 | speechiness: "Speechiness",
26 | acousticness: "Acousticness",
27 | instrumentalness: "Instrumentalness",
28 | liveness: "Liveness",
29 | valence: "Valence",
30 | tempo: "Tempo",
31 | popularity: "Popularity",
32 | releaseDate: "Release Date",
33 | },
34 | fr: {
35 | titletxt: "Statistique de la musique",
36 | buttontxt: "Voir les statistique de la musique",
37 | danceability: "Capacité à danser",
38 | energy: "Énergie",
39 | key: "Tonalité",
40 | loudness: "Intensité sonore",
41 | speechiness: "Élocution",
42 | acousticness: "Acoustique",
43 | instrumentalness: "instrumentalité",
44 | liveness: "vivacité",
45 | valence: "Mood",
46 | tempo: "Tempo",
47 | popularity: "Popularité",
48 | releaseDate: "Date de sortie",
49 | },
50 | "fr-CA": {
51 | titletxt: "Statistique de la musique",
52 | buttontxt: "Voir les statistique de la musique",
53 | danceability: "Capacité à danser",
54 | energy: "Énergie",
55 | key: "Tonalité",
56 | loudness: "Intensité sonore",
57 | speechiness: "Élocution",
58 | acousticness: "Acoustique",
59 | instrumentalness: "instrumentalité",
60 | liveness: "vivacité",
61 | valence: "Mood",
62 | tempo: "Tempo",
63 | popularity: "Popularité",
64 | releaseDate: "Date de sortie",
65 | },
66 | cs: {
67 | titletxt: "Statistiky písně",
68 | buttontxt: "Zobrazit statistiky písně",
69 | danceability: "Tančitelnost",
70 | energy: "Energie",
71 | key: "Tónina",
72 | loudness: "Hlasitost",
73 | speechiness: "Mluvenost",
74 | acousticness: "Akustičnost",
75 | instrumentalness: "Nástrojovost",
76 | liveness: "Živost",
77 | valence: "Emoční náboj",
78 | tempo: "Tempo",
79 | popularity: "Popularita",
80 | releaseDate: "Datum vydání",
81 | },
82 | de: {
83 | titletxt: "Songstatistiken",
84 | buttontxt: "Songstatistiken anzeigen",
85 | danceability: "Tanzbarkeit",
86 | energy: "Energie",
87 | key: "Tonart",
88 | loudness: "Lautstärke",
89 | speechiness: "Sprechanteil",
90 | acousticness: "Akustik",
91 | instrumentalness: "Instrumentalität",
92 | liveness: "Lebendigkeit",
93 | valence: "Stimmung",
94 | tempo: "Tempo",
95 | popularity: "Beliebtheit",
96 | releaseDate: "Veröffentlichungsdatum",
97 | },
98 | es: {
99 | titletxt: "Estadísticas de la canción",
100 | buttontxt: "Ver estadísticas de la canción",
101 | danceability: "Bailable",
102 | energy: "Energía",
103 | key: "Tono",
104 | loudness: "Volumen",
105 | speechiness: "Habla",
106 | acousticness: "Acústica",
107 | instrumentalness: "Instrumental",
108 | liveness: "Vivacidad",
109 | valence: "Estado de ánimo",
110 | tempo: "Tempo",
111 | popularity: "Popularidad",
112 | releaseDate: "Fecha de lanzamiento",
113 | },
114 | };
115 |
116 | try {
117 | translation[local_language]["buttontxt"];
118 | } catch {
119 | local_language = "en";
120 | }
121 |
122 | const titletxt = translation[local_language]["titletxt"];
123 | const buttontxt = translation[local_language]["buttontxt"];
124 | const danceability = translation[local_language]["danceability"];
125 | const energy = translation[local_language]["energy"];
126 | const key = translation[local_language]["key"];
127 | const loudness = translation[local_language]["loudness"];
128 | const speechiness = translation[local_language]["speechiness"];
129 | const acousticness = translation[local_language]["acousticness"];
130 | const instrumentalness = translation[local_language]["instrumentalness"];
131 | const liveness = translation[local_language]["liveness"];
132 | const valence = translation[local_language]["valence"];
133 | const tempo = translation[local_language]["tempo"];
134 | const popularity = translation[local_language]["popularity"];
135 | const releaseDate = translation[local_language]["releaseDate"];
136 |
137 | //Watch for when the song is changed
138 |
139 | async function getSongStats(uris) {
140 | var request = new XMLHttpRequest();
141 | const uri = uris[0];
142 | const uriObj = Spicetify.URI.fromString(uri);
143 | const uriFinal = uri.split(":")[2];
144 | const res = await CosmosAsync.get("https://api.spotify.com/v1/audio-features/" + uriFinal);
145 | const resTrack = await CosmosAsync.get("https://api.spotify.com/v1/tracks/" + uriFinal);
146 |
147 | const pitchClasses = ["C", "C♯/D♭", "D", "D♯/E♭", "E", "F", "F♯/G♭", "G", "G♯/A♭", "A", "A♯/B♭", "B"];
148 |
149 | let keyText = res.key;
150 | if (res.key === -1) {
151 | keyText = "Undefined";
152 | } else {
153 | const pitchClassIndex = res.key;
154 | keyText = pitchClasses[pitchClassIndex];
155 | }
156 |
157 | Spicetify.PopupModal.display({
158 | title: `${titletxt}`,
159 | content: `
184 |
185 |
186 |
${danceability}:
187 |
${Math.round(100 * res.danceability)} %
188 |
189 |
190 |
${energy}:
191 |
${Math.round(100 * res.energy)} %
192 |
193 |
194 |
${key}:
195 |
${keyText}
196 |
197 |
198 |
${loudness}:
199 |
${res.loudness} dB
200 |
201 |
202 |
${speechiness}:
203 |
${Math.round(100 * res.speechiness)} %
204 |
205 |
206 |
207 |
${acousticness}:
208 |
${Math.round(100 * res.acousticness)} %
209 |
210 |
211 |
${instrumentalness}:
212 |
${Math.round(100 * res.instrumentalness)} %
213 |
214 |
215 |
${liveness}:
216 |
${Math.round(100 * res.liveness)} %
217 |
218 |
219 |
${valence}:
220 |
${Math.round(100 * res.valence)} %
221 |
222 |
223 |
${tempo}:
224 |
${res.tempo} BPM
225 |
226 |
227 |
${popularity}:
228 |
${resTrack.popularity} %
229 |
230 |
231 |
${releaseDate}:
232 |
${resTrack.album.release_date}
233 |
234 |
`,
235 | });
236 | }
237 |
238 | function shouldDisplayContextMenu(uris) {
239 | if (uris.length > 1) {
240 | return false;
241 | }
242 |
243 | const uri = uris[0];
244 | const uriObj = Spicetify.URI.fromString(uri);
245 | if (uriObj.type === Spicetify.URI.Type.TRACK) {
246 | return true;
247 | }
248 | return false;
249 | }
250 |
251 | const cntxMenu = new Spicetify.ContextMenu.Item(buttontxt, getSongStats, shouldDisplayContextMenu);
252 |
253 | cntxMenu.register();
254 | })();
255 |
--------------------------------------------------------------------------------
/songstats/songstats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/songstats/songstats.png
--------------------------------------------------------------------------------
/wikify/README.md:
--------------------------------------------------------------------------------
1 | # Wikify
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to learn more about an artist.
4 |
5 | * Right click an artist on spotify and click "View Wiki", it will display a Wikipedia page for the artist in your spotify client!
6 |
7 | ## Install
8 |
9 | Copy `wikify.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions wikify.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|wikify.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/wikify/wikify.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | // NAME: Wikify
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: View an artists wikipedia page to learn more about them
6 |
7 | ///
8 | (function WikiFy() {
9 | if (!document.body.classList.contains('wikify-injected')) {
10 | var styleSheet = document.createElement("style")
11 |
12 | styleSheet.innerHTML =
13 | `body > generic-modal > div > div {
14 | background-color: beige !important;
15 | color: black !important;
16 | } `
17 | document.body.appendChild(styleSheet)
18 | document.body.classList.add('wikify-injected');
19 | }
20 | const {
21 | CosmosAsync,
22 | URI
23 | } = Spicetify;
24 | if (!(CosmosAsync && URI)) {
25 | setTimeout(WikiFy, 10);
26 | return;
27 | }
28 | // @ts-ignore
29 | const lang = Spicetify.Locale.getLocale();
30 | const buttontxt = "View Wiki"
31 | //Watch for when the song is changed
32 |
33 | function error() {
34 | Spicetify.PopupModal.display({
35 | title: "Error",
36 | content: "Selected artist does not have a WikiPedia page, Sorry."
37 | });
38 | }
39 |
40 | async function getWikiText(uris) {
41 |
42 | const rawUri = uris[0];
43 | const uri = rawUri.split(":")[2]
44 | const artistName = await CosmosAsync.get(`https://api.spotify.com/v1/artists/${uri}`)
45 | const artistNameTrimmed = (artistName.name).replace(/\s/g, "%20");
46 |
47 | if (artistName != null) {
48 | try {
49 | const wikiInfo = await CosmosAsync.get(`https://${lang}.wikipedia.org/w/api.php?action=query&format=json&prop=extracts%7Cdescription&titles=${artistNameTrimmed}`)
50 | //TODO: option to choose local language or english / english fallback? / subcontextmenu to choose?
51 | //https://en.wikipedia.org/w/api.php?action=query&format=json&uselang=en&list=search&srsearch=${artistNameTrimmed}
52 |
53 | const wikiInfoArr = wikiInfo.query.pages
54 | const page = Object.values(wikiInfoArr)[0];
55 | if (page != null || page != undefined) {
56 | const pageText = page.extract.replace(//g, '');
57 | if (pageText != "\n") {
58 | Spicetify.PopupModal.display({
59 | title: "WikiFy",
60 | content: page.extract
61 | });
62 | } else {
63 | error();
64 | }
65 | } else {
66 | error();
67 | }
68 | } catch {
69 | Spicetify.PopupModal.display({
70 | title: "Error",
71 | content: "Request failed",
72 | })
73 | }
74 | }
75 | }
76 |
77 | function shouldDisplayContextMenu(uris) {
78 | if (uris.length > 1) {
79 | return false;
80 | }
81 | const uri = uris[0];
82 | const uriObj = Spicetify.URI.fromString(uri);
83 | if (uriObj.type === Spicetify.URI.Type.TRACK || uriObj.type === Spicetify.URI.Type.ARTIST) {
84 | return true;
85 | }
86 | return false;
87 | }
88 |
89 | const cntxMenu = new Spicetify.ContextMenu.Item(
90 | buttontxt,
91 | getWikiText,
92 | shouldDisplayContextMenu,
93 | );
94 |
95 | cntxMenu.register();
96 |
97 | })();
98 |
--------------------------------------------------------------------------------
/wikify/wikify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/wikify/wikify.png
--------------------------------------------------------------------------------
/writeify/README.md:
--------------------------------------------------------------------------------
1 | # Writeify
2 |
3 | [Spicetify](https://github.com/khanhas/spicetify-cli) extension to take notes on different songs
4 |
5 | * Right click any song or click it's note in the note column of the albums to write a note, remember to hit save!
6 |
7 | ## Install
8 |
9 | Copy `writeify.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:
10 | | **Platform** | **Path** |
11 | |----------------|--------------------------------------------------------------------------------------|
12 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |
13 | | **MacOS** | `~/spicetify_data/Extensions` or `$SPICETIFY_CONFIG/Extensions` |
14 | | **Windows** | `%appdata%\spicetify\Extensions\` |
15 |
16 | After putting the extension file into the correct folder, run the following command to install the extension or install through marketplace:
17 |
18 | ```sh
19 | spicetify config extensions writeify.js
20 | spicetify apply
21 | ```
22 |
23 | Note: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.
24 |
25 | Or you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.
26 | Example:
27 |
28 | ```ini
29 | [AdditionalOptions]
30 | ...
31 | extensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|writeify.js
32 | ```
33 |
34 | Then run:
35 |
36 | ```sh
37 | spicetify apply
38 | ```
39 |
40 | ## Usage
41 |
42 | Toggle in the Profile menu.
43 |
44 | 
45 |
46 | ## More
47 |
48 | 🌟 Like it? Gimme some love!
49 | [](https://github.com/CharlieS1103/spicetify-extensions/)
50 |
51 | If you find any bugs, please [create a new issue](https://github.com/CharlieS1103/spicetify-extensions/issues/new/choose) on the GitHub repo.
52 | 
53 |
--------------------------------------------------------------------------------
/writeify/writeify.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | // NAME: Song Notes
4 | // AUTHOR: CharlieS1103
5 | // DESCRIPTION: Create and edit notes for specific songs
6 |
7 | ///
8 |
9 | (async function SongNotes() {
10 | console.log('Song Notes extension loaded');
11 |
12 | const { CosmosAsync, Menu, PopupModal, Platform } = Spicetify;
13 | if (!(CosmosAsync && Menu && PopupModal)) {
14 | setTimeout(SongNotes, 10);
15 | console.log('Waiting for Spicetify objects to be available');
16 | return;
17 | }
18 |
19 | // Make a sleep function
20 |
21 | async function sleep(ms) {
22 | return new Promise(resolve => setTimeout(resolve, ms));
23 | }
24 | /* TODO: Fix this part
25 | Platform.History.listen(async ({ pathname }) => {
26 | if (pathname.includes("/playlist")) {
27 | await sleep(2500)
28 | insertTrackNotes(".main-trackList-trackListRow");
29 | }
30 | });
31 | */
32 |
33 |
34 |
35 |
36 | // Inject CSS
37 | const style = document.createElement("style");
38 | style.innerHTML = `
39 |
40 | .song-notes-textarea {
41 | width: 100%;
42 | height: 200px;
43 | padding: 10px;
44 | font-size: 14px;
45 | background-color: var(--spice-main);
46 | resize: none;
47 | border: 1px var(--spice-misc);
48 | box-shadow: 0px 0px 20px 0px var(--spice-misc);
49 | color: var(--spice-subtext);
50 | }
51 | .song-notes-save {
52 | background-color: var(--spice-button);
53 | color: var(--spice-text);
54 | border: none;
55 | padding: 10px 20px;
56 | font-size: 16px;
57 | cursor: pointer;
58 | border-radius: 5px;
59 | }
60 | .song-notes-save:hover {
61 | background-color: var(--spice-button-active);
62 | }
63 |
64 | .track-notes-column {
65 | -webkit-box-align: center;
66 | -ms-flex-align: center;
67 | align-items: center;
68 | display: -webkit-box;
69 | display: -ms-flexbox;
70 | display: flex;
71 | grid-column: 4;
72 | font-size: 0.750rem;
73 | justify-self: end;
74 | width: 175%;
75 | }
76 | `;
77 | document.head.appendChild(style);
78 |
79 |
80 | // Open the popup modal with the song notes
81 | function openNotesModal(uris) {
82 | console.log('Open notes modal:', uris);
83 | const songUri = uris[0];
84 | const storedNotes = localStorage.getItem("songNotes");
85 | let notes = "";
86 |
87 | if (songUri && storedNotes) {
88 | const notesMap = JSON.parse(storedNotes);
89 | notes = notesMap[songUri] || "";
90 | }
91 |
92 | PopupModal.display({
93 | title: "Song Notes",
94 | content: createNotesModal(notes),
95 | });
96 |
97 | const saveButton = document.querySelector("#song-notes-save");
98 | if (!saveButton) return;
99 | saveButton.addEventListener("click", () => saveSongNotes(songUri), false);
100 | }
101 |
102 | // Create the popup modal to display the notes
103 | function createNotesModal(notes) {
104 | const container = document.createElement("div");
105 | container.innerHTML = `
106 |
107 |
108 |
109 | `;
110 | return container;
111 | }
112 |
113 | // Save the notes for the given song URI to local storage
114 | function saveSongNotes(songUri) {
115 | console.log('Save song notes:', songUri);
116 |
117 | const textarea = document.querySelector("#song-notes-textarea")
118 |
119 | if (!textarea) return;
120 | // @ts-ignore
121 | const notes = textarea.value;
122 |
123 | const storedNotes = localStorage.getItem("songNotes");
124 | const notesMap = storedNotes ? JSON.parse(storedNotes) : {};
125 |
126 | notesMap[songUri] = notes;
127 | localStorage.setItem("songNotes", JSON.stringify(notesMap));
128 |
129 | PopupModal.hide();
130 | }
131 |
132 | // Register the context menu item
133 | const cntxMenu = new Spicetify.ContextMenu.Item(
134 | "Add Note",
135 | openNotesModal,
136 | (uris) => uris.length === 1
137 | );
138 |
139 | cntxMenu.register();
140 |
141 |
142 | /* Function taken from https://github.com/L3-N0X/spicetify-dj-info*/
143 | function getTracklistTrackUri(tracklistElement) {
144 | let values = Object.values(tracklistElement)
145 | if (!values) return null
146 |
147 | return (
148 | values[0]?.pendingProps?.children[0]?.props?.children?.props?.uri ||
149 | values[0]?.pendingProps?.children[0]?.props?.children?.props?.children?.props?.uri ||
150 | values[0]?.pendingProps?.children[0]?.props?.children[0]?.props?.uri
151 | )
152 | }
153 |
154 | function getSongSnippets(uris) {
155 | const storedNotes = localStorage.getItem("songNotes");
156 | const mappedNotes= storedNotes ? JSON.parse(storedNotes) : {};
157 | if(!mappedNotes) return {};
158 | const snippetMap = {};
159 |
160 | uris.forEach((uri) => {
161 | console.log(uri)
162 | if (mappedNotes[uri]) {
163 | snippetMap[uri] = mappedNotes[uri].substring(0, 55) + "...";
164 | }
165 | });
166 | console.log(snippetMap)
167 | return snippetMap;
168 | }
169 |
170 |
171 | function insertTrackNotes(rowSelector) {
172 |
173 | const tracklist = document.getElementsByClassName("main-trackList-trackList main-trackList-indexable")[0];
174 | tracklist.ariaColCount = "6";
175 | const rows = document.querySelectorAll(rowSelector);
176 | if (!rows) return;
177 | // Get the uri for each track row using getTracklistTrackUri and store it in an array
178 | const uris = Array.from(rows, (row) => getTracklistTrackUri(row))
179 | const snippetMap = getSongSnippets(uris);
180 |
181 | const headerRow = document.getElementsByClassName("main-trackList-trackListHeaderRow main-trackList-trackListRowGrid")[0];
182 | if (!headerRow) return;
183 | const headerColumn = document.createElement("div");
184 | headerColumn.classList.add("main-trackList-rowSectionVariable");
185 | headerColumn.ariaColIndex = "6";
186 | headerColumn.innerHTML = "Notes";
187 | // Insert it before main-trackList-rowSectionEnd
188 | const endColumn = headerRow.querySelector(".main-trackList-rowSectionEnd");
189 | if (!endColumn) return;
190 | headerRow.insertBefore(headerColumn, endColumn);
191 |
192 |
193 |
194 | rows.forEach((row) => {
195 | const uri = getTracklistTrackUri(row);
196 | const notes = snippetMap[uri] || "";
197 | const column = document.createElement("div");
198 | column.classList.add("track-notes-column");
199 | column.ariaColIndex = "6";
200 | column.innerHTML = `${notes}`;
201 | const endColumn = row.querySelector(".main-trackList-rowSectionEnd");
202 | if (!endColumn) return;
203 | row.insertBefore(column, endColumn);
204 | });
205 | }
206 |
207 |
208 |
209 | })();
210 |
--------------------------------------------------------------------------------
/writeify/writeify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/ab8fe9ccabc86477bc55a561fc532a0fa416d58c/writeify/writeify.png
--------------------------------------------------------------------------------