Skip to content

Commit 21998a9

Browse files
committed
2.1.2
1 parent ee39a3a commit 21998a9

16 files changed

Lines changed: 224 additions & 423 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [2.1.2] - 2026-03-29
4+
5+
### Fixed
6+
7+
- Improved track and disc indexing algorithm
8+
39
## [2.1.1] - 2026-03-06
410

511
### Added

bun.lock

Lines changed: 15 additions & 114 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/aotyfy.js

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "aotyfy",
33
"description": "🔹 Album of the Year user & critic ratings for Spotify.",
4-
"version": "2.1.1",
4+
"version": "2.1.2",
55
"author": "woidzero",
66
"private": true,
77
"license": "MIT",
@@ -13,19 +13,18 @@
1313
"release": "bun run build:extras && spicetify-creator --out=dist --minify"
1414
},
1515
"dependencies": {
16-
"axios": "^1.13.2",
17-
"cheerio": "^1.1.2",
16+
"axios": "^1.14.0",
17+
"cheerio": "^1.2.0",
1818
"jquery": "^4.0.0"
1919
},
2020
"devDependencies": {
2121
"@types/jquery": "^4.0.0",
2222
"@types/react": "^19.2.14",
2323
"@types/react-dom": "^19.2.3",
24-
"knip": "^5.78.0",
25-
"marked": "^17.0.1",
26-
"prettier": "^3.7.4",
24+
"marked": "^17.0.5",
25+
"prettier": "^3.8.1",
2726
"spicetify-creator": "^1.0.17",
28-
"typescript": "^5.9.3"
27+
"typescript": "^6.0.2"
2928
},
3029
"type": "module"
3130
}

src/app.tsx

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@ let ui: UI;
1919
/**
2020
* extension states
2121
*/
22-
const State: Record<string, any> = {
22+
const State: AOTYFY._State = {
2323
lock: false,
2424
cache: {
2525
uri: null,
2626
album: null,
2727
},
2828
prev: {
2929
track: null,
30-
request: 0
31-
}
32-
}
30+
request: 0,
31+
},
32+
};
3333

