spicetify added

This commit is contained in:
Sergio Laín 2023-07-05 20:29:17 +02:00
parent 030529e98c
commit a9eeb3517a
16 changed files with 6513 additions and 0 deletions

View file

@ -0,0 +1,59 @@
//@ts-check
// NAME: adblock
// AUTHOR: CharlieS1103
// DESCRIPTION: Block all audio and UI ads on Spotify
/// <reference path="../../spicetify-cli/globals.d.ts" />
(function adblock() {
const { Platform} = Spicetify;
if (!(Platform)) {
setTimeout(adblock, 300)
return
}
var styleSheet = document.createElement("style")
styleSheet.innerHTML =
`
.MnW5SczTcbdFHxLZ_Z8j, .WiPggcPDzbwGxoxwLWFf, .ReyA3uE3K7oEz7PTTnAn, .main-leaderboardComponent-container, .sponsor-container, a.link-subtle.main-navBar-navBarLink.GKnnhbExo0U9l7Jz2rdc{
display: none !important;
}
`
document.body.appendChild(styleSheet)
delayAds()
var billboard = Spicetify.Platform.AdManagers.billboard.displayBillboard;
Spicetify.Platform.AdManagers.billboard.displayBillboard = function (arguments) {
Spicetify.Platform.AdManagers.billboard.finish()
// hook before call
var ret = billboard.apply(this, arguments);
// hook after call
console.log("Adblock.js: Billboard blocked! Leave a star!")
Spicetify.Platform.AdManagers.billboard.finish()
const observer = new MutationObserver((mutations, obs) => {
const billboardAd = document.getElementById('view-billboard-ad');
if (billboardAd) {
Spicetify.Platform.AdManagers.billboard.finish()
obs.disconnect();
return;
}
});
observer.observe(document, {
childList: true,
subtree: true
});
return ret;
};
function delayAds() {
console.log("Ads delayed: Adblock.js")
Spicetify.Platform.AdManagers.audio.audioApi.cosmosConnector.increaseStreamTime(-100000000000)
Spicetify.Platform.AdManagers.billboard.billboardApi.cosmosConnector.increaseStreamTime(-100000000000)
}
setInterval(delayAds, 720 *10000);
})()

View file

@ -0,0 +1,39 @@
// Color map
let colorPalette = {
rosewater: "#f4dbd6",
flamingo: "#f0c6c6",
pink: "#f5bde6",
maroon: "#ee99a0",
red: "#ed8796",
peach: "#f5a97f",
yellow: "#eed49f",
green: "#a6da95",
teal: "#8bd5ca",
blue: "#8aadf4",
sky: "#91d7e3",
lavender: "#b7bdf8",
white: "#d9e0ee"
}
// waitForElement borrowed from:
// https://github.com/morpheusthewhite/spicetify-themes/blob/master/Dribbblish/dribbblish.js
function waitForElement(els, func, timeout = 100) {
const queries = els.map(el => document.querySelector(el));
if (queries.every(a => a)) {
func(queries);
} else if (timeout > 0) {
setTimeout(waitForElement, 300, els, func, --timeout);
}
}
// Return the color label for a given hex color value
function getKeyByValue(object, value) {
return Object.keys(object).find(key => object[key] === value.trim());
}
// Used to select matching equalizer-animated-COLOR.gif
waitForElement([".Root"], (root) => {
let spiceEq = getComputedStyle(document.querySelector(":root")).getPropertyValue("--spice-equalizer");
let eqColor = getKeyByValue(colorPalette, spiceEq);
root[0].classList.add(`catppuccin-eq-${eqColor}`);
});

View file

