Skip to content

Commit

Permalink
Initial migration to native modules
Browse files Browse the repository at this point in the history
  • Loading branch information
Inrixia committed Jun 25, 2024
1 parent 0c1d58c commit b005f1c
Show file tree
Hide file tree
Showing 43 changed files with 346 additions and 290 deletions.
37 changes: 36 additions & 1 deletion build.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,42 @@ for (const plugin of plugins) {
esbuild
.build({
entryPoints: ["./" + path.join(pluginPath, pluginManifest.main ?? "index.js")],
plugins: [
{
name: "neptuneNativeImports",
setup(build) {
build.onLoad({ filter: /.*[\/\\].+\.native\.[a-z]+/g }, async (args) => {
const result = await esbuild.build({
entryPoints: [args.path],
bundle: true,
minify: true,
platform: "node",
format: "iife",
globalName: "neptuneExports",
write: false,
});

const outputCode = result.outputFiles[0].text;

// HATE! WHY WHY WHY WHY WHY (globalName breaks metafile. crying emoji)
const { metafile } = await esbuild.build({
entryPoints: [args.path],
platform: "node",
write: false,
metafile: true,
});

const builtExports = Object.values(metafile.outputs)[0].exports;

return {
contents: `import {addUnloadable} from "@plugin";const contextId=NeptuneNative.createEvalScope(${JSON.stringify(outputCode)});${builtExports
.map((e) => `export ${e == "default" ? "default " : `const ${e} =`} NeptuneNative.getNativeValue(contextId,${JSON.stringify(e)})`)
.join(";")};addUnloadable(() => NeptuneNative.deleteEvalScope(contextId))`,
};
});
},
},
],
bundle: true,
minify: true,
format: "esm",
Expand All @@ -36,7 +72,6 @@ for (const plugin of plugins) {
hash: this.read(),
})
);

console.log("Built " + pluginManifest.name + "!");
});
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@
"ext": "*",
"exec": "npm run build"
}
}
}
6 changes: 3 additions & 3 deletions plugins/LastFM/src/LastFM.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { findModuleFunction } from "@inrixia/lib/findModuleFunction";
import type crypto from "crypto";
const { createHash } = <typeof crypto>require("crypto");
import { requestStream, toJson } from "@inrixia/lib/fetch";

import storage from "./storage";

Expand All @@ -13,6 +12,7 @@ if (lastFmApiKey === undefined) throw new Error("Last.fm API key not found");

import { NowPlaying } from "./types/lastfm/NowPlaying";
import { Scrobble } from "./types/lastfm/Scrobble";
import { requestJson } from "@inrixia/lib/nativeBridge";

