├── .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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/adblock/adblock.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | [![Screenshot](screenshot.png)](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/FeatureShuffle/FeatureShuffle.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | 50 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/formatColors/formatColors.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | 22 | 23 | 24 | 25 | 29 | 31 | 33 | 34 | 35 | 36 | 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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/oldSidebar/oldSidebar.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | 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 = `` 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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/phraseToPlaylist/phraseToPlaylist.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/songstats/songstats.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/wikify/wikify.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 | ![Screenshot](https://raw.githubusercontent.com/CharlieS1103/spicetify-extensions/main/writeify/writeify.png) 45 | 46 | ## More 47 | 48 | 🌟 Like it? Gimme some love! 49 | [![Github Stars badge](https://img.shields.io/github/stars/CharlieS1103/spicetify-extensions?logo=github&style=social)](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 | ![https://github.com/CharlieS1103/spicetify-extensions/issues](https://img.shields.io/github/issues/CharlieS1103/spicetify-extensions?logo=github) 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 --------------------------------------------------------------------------------