@ -0,0 +1,654 @@
// NAME: Copy Playlists
// AUTHOR: einzigartigerName
// DESCRIPTION: copy/combine playlist/queue directly in Spotify
(function CopyPlaylist() {
const { CosmosAPI, BridgeAPI, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify
if (!(CosmosAPI || BridgeAPI)) {
setTimeout(CopyPlaylist, 1000);
return;
}
const STORAGE_KEY = "combine_buffer_spicetify"
const TOP_BTN_TOOLTIP = "Combine Playlists"
const MENU_BTN_CREATE_NEW = "Create Playlist"
const MENU_BTN_INSERT_BUFFER = "Copy to Buffer"
class PlaylistCollection {
constructor() {
const menu = createMenu()
this.container = menu.container
this.items = menu.menu
this.lastScroll = 0
this.container.onclick = () => {
this.storeScroll()
this.container.remove()
}
this.pattern
this.apply()
}
apply() {
this.items.textContent = '' // Remove all childs
this.items.append(createMenuItem("Create Playlist", () => highjackCreateDialog(mergePlaylists(this.pattern))))
this.items.append(createMenuItem("Clear Buffer", () => LIST.clearStorage()))
const select = createPatternSelect(this.filter);
select.onchange = (event) => {
this.pattern = event.srcElement.selectedIndex;
}
this.items.append(select);
const collection = this.getStorage();
collection.forEach((item) => this.items.append(new CardContainer(item)))
}
getStorage() {
const storageRaw = LocalStorage.get(STORAGE_KEY);
let storage = [];
if (storageRaw) {
storage = JSON.parse(storageRaw);
} else {
LocalStorage.set(STORAGE_KEY, "[]")
}
return storage;
}
addToStorage(data) {
/** @type {Object[]} */
const storage = this.getStorage();
storage.push(data);
LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
this.apply()
}
removeFromStorage(id) {
const storage = this.getStorage()
.filter(item => item.id !== id)
LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
this.apply()
}
clearStorage() {
LocalStorage.set(STORAGE_KEY, "[]");
this.apply()
}
moveItem(uri, direction) {
var storage = this.getStorage()
var from;
for (var i = 0; i < storage.length; i++) {
if (storage[i].uri === uri) {
from = i
break;
}
}
if (!from) { return }
var to = from + direction
if (to < 0 || to >= storage.length) { return }
var tmp = storage[from]
storage[from] = storage[to]
storage[to] = tmp
LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
this.apply()
}
changePosition(x, y) {
this.items.style.left = x + "px"
this.items.style.top = y + 10 + "px"
}
storeScroll() {
this.lastScroll = this.items.scrollTop
}
setScroll() {
this.items.scrollTop = this.lastScroll
}
}
/*
* Displays Stored Playlist
* {id, uri, name, tracks, imgUri, owner}
*/
class CardContainer extends HTMLElement {
constructor(info) {
super()
this.innerHTML = `
<div class="card card-horizontal card-type-album ${info.imgUri ? "" : "card-hidden-image"}" data-uri="${info.uri}" data-contextmenu="">
<div class="card-attention-highlight-box"></div>
<div class="card-horizontal-interior-wrapper">
${info.imgUri ? `
<div class="card-image-wrapper">
<div class="card-image-hit-area">
<a class="card-image-link" link="${info.uri}">
<div class="card-hit-area-counter-scale-left"></div>
<div class="card-image-content-wrapper">
<div class="card-image" style="background-image: url('${info.imgUri}')"></div>
</div>
</a>
<div class="card-overlay"></div>
</div>
</div>
` : ""}
<div class="card-info-wrapper">
<div class="order-controls">
<div class="order-controls-up">
<button class="button button-green button-icon-only spoticon-chevron-up-16" data-tooltip="Move Up"></button>
</div>
<div class="order-controls-remove">
<button class="button button-green button-icon-only spoticon-x-16" data-tooltip="Remove"></button>
</div>
<div class="order-controls-down">
<button class="button button-green button-icon-only spoticon-chevron-down-16" data-tooltip="Move Down"></button>
</div>
</div>
<a class="card-info-link" ${info.uri}>
<div class="card-info-content-wrapper">
<div class="card-info-title"><span class="card-info-title-text">${info.name}</span></div>
<div class="card-info-subtitle-owner"><span>${info.owner}</span></div>
<div class="card-info-subtitle-tracks"><span>${info.tracks.length === 1 ? "1 Track" : `${info.tracks.length} Tracks`}</span></div>
</div>
</a>
</div>
</div>
</div>`
const up = this.querySelector(".order-controls-up")
up.onclick = (event) => {
LIST.moveItem(info.uri, -1)
event.stopPropagation()
}
const remove = this.querySelector(".order-controls-remove")
remove.onclick = (event) => {
LIST.removeFromStorage(info.id)
event.stopPropagation()
}
const down = this.querySelector(".order-controls-down")
down.onclick = (event) => {
LIST.moveItem(info.uri, +1)
event.stopPropagation()
}
const imageLink = this.querySelector(".card-image-link");
const infoLink = this.querySelector(".card-info-link");
if (imageLink) imageLink.addEventListener("click", ((e) => showPlaylist(e)));
if (infoLink) infoLink.addEventListener("click", ((e) => showPlaylist(e)));
}
}
customElements.define("combine-buffer-card-container", CardContainer)
const LIST = new PlaylistCollection()
// New Playlist Button
const playlistDialogButton = document.querySelector("#new-playlist-button-mount-point > div > button")
if (!playlistDialogButton) return;
document.querySelector("#view-browser-navigation-top-bar")
.append(createTopBarButton())
createPlaylistContextMenu().register()
/**************************************************************************
UI Building
**************************************************************************/
// If Queue Page add Buttons
const iframeInterval = setInterval(() => {
/** @type {HTMLIFrameElement} */
const currentIframe = document.querySelector("iframe.active");
if (!currentIframe ||
currentIframe.id !== "app-queue"
) {
return;
}
const headers = currentIframe.contentDocument.querySelectorAll(
".glue-page-header__buttons"
);
for (const e of headers) {
e.append(createQueueButton(
"Save as Playlist",
"Save the current Queue as a new Playlist",
() => {
let tracks = getQueueTracks();
highjackCreateDialog(tracks);
},
));
e.append(createQueueButton(
"Copy into Buffer",
"Insert the current Queue into the Buffer",
() => { queueToBuffer() },
));
}
if (headers.length > 0) clearInterval(iframeInterval);
}, 500)
// Creates the Main Menu
function createMenu() {
const container = document.createElement("div")
container.id = "combine-playlist-spicetify"
container.className = "context-menu-container"
container.style.zIndex = "1029"
const style = document.createElement("style")
style.textContent = `
#combine-menu {
display: inline-block;
width: 33%;
max-height: 70%;
overflow: hidden auto;
padding: 10px
}
.combine-pattern {
margin-top: 7px;
}
.order-controls {
position: absolute;
right: 0;
padding: 0 5px 5px 0;
z-index: 3
}
.button.button-icon-only::before {
color: var(--modspotify_main_fg);
}
.order-controls-up {
position: relative;
top: 100%;
}
.order-controls-remove {
position: relative;
top: 50%;
}
.order-controls-down {
position: relative;
bottom: 100%;
}
.card-info-subtitle-owner {
color: var(--modspotify_secondary_fg);
}
.card-info-subtitle-tracks {
font-weight: lighter;
color: var(--modspotify_secondary_fg);
}
`
const menu = document.createElement("ul")
menu.id = "combine-menu"
menu.className = "context-menu"
container.append(style, menu)
return { container, menu }
}
// Creates a Button in the Combine Menu
function createMenuItem(name, callback) {
const item = document.createElement("div");
item.classList.add("item");
item.onclick = callback;
item.onmouseover = () => item.classList.add("hover");
item.onmouseleave = () => item.classList.remove("hover");
const text = document.createElement("span");
text.classList.add("text");
text.innerText = name;
item.append(text);
return item;
}
// Creates the SubMenu in Playlist Context
function createPlaylistContextMenu() {
var createFromCurrent = new Spicetify.ContextMenu.Item(
MENU_BTN_CREATE_NEW,
(uris) => {
if (uris.length === 1) {
fetchPlaylist(uris[0])
.then((buffer) => highjackCreateDialog(buffer.tracks))
.catch((err) => Spicetify.showNotification(`${err}`));
return;
} else {
Spicetify.showNotification("Unable to find Playlist URI")
}
},
(_) => true
)
var insertIntoBuffer = new Spicetify.ContextMenu.Item(
MENU_BTN_INSERT_BUFFER,
(uris) => {
if (uris.length === 1) {
fetchPlaylist(uris[0])
.then((buffer) => {LIST.addToStorage(buffer)})
.catch((err) => Spicetify.showNotification(`${err}`));
return;
}
},
(_) => true
)
return new Spicetify.ContextMenu.SubMenu(
"Copy Playlist",
[ createFromCurrent, insertIntoBuffer],
(uris) => {
if (uris.length === 1) {
const uriObj = Spicetify.URI.fromString(uris[0]);
switch (uriObj.type) {
case Spicetify.URI.Type.PLAYLIST:
case Spicetify.URI.Type.PLAYLIST_V2:
return true;
}
return false;
}
// Multiple Items selected.
return false;
}
)
}
// Creates the Button to View Merge Buffer
function createTopBarButton() {
const button = document.createElement("button")
button.classList.add("button", "spoticon-copy-16", "merge-button")
button.setAttribute("data-tooltip", TOP_BTN_TOOLTIP)
button.setAttribute("data-contextmenu", "")
button.setAttribute("data-uri", "spotify:special:copy")
button.onclick = () => {
const bound = button.getBoundingClientRect()
LIST.changePosition(bound.left, bound.top)
document.body.append(LIST.container)
LIST.setScroll()
}
return button
}
// Creates the Dropdown Menu for Merge Pattern
function createPatternSelect(defaultOpt = 0) {
const select = document.createElement("select");
select.className = "GlueDropdown combine-pattern";
const appendOpt = document.createElement("option");
appendOpt.text = "Append";
const shuffleOpt = document.createElement("option");
shuffleOpt.text = "Shuffle";
const alternateOpt = document.createElement("option");
alternateOpt.text = "Alternate";
select.onclick = (ev) => ev.stopPropagation();
select.append(appendOpt, shuffleOpt, alternateOpt);
select.options[defaultOpt].selected = true;
return select;
}
// Queue button
function createQueueButton(name, tooltip, callback) {
const b = document.createElement("button");
b.classList.add("button", "button-green");
b.innerText = name;
b.setAttribute("data-tooltip", tooltip);
b.onclick = callback;
return b;
}
// Highjack Spotifies 'New Playlist' Dialog
function highjackCreateDialog(tracks) {
playlistDialogButton.click()
var createButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container > button")
var buttonContainer = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container")
var highjackedButton = createButton.cloneNode(true)
highjackedButton.addEventListener("click", () => onCreateNewPlaylist(tracks))
window.addEventListener("keypress", (event) => {
if (event.code === `Enter`) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
createButton.click();
}
});
createButton.remove()
buttonContainer.insertAdjacentElement("afterbegin", highjackedButton)
}
/**************************************************************************
OnCLick Functions
**************************************************************************/
// Create a new Playlist from Inputs
function onCreateNewPlaylist(tracks) {
var exitButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__close-button > button");
var nameInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-name > input")
var descInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-description > textarea")
var imageInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__img-container > div > div.PlaylistAnnotationModal__img-holder > img")
var name = nameInput.value
if (!name) {
name = nameInput.getAttribute("placeholder")
}
var desc = descInput.value
var img;
if (imageInput) {
img = imageInput.getAttribute("src")
}
createPlaylist(name)
.then(res => addTracks(res.uri, tracks))
.then((_) => Spicetify.showNotification(`Created Playlist: "${name}"`))
.catch((err) => Spicetify.showNotification(`${err}`));
exitButton.click()
if (exitButton) {
exitButton.click()
}
}
// Get All Tracks in Queue and remove delimiter
function getQueueTracks() {
return Spicetify.Queue.next_tracks
.map((t) => t.uri)
.filter((t) => { return t != "spotify:delimiter"; })
}
// Copy the Queue to the Combine Buffer
function queueToBuffer() {
let tracks = getQueueTracks();
var date = new Date()
var year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
var month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date);
var day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
const timeOptions = { hour: 'numeric', minute: 'numeric', hour12: false};
var time = new Intl.DateTimeFormat(`en`, timeOptions).format(date);
let queue = {
id: `spotify:queue-${date}`,
uri: `spotify:queue`,
name: "Queue",
imgUri: undefined,
tracks: tracks,
owner: `${time} - ${day} ${month} ${year}`,
}
LIST.addToStorage(queue);
}
// Show the clicked Playlist
async function showPlaylist(event) {
console.log(event)
}
/**************************************************************************
Merge Playlists
**************************************************************************/
// Merge all Playlists
function mergePlaylists(pattern) {
var tracks = LIST.getStorage().map((pl) => pl.tracks)
switch (pattern) {
case 1: return shuffle(tracks);
case 2: return alternate(tracks);
default: return append(tracks);
}
}
// Alternate Playlists
function alternate(arrays) {
var combined = []
while (arrays.length != 0) {
var current = arrays.shift()
if (current.length != 0) {
combined.push(current.shift())
if (current.length != 0) {
arrays.push(current)
}
}
}
return combined
}
// Shuffle all tracks using the Durstenfeld Shuffle
function shuffle(arrays) {
var combined = append(arrays)
for (var i = combined.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = combined[i];
combined[i] = combined[j];
combined[j] = temp;
}
return combined;
}
// Simply Concat all Playlist
function append(arrays) {
var combined = []
arrays.forEach((arr) => combined = combined.concat(arr))
return combined;
}
/**************************************************************************
Calls to the CosmosAPI
**************************************************************************/
// Fetch all Track from Playlist URI
async function fetchPlaylist(uri) {
return await new Promise((resolve, reject) => {
Spicetify.BridgeAPI.cosmosJSON(
{
method: "GET",
uri: `sp://core-playlist/v1/playlist/${uri}/`,
body: {
policy: {
link: true,
},
},
},
(error, res) => {
if (error) {
reject(error);
return;
}
let id = `${uri}-${new Date()}`
let tracks = res.items.map((track) => track.link)
let img = res.playlist.picture
let name = res.playlist.name
let owner = res.playlist.owner.name
let playlist = {id: id, uri: uri, name: name, tracks: tracks, imgUri: img, owner: owner}
resolve(playlist);
}
);
});
}
// Create a new Playlist
async function createPlaylist(name) {
return await new Promise((resolve, reject) => {
Spicetify.BridgeAPI.cosmosJSON(
{
method: "POST",
uri: `sp://core-playlist/v1/rootlist`,
body: {
operation: "create",
playlist: !0,
before: "start",
name: name,
},
},
(error, res) => {
if (error) {
reject(error);
return;
}
resolve(res);
}
);
});
}
// add track list to playlist
async function addTracks(uri, tracks) {
return await new Promise((resolve, reject) => {
Spicetify.BridgeAPI.cosmosJSON(
{
method: "POST",
uri: `sp://core-playlist/v1/playlist/${uri}`,
body: {
operation: "add",
uris: tracks,
after: "end"
}
},
(error, res) => {
if (error) {
reject(error);
return;
}
resolve(res);
}
);
});
}
})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,125 @@
// <reference path="./globals.d.ts" />
(function Genre() {
const { CosmosAsync, Player } = Spicetify;
/**
* Fetch genre from artist
*
* @param artistURI {string}
* @return {Promise<Array>}
*/
const fetchGenres = async (artistURI) => {
const res = await CosmosAsync.get(
`https://api.spotify.com/v1/artists/${artistURI}`
);
// noinspection JSUnresolvedVariable
return res.genres.slice(0, 3) // Only keep the first 3 genres
};
/**
* Fetch playlist from The Sound of Spotify for a given genre
* @param {String} genre
* @return {String|null}
*/
const fetchSoundOfSpotifyPlaylist = async (genre) => {
const query = encodeURIComponent(`The Sound of ${genre}`);
// Check localStorage for playlist
const cached = localStorage.getItem(`everynoise:${query}`);
if (cached !== null) {
return cached;
}
// Search for playlist and check results for the everynoise account
const re = new RegExp(`^the sound of ${genre.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i');
const res = await CosmosAsync.get(`https://api.spotify.com/v1/search?q=${query}&type=playlist`)
for (const item of res.playlists.items) {
if (item.owner.id === "thesoundsofspotify" && re.test(item.name)) {
localStorage.setItem(`everynoise:${genre}`, item.uri);
return item.uri
}
}
return null;
};
// Store the current playback id
let playback = null;
/**
*
* @type {Node}
*/
let genreContainer = null;
let infoContainer = document.querySelector('div.main-trackInfo-container');
/**
* Remove genre injection in the UI
*/
const cleanInjection = () => {
if (genreContainer !== null) {
try {
infoContainer.removeChild(genreContainer);
} catch (e) {}
}
};
/**
* Inject genres to UI
*/
const inject = () => {
Player.addEventListener('onprogress', async () => {
if (Player.data.track.metadata.hasOwnProperty('artist_uri')) {
// If the registered song isn't the same as the one currently being played then fetch genres
if (playback !== Player.data.playback_id) {
// Save the new track
playback = Player.data.playback_id;
const id = Player.data.track.metadata.artist_uri.split(':')[2];
const genres = await fetchGenres(id);
cleanInjection();
genreContainer = document.createElement('div');
// noinspection JSUndefinedPropertyAssignment
genreContainer.className = 'main-trackInfo-genres ellipsis-one-line main-type-finale';
// noinspection JSUnresolvedVariable
genreContainer.style.color = 'var(--spice-extratext)';
for (const i in genres) {
let element;
const uri = await fetchSoundOfSpotifyPlaylist(genres[i]);
if (uri !== null) {
element = document.createElement('a');
element.innerHTML = genres[i];
element.href = uri;
} else {
element = document.createElement('span');
}
element.innerHTML = genres[i];
element.style.fontSize ="11px";
genreContainer.appendChild(element);
if (i < genres.length-1) {
const separator = document.createElement('span');
separator.innerHTML = ', ';
genreContainer.appendChild(separator);
}
}
infoContainer = document.querySelector('div.main-trackInfo-container');
if(!infoContainer) cleanInjection();
infoContainer.appendChild(genreContainer);
}
} else {
cleanInjection();
}
});
};
if (!CosmosAsync) {
setTimeout(Genre, 500);
} else {
inject();
}
})();

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,127 @@
// NAME: History Shortcut
// AUTHOR: einzigartigerName
// DESCRIPTION: Adds a Shortcut to your Listening History to the Sidebar
(function HistoryShortcut() {
const { CosmosAPI, Player, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify
if (!(CosmosAPI && Player && LocalStorage && PlaybackControl && ContextMenu && URI)) {
setTimeout(HistoryShortcut, 300)
return
}
const ITEM_LABEL = "History"
const HISTORY_DIV_CLASS = "SidebarListItem"
const HISTORY_DIV_CLASS_ACTIVE = "SidebarListItem SidebarListItem--is-active"
const HISTORY_ANKER_CLASS = "SidebarListItemLink SidebarListItemLink--tall spoticon-time-24"
const HISTORY_ANKER_CLASS_ACTIVE = "SidebarListItemLink SidebarListItemLink--is-highlighted SidebarListItemLink--tall spoticon-time-24"
let historyItem = createHistoyItem()
// Get Sidebar Lists
var topicList = document.querySelector("#view-navigation-bar > div > div.LeftSidebar__section > div > ul")
if (topicList) {
// Add to first in list
// On default layout this would be the Home/Browse/Radio List
topicList.appendChild(historyItem.div)
} else {
return
}
const toCheckMutate = document.getElementById('view-content');
const config = { attributes: true, childList: true, subtree: true };
let observerCallback = function(_, _) {
appQueue = document.getElementById("app-queue")
if (!appQueue){ return }
if (appQueue.getAttribute("class") === "active"
&& appQueue.getAttribute("data-app-uri") === "spotify:app:queue:history"
) {
onClickHistory()
} else {
onLeaveHistory()
}
};
let observer = new MutationObserver(observerCallback)
observer.observe(toCheckMutate, config)
// Deactivate Active Status for History Item
function onLeaveHistory() {
historyItem.div.setAttribute("class",HISTORY_DIV_CLASS)
historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS)
}
// Activate Active Status for History Item
function onClickHistory() {
historyItem.div.setAttribute("class", HISTORY_DIV_CLASS_ACTIVE)
historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS_ACTIVE)
}
// Construct the List Item
function createHistoyItem() {
/* List Item
* <li class="SidebarListItem">
*/
let listItem = document.createElement("li")
listItem.setAttribute("class", HISTORY_DIV_CLASS)
/* Outer Div Element
* <div class="DropTarget SidebarListItem__drop-target DropTarget--tracks DropTarget--albums DropTarget--artists DropTarget--playlists">
*/
let outer = document.createElement("div")
outer.setAttribute("class", "DropTarget SidebarListItem__drop-target")
/* Middle Div Element
* <div class="SidebarListItem__inner">
*/
let inner = document.createElement("div")
inner.setAttribute("class", "SidebarListItem__inner")
/* Link Div Element
* <div class="SidebarListItem__link">
*/
let link = document.createElement("div")
link.setAttribute("class", "SidebarListItem__link")
/* Anker
* <a class="SidebarListItemLink SidebarListItemLink--tall spoticon-time-24"
* draggable="false"
* href="spotify:app:queue:history"
* data-sidebar-list-item-uri="spotify:app:queue:history"
* data-ta-id="sidebar-list-item-link">
*/
anker = document.createElement("a")
anker.setAttribute("class", HISTORY_ANKER_CLASS)
anker.setAttribute("draggable", "false")
anker.setAttribute("href", "spotify:app:queue:history")
anker.setAttribute("data-sidebar-list-item-uri", "spotify:app:queue:history")
anker.setAttribute("data-ta-id", "sidebar-list-item-link")
/* Item Text
* <span class="SidebarListItem__label"
* dir="auto">
* History
* </span>
*/
span = document.createElement("span")
span.setAttribute("class", "SidebarListItem__label")
span.setAttribute("dir", "auto")
span.textContent = ITEM_LABEL
anker.appendChild(span)
link.appendChild(anker)
inner.appendChild(link)
outer.appendChild(inner)
listItem.appendChild(outer)
listItem.addEventListener("click", onClickHistory)
return {div: listItem, anker: anker}
}
})();