export type NowPlayingOpts = {
track: string;
Expand Down Expand Up @@ -61,14 +61,14 @@ export class LastFM {
params.format = "json";
params.api_sig = this.generateApiSignature(params);

const data = await requestStream(`https://ws.audioscrobbler.com/2.0/`, {
const data = await requestJson<ResponseType<T>>(`https://ws.audioscrobbler.com/2.0/`, {
headers: {
"Content-type": "application/x-www-form-urlencoded",
"Accept-Charset": "utf-8",
},
method: "POST",
body: new URLSearchParams(params).toString(),
}).then(toJson<ResponseType<T>>);
});

if (data.message) throw new Error(data.message);
else return <T>data;
Expand Down
1 change: 1 addition & 0 deletions plugins/NoBuffer/src/VoidStream.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Writable } from "stream";
12 changes: 5 additions & 7 deletions plugins/NoBuffer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { intercept, store } from "@neptune";
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache";

import { fetchTrack } from "@inrixia/lib/trackBytes/download";

import * as stream from "stream";
const { Writable } = <typeof stream>require("stream");
// import { fetchTrack } from "@inrixia/lib/trackBytes/download.native";
import getPlaybackControl from "@inrixia/lib/getPlaybackControl";
import { Writable } from "./VoidStream.native";

import { Tracer } from "@inrixia/lib/trace";
import getPlaybackControl from "@inrixia/lib/getPlaybackControl";
const trace = Tracer("[NoBuffer]");

let unblocking = false;
Expand All @@ -20,11 +18,11 @@ export const onUnload = intercept("playbackControls/SET_PLAYBACK_STATE", ([state
const trackItem = await TrackItemCache.current(playbackContext);
if (trackItem === undefined) return;
trace.msg.log(`Playback stalled for ${trackItem?.title} - Kicking tidal CDN`);
const { stream } = await fetchTrack({ trackId: trackItem.id!, desiredQuality: playbackContext.actualAudioQuality });
// const { stream } = await fetchTrack({ trackId: trackItem.id!, desiredQuality: playbackContext.actualAudioQuality });
const voidStream = new Writable({
write: (_: any, __: any, cb: () => void) => cb(),
});
stream.pipe(voidStream);
// stream.pipe(voidStream);
await new Promise((res) => voidStream.on("end", res));
unblocking = false;
})();
Expand Down
8 changes: 3 additions & 5 deletions plugins/Shazam/src/shazamApi/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { rejectNotOk, requestStream, toJson } from "@inrixia/lib/fetch";
import { requestJson } from "@inrixia/lib/nativeBridge";
import { ShazamData } from "./shazamTypes";
import { v4 } from "uuid";

export const fetchShazamData = async (signature: { samplems: number; uri: string }) =>
requestStream(
requestJson<ShazamData>(
`https://amp.shazam.com/discovery/v5/en-US/US/iphone/-/tag/${v4()}/${v4()}?sync=true&webv3=true&sampling=true&connected=&shazamapiversion=v3&sharehub=true&hubv5minorversion=v5.1&hidelb=true&video=v3`,
{
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify({ signature }),
}
)
.then(rejectNotOk)
.then(toJson<ShazamData>);
);
21 changes: 10 additions & 11 deletions plugins/SongDownloader/src/addMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { utils } from "@neptune";
import { TrackItem } from "neptune-types/tidal";
import { fullTitle } from "@inrixia/lib/fullTitle";
import { ExtendedPlaybackInfoWithBytes } from "@inrixia/lib/trackBytes/download";
import { rejectNotOk, requestStream, toBuffer } from "@inrixia/lib/fetch";
import { ManifestMimeType } from "@inrixia/lib/Caches/PlaybackInfoCache";
import type { ExtendedPlaybackInfoWithBytes } from "@inrixia/lib/trackBytes/download";
import { ManifestMimeType } from "@inrixia/lib/Caches/PlaybackInfoTypes";
import { actions } from "@neptune";
import { interceptPromise } from "@inrixia/lib/intercept/interceptPromise";

Expand Down Expand Up @@ -58,13 +57,13 @@ async function makeTags(track: TrackItem) {
}

let picture;
if (cover !== undefined) {
try {
picture = {
pictureType: PictureType.FrontCover,
buffer: await requestStream(utils.getMediaURLFromID(cover)).then(rejectNotOk).then(toBuffer),
};
} catch {}
}
// if (cover !== undefined) {
// try {
// picture = {
// pictureType: PictureType.FrontCover,
// buffer: await requestStream(utils.getMediaURLFromID(cover)).then(rejectNotOk).then(toBuffer),
// };
// } catch {}
// }
return { tagMap, picture };
}
28 changes: 10 additions & 18 deletions plugins/SongDownloader/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { intercept, actions, store } from "@neptune";

import "./styles";

import { fetchTrack, DownloadTrackOptions, TrackOptions } from "@inrixia/lib/trackBytes/download";
import { ItemId, MediaItem, TrackItem, VideoItem } from "neptune-types/tidal";
import { TrackItem } from "neptune-types/tidal";
import { saveFile, saveFileNode } from "./lib/saveFile";

import { interceptPromise } from "@inrixia/lib/intercept/interceptPromise";

import { addMetadata } from "./addMetadata";
import { fileNameFromInfo } from "./lib/fileName";
import { toBuffer } from "@inrixia/lib/fetch";
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache";

import { Tracer } from "@inrixia/lib/trace";
const trace = Tracer("[SongDownloader]");
Expand Down Expand Up @@ -80,19 +73,18 @@ ContextMenu.onOpen(async (contextSource, contextMenu, trackItems) => {
prep();
for (const trackItem of trackItems) {
if (trackItem.id === undefined) continue;
await downloadTrack(trackItem, { trackId: trackItem.id, desiredQuality: settings.desiredDownloadQuality }, { onProgress }).catch(trace.msg.err.withContext("Error downloading track"));
// await downloadTrack(trackItem, { trackId: trackItem.id, desiredQuality: settings.desiredDownloadQuality }, { onProgress }).catch(trace.msg.err.withContext("Error downloading track"));
}
clear();
});
contextMenu.appendChild(downloadButton);
});

