From eb3e73f69071d3176c4bf7d80a1f3276841530cf Mon Sep 17 00:00:00 2001 From: Violet Millie Date: Wed, 24 Jan 2024 20:06:57 +0000 Subject: [PATCH] Write boilerplate (manifest.json, content.ts, background.ts), start works on SpotifyAPI and SpotifyScraper modules --- index.ts | 1 - src/build.ts | 30 ++++++++ src/extension/manifest.json | 29 ++++++++ src/extension/typescript/background.ts | 4 ++ src/extension/typescript/content.ts | 7 ++ .../typescript/modules/SpotifyAPI.ts | 70 +++++++++++++++++++ .../typescript/modules/SpotifyScraper.ts | 45 ++++++++++++ 7 files changed, 185 insertions(+), 1 deletion(-) delete mode 100644 index.ts create mode 100644 src/build.ts create mode 100644 src/extension/manifest.json create mode 100644 src/extension/typescript/background.ts create mode 100644 src/extension/typescript/content.ts create mode 100644 src/extension/typescript/modules/SpotifyAPI.ts create mode 100644 src/extension/typescript/modules/SpotifyScraper.ts diff --git a/index.ts b/index.ts deleted file mode 100644 index f67b2c6..0000000 --- a/index.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello via Bun!"); \ No newline at end of file diff --git a/src/build.ts b/src/build.ts new file mode 100644 index 0000000..f4e28cf --- /dev/null +++ b/src/build.ts @@ -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); +} + diff --git a/src/extension/manifest.json b/src/extension/manifest.json new file mode 100644 index 0000000..e3296ea --- /dev/null +++ b/src/extension/manifest.json @@ -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"] + } + ] +} diff --git a/src/extension/typescript/background.ts b/src/extension/typescript/background.ts new file mode 100644 index 0000000..746724f --- /dev/null +++ b/src/extension/typescript/background.ts @@ -0,0 +1,4 @@ +function onRuntimeMessage(message: any) {} + +browser.runtime.onMessage.addListener(onRuntimeMessage); + diff --git a/src/extension/typescript/content.ts b/src/extension/typescript/content.ts new file mode 100644 index 0000000..a9d2e62 --- /dev/null +++ b/src/extension/typescript/content.ts @@ -0,0 +1,7 @@ +import SpotifyAPI from "./modules/SpotifyAPI"; + +const API = new SpotifyAPI(); + +// use the API + +API.getCurrentTrack(); diff --git a/src/extension/typescript/modules/SpotifyAPI.ts b/src/extension/typescript/modules/SpotifyAPI.ts new file mode 100644 index 0000000..f50f45d --- /dev/null +++ b/src/extension/typescript/modules/SpotifyAPI.ts @@ -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) +} + diff --git a/src/extension/typescript/modules/SpotifyScraper.ts b/src/extension/typescript/modules/SpotifyScraper.ts new file mode 100644 index 0000000..7fc9127 --- /dev/null +++ b/src/extension/typescript/modules/SpotifyScraper.ts @@ -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); + } +} +