View file

@ -0,0 +1,532 @@
//@ts-check
// NAME: Keyboard Shortcut
// AUTHOR: dax
// DESCRIPTION: Register a few more keybinds to support keyboard-driven navigation in Spotify client.
/// <reference path="../spicetify-cli/globals.d.ts" />
(function KeyboardShortcutMy() {
if (!Spicetify.Keyboard) {
setTimeout(KeyboardShortcutMy, 1000);
return;
}
const SCROLL_STEP = 50;
/**
* Register your own keybind with function `registerBind`
*
* Syntax:
* registerBind(keyName, ctrl, shift, alt, callback)
*
* ctrl, shift and alt are boolean, true or false
*
* Valid keyName:
* - BACKSPACE - C - Y - F3
* - TAB - D - Z - F4
* - ENTER - E - WINDOW_LEFT - F5
* - SHIFT - F - WINDOW_RIGHT - F6
* - CTRL - G - SELECT - F7
* - ALT - H - NUMPAD_0 - F8
* - PAUSE/BREAK - I - NUMPAD_1 - F9
* - CAPS - J - NUMPAD_2 - F10
* - ESCAPE - K - NUMPAD_3 - F11
* - SPACE - L - NUMPAD_4 - F12
* - PAGE_UP - M - NUMPAD_5 - NUM_LOCK
* - PAGE_DOWN - N - NUMPAD_6 - SCROLL_LOCK
* - END - O - NUMPAD_7 - ;
* - HOME - P - NUMPAD_8 - =
* - ARROW_LEFT - Q - NUMPAD_9 - ,
* - ARROW_UP - R - MULTIPLY - -
* - ARROW_RIGHT - S - ADD - /
* - ARROW_DOWN - T - SUBTRACT - `
* - INSERT - U - DECIMAL_POINT - [
* - DELETE - V - DIVIDE - \
* - A - W - F1 - ]
* - B - X - F2 - "
*
* Use one of keyName as a string. If key that you want isn't in that list,
* you can also put its keycode number in keyName as a number.
*
* callback is name of function you want your shortcut to bind to. It also
* returns one KeyboardEvent parameter.
*
* Following are my default keybinds, use them as examples.
*/
//My Personal Binds----------------------------------------------
// Seek to progress percent of song
registerBind("NUMPAD_0", false, false, false, ()=>Spicetify.Player.seek(0));
registerBind("NUMPAD_1", false, false, false, ()=>Spicetify.Player.seek(.1));
registerBind("NUMPAD_2", false, false, false, ()=>Spicetify.Player.seek(.2));
registerBind("NUMPAD_3", false, false, false, ()=>Spicetify.Player.seek(.3));
registerBind("NUMPAD_4", false, false, false, ()=>Spicetify.Player.seek(.4));
registerBind("NUMPAD_5", false, false, false, ()=>Spicetify.Player.seek(.5));
registerBind("NUMPAD_6", false, false, false, ()=>Spicetify.Player.seek(.6));
registerBind("NUMPAD_7", false, false, false, ()=>Spicetify.Player.seek(.7));
registerBind("NUMPAD_8", false, false, false, ()=>Spicetify.Player.seek(.8));
registerBind("NUMPAD_9", false, false, false, ()=>Spicetify.Player.seek(.9));
// Q to open Queue page
registerBind("Q", false, false, false, clickQueueButton);
// C to open current playing context, L to open Lyrics, H to open Home Tab , Y to Open the Ypur Library, S to open the Lyrics Plus Custom App
registerBind("C", false, false, false, openContext)
registerBind("L", false, false, false, toggleLyrics);
registerBind("H", false, false, false, openHome);
registerBind("Y", false, false, false, openLibrary);
registerBind("S", false, false, false, openLyrics);
// Arrow keys to change volume
registerBind("ARROW_DOWN", false, false, false, decreaseVolume);
registerBind("ARROW_UP" , false, false, false, increaseVolume);
// Arrow keys to seek track
registerBind("ARROW_RIGHT", false, false, false, seekForward);
registerBind("ARROW_LEFT", false, false, false, seekBack);
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function clickQueueButton() {
document.querySelector('.control-button-wrapper>button[Aria-label="Queue"]').click();
}
function openContext(){
big = document.querySelector("#main > div > div.Root__top-container > nav > div.main-navBar-navBar > div:nth-child(3) > div > div > a > div")
small = document.querySelector("#main > div > div.Root__top-container > div.Root__now-playing-bar > footer > div > div.main-nowPlayingBar-left > div > div.main-coverSlotCollapsed-container > div > a > div")
if(big)
big.click();
else
small.click()
}
function toggleLyrics() {
document.querySelector('button[title="Popup Lyrics"]').click()
}
function openHome(){
ele = document.querySelector(`.main-navBar-navBar a[href="/"]`)
if(ele)
ele.click();
}
function openLyrics(){
ele = document.querySelector(`.main-navBar-navBar a[href="/lyrics-plus"]`)
if(ele)
ele.click();
}
function openLibrary(){
ele = document.querySelector(`.main-navBar-navBar a[href="/collection"]`)
if(ele)
ele.click();
}
function seekForward(){
Spicetify.Player.skipForward(5000)
}
function seekBack(){
Spicetify.Player.skipBack(5000)
}
async function decreaseVolume(){
if(!document.querySelector(".main-trackList-selected")){
if(Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player?.origin?.setVolume(getVolume() - 0.05)
else await Spicetify.Platform.PlaybackAPI.setVolume(getVolume() - 0.05)
}
}
async function increaseVolume(){
if(!document.querySelector(".main-trackList-selected")){
if(Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player?.origin?.setVolume(getVolume() + 0.05)
else await Spicetify.Platform.PlaybackAPI.setVolume(getVolume() + 0.05)
}
}
function getVolume(){
return (Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume)
}
// ---------------------------------------------------------------------------------------
// Ctrl + Tab and Ctrl + Shift + Tab to switch sidebar items
registerBind("TAB", true, false, false, rotateSidebarDown);
registerBind("TAB", true, true, false, rotateSidebarUp);
// Ctrl + Q to open Queue page
// registerBind("Q", true, false, false, clickQueueButton);
// Shift + H and Shift + L to go back and forward page
registerBind("H", false, true, false, clickNavigatingBackButton);
registerBind("L", false, true, false, clickNavigatingForwardButton);
// PageUp, PageDown to focus on iframe app before scrolling
registerBind("PAGE_UP", false, true, false, focusOnApp);
registerBind("PAGE_DOWN", false, true, false, focusOnApp);
// J and K to vertically scroll app
registerBind("J", false, false, false, appScrollDown);
registerBind("K", false, false, false, appScrollUp);
// G and Shift + G to scroll to top and to bottom
registerBind("G", false, false, false, appScrollTop);
registerBind("G", false, true, false, appScrollBottom);
// M to Like/Unlike track
registerBind("M", false, false, false, Spicetify.Player.toggleHeart);
// Forward Slash to open search page
registerBind("/", false, false, false, openSearchPage);
// A to activate Link Follow function
const vim = new VimBind();
registerBind("A", false, false, false, vim.activate.bind(vim));
// Esc to cancel Link Follow
vim.setCancelKey("ESCAPE")
vim.setCancelKey("Z")
function rotateSidebarDown() {
rotateSidebar(1)
}
function rotateSidebarUp() {
rotateSidebar(-1)
}
// function clickQueueButton() {
// document.querySelector(".control-button-wrapper .spoticon-queue-16").click();
// }
function clickNavigatingBackButton() {
document.querySelector(".main-topBar-historyButtons .main-topBar-back").click();
}
function clickNavigatingForwardButton() {
document.querySelector(".main-topBar-historyButtons .main-topBar-forward").click();
}
function appScrollDown() {
const app = focusOnApp();
if (app) {
app.scrollBy(0, SCROLL_STEP);
}
}
function appScrollUp() {
const app = focusOnApp();
if (app) {
app.scrollBy(0, -SCROLL_STEP);
}
}
function appScrollBottom() {
const app = focusOnApp();
app.scroll(0, app.scrollHeight);
}
function appScrollTop() {
const app = focusOnApp();
app.scroll(0, 0);
}
/**
*
* @param {KeyboardEvent} event
*/
function openSearchPage(event) {
const searchInput = document.querySelector(".main-topBar-topbarContentWrapper input");
if (searchInput) {
searchInput.focus();
} else {
const sidebarItem = document.querySelector(`.main-navBar-navBar a[href="/search"]`);
if (sidebarItem) {
sidebarItem.click();
}
}
event.preventDefault();
}
/**
*
* @param {Spicetify.Keyboard.ValidKey} keyName
* @param {boolean} ctrl
* @param {boolean} shift
* @param {boolean} alt
* @param {(event: KeyboardEvent) => void} callback
*/
function registerBind(keyName, ctrl, shift, alt, callback) {
const key = Spicetify.Keyboard.KEYS[keyName];
Spicetify.Keyboard.registerShortcut(
{
key,
ctrl,
shift,
alt,
},
(event) => {
if (!vim.isActive) {
callback(event);
}
},
);
}
function focusOnApp() {
return document.querySelector("main .os-viewport");
}
/**
* @returns {number}
*/
function findActiveIndex(allItems) {
const active = document.querySelector(
".main-navBar-navBarLinkActive, .main-collectionLinkButton-selected, .main-rootlist-rootlistItemLinkActive"
);
if (!active) {
return -1;
}
let index = 0;
for (const item of allItems) {
if (item === active) {
return index;
}
index++;
}
}
/**
*
* @param {1 | -1} direction
*/
function rotateSidebar(direction) {
const allItems = document.querySelectorAll(
".main-navBar-navBarLink, .main-collectionLinkButton-collectionLinkButton, .main-rootlist-rootlistItemLink"
);
const maxIndex = allItems.length - 1;
let index = findActiveIndex(allItems) + direction;
if (index < 0) index = maxIndex;
else if (index > maxIndex) index = 0;
let toClick = allItems[index];
if (!toClick.hasAttribute("href")) {
toClick = toClick.querySelector(".main-rootlist-rootlistItemLink");
}
toClick.click();
}
})();
function VimBind() {
const elementQuery = ["[href]", "button", "td.tl-play", "td.tl-number", "tr.TableRow"].join(",");
const keyList = "qwertasdfgzxcvyuiophjklbnm".split("");
const lastKeyIndex = keyList.length - 1;
this.isActive = false;
const vimOverlay = document.createElement("div");
vimOverlay.id = "vim-overlay";
vimOverlay.style.zIndex = "9999";
vimOverlay.style.position = "absolute";
vimOverlay.style.width = "100%";
vimOverlay.style.height = "100%";
vimOverlay.style.display = "none";
vimOverlay.innerHTML = `<style>
.vim-key {
position: fixed;
padding: 3px 6px;
background-color: black;
border-radius: 3px;
border: solid 2px white;
color: white;
text-transform: lowercase;
line-height: normal;
font-size: 14px;
font-weight: 500;
}
</style>`;
document.body.append(vimOverlay);
const mousetrap = new Spicetify.Mousetrap(document);
mousetrap.bind(keyList, listenToKeys.bind(this), "keypress");
// Pause mousetrap event emitter
const orgStopCallback = mousetrap.stopCallback;
mousetrap.stopCallback = () => true;
/**
*
* @param {KeyboardEvent} event
*/
this.activate = function (event) {
vimOverlay.style.display = "block";
const vimkey = getVims();
if (vimkey.length > 0) {
vimkey.forEach((e) => e.remove());
return;
}
let firstKey = 0;
let secondKey = 0;
getLinks().forEach((e) => {
if (e.style.display === "none" || e.style.visibility === "hidden" || e.style.opacity === "0") {
return;
}
const bound = e.getBoundingClientRect();
let owner = document.body;
let top = bound.top;
let left = bound.left;
if (
bound.bottom > owner.clientHeight ||
bound.left > owner.clientWidth ||
bound.right < 0 ||
bound.top < 0 ||
bound.width === 0 ||
bound.height === 0
) {
return;
}
vimOverlay.append(createKey(e, keyList[firstKey] + keyList[secondKey], top, left));
secondKey++;
if (secondKey > lastKeyIndex) {
secondKey = 0;
firstKey++;
}
});
this.isActive = true;
setTimeout(() => (mousetrap.stopCallback = orgStopCallback.bind(mousetrap)), 100);
};
/**
*
* @param {KeyboardEvent} event
*/
this.deactivate = function (event) {
mousetrap.stopCallback = () => true;
this.isActive = false;
vimOverlay.style.display = "none";
getVims().forEach((e) => e.remove());
};
function getLinks() {
const elements = Array.from(document.querySelectorAll(elementQuery));
return elements;
}
function getVims() {
return Array.from(vimOverlay.getElementsByClassName("vim-key"));
}
/**
* @param {KeyboardEvent} event
*/
function listenToKeys(event) {
if (!this.isActive) {
return;
}
const vimkey = getVims();
if (vimkey.length === 0) {
this.deactivate(event);
return;
}
for (const div of vimkey) {
const text = div.innerText.toLowerCase();
if (text[0] !== event.key) {
div.remove();
continue;
}
const newText = text.slice(1);
if (newText.length === 0) {
click(div.target);
this.deactivate(event);
return;
}
div.innerText = newText;
}
if (vimOverlay.childNodes.length === 1) {
this.deactivate(event);
}
}
function click(element) {
if (element.hasAttribute("href") || element.tagName === "BUTTON") {
element.click();
return;
}
const findButton = element.querySelector(`button[data-ta-id="play-button"]`) || element.querySelector(`button[data-button="play"]`);
if (findButton) {
findButton.click();
return;
}
alert("Let me know where you found this button, please. I can't click this for you without that information.");
return;
// TableCell case where play button is hidden
// Index number is in first column
const index = parseInt(element.firstChild.innerText) - 1;
const context = getContextUri();
if (index >= 0 && context) {
console.log(index);
console.log(context);
//Spicetify.PlaybackControl.playFromResolver(context, { index }, () => {});
return;
}
}
function createKey(target, key, top, left) {
const div = document.createElement("span");
div.classList.add("vim-key");
div.innerText = key;
div.style.top = top + "px";
div.style.left = left + "px";
div.target = target;
return div;
}
function getContextUri() {
const username = __spotify.username;
const activeApp = localStorage.getItem(username + ":activeApp");
if (activeApp) {
try {
return JSON.parse(activeApp).uri.replace("app:", "");
} catch {
return null;
}
}
return null;
}
/**
*
* @param {Spicetify.Keyboard.ValidKey} key
*/
this.setCancelKey = function (key) {
mousetrap.bind(Spicetify.Keyboard.KEYS[key], this.deactivate.bind(this));
};
return this;
}

View file

@ -0,0 +1,3 @@
var playlistDicons=(()=>{var s=Object.create,o=Object.defineProperty,c=Object.getOwnPropertyDescriptor,p=Object.getOwnPropertyNames,d=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,e=(e=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(e,{get:(e,t)=>("undefined"!=typeof require?require:e)[t]}):e)(function(e){if("react"===e)return Spicetify.React;if("react-dom"===e)return Spicetify.ReactDOM;if("undefined"!=typeof require)return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')}),t=(e,t,i)=>{i=null!=e?s(d(e)):{};var a=!t&&e&&e.__esModule?i:o(i,"default",{value:e,enumerable:!0}),n=e,l=void 0,r=void 0;if(n&&"object"==typeof n||"function"==typeof n)for(let e of p(n))m.call(a,e)||e===l||o(a,e,{get:()=>n[e],enumerable:!(r=c(n,e))||r.enumerable});return a},u=t(e("react")),y=t(e("react-dom"));var i=t(e("react"));function g(){return i.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",height:"24px",viewBox:"0 0 24 24",width:"24px",fill:"#FFFFFF"},i.default.createElement("path",{d:"M0 0h24v24H0V0z",fill:"none"}),i.default.createElement("path",{d:"M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"}))}var f=new Map;async function r(o){const e=await new Promise(t=>{const i=setInterval(()=>{var e=document.querySelectorAll("#spicetify-playlist-list li a");0<e.length&&(clearInterval(i),t(Array.from(e)))},100)});e.forEach(e=>{var t,i;const a=e.href.split("/").at(-1);var n=e.href.split("/").at(-2),l=f.get(a);if(null!=(t=e.parentElement)&&t.classList.add("playlist-item"),l)null!=(t=e.parentElement)&&t.prepend(l);else switch(n){case"playlist":var r=o.find(e=>e.id===a),r=function(e){const t=document.createElement(e?"img":"div");return t.classList.add("playlist-item__img"),e?t.setAttribute("src",e):t.classList.add("no-icon"),t}((null==(r=null==r?void 0:r.images[0])?void 0:r.url)||"");null!=(i=e.parentElement)&&i.prepend(r),f.set(a,r);break;case"folder":{const s=document.createElement("div");s.classList.add("playlist-item__img","folder"),y.default.render(u.default.createElement(g,null),s),null!=(i=e.parentElement)&&i.prepend(s),f.set(a,s);break}default:console.warn("[playlist-icons] playlist list anchor type not recognized: "+n)}})}var v="playlist-icons_big";var a=async function(){for(var e,a;null==Spicetify||!Spicetify.Platform||null==Spicetify||!Spicetify.CosmosAsync;)await new Promise(e=>setTimeout(e,100));const t=null!=(e=JSON.parse(localStorage.getItem(v)))&&e,i=await async function e(t){t=await Spicetify.CosmosAsync.get(t);return[...t.items,...t.next?await e(t.next):[]]}("https://api.spotify.com/v1/me/playlists?limit=50"),n=(a="#spicetify-playlist-list",await new Promise(t=>{const i=setInterval(()=>{var e=document.querySelector(a);e&&(clearInterval(i),t(e))},100)})),l=new MutationObserver(async()=>{l.disconnect(),await r(i),l.observe(n,{childList:!0,subtree:!0})});await r(i),l.observe(n,{childList:!0,subtree:!0}),t&&n.classList.add("big-icons"),new Spicetify.Menu.Item("Big playlist icons",t,e=>{e.setState(!e.isEnabled),localStorage.setItem(v,JSON.stringify(!t)),n.classList.toggle("big-icons")}).register()};(async()=>{await a()})()})();(async()=>{var e;document.getElementById("playlistDicons")||((e=document.createElement("style")).id="playlistDicons",e.textContent=String.raw`
:root{---playlist-img-spacing:6px}.playlist-item{padding-top:0;padding-bottom:0;align-items:center}.playlist-item__img{width:1.5em;height:1.5em;border-radius:2px;margin-right:12px;filter:brightness(85%)}.playlist-item__img.folder{background-color:var(--spice-tab-active);display:flex;align-items:center;justify-content:center}.playlist-item__img.folder svg{width:1.1em;height:1.1em}.playlist-item__img.no-icon{background-color:var(--spice-tab-active);height:1.5em}.playlist-item:hover .playlist-item__img{transition:.2s ease-out;filter:brightness(100%)}.big-icons .playlist-item{padding-top:var(---playlist-img-spacing);padding-bottom:var(---playlist-img-spacing)}.big-icons .playlist-item__img{border-radius:4px;width:2em;height:2em}.big-icons .playlist-item__img.folder{padding:4px}.big-icons .playlist-item__img.folder svg{width:1.5em;height:1.5em}.big-icons>div{contain:unset}
`.trim(),document.head.appendChild(e))})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
// @ts-check
// NAME: Add Volume Percentage
// AUTHOR: daksh2k
// DESCRIPTION: Add the Volume Percentage to the Volume Bar
/// <reference path="../globals.d.ts" />
(function addVolumep() {
const volumeBar = document.querySelector(".volume-bar");
if (!(volumeBar && Spicetify.Player)) {
setTimeout(addVolumep, 200);
return;
}
const ele = document.createElement("span");
ele.classList.add("volume-percent");
ele.setAttribute("style", "font-size: 14px; padding-left: 10px; min-width: 45px;");
volumeBar.append(ele);
// @ts-ignore
volumeBar.style.flex = "0 1 180px";
updatePercentage();
function updatePercentage() {
const currVolume = Math.round((Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume) * 100);
ele.innerText = currVolume == -100 ? `` : `${currVolume}%`;
// @ts-ignore
document.querySelector(".main-connectBar-connectBar")?.style.setProperty("--triangle-position", "229px");
}
if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.addListener("volume", updatePercentage);
else Spicetify.Platform.PlaybackAPI._events.addListener("volume", updatePercentage);
})();

View file

@ -0,0 +1,96 @@
//@ts-check
// NAME: Wikify
// AUTHOR: CharlieS1103
// DESCRIPTION: View an artists wikipedia page to learn more about them
/// <reference path="../../spicetify-cli/globals.d.ts" />
(function WikiFy() {
if (!document.body.classList.contains('wikify-injected')) {
var styleSheet = document.createElement("style")
styleSheet.innerHTML =
`body > generic-modal > div > div {
background-color: beige !important;
color: black !important;
} `
document.body.appendChild(styleSheet)
document.body.classList.add('wikify-injected');
}
const {
CosmosAsync,
URI
} = Spicetify;
if (!(CosmosAsync && URI)) {
setTimeout(WikiFy, 10);
return;
}
const lang = Spicetify.Locale._locale;
const buttontxt = "View Wiki"
//Watch for when the song is changed
function error() {
Spicetify.PopupModal.display({
title: "Error",
content: "Selected artist does not have a WikiPedia page, Sorry."
});
}
async function getWikiText(uris) {
const rawUri = uris[0];
const uri = rawUri.split(":")[2]
const artistName = await CosmosAsync.get(`https://api.spotify.com/v1/artists/${uri}`)
const artistNameTrimmed = (artistName.name).replace(/\s/g, "%20");
if (artistName != null) {
try {
const wikiInfo = await CosmosAsync.get(`https://${lang}.wikipedia.org/w/api.php?action=query&format=json&prop=extracts%7Cdescription&titles=${artistNameTrimmed}`)
//TODO: option to choose local language or english / english fallback? / subcontextmenu to choose?
//https://en.wikipedia.org/w/api.php?action=query&format=json&uselang=en&list=search&srsearch=${artistNameTrimmed}
const wikiInfoArr = wikiInfo.query.pages
const page = Object.values(wikiInfoArr)[0];
if (page != null || page != undefined) {
const pageText = page.extract.replace(/<!--[\s\S]*?-->/g, '');
if (pageText != "\n") {
Spicetify.PopupModal.display({
title: "WikiFy",
content: page.extract
});
} else {
error();
}
} else {
error();
}
} catch {
Spicetify.PopupModal.display({
title: "Error",
content: "Request failed",
})
}
}
}
function shouldDisplayContextMenu(uris) {
if (uris.length > 1) {
return false;
}
const uri = uris[0];
const uriObj = Spicetify.URI.fromString(uri);
if (uriObj.type === Spicetify.URI.Type.TRACK || uriObj.type === Spicetify.URI.Type.ARTIST) {
return true;
}
return false;
}
const cntxMenu = new Spicetify.ContextMenu.Item(
buttontxt,
getWikiText,
shouldDisplayContextMenu,
);
cntxMenu.register();
})();

View file

@ -0,0 +1,406 @@
[Comfy]
text = FFFFFF
subtext = B9BBBE
main = 23283D
main-transition = 1E2233
highlight = 45495b
highlight-elevated = 32364a
sidebar = 1E2233
player = 101320
card = 191F2E
shadow = 1E2233
selected-row = F1F1F1
button = 7289DA
button-active = 5C6FB1
button-disabled = 4B588C
tab-active = 1E2233
notification = 7289DA
notification-error = d25050
misc = 000000
play-button = 7289da
play-button-active = 869adf
progress-fg = 1ed760
progress-bg = 7289da
heart = d25050
liked-left = 3a62f5
liked-right = c4efd9
pagelink-active = 5c6eb1
radio-btn-active = 7289DA
[Nord]
text = B2BCCC
subtext = B2BCCC
main = 2E3440
main-transition = 262B35
highlight = 50555f
highlight-elevated = 3d424d
sidebar = 262B35
player = 363d4c
card = 363d4c
shadow = 1d2128
button = 8A99AF
button-active = 718CAD
button-disabled = 434C5E
tab-active = 363d4c
notification = 363d4c
notification-error = A9555E
misc = FFFFFF
play-button = 8A99AF
play-button-active = 718CAD
progress-fg = 8A99AF
progress-bg = 4b566a
heart = 718CAD
liked-left = 262B35
liked-right = B2BCCC
pagelink-active = b7c1d5
radio-btn-active = 363d4c
[Lunar]
text = f3f3f3
subtext = cecece
main = 161616
main-transition = 101010
highlight = 2a2a2a
highlight-elevated = 232323
sidebar = 202020
player = 202020
card = 202020
shadow = 252525
selected-row = cecece
button = 3281ea
button-active = 0284e8
button-disabled = 303030
tab-active = ebbcba
notification = 3281ea
notification-error = b10c0c
misc = 252525
play-button = ebbcba
play-button-active = eba9a7
progress-fg = 025ca1
progress-bg = 202020
heart = ebbcba
liked-left = 3a62f5
liked-right = c4efd9
pagelink-active = ffffff
radio-btn-active = 0284e8
[catppuccin-dark]
text = cad3f5
subtext = b8c0e0
main = 181926
main-transition = 181926
highlight = 333645
highlight-elevated = 232533
sidebar = 24273a
player = 24273a
card = 494d64
shadow = 5b6078
selected-row = b8c0e0
button = 8aadf4
button-active = 7dc4e4
button-disabled = 494d64
tab-active = ed8796
notification = 8aadf4
notification-error = ed8796
misc = 5b6078
play-button = f5bde6
play-button-active = f0c6c6
progress-fg = 91d7e3
progress-bg = 494d64
heart = f5a97f
liked-left = a6da95
liked-right = b7bdf8
pagelink-active = ffffff
radio-btn-active = 7dc4e4
[catppuccin-light]
text = cad3f5
subtext = b8c0e0
main = 494d64
main-transition = 181926
highlight = 5c6179
highlight-elevated = 545a71
sidebar = 24273a
player = 24273a
card = 494d64
shadow = 5b6078
selected-row = b8c0e0
button = 8aadf4
button-active = 7dc4e4
button-disabled = 494d64
tab-active = ed8796
notification = 8aadf4
notification-error = ed8796
misc = 5b6078
play-button = f5bde6
play-button-active = f0c6c6
progress-fg = 91d7e3
progress-bg = 494d64
heart = f5a97f
liked-left = a6da95
liked-right = b7bdf8
pagelink-active = ffffff
radio-btn-active = 7dc4e4
[Mono]
text = FFFFFF
subtext = B9BBBE
main = 171717
main-transition = FFFFFF
highlight = 3b3b3b
highlight-elevated = 262626
sidebar = 101010
player = 101010
card = 343434
shadow = 595858
selected-row = F1F1F1
button = FFFFFF
button-active = c5c5c5
button-disabled = 4a4949
tab-active = 303030
notification = 101010
notification-error = d25050
misc = 000000
play-button = FFFFFF
play-button-active = FFFFFF
progress-fg = FFFFFF
progress-bg = 343434
heart = FFFFFF
liked-left = 000000
liked-right = ffffff
pagelink-active = 787878
radio-btn-active = 737373
[Deep]
text = 4f9a87
subtext = 406560
button-text = 4f9a87
main = 040614
main-transition = 0F111A
highlight = 0f1f28
highlight-elevated = 09111d
sidebar = 0F111A
player = 0F111A
subbutton-text = 040614
card = 0f1118
shadow = 0f1118
selected-row = 4f9a87
sub-button = 4f9a87
button = 106165
button-active = 4f9a87
button-disabled = 0C1C19
tab-active = 0a1527
notification = 051024
notification-error = 051024
playback-bar = 4f9a87
misc = 406560
play-button = 106165
play-button-active = 4f9a87
progress-fg = 4f9a87
progress-bg = 106165
heart = d25050
liked-left = 3a62f5
liked-right = c4efd9
pagelink-active = 4f9a87
radio-btn-active = 4f9a87
[Sunset]
text = ffce3f
subtext = fef3bb
main = 171717
main-transition = 000000
highlight = 3d3c32
highlight-elevated = 272722
sidebar = 101010
player = 101010
card = cc9756
shadow = e3b47b
selected-row = fef3bb
button = ffce3f
button-active = bf9b30
button-disabled = 4a4949
tab-active = 303030
notification = ffffff
notification-error = d25050
misc = 000000
play-button = ffce3f
play-button-active = fc9e3a
progress-fg = ff8300
progress-bg = 343434
heart = ff8300
liked-left = 000000
liked-right = ffffff
pagelink-active = fef3bb
radio-btn-active = fef3bb
[Neon]
text = 588bae
subtext = eaffff
main = 171717
main-transition = 000000
highlight = 3b3b3b
highlight-elevated = 262626
sidebar = 101010
player = 101010
card = 7fa1b5
shadow = a9c9db
selected-row = F1F1F1
button = 588bae
button-active = 3b5d75
button-disabled = 4a4949
tab-active = 303030
notification = FFFFFF
notification-error = d25050
misc = 000000
play-button = 588bae
play-button-active = 5085ab
progress-fg = 00afdb
progress-bg = 343434
heart = 00afdb
liked-left = 000000
liked-right = ffffff
pagelink-active = bbe7fe
radio-btn-active = eaffff
[Forest]
text = B2C5B3
subtext = d5ddde
main = 171717
main-transition = 000000
highlight = 3b3b3b
highlight-elevated = 262626
sidebar = 101010
player = 101010
card = 5c6e59
shadow = 3c5148
selected-row = F1F1F1
button = B2C5B3
button-active = F1F1F1
button-disabled = 4a4949
tab-active = 303030
notification = FFFFFF
notification-error = d25050
misc = 000000
play-button = 3c5148
play-button-active = 43705d
progress-fg = 3c5148
progress-bg = 343434
heart = 3c5148
liked-left = 000000
liked-right = ffffff
pagelink-active = 3c5148
radio-btn-active = 737373
[Sakura]
text = fcb4ca
subtext = ffdcdc
main = 171717
main-transition = 000000
highlight = 3d3838
highlight-elevated = 272525
sidebar = 101010
player = 101010
card = d68ba2
shadow = fcb4ca
selected-row = ffdcdc
button = fcb4ca
button-active = d48aa0
button-disabled = 4a4949
tab-active = 303030
notification = FFFFFF
notification-error = d25050
misc = 000000
play-button = f42c38
play-button-active = ba182b
progress-fg = Cfeefa
progress-bg = 343434
heart = f25477
liked-left = 000000
liked-right = ffffff
pagelink-active = f5bcdb
radio-btn-active = ffdcdc
[Vaporwave]
text = 01CDFE
subtext = eaffff
main = 171717
main-transition = 000000
highlight = 3b3b3b
highlight-elevated = 262626
sidebar = 101010
player = 101010
card = 007f9e
shadow = 2ec2e6
selected-row = F1F1F1
button = 01CDFE
button-active = 118ba8
button-disabled = 4a4949
tab-active = 303030
notification = FFFFFF
notification-error = d25050
misc = 000000
play-button = ffd300
play-button-active = e3c01b
progress-fg = f706cf
progress-bg = 343434
heart = f706cf
liked-left = 000000
liked-right = ffffff
pagelink-active = c0d6fa
radio-btn-active = eaffff
[Velvet]
text = AD434E
subtext = 762F37
main = 1E1E1E
main-transition = 161616
highlight = 322324
highlight-elevated = 272021
sidebar = 161616
player = 080808
card = 0F0F0F
shadow = 161616
selected-row = 973B45
button = 6B2B32
button-active = 552328
button-disabled = 262626
tab-active = 161616
notification = 6B2B32
notification-error = 60272D
misc = 000000
play-button = 552328
play-button-active = 6B2B32
progress-fg = 81333B
progress-bg = A23F49
heart = 60272D
liked-left = 2D2D2D
liked-right = 8C3740
pagelink-active = 4A1F23
radio-btn-active = 6B2B32
[wal16]
text = ${xrdb:color15:FFFFFF}
subtext = ${xrdb:color6:B9BBBE}
main = ${xrdb:color0:23283D}
main-transition = ${xrdb:color0:000000}
sidebar = ${xrdb:color8:1E2233}
player = ${xrdb:color4:101320}
card = ${xrdb:color8:191F2E}
shadow = ${xrdb:color0:1E2233}
selected-row = ${xrdb:color15:F1F1F1}
button = ${xrdb:color6:7289DA}
button-active = ${xrdb:color14:5C6FB1}
button-disabled = ${xrdb:color8:4B588C}
tab-active = ${xrdb:color9:1E2233}
notification = FFFFFF
notification-error = d25050
misc = ${xrdb:color0:000000}
play-button = ${xrdb:color11:5C6FB1}
play-button-active = ${xrdb:color3:7289DA}
progress-fg = ${xrdb:color10:1ed760}
progress-bg = ${xrdb:color0:f1f1f1}
heart = ${xrdb:color10:d25050}
liked-left = ${xrdb:color10:3a62f5}
liked-right = ${xrdb:color11:c4efd9}
pagelink-active = ${xrdb:color13:5c6eb1}
radio-btn-active = ${xrdb:color13:7289DA}

View file

@ -0,0 +1,9 @@
(() => {
const themeScript = document.createElement("SCRIPT");
themeScript.setAttribute("type", "text/javascript");
themeScript.setAttribute(
"src",
"https://comfy-themes.github.io/Spicetify/Comfy/theme.script.js"
);
document.head.appendChild(themeScript);
})();

View file

@ -0,0 +1 @@
@import url("https://comfy-themes.github.io/Spicetify/Comfy/app.css");

View file

@ -0,0 +1,32 @@
[Preprocesses]
disable_ui_logging = 1
remove_rtl_rule = 1
expose_apis = 1
disable_upgrade_check = 1
disable_sentry = 1
[AdditionalOptions]
sidebar_config = 1
home_config = 1
experimental_features = 1
extensions = adblock.js|hidePodcasts.js|historyShortcut.js|copyPlaylist.js|wikify.js|fullScreen.js|volumePercentage.js|bookmark.js|loopyLoop.js|keyboardShortcutMy.js|genre.js|playlistIcons.js|catppuccin-macchiato.js
custom_apps = lyrics-plus|marketplace
[Patch]
[Setting]
spotify_path = /opt/spotify
color_scheme = Comfy
overwrite_assets = 1
spotify_launch_flags =
check_spicetify_upgrade = 0
prefs_path = ~/.config/spotify/prefs
current_theme = Comfy
inject_css = 1
replace_colors = 1
inject_theme_js = 1
; DO NOT CHANGE!
[Backup]
version = 1.2.13.661.ga588f749
with = Dev