export const downloadTrack = async (track: TrackItem, trackOptions: TrackOptions, options?: DownloadTrackOptions) => {
// Download the bytes
const trackInfo = await fetchTrack(trackOptions, options);
const streamWithTags = await addMetadata(trackInfo, track);
const fileName = fileNameFromInfo(track, trackInfo);

if (settings.defaultDownloadPath !== "") return saveFileNode(streamWithTags ?? trackInfo.stream, settings.defaultDownloadPath, fileName);
return saveFile(streamWithTags ?? trackInfo.stream, fileName);
};
// export const downloadTrack = async (track: TrackItem, trackOptions: TrackOptions, options?: DownloadTrackOptions) => {
// // Download the bytes
// const trackInfo = await fetchTrack(trackOptions, options);
// const streamWithTags = await addMetadata(trackInfo, track);
// const fileName = fileNameFromInfo(track, trackInfo);
// if (settings.defaultDownloadPath !== "") return saveFileNode(streamWithTags ?? trackInfo.stream, settings.defaultDownloadPath, fileName);
// return saveFile(streamWithTags ?? trackInfo.stream, fileName);
// };
2 changes: 1 addition & 1 deletion plugins/SongDownloader/src/lib/fileName.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TrackItem } from "neptune-types/tidal";
import { ExtendedPlayackInfo, ManifestMimeType } from "@inrixia/lib/Caches/PlaybackInfoCache";
import { type ExtendedPlayackInfo, ManifestMimeType } from "@inrixia/lib/Caches/PlaybackInfoTypes";
import { fullTitle } from "@inrixia/lib/fullTitle";

export const fileNameFromInfo = (track: TrackItem, { manifest, manifestMimeType }: ExtendedPlayackInfo): string => {
Expand Down
5 changes: 2 additions & 3 deletions plugins/SongDownloader/src/lib/saveFile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { toBlob } from "@inrixia/lib/fetch";
import type * as fs from "fs/promises";
const { writeFile } = <typeof fs>require("fs/promises");

Expand All @@ -11,9 +10,9 @@ export const saveFileNode = (stream: Readable, path: string, fileName: string) =
return writeFile(`${path}/${sanitizeFilename(fileName)}`, stream);
};

