Compare commits

...

2 Commits

8 changed files with 187 additions and 2 deletions

View File

@ -1 +0,0 @@
console.log("Hello via Bun!");

30
src/build.ts Normal file
View File

@ -0,0 +1,30 @@
import fs from "fs";
const outDir = "dist";
// Removes old files
fs.rmdirSync("dist", { recursive: true });
await Bun.build({
entrypoints: [
"src/extension/typescript/content.ts",
"src/extension/typescript/background.ts",
],
outdir: outDir,
});
const glob = new Bun.Glob("!.ts");
const extensionPath = "src/extension";
// TODO: Update script to preserve folders (not necessary for this extension, as of now)
// TODO: Setup directory watch (bun build --watch works, but only for TS files)
for await (const node of glob.scan(extensionPath)) {
const file = Bun.file(`${extensionPath}/${node}`);
// console.log(`${outDir}/${node}`);
Bun.write(`${outDir}/${node}`, file);
}

View File

@ -0,0 +1,29 @@
{
"name": "Spotify Web Notifications",
"description": "Spotify Web Notifications",
"version": "0.1",
"homepage_url": "https://code.violetmillie.me/Millie/spotify-web-notifications",
"manifest_version": 2,
"permissions": ["notifications"],
"browser_specific_settings": {
"gecko": {
"id": "spotifywebextension@violet.millie.me",
"strict_min_version": "42.0"
}
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"exclude_matches": ["*://developer.mozilla.org/*"],
"matches": ["*://open.spotify.com/*"],
"js": ["content.js"]
}
]
}

View File

@ -0,0 +1,4 @@
function onRuntimeMessage(message: any) {}
browser.runtime.onMessage.addListener(onRuntimeMessage);

View File

@ -0,0 +1,7 @@
import SpotifyAPI from "./modules/SpotifyAPI";
const API = new SpotifyAPI();
// use the API
API.getCurrentTrack();

View File

@ -0,0 +1,70 @@
import SpotifyScraper from "./SpotifyScraper";
export type Artist = {
name: string;
URL: string;
};
export type Track = {
track: {
title: string;
URL: string;
coverArt: string;
artist: Artist | Artist[];
};
playing: boolean;
};
export type Playstate = {
track: Track;
isPlaying: boolean;
};
export default class SpotifyAPI {
currentPlaystate = {};
private scraper: SpotifyScraper;
constructor() {
this.scraper = new SpotifyScraper();
}
/**
* Generates the full URL from a relative Spotify URL
* @example `/album/` -> `https://open.spotify.com/album/`
*/
private generateFullURL(relativeURL: string) {
return `${window.location.host}${relativeURL}`;
}
getCurrentTrack() {
const titleElement = this.scraper.getBarElement("title")!;
const trackURL = `${titleElement.getAttribute("href")}`;
const artistElement = this.scraper.getBarElement("artist")!;
const artistName = artistElement.textContent;
const artistURL = artistElement.getAttribute("href");
// console.log(trackElement.textContent);
console.log(
`Now playing: ${
titleElement.textContent
} by ${artistName} (${this.generateFullURL(trackURL)})`
);
}
getPlaystate() {}
// TODO: Implement events: onPlay, onPause (?), trackChanged, playstateChanged
// trackChanged: fire on track changes
// onPlay: fired when playback resumes
// onPause: fired when playback is paused
// playStateChagned: fired whenever the playstate changes (any of the above)
}

View File

@ -0,0 +1,45 @@
type BarElement = {
name: string;
selector: string;
element?: HTMLElement | null | undefined;
// isPersisent?: boolean;
};
export default class SpotifyScraper {
constructor() {}
barElements: BarElement[] = [
{
name: "playbackControlButton",
selector: "button[data-testid=control-button-playpause]",
},
{
name: "title",
selector: "a[data-testid=context-item-link]",
},
{
name: "artist",
selector: "a[data-testid=context-item-info-artist]",
},
{
name: "coverArtImage",
selector: "img[data-testid=cover-art-image]",
},
];
getBarElement(name: string) {
const barElementData = this.barElements.find(
(element) => element.name === name
);
if (!barElementData) {
// Raise some error
return;
}
return document.querySelector(barElementData.selector);
}
}

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
@ -20,3 +20,4 @@
"forceConsistentCasingInFileNames": true
}
}