mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Change ND genre ids to b64 encoded strings of genre, so as to differentiate between genre name and id (#54)
This commit is contained in:
@@ -5,6 +5,7 @@ import crypto from "crypto";
|
|||||||
import { Encryption } from "./encryption";
|
import { Encryption } from "./encryption";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { Clock, SystemClock } from "./clock";
|
import { Clock, SystemClock } from "./clock";
|
||||||
|
import { b64Encode, b64Decode } from "./b64";
|
||||||
|
|
||||||
type AccessToken = {
|
type AccessToken = {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -60,14 +61,12 @@ export class EncryptedAccessTokens implements AccessTokens {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mint = (authToken: string): string =>
|
mint = (authToken: string): string =>
|
||||||
Buffer.from(JSON.stringify(this.encryption.encrypt(authToken))).toString(
|
b64Encode(JSON.stringify(this.encryption.encrypt(authToken)));
|
||||||
"base64"
|
|
||||||
);
|
|
||||||
|
|
||||||
authTokenFor(value: string): string | undefined {
|
authTokenFor(value: string): string | undefined {
|
||||||
try {
|
try {
|
||||||
return this.encryption.decrypt(
|
return this.encryption.decrypt(
|
||||||
JSON.parse(Buffer.from(value, "base64").toString("ascii"))
|
JSON.parse(b64Decode(value))
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
logger.warn("Failed to decrypt access token...");
|
logger.warn("Failed to decrypt access token...");
|
||||||
|
|||||||
2
src/b64.ts
Normal file
2
src/b64.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const b64Encode = (value: string) => Buffer.from(value).toString("base64");
|
||||||
|
export const b64Decode = (value: string) => Buffer.from(value, "base64").toString("ascii");
|
||||||
@@ -21,11 +21,12 @@ import {
|
|||||||
} from "./music_service";
|
} from "./music_service";
|
||||||
import X2JS from "x2js";
|
import X2JS from "x2js";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import _, { pick } from "underscore";
|
import _ from "underscore";
|
||||||
|
|
||||||
import axios, { AxiosRequestConfig } from "axios";
|
import axios, { AxiosRequestConfig } from "axios";
|
||||||
import { Encryption } from "./encryption";
|
import { Encryption } from "./encryption";
|
||||||
import randomString from "./random_string";
|
import randomString from "./random_string";
|
||||||
|
import { b64Encode, b64Decode } from "./b64";
|
||||||
|
|
||||||
export const BROWSER_HEADERS = {
|
export const BROWSER_HEADERS = {
|
||||||
accept:
|
accept:
|
||||||
@@ -262,7 +263,7 @@ const asAlbum = (album: album) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const asGenre = (genreName: string) => ({
|
export const asGenre = (genreName: string) => ({
|
||||||
id: genreName,
|
id: b64Encode(genreName),
|
||||||
name: genreName,
|
name: genreName,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -374,20 +375,16 @@ export class Navidrome implements MusicService {
|
|||||||
generateToken = async (credentials: Credentials) =>
|
generateToken = async (credentials: Credentials) =>
|
||||||
this.getJSON(credentials, "/rest/ping.view")
|
this.getJSON(credentials, "/rest/ping.view")
|
||||||
.then(() => ({
|
.then(() => ({
|
||||||
authToken: Buffer.from(
|
authToken: b64Encode(
|
||||||
JSON.stringify(this.encryption.encrypt(JSON.stringify(credentials)))
|
JSON.stringify(this.encryption.encrypt(JSON.stringify(credentials)))
|
||||||
).toString("base64"),
|
),
|
||||||
userId: credentials.username,
|
userId: credentials.username,
|
||||||
nickname: credentials.username,
|
nickname: credentials.username,
|
||||||
}))
|
}))
|
||||||
.catch((e) => ({ message: `${e}` }));
|
.catch((e) => ({ message: `${e}` }));
|
||||||
|
|
||||||
parseToken = (token: string): Credentials =>
|
parseToken = (token: string): Credentials =>
|
||||||
JSON.parse(
|
JSON.parse(this.encryption.decrypt(JSON.parse(b64Decode(token))));
|
||||||
this.encryption.decrypt(
|
|
||||||
JSON.parse(Buffer.from(token, "base64").toString("ascii"))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
getArtists = (
|
getArtists = (
|
||||||
credentials: Credentials
|
credentials: Credentials
|
||||||
@@ -519,8 +516,8 @@ export class Navidrome implements MusicService {
|
|||||||
})),
|
})),
|
||||||
artist: async (id: string): Promise<Artist> =>
|
artist: async (id: string): Promise<Artist> =>
|
||||||
navidrome.getArtistWithInfo(credentials, id),
|
navidrome.getArtistWithInfo(credentials, id),
|
||||||
albums: async (q: AlbumQuery): Promise<Result<AlbumSummary>> => {
|
albums: async (q: AlbumQuery): Promise<Result<AlbumSummary>> =>
|
||||||
return Promise.all([
|
Promise.all([
|
||||||
navidrome
|
navidrome
|
||||||
.getArtists(credentials)
|
.getArtists(credentials)
|
||||||
.then((it) =>
|
.then((it) =>
|
||||||
@@ -528,7 +525,8 @@ export class Navidrome implements MusicService {
|
|||||||
),
|
),
|
||||||
navidrome
|
navidrome
|
||||||
.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList2", {
|
.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList2", {
|
||||||
...pick(q, "type", "genre"),
|
type: q.type,
|
||||||
|
...(q.genre ? { genre: b64Decode(q.genre) } : {}),
|
||||||
size: 500,
|
size: 500,
|
||||||
offset: q._index,
|
offset: q._index,
|
||||||
})
|
})
|
||||||
@@ -536,12 +534,8 @@ export class Navidrome implements MusicService {
|
|||||||
.then(navidrome.toAlbumSummary),
|
.then(navidrome.toAlbumSummary),
|
||||||
]).then(([total, albums]) => ({
|
]).then(([total, albums]) => ({
|
||||||
results: albums.slice(0, q._count),
|
results: albums.slice(0, q._count),
|
||||||
total:
|
total: albums.length == 500 ? total : q._index + albums.length,
|
||||||
albums.length == 500
|
})),
|
||||||
? total
|
|
||||||
: q._index + albums.length,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
album: (id: string): Promise<Album> =>
|
album: (id: string): Promise<Album> =>
|
||||||
navidrome.getAlbum(credentials, id),
|
navidrome.getAlbum(credentials, id),
|
||||||
genres: () =>
|
genres: () =>
|
||||||
@@ -553,7 +547,7 @@ export class Navidrome implements MusicService {
|
|||||||
A.filter((it) => Number.parseInt(it._albumCount) > 0),
|
A.filter((it) => Number.parseInt(it._albumCount) > 0),
|
||||||
A.map((it) => it.__text),
|
A.map((it) => it.__text),
|
||||||
A.sort(ordString),
|
A.sort(ordString),
|
||||||
A.map((it) => ({ id: it, name: it }))
|
A.map((it) => ({ id: b64Encode(it), name: it }))
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tracks: (albumId: string) =>
|
tracks: (albumId: string) =>
|
||||||
|
|||||||
17
tests/b64.test.ts
Normal file
17
tests/b64.test.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { b64Encode, b64Decode } from "../src/b64";
|
||||||
|
|
||||||
|
describe("b64", () => {
|
||||||
|
const value = "foobar100";
|
||||||
|
const encoded = Buffer.from(value).toString("base64");
|
||||||
|
|
||||||
|
describe("encode", () => {
|
||||||
|
it("should encode", () => {
|
||||||
|
expect(b64Encode(value)).toEqual(encoded);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("decode", () => {
|
||||||
|
it("should decode", () => {
|
||||||
|
expect(b64Decode(encoded)).toEqual(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,7 @@ import { Credentials } from "../src/smapi";
|
|||||||
import { Service, Device } from "../src/sonos";
|
import { Service, Device } from "../src/sonos";
|
||||||
import { Album, Artist, Track, albumToAlbumSummary, artistToArtistSummary, PlaylistSummary, Playlist } from "../src/music_service";
|
import { Album, Artist, Track, albumToAlbumSummary, artistToArtistSummary, PlaylistSummary, Playlist } from "../src/music_service";
|
||||||
import randomString from "../src/random_string";
|
import randomString from "../src/random_string";
|
||||||
|
import { b64Encode } from "../src/b64";
|
||||||
|
|
||||||
const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
|
const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
|
||||||
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
|
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
|
||||||
@@ -111,16 +112,18 @@ export function anArtist(fields: Partial<Artist> = {}): Artist {
|
|||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HIP_HOP = { id: "genre_hip_hop", name: "Hip-Hop" };
|
export const aGenre = (name: string) => ({ id: b64Encode(name), name })
|
||||||
export const METAL = { id: "genre_metal", name: "Metal" };
|
|
||||||
export const NEW_WAVE = { id: "genre_new_wave", name: "New Wave" };
|
export const HIP_HOP = aGenre("Hip-Hop");
|
||||||
export const POP = { id: "genre_pop", name: "Pop" };
|
export const METAL = aGenre("Metal");
|
||||||
export const POP_ROCK = { id: "genre_pop_rock", name: "Pop Rock" };
|
export const NEW_WAVE = aGenre("New Wave");
|
||||||
export const REGGAE = { id: "genre_reggae", name: "Reggae" };
|
export const POP = aGenre("Pop");
|
||||||
export const ROCK = { id: "genre_rock", name: "Rock" };
|
export const POP_ROCK = aGenre("Pop Rock");
|
||||||
export const SKA = { id: "genre_ska", name: "Ska" };
|
export const REGGAE = aGenre("Reggae");
|
||||||
export const PUNK = { id: "genre_punk", name: "Punk" };
|
export const ROCK = aGenre("Rock");
|
||||||
export const TRIP_HOP = { id: "genre_trip_hop", name: "Trip Hop" };
|
export const SKA = aGenre("Ska");
|
||||||
|
export const PUNK = aGenre("Punk");
|
||||||
|
export const TRIP_HOP = aGenre("Trip Hop");
|
||||||
|
|
||||||
export const SAMPLE_GENRES = [HIP_HOP, METAL, NEW_WAVE, POP, POP_ROCK, REGGAE, ROCK, SKA];
|
export const SAMPLE_GENRES = [HIP_HOP, METAL, NEW_WAVE, POP, POP_ROCK, REGGAE, ROCK, SKA];
|
||||||
export const randomGenre = () => SAMPLE_GENRES[randomInt(SAMPLE_GENRES.length)];
|
export const randomGenre = () => SAMPLE_GENRES[randomInt(SAMPLE_GENRES.length)];
|
||||||
|
|||||||
@@ -467,9 +467,9 @@ describe("InMemoryMusicService", () => {
|
|||||||
it("should provide an array of artists", async () => {
|
it("should provide an array of artists", async () => {
|
||||||
expect(await musicLibrary.genres()).toEqual([
|
expect(await musicLibrary.genres()).toEqual([
|
||||||
HIP_HOP,
|
HIP_HOP,
|
||||||
|
SKA,
|
||||||
POP,
|
POP,
|
||||||
ROCK,
|
ROCK,
|
||||||
SKA,
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { pipe } from "fp-ts/lib/function";
|
|||||||
import { ordString, fromCompare } from "fp-ts/lib/Ord";
|
import { ordString, fromCompare } from "fp-ts/lib/Ord";
|
||||||
import { shuffle } from "underscore";
|
import { shuffle } from "underscore";
|
||||||
|
|
||||||
|
import { b64Encode, b64Decode } from "../src/b64";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MusicService,
|
MusicService,
|
||||||
Credentials,
|
Credentials,
|
||||||
@@ -37,9 +39,7 @@ export class InMemoryMusicService implements MusicService {
|
|||||||
this.users[username] == password
|
this.users[username] == password
|
||||||
) {
|
) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
authToken: Buffer.from(JSON.stringify({ username, password })).toString(
|
authToken: b64Encode(JSON.stringify({ username, password })),
|
||||||
"base64"
|
|
||||||
),
|
|
||||||
userId: username,
|
userId: username,
|
||||||
nickname: username,
|
nickname: username,
|
||||||
});
|
});
|
||||||
@@ -49,9 +49,7 @@ export class InMemoryMusicService implements MusicService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
login(token: string): Promise<MusicLibrary> {
|
login(token: string): Promise<MusicLibrary> {
|
||||||
const credentials = JSON.parse(
|
const credentials = JSON.parse(b64Decode(token)) as Credentials;
|
||||||
Buffer.from(token, "base64").toString("ascii")
|
|
||||||
) as Credentials;
|
|
||||||
if (this.users[credentials.username] != credentials.password)
|
if (this.users[credentials.username] != credentials.password)
|
||||||
return Promise.reject("Invalid auth token");
|
return Promise.reject("Invalid auth token");
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ import {
|
|||||||
SimilarArtist,
|
SimilarArtist,
|
||||||
} from "../src/music_service";
|
} from "../src/music_service";
|
||||||
import {
|
import {
|
||||||
|
aGenre,
|
||||||
anAlbum,
|
anAlbum,
|
||||||
anArtist,
|
anArtist,
|
||||||
aPlaylist,
|
aPlaylist,
|
||||||
aPlaylistSummary,
|
aPlaylistSummary,
|
||||||
aTrack,
|
aTrack,
|
||||||
} from "./builders";
|
} from "./builders";
|
||||||
|
import { b64Encode } from "../src/b64";
|
||||||
|
|
||||||
describe("t", () => {
|
describe("t", () => {
|
||||||
it("should be an md5 of the password and the salt", () => {
|
it("should be an md5 of the password and the salt", () => {
|
||||||
@@ -547,7 +549,7 @@ describe("Navidrome", () => {
|
|||||||
.then((it) => navidrome.login(it.authToken))
|
.then((it) => navidrome.login(it.authToken))
|
||||||
.then((it) => it.genres());
|
.then((it) => it.genres());
|
||||||
|
|
||||||
expect(result).toEqual([{ id: "genre1", name: "genre1" }]);
|
expect(result).toEqual([{ id: b64Encode("genre1"), name: "genre1" }]);
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getGenres`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getGenres`, {
|
||||||
params: asURLSearchParams(authParams),
|
params: asURLSearchParams(authParams),
|
||||||
@@ -579,10 +581,10 @@ describe("Navidrome", () => {
|
|||||||
.then((it) => it.genres());
|
.then((it) => it.genres());
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{ id: "g1", name: "g1" },
|
{ id: b64Encode("g1"), name: "g1" },
|
||||||
{ id: "g2", name: "g2" },
|
{ id: b64Encode("g2"), name: "g2" },
|
||||||
{ id: "g3", name: "g3" },
|
{ id: b64Encode("g3"), name: "g3" },
|
||||||
{ id: "g4", name: "g4" },
|
{ id: b64Encode("g4"), name: "g4" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getGenres`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getGenres`, {
|
||||||
@@ -1234,11 +1236,11 @@ describe("Navidrome", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should pass the filter to navidrome", async () => {
|
it("should map the 64 encoded genre back into the subsonic genre", async () => {
|
||||||
const q: AlbumQuery = {
|
const q: AlbumQuery = {
|
||||||
_index: 0,
|
_index: 0,
|
||||||
_count: 100,
|
_count: 100,
|
||||||
genre: "Pop",
|
genre: b64Encode("Pop"),
|
||||||
type: "byGenre",
|
type: "byGenre",
|
||||||
};
|
};
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
@@ -2300,7 +2302,7 @@ describe("Navidrome", () => {
|
|||||||
|
|
||||||
describe("streaming a track", () => {
|
describe("streaming a track", () => {
|
||||||
const trackId = uuid();
|
const trackId = uuid();
|
||||||
const genre = { id: "foo", name: "foo" };
|
const genre = aGenre("foo");
|
||||||
|
|
||||||
const album = anAlbum({ genre });
|
const album = anAlbum({ genre });
|
||||||
const artist = anArtist({
|
const artist = anArtist({
|
||||||
@@ -3535,7 +3537,7 @@ describe("Navidrome", () => {
|
|||||||
it("should return true", async () => {
|
it("should return true", async () => {
|
||||||
const album = anAlbum({
|
const album = anAlbum({
|
||||||
name: "foo woo",
|
name: "foo woo",
|
||||||
genre: { id: "pop", name: "pop" },
|
genre: { id: b64Encode("pop"), name: "pop" },
|
||||||
});
|
});
|
||||||
const artist = anArtist({ name: "#1", albums: [album] });
|
const artist = anArtist({ name: "#1", albums: [album] });
|
||||||
|
|
||||||
@@ -3570,13 +3572,13 @@ describe("Navidrome", () => {
|
|||||||
it("should return true", async () => {
|
it("should return true", async () => {
|
||||||
const album1 = anAlbum({
|
const album1 = anAlbum({
|
||||||
name: "album1",
|
name: "album1",
|
||||||
genre: { id: "pop", name: "pop" },
|
genre: { id: b64Encode("pop"), name: "pop" },
|
||||||
});
|
});
|
||||||
const artist1 = anArtist({ name: "artist1", albums: [album1] });
|
const artist1 = anArtist({ name: "artist1", albums: [album1] });
|
||||||
|
|
||||||
const album2 = anAlbum({
|
const album2 = anAlbum({
|
||||||
name: "album2",
|
name: "album2",
|
||||||
genre: { id: "pop", name: "pop" },
|
genre: { id: b64Encode("pop"), name: "pop" },
|
||||||
});
|
});
|
||||||
const artist2 = anArtist({ name: "artist2", albums: [album2] });
|
const artist2 = anArtist({ name: "artist2", albums: [album2] });
|
||||||
|
|
||||||
@@ -3904,11 +3906,11 @@ describe("Navidrome", () => {
|
|||||||
const id = uuid();
|
const id = uuid();
|
||||||
const name = "Great Playlist";
|
const name = "Great Playlist";
|
||||||
const track1 = aTrack({
|
const track1 = aTrack({
|
||||||
genre: { id: "pop", name: "pop" },
|
genre: { id: b64Encode("pop"), name: "pop" },
|
||||||
number: 66,
|
number: 66,
|
||||||
});
|
});
|
||||||
const track2 = aTrack({
|
const track2 = aTrack({
|
||||||
genre: { id: "rock", name: "rock" },
|
genre: { id: b64Encode("rock"), name: "rock" },
|
||||||
number: 77,
|
number: 77,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user