mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
reorg & impl getIndexes
This commit is contained in:
parent
b51d0eb685
commit
5ce3b5bcdf
5
App.tsx
5
App.tsx
@ -12,7 +12,7 @@ const App = () => {
|
|||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
import { SubsonicApiClient } from './src/subsonic/api';
|
import { SubsonicApiClient } from './src/subsonic/client';
|
||||||
import md5 from 'md5';
|
import md5 from 'md5';
|
||||||
|
|
||||||
const password = 'test';
|
const password = 'test';
|
||||||
@ -21,5 +21,4 @@ const token = md5(password + salt);
|
|||||||
|
|
||||||
const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
|
const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
|
||||||
|
|
||||||
client.ping();
|
client.getIndexes();
|
||||||
client.getArtists();
|
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
import { DOMParser } from 'xmldom';
|
|
||||||
|
|
||||||
export class SubsonicApiClient {
|
|
||||||
public address: string;
|
|
||||||
public username: string;
|
|
||||||
|
|
||||||
private params: URLSearchParams
|
|
||||||
|
|
||||||
constructor(address: string, username: string, token: string, salt: string) {
|
|
||||||
this.address = address;
|
|
||||||
this.username = username;
|
|
||||||
|
|
||||||
this.params = new URLSearchParams();
|
|
||||||
this.params.append('u', username);
|
|
||||||
this.params.append('t', token);
|
|
||||||
this.params.append('s', salt);
|
|
||||||
this.params.append('v', '1.15.0');
|
|
||||||
this.params.append('c', 'subsonify-cool-unique-app-string')
|
|
||||||
}
|
|
||||||
|
|
||||||
private async apiRequest(method: string, params?: URLSearchParams): Promise<Document> {
|
|
||||||
const url = `${this.address}/rest/${method}?${(params || this.params).toString()}`;
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
|
||||||
const text = await response.text();
|
|
||||||
|
|
||||||
console.log(text);
|
|
||||||
|
|
||||||
const xml = new DOMParser().parseFromString(text);
|
|
||||||
if (xml.documentElement.getAttribute('status') !== 'ok') {
|
|
||||||
throw new SubsonicApiException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ping(): Promise<SubsonicResponse<null>> {
|
|
||||||
const xml = await this.apiRequest('ping');
|
|
||||||
const response = new SubsonicResponse<null>(xml, null);
|
|
||||||
|
|
||||||
console.log(response.status);
|
|
||||||
console.log(response.version);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getArtists(): Promise<SubsonicResponse<ArtistID3Response>> {
|
|
||||||
const xml = await this.apiRequest('getArtists');
|
|
||||||
const data = new ArtistID3Response(xml);
|
|
||||||
const response = new SubsonicResponse<ArtistID3Response>(xml, data);
|
|
||||||
|
|
||||||
console.log(response.status);
|
|
||||||
console.log(response.version);
|
|
||||||
console.log(response.data.artists);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubsonicApiException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseStatus = 'ok' | 'failed';
|
|
||||||
|
|
||||||
class SubsonicResponse<T> {
|
|
||||||
public status: ResponseStatus;
|
|
||||||
public version: string;
|
|
||||||
public data: T;
|
|
||||||
|
|
||||||
constructor(xml: Document, data: T) {
|
|
||||||
this.data = data;
|
|
||||||
this.status = xml.documentElement.getAttribute('status') as ResponseStatus;
|
|
||||||
this.version = xml.documentElement.getAttribute('version') as string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArtistID3 {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
coverArt?: string;
|
|
||||||
albumCount: number;
|
|
||||||
starred?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArtistID3Response {
|
|
||||||
public ignoredArticles: string;
|
|
||||||
public artists: ArtistID3[] = [];
|
|
||||||
|
|
||||||
constructor(xml: Document) {
|
|
||||||
this.ignoredArticles = xml.getElementsByTagName('artists')[0].getAttribute('ignoredArticles') as string;
|
|
||||||
|
|
||||||
const artistElements = xml.getElementsByTagName('artist');
|
|
||||||
for (let i = 0; i < artistElements.length; i++) {
|
|
||||||
const a = artistElements[i];
|
|
||||||
|
|
||||||
this.artists.push({
|
|
||||||
id: a.getAttribute('id') as string,
|
|
||||||
name: a.getAttribute('name') as string,
|
|
||||||
coverArt: a.getAttribute('coverArt') || undefined,
|
|
||||||
albumCount: parseInt(a.getAttribute('albumCount') as string),
|
|
||||||
starred: a.getAttribute('starred') ? new Date(a.getAttribute('starred') as string) : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
91
src/subsonic/client.ts
Normal file
91
src/subsonic/client.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { DOMParser } from 'xmldom';
|
||||||
|
import { GetArtistsResponse, GetIndexesResponse, SubsonicResponse } from './response';
|
||||||
|
|
||||||
|
export class SubsonicApiClient {
|
||||||
|
address: string;
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
private params: URLSearchParams
|
||||||
|
|
||||||
|
constructor(address: string, username: string, token: string, salt: string) {
|
||||||
|
this.address = address;
|
||||||
|
this.username = username;
|
||||||
|
|
||||||
|
this.params = new URLSearchParams();
|
||||||
|
this.params.append('u', username);
|
||||||
|
this.params.append('t', token);
|
||||||
|
this.params.append('s', salt);
|
||||||
|
this.params.append('v', '1.15.0');
|
||||||
|
this.params.append('c', 'subsonify-cool-unique-app-string')
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiRequest(method: string, params?: URLSearchParams): Promise<Document> {
|
||||||
|
let query = this.params.toString();
|
||||||
|
if (params !== undefined && Array.from(params as any).length > 0) {
|
||||||
|
query += '&' + params.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.address}/rest/${method}?${query}`;
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
console.log(text);
|
||||||
|
|
||||||
|
const xml = new DOMParser().parseFromString(text);
|
||||||
|
if (xml.documentElement.getAttribute('status') !== 'ok') {
|
||||||
|
throw new SubsonicApiError(method, xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ping(): Promise<SubsonicResponse<null>> {
|
||||||
|
const xml = await this.apiRequest('ping');
|
||||||
|
const response = new SubsonicResponse<null>(xml, null);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArtists(): Promise<SubsonicResponse<GetArtistsResponse>> {
|
||||||
|
const xml = await this.apiRequest('getArtists');
|
||||||
|
const data = new GetArtistsResponse(xml);
|
||||||
|
const response = new SubsonicResponse<GetArtistsResponse>(xml, data);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIndexes(ifModifiedSince?: number): Promise<SubsonicResponse<GetIndexesResponse>> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
console.log(params);
|
||||||
|
if (ifModifiedSince !== undefined) {
|
||||||
|
params.append('ifModifiedSince', ifModifiedSince.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const xml = await this.apiRequest('getIndexes', params);
|
||||||
|
const data = new GetIndexesResponse(xml);
|
||||||
|
const response = new SubsonicResponse<GetIndexesResponse>(xml, data);
|
||||||
|
|
||||||
|
console.log(response.status);
|
||||||
|
console.log(response.version);
|
||||||
|
console.log(response.data.lastModified);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SubsonicApiError extends Error {
|
||||||
|
method: string;
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
constructor(method: string, xml: Document) {
|
||||||
|
const errorElement = xml.getElementsByTagName('error')[0];
|
||||||
|
|
||||||
|
super(errorElement.getAttribute('message') as string);
|
||||||
|
|
||||||
|
this.name = method;
|
||||||
|
this.method = method;
|
||||||
|
this.code = errorElement.getAttribute('code') as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/subsonic/element.ts
Normal file
46
src/subsonic/element.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export class BaseArtist {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
starred?: string;
|
||||||
|
|
||||||
|
constructor(e: Element) {
|
||||||
|
this.id = e.getAttribute('id') as string;
|
||||||
|
this.name = e.getAttribute('name') as string;
|
||||||
|
|
||||||
|
if (e.getAttribute('starred') !== null) {
|
||||||
|
this.starred = e.getAttribute('starred') as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArtistID3 extends BaseArtist {
|
||||||
|
coverArt?: string;
|
||||||
|
albumCount?: number;
|
||||||
|
|
||||||
|
constructor(e: Element) {
|
||||||
|
super(e);
|
||||||
|
|
||||||
|
if (e.getAttribute('coverArt') !== null) {
|
||||||
|
this.coverArt = e.getAttribute('coverArt') as string;
|
||||||
|
}
|
||||||
|
if (e.getAttribute('albumCount') !== null) {
|
||||||
|
this.albumCount = parseInt(e.getAttribute('albumCount') as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Artist extends BaseArtist {
|
||||||
|
userRating?: number;
|
||||||
|
averageRating?: number;
|
||||||
|
|
||||||
|
constructor(e: Element) {
|
||||||
|
super(e);
|
||||||
|
|
||||||
|
if (e.getAttribute('userRating') !== null) {
|
||||||
|
this.userRating = parseInt(e.getAttribute('userRating') as string);
|
||||||
|
}
|
||||||
|
if (e.getAttribute('averageRating') !== null) {
|
||||||
|
this.averageRating = parseFloat(e.getAttribute('averageRating') as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/subsonic/response.ts
Normal file
47
src/subsonic/response.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Artist, ArtistID3 } from "./element";
|
||||||
|
|
||||||
|
export type ResponseStatus = 'ok' | 'failed';
|
||||||
|
|
||||||
|
export class SubsonicResponse<T> {
|
||||||
|
status: ResponseStatus;
|
||||||
|
version: string;
|
||||||
|
data: T;
|
||||||
|
|
||||||
|
constructor(xml: Document, data: T) {
|
||||||
|
this.data = data;
|
||||||
|
this.status = xml.documentElement.getAttribute('status') as ResponseStatus;
|
||||||
|
this.version = xml.documentElement.getAttribute('version') as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetArtistsResponse {
|
||||||
|
ignoredArticles: string;
|
||||||
|
artists: ArtistID3[] = [];
|
||||||
|
|
||||||
|
constructor(xml: Document) {
|
||||||
|
this.ignoredArticles = xml.getElementsByTagName('artists')[0].getAttribute('ignoredArticles') as string;
|
||||||
|
|
||||||
|
const artistElements = xml.getElementsByTagName('artist');
|
||||||
|
for (let i = 0; i < artistElements.length; i++) {
|
||||||
|
this.artists.push(new ArtistID3(artistElements[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetIndexesResponse {
|
||||||
|
ignoredArticles: string;
|
||||||
|
lastModified: number;
|
||||||
|
artists: Artist[] = [];
|
||||||
|
|
||||||
|
constructor(xml: Document) {
|
||||||
|
const indexesElement = xml.getElementsByTagName('indexes')[0];
|
||||||
|
|
||||||
|
this.ignoredArticles = indexesElement.getAttribute('ignoredArticles') as string;
|
||||||
|
this.lastModified = parseInt(indexesElement.getAttribute('lastModified') as string);
|
||||||
|
|
||||||
|
const artistElements = xml.getElementsByTagName('artist');
|
||||||
|
for (let i = 0; i < artistElements.length; i++) {
|
||||||
|
this.artists.push(new Artist(artistElements[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user