3434
/**
3535
* button cooldown
@@ -49,10 +49,9 @@ async function refresh() {
4949
State.prev.request = Date.now();
5050
State.prev.track = null;
5151

52-
await update(true)
53-
.then(() => {
54-
Spicetify.showNotification("[aotyfy] Scores refreshed", false);
55-
});
52+
await update(true).then(() => {
53+
Spicetify.showNotification("[aotyfy] Scores refreshed", false);
54+
});
5655

5756
console.info("[aotyfy] scores refreshed");
5857
}
@@ -80,26 +79,31 @@ async function update(force: boolean = true) {
8079

8180
// fetch
8281
const Meta: AOTYFY._Meta = getMeta(data);
83-
console.debug("[aotyfy] fetched meta", Meta)
82+
console.debug("[aotyfy] fetched meta", Meta);
8483

8584
// globals
8685
let Album: AOTYFY._Album;
8786

88-
if (State.cache.uri === Meta.album.uri && !force) {
87+
const isLocal = Meta.track.uri.startsWith("spotify:local");
88+
89+
// use track uri if song from local files
90+
const cacheKey = isLocal ? Meta.track.uri : Meta.album.uri;
91+
92+
if (State.cache.uri === cacheKey && !force && State.cache.album) {
8993
Album = State.cache.album;
90-
console.debug("[aotyfy] cache", Album)
94+
console.debug("[aotyfy] cache", Album);
9195
} else {
9296
try {
9397
Album = await getAPI(Meta, true, force);
9498

9599
if (!Album.valid && Settings.strict.value) {
96-
console.debug("[aotyfy] unable to find, trying again ...")
97-
Album = await getAPI(Meta, false)
100+
console.debug("[aotyfy] unable to find, trying again ...");
101+
Album = await getAPI(Meta, false);
98102
}
99103

100104
// set album in cache
101105
State.cache.album = Album;
102-
State.cache.uri = Meta.album.uri;
106+
State.cache.uri = cacheKey;
103107
} catch (e: any) {
104108
ui.hide();
105109
State.cache.uri = null;
@@ -123,17 +127,17 @@ async function update(force: boolean = true) {
123127
}
124128

125129
if (Settings.showSidebar.value) {
126-
console.debug("[aotyfy] showing sidebar")
130+
console.debug("[aotyfy] showing sidebar");
127131
ui.updateSidebar(Album);
128132
} else {
129-
ui.hideSidebar()
133+
ui.hideSidebar();
130134
}
131135
} catch (e: any) {
132136
if (e instanceof AOTYFYError) throw e;
133137
throw new CriticalError(`updating failed: ${e.message}`);
134138
} finally {
135139
State.lock = false;
136-
console.info("[aotyfy] scores updated")
140+
console.info("[aotyfy] scores updated");
137141
}
138142
}
139143

@@ -145,23 +149,25 @@ export default async function main() {
145149
new Spicetify.Menu.SubMenu("AOTYfy", [
146150
new Spicetify.Menu.Item("Enabled", Settings.isEnabled.get(), async (i) => {
147151
Settings.isEnabled.toggle(i);
148-
await update()
152+
await update();
149153
Spicetify.showNotification("[aotyfy] Extension: " + (Settings.isEnabled.get() ? "enabled" : "disabled"));
150154
}),
151155
new Spicetify.Menu.Item("Show In Sidebar", Settings.showSidebar.get(), async (i) => {
152156
Settings.showSidebar.toggle(i);
153-
await update()
157+
await update();
154158
Spicetify.showNotification("[aotyfy] Showing in sidebar: " + (Settings.showSidebar.get() ? "enabled" : "disabled"));
155159
}),
156160
new Spicetify.Menu.Item("Show In Now Playing Bar", Settings.showNPB.get(), async (i) => {
157161
Settings.showNPB.toggle(i);
158-
await update()
162+
await update();
159163
Spicetify.showNotification("[aotyfy] Showing in now playing bar: " + (Settings.showNPB.get() ? "enabled" : "disabled"));
160164
}),
161165
new Spicetify.Menu.Item("Strict Search", Settings.strict.get(), async (i) => {
162166
Settings.strict.toggle(i);
163-
await update()
164-
Spicetify.showNotification("[aotyfy] Strict search: " + (Settings.strict.get() ? "enabled" : "disabled. Disabling this setting will allow extension to use only title instead of `title - artist` to search release."));
167+
await update();
168+
Spicetify.showNotification(
169+
"[aotyfy] Strict search: " + (Settings.strict.get() ? "enabled" : "disabled. Disabling this setting will allow extension to use only title instead of `title - artist` to search release."),
170+
);
165171
}),
166172
new Spicetify.Menu.Item("Show Notifications", Settings.notifications.get(), (i) => {
167173
Settings.notifications.toggle(i);

src/components/ScoreItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { React } = Spicetify;
22
import { setAppearance } from "../core/dom";
33

4-
export const ScoreItem = ({ label, score, ratings, url }: { label: string, score: number, ratings: number, url: string }) => (
4+
export const ScoreItem = ({ label, score, ratings, url }: { label: string; score: number; ratings: number; url: string }) => (
55
<div
66
className="e-91000-box e-91000-baseline e-91000-box--naked e-91000-box--browser-default-focus e-91000-box--padding-custom e-91000-box--min-size e-91000-Box-sc-8t9c76-0 Box-group-naked-listRow-minBlockSize_32px Box-sc-8t9c76-0 Box-group-naked-listRow-minBlockSize_32px"
77
data-encore-id="listRow"

src/core/api.ts

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ async function parse(url: string) {
128128
artist: artists,
129129
score: isNaN(score) ? -1 : score,
130130
ratings: isNaN(ratings) ? -1 : ratings,
131-
discNumber: discIndex,
131+
discNumber: discIndex + 1,
132132
url: trackURL,
133133
};
134134

@@ -190,14 +190,14 @@ async function parse(url: string) {
190190
score: criticScore,
191191
ratings: criticRatingsCount,
192192
},
193-
}
194-
Album.format = format
195-
Album.year = year
196-
Album.label = labels
197-
Album.url = url
198-
Album.verified = verified
199-
Album.tracks = Tracks
200-
Album.valid = true
193+
};
194+
Album.format = format;
195+
Album.year = year;
196+
Album.label = labels;
197+
Album.url = url;
198+
Album.verified = verified;
199+
Album.tracks = Tracks;
200+
Album.valid = true;
201201

202202
console.log(`[aotyfy] api response:`, Album);
203203
return Album;
@@ -214,7 +214,7 @@ export async function getAPI(meta: AOTYFY._Meta, firstIteration: boolean = true,
214214
* caching albums in local storage
215215
*/
216216
const SAID = extractStr(/^spotify:album:(.+)$/, meta.album.uri);
217-
const Storage = Settings.storage.get()
217+
const Storage = Settings.storage.get();
218218