export const saveFile = async (blob: Readable, fileName: string) => {
export const saveFile = async (blob: Blob, fileName: string) => {
// Create a new Object URL for the Blob
const objectUrl = URL.createObjectURL(await toBlob(blob));
const objectUrl = URL.createObjectURL(blob);

// Create a link element
const a = document.createElement("a");
Expand Down
1 change: 0 additions & 1 deletion plugins/Testing/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import { interceptActions } from "@inrixia/lib/intercept/interceptActions";

export const onUnload = interceptActions(/.*/, console.log);
2 changes: 0 additions & 2 deletions plugins/_lib/Caches/AlbumCache.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { actions, store } from "@neptune";
import type { Album, ItemId, MediaItem, TrackItem } from "neptune-types/tidal";
import { interceptPromise } from "../intercept/interceptPromise";
import { SharedObjectStoreExpirable } from "../storage/SharedObjectStoreExpirable";
import { retryPending } from "./retryPending";

import { libTrace } from "../trace";
import { SharedObjectStore } from "../storage/SharedObjectStore";
Expand Down
5 changes: 1 addition & 4 deletions plugins/_lib/Caches/ExtendedTrackItem.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import type { ItemId, TrackItem, Album } from "neptune-types/tidal";
import type { PlaybackContext } from "../AudioQualityTypes";
import { MusicBrainz } from "../api/musicbrainz";
import { Recording } from "../api/musicbrainz/types/Recording";
import { Release } from "../api/musicbrainz/types/UPCData";
import { MusicBrainz, Release, Recording } from "../api/musicbrainz";
import { TrackItemCache } from "./TrackItemCache";
import { AlbumCache } from "./AlbumCache";
import { libTrace } from "../trace";
import { store } from "@neptune";
import getPlaybackControl from "../getPlaybackControl";

export class ExtendedTrackItem {
Expand Down
50 changes: 11 additions & 39 deletions plugins/_lib/Caches/PlaybackInfoCache.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,13 @@
import { getHeaders } from "../fetch";
import { audioQualities, AudioQuality } from "../AudioQualityTypes";
import { TrackItem } from "neptune-types/tidal";
import type { Manifest as DashManifest } from "dasha";

import { Semaphore } from "../Semaphore";
import { SharedObjectStore } from "../storage/SharedObjectStore";

import type * as dasha from "dasha";
import { SharedObjectStoreExpirable } from "../storage/SharedObjectStoreExpirable";
const { parse } = <typeof dasha>require("dasha");

export enum ManifestMimeType {
Tidal = "application/vnd.tidal.bts",
Dash = "application/dash+xml",
}

export type PlaybackInfo = {
trackId: number;
assetPresentation: string;
audioMode: NonNullable<TrackItem["audioModes"]>;
audioQuality: NonNullable<TrackItem["audioQuality"]>;
manifestMimeType: ManifestMimeType;
manifestHash: string;
manifest: string;
albumReplayGain: number;
albumPeakAmplitude: number;
trackReplayGain: number;
trackPeakAmplitude: number;
};

export type TidalManifest = {
mimeType: string;
codecs: string;
encryptionType: string;
keyId: string;
urls: string[];
};
import { parseDasha } from "../nativeBridge";
import { findModuleFunction } from "../findModuleFunction";
import { ExtendedPlayackInfo, PlaybackInfo, ManifestMimeType, TidalManifest } from "./PlaybackInfoTypes";

export type ExtendedPlayackInfo =
| { playbackInfo: PlaybackInfo; manifestMimeType: ManifestMimeType.Dash; manifest: DashManifest }
| { playbackInfo: PlaybackInfo; manifestMimeType: ManifestMimeType.Tidal; manifest: TidalManifest };
const getCredentials = findModuleFunction<() => Promise<{ token: string; clientId: string }>>("getCredentials", "function");
if (getCredentials === undefined) throw new Error("getCredentials method not found");

export class PlaybackInfoCache {
private static readonly _store: SharedObjectStoreExpirable<[ExtendedPlayackInfo["playbackInfo"]["trackId"], ExtendedPlayackInfo["playbackInfo"]["audioQuality"]], ExtendedPlayackInfo> =
Expand All @@ -62,8 +30,12 @@ export class PlaybackInfoCache {
try {
const url = `https://desktop.tidal.com/v1/tracks/${trackId}/playbackinfo?audioquality=${audioQuality}&playbackmode=STREAM&assetpresentation=FULL`;

const { clientId, token } = await getCredentials!();
const playbackInfo: PlaybackInfo = await fetch(url, {
headers: await getHeaders(),
headers: {
Authorization: `Bearer ${token}`,
"x-tidal-token": clientId,
},
}).then((r) => {
if (r.status === 401) {
alert("Failed to fetch Stream Info... Invalid OAuth Access Token!");
Expand All @@ -81,7 +53,7 @@ export class PlaybackInfoCache {
return extPlaybackInfo;
}
case ManifestMimeType.Dash: {
const manifest = await parse(atob(playbackInfo.manifest), "https://sp-ad-cf.audio.tidal.com");
const manifest = await parseDasha(atob(playbackInfo.manifest), "https://sp-ad-cf.audio.tidal.com");
return { playbackInfo, manifestMimeType: playbackInfo.manifestMimeType, manifest };
}
default: {
Expand Down
33 changes: 33 additions & 0 deletions plugins/_lib/Caches/PlaybackInfoTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { TrackItem } from "neptune-types/tidal";
import type { DashManifest } from "../nativeBridge";

export enum ManifestMimeType {
Tidal = "application/vnd.tidal.bts",
Dash = "application/dash+xml",
}

export type PlaybackInfo = {
trackId: number;
assetPresentation: string;
audioMode: NonNullable<TrackItem["audioModes"]>;
audioQuality: NonNullable<TrackItem["audioQuality"]>;
manifestMimeType: ManifestMimeType;
manifestHash: string;
manifest: string;
albumReplayGain: number;
albumPeakAmplitude: number;
trackReplayGain: number;
trackPeakAmplitude: number;
};

export type TidalManifest = {
mimeType: string;
codecs: string;
encryptionType: string;
keyId: string;
urls: string[];
};

export type ExtendedPlayackInfo =
| { playbackInfo: PlaybackInfo; manifestMimeType: ManifestMimeType.Dash; manifest: DashManifest }
| { playbackInfo: PlaybackInfo; manifestMimeType: ManifestMimeType.Tidal; manifest: TidalManifest };
1 change: 0 additions & 1 deletion plugins/_lib/Caches/PlaylistItemCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { actions } from "@neptune";
import type { ItemId, MediaItem, TrackItem } from "neptune-types/tidal";
import { interceptPromise } from "../intercept/interceptPromise";
import { SharedObjectStoreExpirable } from "../storage/SharedObjectStoreExpirable";
import { retryPending } from "./retryPending";

import { libTrace } from "../trace";

Expand Down
Loading

0 comments on commit b005f1c

Please sign in to comment.