Write boilerplate (manifest.json, content.ts, background.ts), start works on SpotifyAPI and SpotifyScraper modules
This commit is contained in:
parent
872b7f2c2a
commit
eb3e73f690
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
function onRuntimeMessage(message: any) {}
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener(onRuntimeMessage);
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import SpotifyAPI from "./modules/SpotifyAPI";
|
||||||
|
|
||||||
|
const API = new SpotifyAPI();
|
||||||
|
|
||||||
|
// use the API
|
||||||
|
|
||||||
|
API.getCurrentTrack();
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue