import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; import 'package:ocarina/api/subsonic/album.dart'; import 'package:ocarina/api/subsonic/artistindex.dart'; import 'package:ocarina/api/subsonic/song.dart'; import 'package:ocarina/util/util.dart'; /// Used to communicate with Subsonic API class SubsonicApiService { /// Used to communicate with Subsonic API factory SubsonicApiService() { return _subsonicService; } SubsonicApiService._internal(); static final SubsonicApiService _subsonicService = SubsonicApiService._internal(); Dio get _client => Dio(BaseOptions(contentType: "application/json", baseUrl: baseUrl)); // ignore: avoid_setters_without_getters set password(String p) { _salt = Util.makeSalt; _hash = md5.convert(utf8.encode(p + _salt)).toString(); } Future>> _get( String path, { Map params = const {}, }) async { return _client.get>( path, queryParameters: { "u": user, "t": _hash, "s": _salt, "c": "Ocarina", "f": "json", "v": _version, ...params, }, ); } /// Subsonic-compatible server's base URL String baseUrl = ""; /// Subsonic username String user = ""; String _hash = ""; final String _version = "1.16.1"; String _salt = ""; /// Checks if saved details return an OK from server Future verifyCredentials() async { try { final r = await _get("/rest/ping.view"); if (r.data == null || r.data!.containsKey("error")) return false; return true; } catch (e) { return false; } } Future?> getArtistIndex() async { try { final r = await _get("/rest/getIndexes"); if (r.data == null) { return null; } final response = r.data!["subsonic-response"] as Map; if (response["status"] != "ok") return null; final indexes = List>.from( (response["indexes"] as Map)["index"] as List, ); return List.generate( indexes.length, (index) => ArtistIndex.fromJson(indexes[index]), ); } catch (e) { logger.e(e); return null; } } /// Returns data from the `getMusicDirectory` endpoint formatted as [Album] /// /// Checks if it is really a directory and tries to format it with fromJson Future?> getAlbumList(String artistId) async { final r = await _get("/rest/getMusicDirectory", params: {"id": artistId}); if (r.data == null || r.data!["subsonic-response"]["status"] != "ok") { return null; } final albums = []; for (final albumData in r.data!["subsonic-response"]["directory"]["child"] as List) { if ((albumData as Map)["isDir"] == false) { continue; } try { albums.add(Album.fromJson(albumData)); } catch (e) { logger.e(e); continue; } } return albums; } /// Returns data from the `getMusicDirectory` endpoint formatted as [Album] /// /// Checks if it is really a directory and tries to format it with fromJson Future?> getSongList(String albumId) async { final r = await _get("/rest/getMusicDirectory", params: {"id": albumId}); if (r.data == null || r.data!["subsonic-response"]["status"] != "ok") { return null; } final songs = []; for (final songData in r.data!["subsonic-response"]["directory"]["child"] as List) { if ((songData as Map)["isDir"] == true) { continue; } try { songs.add(Song.fromJson(songData)); } catch (e) { logger.e(e); continue; } } return songs; } String getCoverArtUrl(String id) => "$baseUrl/rest/getCoverArt?id=$id&u=$user&s=$_salt&t=$_hash&c=Ocarina&v=1.16.1"; String getStreamUrl(String id) => "$baseUrl/rest/stream?id=$id&u=$user&s=$_salt&t=$_hash&c=Ocarina&v=1.16.1"; }