Skip to content

Commit

Permalink
LastFM - Native Working
Browse files Browse the repository at this point in the history
  • Loading branch information
Inrixia committed Jun 25, 2024
1 parent 1037060 commit 3fbe29f
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 32 deletions.
17 changes: 7 additions & 10 deletions plugins/LastFM/src/LastFM.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { findModuleFunction } from "@inrixia/lib/findModuleFunction";
import type crypto from "crypto";
const { createHash } = <typeof crypto>require("crypto");

import storage from "./storage";

const lastFmSecret = findModuleFunction<string>("lastFmSecret", "string");
Expand All @@ -12,7 +9,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";
import { hash, requestJson } from "@inrixia/lib/nativeBridge";

export type NowPlayingOpts = {
track: string;
Expand Down Expand Up @@ -44,22 +41,22 @@ type ResponseType<T> =
};

export class LastFM {
private static generateApiSignature = (params: Record<string, string>) => {
private static async generateApiSignature(params: Record<string, string>) {
const sig =
Object.keys(params)
.filter((key) => key !== "format" && key !== undefined)
.sort()
.map((key) => `${key}${params[key]}`)
.join("") + lastFmSecret;
return createHash("md5").update(sig, "utf8").digest("hex");
};
return hash(sig);
}

private static sendRequest = async <T>(method: string, params?: Record<string, string>, reqMethod = "GET") => {
private static async sendRequest<T>(method: string, params?: Record<string, string>, reqMethod = "GET") {
params ??= {};
params.method = method;
params.api_key = lastFmApiKey!;
params.format = "json";
params.api_sig = this.generateApiSignature(params);
params.api_sig = await this.generateApiSignature(params);

const data = await requestJson<ResponseType<T>>(`https://ws.audioscrobbler.com/2.0/`, {
headers: {
Expand All @@ -72,7 +69,7 @@ export class LastFM {

if (data.message) throw new Error(data.message);
else return <T>data;
};
}

private static getSession = async (): Promise<LastFmSession> => {
if (storage.lastFmSession !== undefined) return storage.lastFmSession;
Expand Down
5 changes: 3 additions & 2 deletions plugins/LastFM/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PlaybackContext } from "@inrixia/lib/AudioQualityTypes";

import { LastFM, ScrobbleOpts } from "./LastFM";

import type { PlaybackState, TrackItem } from "neptune-types/tidal";
import type { PlaybackState } from "neptune-types/tidal";

import { fullTitle } from "@inrixia/lib/fullTitle";

Expand All @@ -15,9 +15,10 @@ import { debounce } from "@inrixia/lib/debounce";

import safeUnload from "@inrixia/lib/safeUnload";

import { settings } from "./Settings";
import getPlaybackControl from "@inrixia/lib/getPlaybackControl";

export { Settings } from "./Settings";
import { settings } from "./Settings";

let totalPlayTime = 0;
let lastPlayStart: number | null = null;
Expand Down
6 changes: 3 additions & 3 deletions plugins/LastFM/src/types/lastfm/NowPlaying.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ export interface NowPlaying {
nowplaying?: Nowplaying;
}

export interface Nowplaying {
interface Nowplaying {
artist?: Album;
track?: Album;
ignoredMessage?: IgnoredMessage;
albumArtist?: Album;
album?: Album;
}

export interface Album {
interface Album {
corrected?: string;
"#text"?: string;
}

export interface IgnoredMessage {
interface IgnoredMessage {
code?: string;
"#text"?: string;
}
8 changes: 4 additions & 4 deletions plugins/LastFM/src/types/lastfm/Scrobble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export interface Scrobble {
scrobbles?: Scrobbles;
}

export interface Scrobbles {
interface Scrobbles {
scrobble?: ScrobbleClass;
"@attr"?: Attr;
}
Expand All @@ -12,7 +12,7 @@ export interface Attr {
accepted?: number;
}

export interface ScrobbleClass {
interface ScrobbleClass {
artist?: Album;
album?: Album;
track?: Album;
Expand All @@ -21,12 +21,12 @@ export interface ScrobbleClass {
timestamp?: string;
}

export interface Album {
interface Album {
corrected?: string;
"#text"?: string;
}

export interface IgnoredMessage {
interface IgnoredMessage {
code?: string;
"#text"?: string;
}
16 changes: 6 additions & 10 deletions plugins/_lib/nativeBridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@ export type * from "./native";
type UnsafeReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type UnsafeParameters<T> = T extends (...args: infer P) => any ? P : never;
type PromisefulModule<M> = {
[K in keyof M]: M[K] extends Function
? UnsafeReturnType<M[K]> extends PromiseLike<unknown>
? (...args: UnsafeParameters<M[K]>) => UnsafeReturnType<M[K]>
: (...args: UnsafeParameters<M[K]>) => Promise<UnsafeReturnType<M[K]>>
: () => Promise<M[K]>;
[K in keyof M]: M[K] extends Function ? (UnsafeReturnType<M[K]> extends PromiseLike<unknown> ? M[K] : (...args: UnsafeParameters<M[K]>) => Promise<UnsafeReturnType<M[K]>>) : () => Promise<M[K]>;
};

type NativeBridge = PromisefulModule<typeof nb>;
const _invoke: (method: string, ...args: any[]) => Promise<any> = (<any>window).electron.ipcRenderer.invoke;
const invoke =
<K extends keyof NativeBridge>(method: K) =>
(...args: Parameters<NativeBridge[K]>): ReturnType<NativeBridge[K]> =>
<any>_invoke("___nativeBridge___", method, ...args).catch((err: Error) => {
const invoke = <K extends keyof NativeBridge>(method: K) => <NativeBridge[K]>((...args: any) =>
_invoke("___nativeBridge___", method, ...args).catch((err: Error) => {
err.message = err.message.replaceAll("Error invoking remote method '___nativeBridge___': ", "");
throw err;
});
}));
export const getTrackInfo = invoke("getTrackInfo");
export const parseDasha = invoke("parseDasha");
export const requestJson = invoke("requestJson");
export const hash = invoke("hash");
16 changes: 16 additions & 0 deletions plugins/_lib/nativeBridge/native/crypto.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createHash, type Encoding, type HashOptions, type BinaryToTextEncoding } from "crypto";

export const hash = (
data: string,
options: {
algorithm?: string;
hashOptions?: HashOptions;
inputEncoding?: Encoding;
digestEncoding?: BinaryToTextEncoding;
} = {}
) => {
options.algorithm ??= "md5";
options.inputEncoding ??= "utf8";
options.digestEncoding ??= "hex";
return createHash(options.algorithm).update(data, options.inputEncoding).digest(options.digestEncoding);
};
1 change: 1 addition & 0 deletions plugins/_lib/nativeBridge/native/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./request";
export * from "./dasha.native";
export * from "./getTrackInfo.native";
export * from "./crypto.native";
8 changes: 5 additions & 3 deletions plugins/_lib/nativeBridge/nativeBridge.native.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @ts-nocheck
import * as nativeBridge from "./native";
// @ts-ignore
electron.ipcMain.removeAllListeners("___nativeBridge___");
// @ts-ignore
electron.ipcMain.handle("___nativeBridge___", (_, method: string, ...args) => nativeBridge[method](...args));
electron.ipcMain.handle("___nativeBridge___", (_, method: string, ...args) => {
if (nativeBridge[method] === undefined) throw new Error(`Method "${method}" not found! Available methods: ${Object.keys(nativeBridge).join(", ")}.`);
return nativeBridge[method](...args);
});

0 comments on commit 3fbe29f

Please sign in to comment.