219219
if (SAID && Storage && !force) {
220220
const cachedAOTYID = Storage[SAID];
@@ -305,9 +305,7 @@ export async function getAPI(meta: AOTYFY._Meta, firstIteration: boolean = true,
305305
}));
306306

307307
// find the most similar artist
308-
const mostSimilarByArtist = releasesWithSimilarity.reduce((best, current) =>
309-
current.artistSimilarity > best.artistSimilarity ? current : best,
310-
);
308+
const mostSimilarByArtist = releasesWithSimilarity.reduce((best, current) => (current.artistSimilarity > best.artistSimilarity ? current : best));
311309

312310
console.debug(`[AOTYfy] most similar artist: ${mostSimilarByArtist.artist} (${mostSimilarByArtist.artistSimilarity})`);
313311

@@ -324,9 +322,7 @@ export async function getAPI(meta: AOTYFY._Meta, firstIteration: boolean = true,
324322

325323
// if artist has multiple albums, find the most similar album
326324
if (artistReleases.length > 1) {
327-
const bestAlbumMatch = artistReleases.reduce((best, current) =>
328-
current.albumSimilarity! > best.albumSimilarity! ? current : best,
329-
);
325+
const bestAlbumMatch = artistReleases.reduce((best, current) => (current.albumSimilarity! > best.albumSimilarity! ? current : best));
330326
console.debug(`[aotyfy] multiple albums from artist, best match: ${bestAlbumMatch.title}`);
331327
aotyURL = bestAlbumMatch.url;
332328
}
@@ -372,7 +368,7 @@ export async function getAPI(meta: AOTYFY._Meta, firstIteration: boolean = true,
372368

373369
console.debug("[aotyfy] cached release: ", SAID, "->", aotyId);
374370
} else {
375-
console.warn(`[aotyfy] can't extract 'aotyId': ${aotyId}`)
371+
console.warn(`[aotyfy] can't extract 'aotyId': ${aotyId}`);
376372
}
377373
}
378374

@@ -384,22 +380,35 @@ export async function getAPI(meta: AOTYFY._Meta, firstIteration: boolean = true,
384380
*/
385381
export function getTrack(meta: AOTYFY._Meta, album: AOTYFY._Album): AOTYFY._Track | null {
386382
if (!album.tracks || album.tracks.length === 0) {
387-
console.warn("[aotyfy] release doesn't contain any tracks")
383+
console.warn("[aotyfy] release doesn't contain any tracks");
388384
return null;
389385
}
390386

391-
const discIndex = (meta.track.disc ?? 1) - 1;
387+
const normalize = (s: string) =>
388+
decodeURIComponent(s)
389+
.toLowerCase()
390+
.replace(/[^\w\s]/g, "")
391+
.trim();
392+
393+
const isLocal = meta.track.uri.startsWith("spotify:local");
394+
395+
if (isLocal) {
396+
return album.tracks.find((t) => normalize(t.title) === normalize(meta.track.title)) ?? null;
397+
}
398+
399+
const discNumber = meta.track.disc || 1;
392400
const trackIndex = meta.track.number - 1;
393401

394-
const discs = album.tracks.reduce<Record<number, typeof album.tracks>>((acc, t: AOTYFY._Track) => {
402+
const discs = album.tracks.reduce<Record<number, typeof album.tracks>>((acc, t) => {
395403
(acc[t.discNumber] ??= []).push(t);
396404
return acc;
397405
}, {});
398406

399-
const discTracks = discs[discIndex];
407+
const discTracks = discs[discNumber];
408+
400409
if (!discTracks || !discTracks[trackIndex]) {
401-
throw new APIError("unable to find this track")
410+
return album.tracks.find((t) => normalize(t.title) === normalize(meta.track.title)) ?? null;
402411
}
403412

404-
return discTracks[trackIndex] ?? album.tracks.find((t) => t.title.toLowerCase() === meta.track.title.toLowerCase());
413+
return discTracks[trackIndex];
405414
}

src/core/dom.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import $ from "jquery";
77
export const Selectors = {
88
// spotify selectors
99
NOW_PLAYING_WIDGET: ".main-nowPlayingView-nowPlayingWidget",
10-
SONG_TITLE_BOX: "#main > div > div.Root__top-container > div.Root__now-playing-bar > aside > div > div.main-nowPlayingBar-left > div > div.main-nowPlayingWidget-trackInfo.main-trackInfo-container > div.main-trackInfo-name > div > span > span > div > span",
10+
SONG_TITLE_BOX:
11+
"#main > div > div.Root__top-container > div.Root__now-playing-bar > aside > div > div.main-nowPlayingBar-left > div > div.main-nowPlayingWidget-trackInfo.main-trackInfo-container > div.main-trackInfo-name > div > span > span > div > span",
1112
INFO_CONTAINER: ".main-nowPlayingWidget-trackInfo.main-trackInfo-container",
1213
PLAYING_BAR: "div.Root__now-playing-bar > aside > div",
1314

@@ -39,7 +40,7 @@ export function setAppearance(score: number | string, element?: JQuery): string
3940

4041
if (value === -1 || !Number.isFinite(value)) {
4142
if (element) {
42-
element.css({ "color": color });
43+
element.css({ color: color });
4344
return element;
4445
}
4546
return color;
@@ -50,7 +51,7 @@ export function setAppearance(score: number | string, element?: JQuery): string
5051
else color = "#d76666";
5152

5253
if (element) {
53-
element.css({ "color": color });
54+
element.css({ color: color });
5455
return element;
5556
}
5657

@@ -99,4 +100,4 @@ export function waitElement(selector: string | string[], timeout: number = 5000)
99100
return;
100101
}, timeout);
101102
});
102-
}
103+
}

src/core/exceptions.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ const { showNotification } = Spicetify;
22
import { Settings } from "./settings";
33

44
export class AOTYFYError extends Error {
5-
constructor(message: string, opts: {
6-
name?: string,
7-
show?: boolean
8-
} = {
9-
show: false
10-
}) {
5+
constructor(
6+
message: string,
7+
opts: {
8+
name?: string;
9+
show?: boolean;
10+
} = {
11+
show: false,
12+
},
13+
) {
1114
super(message);
1215
this.name = opts.name || "AOTYError";
1316
console.error(`[aotyfy:${this.name}] ` + message);
@@ -39,4 +42,3 @@ export class RateLimitError extends AOTYFYError {
3942
super(message, { name: "RateLimitError", show: true });
4043
}
4144
}
42-

src/core/metadata.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function getMeta(data: Record<string, any>): AOTYFY._Meta {
2323
title: s(md?.title),
2424
},
2525
type: null,
26-
skipSimcheck: false
26+
skipSimcheck: false,
2727
};
2828

2929
const IGNORE_ARTISTS = ["Weezer", "SOPHIE", "Crystal Castles", "underscores", "Ninajirachi", "slayr"];
@@ -56,6 +56,15 @@ export function getMeta(data: Record<string, any>): AOTYFY._Meta {
5656
case "spotify:album:3RDAqHBWBHXRwVSJF9T8VW":
5757
Meta.type = "lp";
5858
break;
59+
// Earl Sweatshirt's 'I Don't Like Shit, I Don't Go Outside'
60+
case "spotify:album:3wUv2IjD5hPrqlPakpczQa":
61+
Meta.album.title = "I Don't Like Shit, I Don't Go Outside";
62+
break;
63+
case "spotify:album:0JB2T1lOZ03obXXun0CLzY":
64+
Meta.type = "reissue";
65+
break;
66+
case "spotify:album:7CwtKHbZbn6nYqBOeoKaUp":
67+
Meta.album.title = "Experimental Age";
5968
default:
6069
break;
6170
}
@@ -84,12 +93,6 @@ export function getMeta(data: Record<string, any>): AOTYFY._Meta {
8493
Meta.type = "ep";
8594
break;
8695
}
87-
switch (Meta.album.uri) {
88-
// product spotify reissue
89-
case "spotify:album:0JB2T1lOZ03obXXun0CLzY":
90-
Meta.type = "reissue";
91-
break;
92-
}
9396
break;
9497
case "David Bowie":
9598
switch (Meta.album.title) {

0 commit comments

Comments
 (0)