From 4e3a291a2a99c592f85e298b7896866a620b55e6 Mon Sep 17 00:00:00 2001 From: Curtis Caulfield <69975259+codedbycurtis@users.noreply.github.com> Date: Sun, 19 Nov 2023 04:56:21 +0000 Subject: [PATCH] Initial commit --- .gitignore | 4 + CHANGELOG.md | 5 + LICENSE | 13 +- README.md | 126 + analysis_options.yaml | 1 + build.yaml | 6 + lib/soundcloud_explode_dart.dart | 8 + lib/src/bridge/soundcloud_controller.dart | 60 + lib/src/constants.dart | 5 + .../client_unauthorized_exception.dart | 12 + .../exceptions/search_result_exception.dart | 10 + .../soundcloud_explode_exception.dart | 10 + .../track_resolution_exception.dart | 12 + lib/src/playlists/playlist.dart | 33 + lib/src/playlists/playlist.freezed.dart | 504 ++++ lib/src/playlists/playlist.g.dart | 51 + lib/src/playlists/playlist_client.dart | 96 + lib/src/playlists/playlists.dart | 3 + lib/src/playlists/soundcloud_playlist.dart | 56 + lib/src/search/search.dart | 3 + lib/src/search/search_client.dart | 181 ++ lib/src/search/search_filter.dart | 20 + lib/src/search/search_result.dart | 99 + lib/src/search/search_result.freezed.dart | 2640 +++++++++++++++++ lib/src/search/search_result.g.dart | 192 ++ lib/src/soundcloud_client.dart | 37 + lib/src/tracks/soundcloud_track.dart | 104 + lib/src/tracks/streams/container.dart | 8 + lib/src/tracks/streams/protocol.dart | 8 + lib/src/tracks/streams/quality.dart | 10 + lib/src/tracks/streams/stream_info.dart | 20 + .../tracks/streams/stream_info.freezed.dart | 220 ++ lib/src/tracks/streams/streams.dart | 6 + lib/src/tracks/track.dart | 43 + lib/src/tracks/track.freezed.dart | 725 +++++ lib/src/tracks/track.g.dart | 70 + lib/src/tracks/track_client.dart | 99 + lib/src/tracks/tracks.dart | 4 + lib/src/users/mini_user.dart | 28 + lib/src/users/mini_user.freezed.dart | 404 +++ lib/src/users/mini_user.g.dart | 43 + lib/src/users/soundcloud_user.dart | 44 + lib/src/users/user.dart | 43 + lib/src/users/user.freezed.dart | 694 +++++ lib/src/users/user.g.dart | 71 + lib/src/users/user_client.dart | 185 ++ lib/src/users/user_track_sort.dart | 8 + lib/src/users/users.dart | 3 + pubspec.yaml | 28 + 49 files changed, 7043 insertions(+), 12 deletions(-) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 build.yaml create mode 100644 lib/soundcloud_explode_dart.dart create mode 100644 lib/src/bridge/soundcloud_controller.dart create mode 100644 lib/src/constants.dart create mode 100644 lib/src/exceptions/client_unauthorized_exception.dart create mode 100644 lib/src/exceptions/search_result_exception.dart create mode 100644 lib/src/exceptions/soundcloud_explode_exception.dart create mode 100644 lib/src/exceptions/track_resolution_exception.dart create mode 100644 lib/src/playlists/playlist.dart create mode 100644 lib/src/playlists/playlist.freezed.dart create mode 100644 lib/src/playlists/playlist.g.dart create mode 100644 lib/src/playlists/playlist_client.dart create mode 100644 lib/src/playlists/playlists.dart create mode 100644 lib/src/playlists/soundcloud_playlist.dart create mode 100644 lib/src/search/search.dart create mode 100644 lib/src/search/search_client.dart create mode 100644 lib/src/search/search_filter.dart create mode 100644 lib/src/search/search_result.dart create mode 100644 lib/src/search/search_result.freezed.dart create mode 100644 lib/src/search/search_result.g.dart create mode 100644 lib/src/soundcloud_client.dart create mode 100644 lib/src/tracks/soundcloud_track.dart create mode 100644 lib/src/tracks/streams/container.dart create mode 100644 lib/src/tracks/streams/protocol.dart create mode 100644 lib/src/tracks/streams/quality.dart create mode 100644 lib/src/tracks/streams/stream_info.dart create mode 100644 lib/src/tracks/streams/stream_info.freezed.dart create mode 100644 lib/src/tracks/streams/streams.dart create mode 100644 lib/src/tracks/track.dart create mode 100644 lib/src/tracks/track.freezed.dart create mode 100644 lib/src/tracks/track.g.dart create mode 100644 lib/src/tracks/track_client.dart create mode 100644 lib/src/tracks/tracks.dart create mode 100644 lib/src/users/mini_user.dart create mode 100644 lib/src/users/mini_user.freezed.dart create mode 100644 lib/src/users/mini_user.g.dart create mode 100644 lib/src/users/soundcloud_user.dart create mode 100644 lib/src/users/user.dart create mode 100644 lib/src/users/user.freezed.dart create mode 100644 lib/src/users/user.g.dart create mode 100644 lib/src/users/user_client.dart create mode 100644 lib/src/users/user_track_sort.dart create mode 100644 lib/src/users/users.dart create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4173746 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.dart_tool/ +build/ +pubspec.lock +doc/api/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4161d30 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 + +- Initial release. diff --git a/LICENSE b/LICENSE index 261eeb9..eb97c02 100644 --- a/LICENSE +++ b/LICENSE @@ -175,18 +175,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2023 Curtis Caulfield Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4821b7 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# SoundcloudExplodeDart + +SoundcloudExplodeDart utilises SoundCloud's internal V2 API to scrape metadata about users, tracks, playlists, and albums, without requiring an account, API key, or rate-limiting. + +This API was **not** intended to be an exhaustive map of all SoundCloud endpoints, but I will be accepting feature requests, so feel free to suggest any functionality you would like to see by opening a new issue. + +> This project takes inspiration from jerry08's [SoundCloudExplode](https://github.com/jerry08/SoundCloudExplode) library for C#. + +## Usage + +### Searching + +Search for users, tracks, playlists, or albums, and apply specific search filters to each query: + +```dart +import 'dart:async'; +import 'soundcloud_explode_dart/soundcloud_explode_dart.dart'; + +final client = SoundcloudClient(); + +// Most functions return a stream of results in the +// form of Stream>. +// The number of results returned in each Iterable, as well as +// the search result offset and search filter are optional parameters. +final stream = client.search( + 'Haddaway - What Is Love', + searchFilter: SearchFilter.tracks, + offset: 0, + limit: 50 +); +final streamIterator = StreamIterator(stream); + +while (await streamIterator.moveNext()) { + for (final result in streamIterator.current) { + print(result.title); + } +} +``` + +### Querying users + +Retrieve metadata about specific users: + +```dart +import 'soundcloud_explode_dart/soundcloud_explode_dart.dart'; + +final client = SoundcloudClient(); + +// Users can be retrieved via URL... +final user1 = await client.users.getByUrl('https://www.soundcloud.com/a-user'); + +// ...or via their user ID. +final user2 = await client.users.get(123456789); + +// Get the tracks/playlists/albums a specific user has uploaded... +final tracks = client.users.getTracks(user1.id); +final playlists = client.users.getPlaylists(user1.id); +final albums = client.users.getAlbums(user1.id); +``` + +### Querying tracks and streams + +Metadata about specific tracks can also be retrieved: + +```dart +import 'soundcloud_explode_dart/soundcloud_explode_dart.dart'; + +final client = SoundcloudClient(); + +// Tracks can also be retrieved via URL... +final track1 = await client.tracks.getByUrl('https://www.soundcloud.com/a-user/a-track'); + +// ...or via their track ID. +final track2 = await client.tracks.get(123456789); +``` + +In order to play a track, you need to resolve the streams available for it: + +```dart +import 'soundcloud_explode_dart/soundcloud_explode_dart.dart'; +import 'some_audio_player/some_audio_player.dart'; + +final client = SoundcloudClient(); +final audioPlayer = SomeAudioPlayer(); + +final track = await client.tracks.getByUrl('https://www.soundcloud.com/a-user/a-track'); + +final streams = await client.tracks.getStreams(track.id); +final stream = streams.firstWhere((s) => s.container == Container.mp3); + +await audioPlayer.play(stream.url); +``` + +> Note: some tracks only provide a 30 second snippet and cannot be played in their entirety; those that require a SoundCloud Go subscription are one such example. +> +> To determine whether or not a track is fully playable: +> +> ```dart +> final track = await client.tracks.get(123456789); +> if (track.duration == track.fullDuration) { +> // Track can be played until completion. +> ... +> } +> ``` + +### Querying playlists/albums + +To retrieve metadata about specific playlists: + +```dart +import 'soundcloud_explode_dart/soundcloud_explode_dart.dart'; + +final client = SoundcloudClient(); + +// Playlists/albums can be retrieved via URL... +final playlist11 = await client.playlists.getByUrl('https://www.soundcloud.com/a-user/sets/a-playlist-or-album'); + +// ...or via their playlist ID. +final playlist2 = await client.playlists.get(123456789); + +// Indicates if the playlist is identified as an album or not. +final isAlbum = playlist1.isAlbum; + +// Get the tracks contained with a playlist/album... +final tracks = client.playlists.getTracks(playlist1.id); +``` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..ea2c9e9 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..840029b --- /dev/null +++ b/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + json_serializable: + options: + field_rename: snake \ No newline at end of file diff --git a/lib/soundcloud_explode_dart.dart b/lib/soundcloud_explode_dart.dart new file mode 100644 index 0000000..65482d0 --- /dev/null +++ b/lib/soundcloud_explode_dart.dart @@ -0,0 +1,8 @@ +/// Scrape metadata about users, tracks, playlists, and albums from SoundCloud without requiring an account, API key, or rate-limiting. +library; + +export 'src/soundcloud_client.dart'; +export 'src/playlists/playlists.dart'; +export 'src/search/search.dart'; +export 'src/tracks/tracks.dart'; +export 'src/users/users.dart'; \ No newline at end of file diff --git a/lib/src/bridge/soundcloud_controller.dart b/lib/src/bridge/soundcloud_controller.dart new file mode 100644 index 0000000..7b500cf --- /dev/null +++ b/lib/src/bridge/soundcloud_controller.dart @@ -0,0 +1,60 @@ +import 'package:http/http.dart'; +import '../exceptions/client_unauthorized_exception.dart'; + +/// Interacts with the SoundCloud API. +/// +/// This should only be used internally. +class SoundcloudController { + final Client _http; + String? _clientId; + + /// Creates a new [SoundcloudController] that uses the provided [httpClient]. + SoundcloudController(Client httpClient) + : _http = httpClient; + + /// Gets the client ID for the current session. + /// + /// This parameter is cached so that only one request is made for the duration of this session. + Future getClientId() async { + if (_clientId != null) return _clientId!; + + var response = await _http.get(Uri.https('soundcloud.com', '')); + final scripts = RegExp(' resolveUrl(String url) async { + final resolvingUrl = Uri + .parse(url) + .replace(host: 'soundcloud.com') + .toString(); + + final clientId = await getClientId(); + + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/resolve', + { + 'url': resolvingUrl, + 'client_id': clientId + } + ); + + final response = await _http.get(uri); + return response.body; + } +} \ No newline at end of file diff --git a/lib/src/constants.dart b/lib/src/constants.dart new file mode 100644 index 0000000..956ba6b --- /dev/null +++ b/lib/src/constants.dart @@ -0,0 +1,5 @@ +/// The default number of results to skip when returning from a SoundCloud query. +const defaultOffset = 0; + +/// The default number of results that are returned from a SoundCloud query. +const defaultLimit = 10; \ No newline at end of file diff --git a/lib/src/exceptions/client_unauthorized_exception.dart b/lib/src/exceptions/client_unauthorized_exception.dart new file mode 100644 index 0000000..6a3d296 --- /dev/null +++ b/lib/src/exceptions/client_unauthorized_exception.dart @@ -0,0 +1,12 @@ +import 'soundcloud_explode_exception.dart'; + +/// Indicates a failure to obtain a valid client ID from the SoundCloud server. +class ClientUnauthorizedException extends SoundcloudExplodeException { + ClientUnauthorizedException(super.message); + + static ClientUnauthorizedException clientId() => ClientUnauthorizedException( + '''Could not resolve a valid client ID for this session. + This may be a bug in the library. If the issue persists, do not hesitate to report it on the + project's GitHub page.''' + ); +} \ No newline at end of file diff --git a/lib/src/exceptions/search_result_exception.dart b/lib/src/exceptions/search_result_exception.dart new file mode 100644 index 0000000..fd2db43 --- /dev/null +++ b/lib/src/exceptions/search_result_exception.dart @@ -0,0 +1,10 @@ +import 'package:soundcloud_explode_dart/src/exceptions/soundcloud_explode_exception.dart'; + +/// Indicates a failure to resolve a [SearchResult] to a specific instance. +class SearchResultException extends SoundcloudExplodeException { + const SearchResultException(super.message); + + static SearchResultException cannotResolve() => SearchResultException( + 'Unable to resolve the provided value to a specific [SearchResult].' + ); +} \ No newline at end of file diff --git a/lib/src/exceptions/soundcloud_explode_exception.dart b/lib/src/exceptions/soundcloud_explode_exception.dart new file mode 100644 index 0000000..cb0ac9f --- /dev/null +++ b/lib/src/exceptions/soundcloud_explode_exception.dart @@ -0,0 +1,10 @@ +/// Superclass of all exceptions thrown by this library. +class SoundcloudExplodeException implements Exception { + final String? message; + + /// Creates a new [SoundcloudExplodeException] with the provided [message]. + const SoundcloudExplodeException(this.message); + + @override + String toString() => 'SoundcloudExplodeException: \n$message'; +} \ No newline at end of file diff --git a/lib/src/exceptions/track_resolution_exception.dart b/lib/src/exceptions/track_resolution_exception.dart new file mode 100644 index 0000000..bbdd983 --- /dev/null +++ b/lib/src/exceptions/track_resolution_exception.dart @@ -0,0 +1,12 @@ +import 'soundcloud_explode_exception.dart'; + +/// Indicates a failure in resolving a track or its streams. +class TrackResolutionException extends SoundcloudExplodeException { + const TrackResolutionException(super.message); + + static TrackResolutionException noStreams(int trackId) => TrackResolutionException( + '''Could not resolve any streams for the specified track ID: $trackId + This may be a bug in the library. If the issue persists, do not hesitate to report it on the + project's GitHub page.''' + ); +} \ No newline at end of file diff --git a/lib/src/playlists/playlist.dart b/lib/src/playlists/playlist.dart new file mode 100644 index 0000000..522c54e --- /dev/null +++ b/lib/src/playlists/playlist.dart @@ -0,0 +1,33 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import '../users/mini_user.dart'; +import 'soundcloud_playlist.dart'; + +part 'playlist.freezed.dart'; +part 'playlist.g.dart'; + +/// Metadata about a SoundCloud playlist. +@freezed +class Playlist with _$Playlist implements SoundcloudPlaylist { + const factory Playlist({ + required Uri? artworkUrl, + required DateTime createdAt, + required String? description, + required double duration, + required String? genre, + required int id, + required String? labelName, + required DateTime? lastModified, + @JsonKey(defaultValue: 0) required double likesCount, + required Uri permalinkUrl, + @JsonKey(defaultValue: 0) required double repostsCount, + required String? tagList, + required String title, + required bool isAlbum, + required MiniUser user, + @JsonKey(defaultValue: 0) required double trackCount + }) = _Playlist; + + factory Playlist.fromJson(Map json) => _$PlaylistFromJson(json); +} \ No newline at end of file diff --git a/lib/src/playlists/playlist.freezed.dart b/lib/src/playlists/playlist.freezed.dart new file mode 100644 index 0000000..74c9ad6 --- /dev/null +++ b/lib/src/playlists/playlist.freezed.dart @@ -0,0 +1,504 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'playlist.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +Playlist _$PlaylistFromJson(Map json) { + return _Playlist.fromJson(json); +} + +/// @nodoc +mixin _$Playlist { + Uri? get artworkUrl => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + double get duration => throw _privateConstructorUsedError; + String? get genre => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + String? get labelName => throw _privateConstructorUsedError; + DateTime? get lastModified => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get likesCount => throw _privateConstructorUsedError; + Uri get permalinkUrl => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get repostsCount => throw _privateConstructorUsedError; + String? get tagList => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + bool get isAlbum => throw _privateConstructorUsedError; + MiniUser get user => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get trackCount => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PlaylistCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PlaylistCopyWith<$Res> { + factory $PlaylistCopyWith(Playlist value, $Res Function(Playlist) then) = + _$PlaylistCopyWithImpl<$Res, Playlist>; + @useResult + $Res call( + {Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount}); + + $MiniUserCopyWith<$Res> get user; +} + +/// @nodoc +class _$PlaylistCopyWithImpl<$Res, $Val extends Playlist> + implements $PlaylistCopyWith<$Res> { + _$PlaylistCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? artworkUrl = freezed, + Object? createdAt = null, + Object? description = freezed, + Object? duration = null, + Object? genre = freezed, + Object? id = null, + Object? labelName = freezed, + Object? lastModified = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? repostsCount = null, + Object? tagList = freezed, + Object? title = null, + Object? isAlbum = null, + Object? user = null, + Object? trackCount = null, + }) { + return _then(_value.copyWith( + artworkUrl: freezed == artworkUrl + ? _value.artworkUrl + : artworkUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as double, + genre: freezed == genre + ? _value.genre + : genre // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + labelName: freezed == labelName + ? _value.labelName + : labelName // ignore: cast_nullable_to_non_nullable + as String?, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + tagList: freezed == tagList + ? _value.tagList + : tagList // ignore: cast_nullable_to_non_nullable + as String?, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + isAlbum: null == isAlbum + ? _value.isAlbum + : isAlbum // ignore: cast_nullable_to_non_nullable + as bool, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as MiniUser, + trackCount: null == trackCount + ? _value.trackCount + : trackCount // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $MiniUserCopyWith<$Res> get user { + return $MiniUserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PlaylistImplCopyWith<$Res> + implements $PlaylistCopyWith<$Res> { + factory _$$PlaylistImplCopyWith( + _$PlaylistImpl value, $Res Function(_$PlaylistImpl) then) = + __$$PlaylistImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount}); + + @override + $MiniUserCopyWith<$Res> get user; +} + +/// @nodoc +class __$$PlaylistImplCopyWithImpl<$Res> + extends _$PlaylistCopyWithImpl<$Res, _$PlaylistImpl> + implements _$$PlaylistImplCopyWith<$Res> { + __$$PlaylistImplCopyWithImpl( + _$PlaylistImpl _value, $Res Function(_$PlaylistImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? artworkUrl = freezed, + Object? createdAt = null, + Object? description = freezed, + Object? duration = null, + Object? genre = freezed, + Object? id = null, + Object? labelName = freezed, + Object? lastModified = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? repostsCount = null, + Object? tagList = freezed, + Object? title = null, + Object? isAlbum = null, + Object? user = null, + Object? trackCount = null, + }) { + return _then(_$PlaylistImpl( + artworkUrl: freezed == artworkUrl + ? _value.artworkUrl + : artworkUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as double, + genre: freezed == genre + ? _value.genre + : genre // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + labelName: freezed == labelName + ? _value.labelName + : labelName // ignore: cast_nullable_to_non_nullable + as String?, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + tagList: freezed == tagList + ? _value.tagList + : tagList // ignore: cast_nullable_to_non_nullable + as String?, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + isAlbum: null == isAlbum + ? _value.isAlbum + : isAlbum // ignore: cast_nullable_to_non_nullable + as bool, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as MiniUser, + trackCount: null == trackCount + ? _value.trackCount + : trackCount // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaylistImpl implements _Playlist { + const _$PlaylistImpl( + {required this.artworkUrl, + required this.createdAt, + required this.description, + required this.duration, + required this.genre, + required this.id, + required this.labelName, + required this.lastModified, + @JsonKey(defaultValue: 0) required this.likesCount, + required this.permalinkUrl, + @JsonKey(defaultValue: 0) required this.repostsCount, + required this.tagList, + required this.title, + required this.isAlbum, + required this.user, + @JsonKey(defaultValue: 0) required this.trackCount}); + + factory _$PlaylistImpl.fromJson(Map json) => + _$$PlaylistImplFromJson(json); + + @override + final Uri? artworkUrl; + @override + final DateTime createdAt; + @override + final String? description; + @override + final double duration; + @override + final String? genre; + @override + final int id; + @override + final String? labelName; + @override + final DateTime? lastModified; + @override + @JsonKey(defaultValue: 0) + final double likesCount; + @override + final Uri permalinkUrl; + @override + @JsonKey(defaultValue: 0) + final double repostsCount; + @override + final String? tagList; + @override + final String title; + @override + final bool isAlbum; + @override + final MiniUser user; + @override + @JsonKey(defaultValue: 0) + final double trackCount; + + @override + String toString() { + return 'Playlist(artworkUrl: $artworkUrl, createdAt: $createdAt, description: $description, duration: $duration, genre: $genre, id: $id, labelName: $labelName, lastModified: $lastModified, likesCount: $likesCount, permalinkUrl: $permalinkUrl, repostsCount: $repostsCount, tagList: $tagList, title: $title, isAlbum: $isAlbum, user: $user, trackCount: $trackCount)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaylistImpl && + (identical(other.artworkUrl, artworkUrl) || + other.artworkUrl == artworkUrl) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.genre, genre) || other.genre == genre) && + (identical(other.id, id) || other.id == id) && + (identical(other.labelName, labelName) || + other.labelName == labelName) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.likesCount, likesCount) || + other.likesCount == likesCount) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.repostsCount, repostsCount) || + other.repostsCount == repostsCount) && + (identical(other.tagList, tagList) || other.tagList == tagList) && + (identical(other.title, title) || other.title == title) && + (identical(other.isAlbum, isAlbum) || other.isAlbum == isAlbum) && + (identical(other.user, user) || other.user == user) && + (identical(other.trackCount, trackCount) || + other.trackCount == trackCount)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + artworkUrl, + createdAt, + description, + duration, + genre, + id, + labelName, + lastModified, + likesCount, + permalinkUrl, + repostsCount, + tagList, + title, + isAlbum, + user, + trackCount); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaylistImplCopyWith<_$PlaylistImpl> get copyWith => + __$$PlaylistImplCopyWithImpl<_$PlaylistImpl>(this, _$identity); + + @override + Map toJson() { + return _$$PlaylistImplToJson( + this, + ); + } +} + +abstract class _Playlist implements Playlist { + const factory _Playlist( + {required final Uri? artworkUrl, + required final DateTime createdAt, + required final String? description, + required final double duration, + required final String? genre, + required final int id, + required final String? labelName, + required final DateTime? lastModified, + @JsonKey(defaultValue: 0) required final double likesCount, + required final Uri permalinkUrl, + @JsonKey(defaultValue: 0) required final double repostsCount, + required final String? tagList, + required final String title, + required final bool isAlbum, + required final MiniUser user, + @JsonKey(defaultValue: 0) required final double trackCount}) = + _$PlaylistImpl; + + factory _Playlist.fromJson(Map json) = + _$PlaylistImpl.fromJson; + + @override + Uri? get artworkUrl; + @override + DateTime get createdAt; + @override + String? get description; + @override + double get duration; + @override + String? get genre; + @override + int get id; + @override + String? get labelName; + @override + DateTime? get lastModified; + @override + @JsonKey(defaultValue: 0) + double get likesCount; + @override + Uri get permalinkUrl; + @override + @JsonKey(defaultValue: 0) + double get repostsCount; + @override + String? get tagList; + @override + String get title; + @override + bool get isAlbum; + @override + MiniUser get user; + @override + @JsonKey(defaultValue: 0) + double get trackCount; + @override + @JsonKey(ignore: true) + _$$PlaylistImplCopyWith<_$PlaylistImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/playlists/playlist.g.dart b/lib/src/playlists/playlist.g.dart new file mode 100644 index 0000000..6b3f6ed --- /dev/null +++ b/lib/src/playlists/playlist.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'playlist.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$PlaylistImpl _$$PlaylistImplFromJson(Map json) => + _$PlaylistImpl( + artworkUrl: json['artwork_url'] == null + ? null + : Uri.parse(json['artwork_url'] as String), + createdAt: DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + duration: (json['duration'] as num).toDouble(), + genre: json['genre'] as String?, + id: json['id'] as int, + labelName: json['label_name'] as String?, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + likesCount: (json['likes_count'] as num?)?.toDouble() ?? 0, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + repostsCount: (json['reposts_count'] as num?)?.toDouble() ?? 0, + tagList: json['tag_list'] as String?, + title: json['title'] as String, + isAlbum: json['is_album'] as bool, + user: MiniUser.fromJson(json['user'] as Map), + trackCount: (json['track_count'] as num?)?.toDouble() ?? 0, + ); + +Map _$$PlaylistImplToJson(_$PlaylistImpl instance) => + { + 'artwork_url': instance.artworkUrl?.toString(), + 'created_at': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'duration': instance.duration, + 'genre': instance.genre, + 'id': instance.id, + 'label_name': instance.labelName, + 'last_modified': instance.lastModified?.toIso8601String(), + 'likes_count': instance.likesCount, + 'permalink_url': instance.permalinkUrl.toString(), + 'reposts_count': instance.repostsCount, + 'tag_list': instance.tagList, + 'title': instance.title, + 'is_album': instance.isAlbum, + 'user': instance.user, + 'track_count': instance.trackCount, + }; diff --git a/lib/src/playlists/playlist_client.dart b/lib/src/playlists/playlist_client.dart new file mode 100644 index 0000000..a8964cd --- /dev/null +++ b/lib/src/playlists/playlist_client.dart @@ -0,0 +1,96 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import '../bridge/soundcloud_controller.dart'; +import '../constants.dart'; +import '../tracks/track.dart'; +import 'playlist.dart'; + +/// Scrapes metadata about SoundCloud playlists and albums. +class PlaylistClient { + final Client _http; + final SoundcloudController _controller; + + /// Creates a new [PlaylistClient] that uses the provided [httpClient] and [controller]. + PlaylistClient(Client httpClient, SoundcloudController controller) + : _http = httpClient, + _controller = controller; + + /// Gets the [Playlist] with the specified [playlistId]. + Future get(int playlistId) async { + final json = await _getPlaylistResponse(playlistId); + return Playlist.fromJson(json); + } + + /// Gets the [Playlist] associated with the provided [url]. + Future getByUrl(String url) async { + final json = await _controller.resolveUrl(url); + final playlist = jsonDecode(json); + return Playlist.fromJson(playlist); + } + + /// Gets batches of tracks contained in the playlist with the specified [playlistId]. + Stream> getTracks( + int playlistId, { + int offset = defaultOffset, + int limit = defaultLimit + }) async* { + if (offset < 0) { + throw ArgumentError.value( + offset, + 'offset', + 'Offset cannot be less than zero.', + ); + } + + if (limit < 0) { + throw ArgumentError.value( + limit, + 'limit', + 'Limit cannot be less than zero.', + ); + } + + final json = await _getPlaylistResponse(playlistId); + final tracks = json['tracks'] as List; + final trackIds = tracks.map((t) => t['id'] as int); + + final clientId = await _controller.getClientId(); + + var continuationOffset = offset; + + while (true) { + final batchIds = trackIds.skip(continuationOffset).take(limit); + + if (batchIds.isEmpty) return; + + final idsParam = batchIds.join(','); + + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/tracks', { + 'ids': idsParam, + 'client_id': clientId + } + ); + + final response = await _http.get(uri); + final actualTracks = jsonDecode(response.body) as List; + + yield actualTracks.map((t) => Track.fromJson(t)); + + continuationOffset += actualTracks.length; + } + } + + Future> _getPlaylistResponse(int playlistId) async { + final clientId = await _controller.getClientId(); + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/playlists/$playlistId', { + 'client_id': clientId + } + ); + final response = await _http.get(uri); + return jsonDecode(response.body); + } +} \ No newline at end of file diff --git a/lib/src/playlists/playlists.dart b/lib/src/playlists/playlists.dart new file mode 100644 index 0000000..aea5a3d --- /dev/null +++ b/lib/src/playlists/playlists.dart @@ -0,0 +1,3 @@ +export 'playlist_client.dart'; +export 'playlist.dart'; +export 'soundcloud_playlist.dart'; \ No newline at end of file diff --git a/lib/src/playlists/soundcloud_playlist.dart b/lib/src/playlists/soundcloud_playlist.dart new file mode 100644 index 0000000..b45118f --- /dev/null +++ b/lib/src/playlists/soundcloud_playlist.dart @@ -0,0 +1,56 @@ +import '../users/mini_user.dart'; + +/// Metadata about a SoundCloud playlist. +/// +/// This class cannot be instantiated or extended. +abstract interface class SoundcloudPlaylist { + /// The URL of this playlist's thumbnail. + Uri? get artworkUrl; + + /// The date and time this playlist was first created on SoundCloud. + DateTime get createdAt; + + /// A short description of this playlist. + String? get description; + + /// The total duration of all track's in this playlist. + double get duration; + + /// The genre of music this playlist belongs to. + String? get genre; + + /// A unique numeric identifier for this playlist. + int get id; + + /// The record label associated with this playlist. + String? get labelName; + + /// The date and time modifications were last made to this playlist. + DateTime? get lastModified; + + /// The number of likes this playlist has received. + double get likesCount; + + /// The URL of this playlist. + /// + /// E.g. https://www.soundcloud.com/a-user/sets/a-playlist + Uri get permalinkUrl; + + /// The number of times this playlist has been reposted. + double get repostsCount; + + /// A comma-separated list of tags applied to this playlist. + String? get tagList; + + /// The title of this playlist. + String get title; + + /// Whether or not this playlist is identified as an album. + bool get isAlbum; + + /// The user that created this playlist. + MiniUser get user; + + /// The number of tracks in this playlist. + double get trackCount; +} \ No newline at end of file diff --git a/lib/src/search/search.dart b/lib/src/search/search.dart new file mode 100644 index 0000000..c865129 --- /dev/null +++ b/lib/src/search/search.dart @@ -0,0 +1,3 @@ +export 'search_client.dart'; +export 'search_filter.dart'; +export 'search_result.dart'; \ No newline at end of file diff --git a/lib/src/search/search_client.dart b/lib/src/search/search_client.dart new file mode 100644 index 0000000..c62d147 --- /dev/null +++ b/lib/src/search/search_client.dart @@ -0,0 +1,181 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:soundcloud_explode_dart/src/exceptions/search_result_exception.dart'; +import '../bridge/soundcloud_controller.dart'; +import '../constants.dart'; +import 'search_filter.dart'; +import 'search_result.dart'; + +/// Queries SoundCloud about users, tracks, and playlists. +class SearchClient { + final Client _http; + final SoundcloudController _controller; + + /// Creates a new [SearchClient] that uses the provided [httpClient] and [controller]. + SearchClient(Client httpClient, SoundcloudController controller) + : _http = httpClient, + _controller = controller; + + /// Searches for the provided [query] with the specified [searchFilter]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> call( + String query, { + SearchFilter searchFilter = SearchFilter.none, + int offset = defaultOffset, + int limit = defaultLimit + }) => search( + query, + searchFilter: searchFilter, + offset: offset, + limit: limit + ); + + /// Searches for the provided [query] with the specified [searchFilter]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> search( + String query, { + SearchFilter searchFilter = SearchFilter.none, + int offset = defaultOffset, + int limit = defaultLimit + }) async* { + if (offset < 0) { + throw ArgumentError.value( + offset, + 'offset', + 'Offset cannot be less than zero.', + ); + } + + if (limit < 0) { + throw ArgumentError.value( + limit, + 'limit', + 'Limit cannot be less than zero.', + ); + } + + var continuationOffset = offset; + + final filter = switch (searchFilter) { + SearchFilter.tracks => '/tracks', + SearchFilter.playlists => '/playlists', + SearchFilter.playlistsWithoutAlbums => '/playlists_without_albums', + SearchFilter.albums => '/albums', + SearchFilter.users => '/users', + _ => '' + }; + + final clientId = await _controller.getClientId(); + + while (true) { + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/search$filter', { + 'q': query, + 'client_id': clientId, + 'limit': '$limit', + 'offset': '$continuationOffset', + } + ); + + final response = await _http.get(uri); + final json = jsonDecode(response.body); + final collection = json['collection'] as List; + + if (collection.isEmpty) return; + + yield collection.map((result) { + final permalinkUri = Uri.parse(result['permalink_url']); + + return switch (permalinkUri.pathSegments.length) { + 1 => UserSearchResult.fromJson(result), + 2 => TrackSearchResult.fromJson(result), + 3 => PlaylistSearchResult.fromJson(result), + _ => throw SearchResultException.cannotResolve() + }; + }); + + continuationOffset += collection.length; + } + } + + /// Searches for tracks matching the provided [query]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> getTracks( + String query, { + int offset = defaultOffset, + int limit = defaultLimit + }) => search( + query, + searchFilter: SearchFilter.tracks, + offset: offset, + limit: limit + ).cast>(); + + /// Searches for albums matching the provided [query]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> getAlbums( + String query, { + int offset = defaultOffset, + int limit = defaultLimit + }) => search( + query, + searchFilter: SearchFilter.albums, + offset: offset, + limit: limit + ).cast>(); + + /// Searches for playlists or albums matching the provided [query]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> getPlaylists( + String query, { + int offset = defaultOffset, + int limit = defaultLimit + }) => search( + query, + searchFilter: SearchFilter.playlists, + offset: offset, + limit: limit + ).cast>(); + + /// Searches for only playlists matching the provided [query]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> getPlaylistsWithoutAlbums( + String query, { + int offset = defaultOffset, + int limit = defaultLimit + }) => search( + query, + searchFilter: SearchFilter.playlistsWithoutAlbums, + offset: offset, + limit: limit + ).cast>(); + + /// Searches for users matching the provided [query]. + /// + /// Search results are offset by the specified [offset], with each batch containing + /// [limit] results. + Stream> getUsers( + String query, { + int offset = defaultOffset, + int limit = defaultLimit + }) => search( + query, + searchFilter: SearchFilter.users, + offset: offset, + limit: limit + ).cast>(); +} \ No newline at end of file diff --git a/lib/src/search/search_filter.dart b/lib/src/search/search_filter.dart new file mode 100644 index 0000000..2e200ad --- /dev/null +++ b/lib/src/search/search_filter.dart @@ -0,0 +1,20 @@ +/// Filters the results of a SoundCloud search query. +enum SearchFilter { + /// Search results are unfiltered. + none, + + /// Only tracks are returned in results. + tracks, + + /// Both playlists and albums are returned in results. + playlists, + + /// Only playlists are returned in results. + playlistsWithoutAlbums, + + /// Only albums are returned in results. + albums, + + /// Only users are returned in results. + users +} \ No newline at end of file diff --git a/lib/src/search/search_result.dart b/lib/src/search/search_result.dart new file mode 100644 index 0000000..db2b983 --- /dev/null +++ b/lib/src/search/search_result.dart @@ -0,0 +1,99 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import '../playlists/soundcloud_playlist.dart'; +import '../tracks/soundcloud_track.dart'; +import '../users/mini_user.dart'; +import '../users/soundcloud_user.dart'; + +part 'search_result.freezed.dart'; +part 'search_result.g.dart'; + +Object? _readBannerLink(Map map, String key) => map['visuals']?['visuals']?[0]['link']; +Object? _readBannerUrl(Map map, String key) => map['visuals']?['visuals']?[0]['visual_url']; + +/// Defines a result returned by a search query. +/// +/// May be a [TrackSearchResult], [PlaylistSearchResult], or [UserSearchResult]. +@freezed +abstract class SearchResult with _$SearchResult { + @Implements() + const factory SearchResult.track({ + required Uri? artworkUrl, + required String? caption, + required bool commentable, + @JsonKey(defaultValue: 0) required double commentCount, + required DateTime createdAt, + required String? description, + @JsonKey(defaultValue: 0) required double downloadCount, + required double duration, + required double fullDuration, + required String? genre, + required int id, + required String? labelName, + required DateTime? lastModified, + required String? license, + @JsonKey(defaultValue: 0) required double likesCount, + required Uri permalinkUrl, + @JsonKey(defaultValue: 0) required double playbackCount, + required String? purchaseTitle, + required String? purchaseUrl, + @JsonKey(defaultValue: 0) required double repostsCount, + required String? tagList, + required String title, + required String waveformUrl, + required String monetizationModel, + required String policy, + required MiniUser user + }) = TrackSearchResult; + + @Implements() + const factory SearchResult.playlist({ + required Uri? artworkUrl, + required DateTime createdAt, + required String? description, + required double duration, + required String? genre, + required int id, + required String? labelName, + required DateTime? lastModified, + @JsonKey(defaultValue: 0) required double likesCount, + required Uri permalinkUrl, + @JsonKey(defaultValue: 0) required double repostsCount, + required String? tagList, + required String title, + required bool isAlbum, + required MiniUser user, + @JsonKey(defaultValue: 0) required double trackCount + }) = PlaylistSearchResult; + + @Implements() + const factory SearchResult.user({ + required Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) required Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) required Uri? bannerUrl, + required String? city, + @JsonKey(defaultValue: 0) required double commentsCount, + required String? countryCode, + required DateTime createdAt, + required String? description, + required double followersCount, + required double followingsCount, + required String? firstName, + required String? fullName, + @JsonKey(defaultValue: 0) required double groupsCount, + required int id, + required DateTime? lastModified, + required String? lastName, + @JsonKey(defaultValue: 0) required double likesCount, + @JsonKey(defaultValue: 0) required double playlistLikesCount, + required Uri permalinkUrl, + @JsonKey(defaultValue: 0) required double playlistCount, + @JsonKey(defaultValue: 0) required double repostsCount, + @JsonKey(defaultValue: 0) required double trackCount, + required String username, + @JsonKey(name: 'verified') required bool isVerified + }) = UserSearchResult; + + factory SearchResult.fromJson(Map json) => _$SearchResultFromJson(json); +} \ No newline at end of file diff --git a/lib/src/search/search_result.freezed.dart b/lib/src/search/search_result.freezed.dart new file mode 100644 index 0000000..cbccf88 --- /dev/null +++ b/lib/src/search/search_result.freezed.dart @@ -0,0 +1,2640 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'search_result.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +SearchResult _$SearchResultFromJson(Map json) { + switch (json['runtimeType']) { + case 'track': + return TrackSearchResult.fromJson(json); + case 'playlist': + return PlaylistSearchResult.fromJson(json); + case 'user': + return UserSearchResult.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'SearchResult', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$SearchResult { + DateTime get createdAt => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + DateTime? get lastModified => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get likesCount => throw _privateConstructorUsedError; + Uri get permalinkUrl => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get repostsCount => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user) + track, + required TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount) + playlist, + required TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified) + user, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult? Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult? Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(TrackSearchResult value) track, + required TResult Function(PlaylistSearchResult value) playlist, + required TResult Function(UserSearchResult value) user, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TrackSearchResult value)? track, + TResult? Function(PlaylistSearchResult value)? playlist, + TResult? Function(UserSearchResult value)? user, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TrackSearchResult value)? track, + TResult Function(PlaylistSearchResult value)? playlist, + TResult Function(UserSearchResult value)? user, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SearchResultCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SearchResultCopyWith<$Res> { + factory $SearchResultCopyWith( + SearchResult value, $Res Function(SearchResult) then) = + _$SearchResultCopyWithImpl<$Res, SearchResult>; + @useResult + $Res call( + {DateTime createdAt, + String? description, + int id, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount}); +} + +/// @nodoc +class _$SearchResultCopyWithImpl<$Res, $Val extends SearchResult> + implements $SearchResultCopyWith<$Res> { + _$SearchResultCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? createdAt = null, + Object? description = freezed, + Object? id = null, + Object? lastModified = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? repostsCount = null, + }) { + return _then(_value.copyWith( + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TrackSearchResultImplCopyWith<$Res> + implements $SearchResultCopyWith<$Res> { + factory _$$TrackSearchResultImplCopyWith(_$TrackSearchResultImpl value, + $Res Function(_$TrackSearchResultImpl) then) = + __$$TrackSearchResultImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user}); + + $MiniUserCopyWith<$Res> get user; +} + +/// @nodoc +class __$$TrackSearchResultImplCopyWithImpl<$Res> + extends _$SearchResultCopyWithImpl<$Res, _$TrackSearchResultImpl> + implements _$$TrackSearchResultImplCopyWith<$Res> { + __$$TrackSearchResultImplCopyWithImpl(_$TrackSearchResultImpl _value, + $Res Function(_$TrackSearchResultImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? artworkUrl = freezed, + Object? caption = freezed, + Object? commentable = null, + Object? commentCount = null, + Object? createdAt = null, + Object? description = freezed, + Object? downloadCount = null, + Object? duration = null, + Object? fullDuration = null, + Object? genre = freezed, + Object? id = null, + Object? labelName = freezed, + Object? lastModified = freezed, + Object? license = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? playbackCount = null, + Object? purchaseTitle = freezed, + Object? purchaseUrl = freezed, + Object? repostsCount = null, + Object? tagList = freezed, + Object? title = null, + Object? waveformUrl = null, + Object? monetizationModel = null, + Object? policy = null, + Object? user = null, + }) { + return _then(_$TrackSearchResultImpl( + artworkUrl: freezed == artworkUrl + ? _value.artworkUrl + : artworkUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + caption: freezed == caption + ? _value.caption + : caption // ignore: cast_nullable_to_non_nullable + as String?, + commentable: null == commentable + ? _value.commentable + : commentable // ignore: cast_nullable_to_non_nullable + as bool, + commentCount: null == commentCount + ? _value.commentCount + : commentCount // ignore: cast_nullable_to_non_nullable + as double, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + downloadCount: null == downloadCount + ? _value.downloadCount + : downloadCount // ignore: cast_nullable_to_non_nullable + as double, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as double, + fullDuration: null == fullDuration + ? _value.fullDuration + : fullDuration // ignore: cast_nullable_to_non_nullable + as double, + genre: freezed == genre + ? _value.genre + : genre // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + labelName: freezed == labelName + ? _value.labelName + : labelName // ignore: cast_nullable_to_non_nullable + as String?, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + license: freezed == license + ? _value.license + : license // ignore: cast_nullable_to_non_nullable + as String?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + playbackCount: null == playbackCount + ? _value.playbackCount + : playbackCount // ignore: cast_nullable_to_non_nullable + as double, + purchaseTitle: freezed == purchaseTitle + ? _value.purchaseTitle + : purchaseTitle // ignore: cast_nullable_to_non_nullable + as String?, + purchaseUrl: freezed == purchaseUrl + ? _value.purchaseUrl + : purchaseUrl // ignore: cast_nullable_to_non_nullable + as String?, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + tagList: freezed == tagList + ? _value.tagList + : tagList // ignore: cast_nullable_to_non_nullable + as String?, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + waveformUrl: null == waveformUrl + ? _value.waveformUrl + : waveformUrl // ignore: cast_nullable_to_non_nullable + as String, + monetizationModel: null == monetizationModel + ? _value.monetizationModel + : monetizationModel // ignore: cast_nullable_to_non_nullable + as String, + policy: null == policy + ? _value.policy + : policy // ignore: cast_nullable_to_non_nullable + as String, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as MiniUser, + )); + } + + @override + @pragma('vm:prefer-inline') + $MiniUserCopyWith<$Res> get user { + return $MiniUserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _$TrackSearchResultImpl implements TrackSearchResult { + const _$TrackSearchResultImpl( + {required this.artworkUrl, + required this.caption, + required this.commentable, + @JsonKey(defaultValue: 0) required this.commentCount, + required this.createdAt, + required this.description, + @JsonKey(defaultValue: 0) required this.downloadCount, + required this.duration, + required this.fullDuration, + required this.genre, + required this.id, + required this.labelName, + required this.lastModified, + required this.license, + @JsonKey(defaultValue: 0) required this.likesCount, + required this.permalinkUrl, + @JsonKey(defaultValue: 0) required this.playbackCount, + required this.purchaseTitle, + required this.purchaseUrl, + @JsonKey(defaultValue: 0) required this.repostsCount, + required this.tagList, + required this.title, + required this.waveformUrl, + required this.monetizationModel, + required this.policy, + required this.user, + final String? $type}) + : $type = $type ?? 'track'; + + factory _$TrackSearchResultImpl.fromJson(Map json) => + _$$TrackSearchResultImplFromJson(json); + + @override + final Uri? artworkUrl; + @override + final String? caption; + @override + final bool commentable; + @override + @JsonKey(defaultValue: 0) + final double commentCount; + @override + final DateTime createdAt; + @override + final String? description; + @override + @JsonKey(defaultValue: 0) + final double downloadCount; + @override + final double duration; + @override + final double fullDuration; + @override + final String? genre; + @override + final int id; + @override + final String? labelName; + @override + final DateTime? lastModified; + @override + final String? license; + @override + @JsonKey(defaultValue: 0) + final double likesCount; + @override + final Uri permalinkUrl; + @override + @JsonKey(defaultValue: 0) + final double playbackCount; + @override + final String? purchaseTitle; + @override + final String? purchaseUrl; + @override + @JsonKey(defaultValue: 0) + final double repostsCount; + @override + final String? tagList; + @override + final String title; + @override + final String waveformUrl; + @override + final String monetizationModel; + @override + final String policy; + @override + final MiniUser user; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'SearchResult.track(artworkUrl: $artworkUrl, caption: $caption, commentable: $commentable, commentCount: $commentCount, createdAt: $createdAt, description: $description, downloadCount: $downloadCount, duration: $duration, fullDuration: $fullDuration, genre: $genre, id: $id, labelName: $labelName, lastModified: $lastModified, license: $license, likesCount: $likesCount, permalinkUrl: $permalinkUrl, playbackCount: $playbackCount, purchaseTitle: $purchaseTitle, purchaseUrl: $purchaseUrl, repostsCount: $repostsCount, tagList: $tagList, title: $title, waveformUrl: $waveformUrl, monetizationModel: $monetizationModel, policy: $policy, user: $user)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrackSearchResultImpl && + (identical(other.artworkUrl, artworkUrl) || + other.artworkUrl == artworkUrl) && + (identical(other.caption, caption) || other.caption == caption) && + (identical(other.commentable, commentable) || + other.commentable == commentable) && + (identical(other.commentCount, commentCount) || + other.commentCount == commentCount) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.downloadCount, downloadCount) || + other.downloadCount == downloadCount) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.fullDuration, fullDuration) || + other.fullDuration == fullDuration) && + (identical(other.genre, genre) || other.genre == genre) && + (identical(other.id, id) || other.id == id) && + (identical(other.labelName, labelName) || + other.labelName == labelName) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.license, license) || other.license == license) && + (identical(other.likesCount, likesCount) || + other.likesCount == likesCount) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.playbackCount, playbackCount) || + other.playbackCount == playbackCount) && + (identical(other.purchaseTitle, purchaseTitle) || + other.purchaseTitle == purchaseTitle) && + (identical(other.purchaseUrl, purchaseUrl) || + other.purchaseUrl == purchaseUrl) && + (identical(other.repostsCount, repostsCount) || + other.repostsCount == repostsCount) && + (identical(other.tagList, tagList) || other.tagList == tagList) && + (identical(other.title, title) || other.title == title) && + (identical(other.waveformUrl, waveformUrl) || + other.waveformUrl == waveformUrl) && + (identical(other.monetizationModel, monetizationModel) || + other.monetizationModel == monetizationModel) && + (identical(other.policy, policy) || other.policy == policy) && + (identical(other.user, user) || other.user == user)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hashAll([ + runtimeType, + artworkUrl, + caption, + commentable, + commentCount, + createdAt, + description, + downloadCount, + duration, + fullDuration, + genre, + id, + labelName, + lastModified, + license, + likesCount, + permalinkUrl, + playbackCount, + purchaseTitle, + purchaseUrl, + repostsCount, + tagList, + title, + waveformUrl, + monetizationModel, + policy, + user + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TrackSearchResultImplCopyWith<_$TrackSearchResultImpl> get copyWith => + __$$TrackSearchResultImplCopyWithImpl<_$TrackSearchResultImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user) + track, + required TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount) + playlist, + required TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified) + user, + }) { + return track( + artworkUrl, + caption, + commentable, + commentCount, + createdAt, + description, + downloadCount, + duration, + fullDuration, + genre, + id, + labelName, + lastModified, + license, + likesCount, + permalinkUrl, + playbackCount, + purchaseTitle, + purchaseUrl, + repostsCount, + tagList, + title, + waveformUrl, + monetizationModel, + policy, + this.user); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult? Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult? Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + }) { + return track?.call( + artworkUrl, + caption, + commentable, + commentCount, + createdAt, + description, + downloadCount, + duration, + fullDuration, + genre, + id, + labelName, + lastModified, + license, + likesCount, + permalinkUrl, + playbackCount, + purchaseTitle, + purchaseUrl, + repostsCount, + tagList, + title, + waveformUrl, + monetizationModel, + policy, + this.user); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + required TResult orElse(), + }) { + if (track != null) { + return track( + artworkUrl, + caption, + commentable, + commentCount, + createdAt, + description, + downloadCount, + duration, + fullDuration, + genre, + id, + labelName, + lastModified, + license, + likesCount, + permalinkUrl, + playbackCount, + purchaseTitle, + purchaseUrl, + repostsCount, + tagList, + title, + waveformUrl, + monetizationModel, + policy, + this.user); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TrackSearchResult value) track, + required TResult Function(PlaylistSearchResult value) playlist, + required TResult Function(UserSearchResult value) user, + }) { + return track(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TrackSearchResult value)? track, + TResult? Function(PlaylistSearchResult value)? playlist, + TResult? Function(UserSearchResult value)? user, + }) { + return track?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TrackSearchResult value)? track, + TResult Function(PlaylistSearchResult value)? playlist, + TResult Function(UserSearchResult value)? user, + required TResult orElse(), + }) { + if (track != null) { + return track(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TrackSearchResultImplToJson( + this, + ); + } +} + +abstract class TrackSearchResult implements SearchResult, SoundcloudTrack { + const factory TrackSearchResult( + {required final Uri? artworkUrl, + required final String? caption, + required final bool commentable, + @JsonKey(defaultValue: 0) required final double commentCount, + required final DateTime createdAt, + required final String? description, + @JsonKey(defaultValue: 0) required final double downloadCount, + required final double duration, + required final double fullDuration, + required final String? genre, + required final int id, + required final String? labelName, + required final DateTime? lastModified, + required final String? license, + @JsonKey(defaultValue: 0) required final double likesCount, + required final Uri permalinkUrl, + @JsonKey(defaultValue: 0) required final double playbackCount, + required final String? purchaseTitle, + required final String? purchaseUrl, + @JsonKey(defaultValue: 0) required final double repostsCount, + required final String? tagList, + required final String title, + required final String waveformUrl, + required final String monetizationModel, + required final String policy, + required final MiniUser user}) = _$TrackSearchResultImpl; + + factory TrackSearchResult.fromJson(Map json) = + _$TrackSearchResultImpl.fromJson; + + Uri? get artworkUrl; + String? get caption; + bool get commentable; + @JsonKey(defaultValue: 0) + double get commentCount; + @override + DateTime get createdAt; + @override + String? get description; + @JsonKey(defaultValue: 0) + double get downloadCount; + double get duration; + double get fullDuration; + String? get genre; + @override + int get id; + String? get labelName; + @override + DateTime? get lastModified; + String? get license; + @override + @JsonKey(defaultValue: 0) + double get likesCount; + @override + Uri get permalinkUrl; + @JsonKey(defaultValue: 0) + double get playbackCount; + String? get purchaseTitle; + String? get purchaseUrl; + @override + @JsonKey(defaultValue: 0) + double get repostsCount; + String? get tagList; + String get title; + String get waveformUrl; + String get monetizationModel; + String get policy; + MiniUser get user; + @override + @JsonKey(ignore: true) + _$$TrackSearchResultImplCopyWith<_$TrackSearchResultImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PlaylistSearchResultImplCopyWith<$Res> + implements $SearchResultCopyWith<$Res> { + factory _$$PlaylistSearchResultImplCopyWith(_$PlaylistSearchResultImpl value, + $Res Function(_$PlaylistSearchResultImpl) then) = + __$$PlaylistSearchResultImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount}); + + $MiniUserCopyWith<$Res> get user; +} + +/// @nodoc +class __$$PlaylistSearchResultImplCopyWithImpl<$Res> + extends _$SearchResultCopyWithImpl<$Res, _$PlaylistSearchResultImpl> + implements _$$PlaylistSearchResultImplCopyWith<$Res> { + __$$PlaylistSearchResultImplCopyWithImpl(_$PlaylistSearchResultImpl _value, + $Res Function(_$PlaylistSearchResultImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? artworkUrl = freezed, + Object? createdAt = null, + Object? description = freezed, + Object? duration = null, + Object? genre = freezed, + Object? id = null, + Object? labelName = freezed, + Object? lastModified = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? repostsCount = null, + Object? tagList = freezed, + Object? title = null, + Object? isAlbum = null, + Object? user = null, + Object? trackCount = null, + }) { + return _then(_$PlaylistSearchResultImpl( + artworkUrl: freezed == artworkUrl + ? _value.artworkUrl + : artworkUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as double, + genre: freezed == genre + ? _value.genre + : genre // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + labelName: freezed == labelName + ? _value.labelName + : labelName // ignore: cast_nullable_to_non_nullable + as String?, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + tagList: freezed == tagList + ? _value.tagList + : tagList // ignore: cast_nullable_to_non_nullable + as String?, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + isAlbum: null == isAlbum + ? _value.isAlbum + : isAlbum // ignore: cast_nullable_to_non_nullable + as bool, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as MiniUser, + trackCount: null == trackCount + ? _value.trackCount + : trackCount // ignore: cast_nullable_to_non_nullable + as double, + )); + } + + @override + @pragma('vm:prefer-inline') + $MiniUserCopyWith<$Res> get user { + return $MiniUserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaylistSearchResultImpl implements PlaylistSearchResult { + const _$PlaylistSearchResultImpl( + {required this.artworkUrl, + required this.createdAt, + required this.description, + required this.duration, + required this.genre, + required this.id, + required this.labelName, + required this.lastModified, + @JsonKey(defaultValue: 0) required this.likesCount, + required this.permalinkUrl, + @JsonKey(defaultValue: 0) required this.repostsCount, + required this.tagList, + required this.title, + required this.isAlbum, + required this.user, + @JsonKey(defaultValue: 0) required this.trackCount, + final String? $type}) + : $type = $type ?? 'playlist'; + + factory _$PlaylistSearchResultImpl.fromJson(Map json) => + _$$PlaylistSearchResultImplFromJson(json); + + @override + final Uri? artworkUrl; + @override + final DateTime createdAt; + @override + final String? description; + @override + final double duration; + @override + final String? genre; + @override + final int id; + @override + final String? labelName; + @override + final DateTime? lastModified; + @override + @JsonKey(defaultValue: 0) + final double likesCount; + @override + final Uri permalinkUrl; + @override + @JsonKey(defaultValue: 0) + final double repostsCount; + @override + final String? tagList; + @override + final String title; + @override + final bool isAlbum; + @override + final MiniUser user; + @override + @JsonKey(defaultValue: 0) + final double trackCount; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'SearchResult.playlist(artworkUrl: $artworkUrl, createdAt: $createdAt, description: $description, duration: $duration, genre: $genre, id: $id, labelName: $labelName, lastModified: $lastModified, likesCount: $likesCount, permalinkUrl: $permalinkUrl, repostsCount: $repostsCount, tagList: $tagList, title: $title, isAlbum: $isAlbum, user: $user, trackCount: $trackCount)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaylistSearchResultImpl && + (identical(other.artworkUrl, artworkUrl) || + other.artworkUrl == artworkUrl) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.genre, genre) || other.genre == genre) && + (identical(other.id, id) || other.id == id) && + (identical(other.labelName, labelName) || + other.labelName == labelName) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.likesCount, likesCount) || + other.likesCount == likesCount) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.repostsCount, repostsCount) || + other.repostsCount == repostsCount) && + (identical(other.tagList, tagList) || other.tagList == tagList) && + (identical(other.title, title) || other.title == title) && + (identical(other.isAlbum, isAlbum) || other.isAlbum == isAlbum) && + (identical(other.user, user) || other.user == user) && + (identical(other.trackCount, trackCount) || + other.trackCount == trackCount)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + artworkUrl, + createdAt, + description, + duration, + genre, + id, + labelName, + lastModified, + likesCount, + permalinkUrl, + repostsCount, + tagList, + title, + isAlbum, + user, + trackCount); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaylistSearchResultImplCopyWith<_$PlaylistSearchResultImpl> + get copyWith => + __$$PlaylistSearchResultImplCopyWithImpl<_$PlaylistSearchResultImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user) + track, + required TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount) + playlist, + required TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified) + user, + }) { + return playlist( + artworkUrl, + createdAt, + description, + duration, + genre, + id, + labelName, + lastModified, + likesCount, + permalinkUrl, + repostsCount, + tagList, + title, + isAlbum, + this.user, + trackCount); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult? Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult? Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + }) { + return playlist?.call( + artworkUrl, + createdAt, + description, + duration, + genre, + id, + labelName, + lastModified, + likesCount, + permalinkUrl, + repostsCount, + tagList, + title, + isAlbum, + this.user, + trackCount); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + required TResult orElse(), + }) { + if (playlist != null) { + return playlist( + artworkUrl, + createdAt, + description, + duration, + genre, + id, + labelName, + lastModified, + likesCount, + permalinkUrl, + repostsCount, + tagList, + title, + isAlbum, + this.user, + trackCount); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TrackSearchResult value) track, + required TResult Function(PlaylistSearchResult value) playlist, + required TResult Function(UserSearchResult value) user, + }) { + return playlist(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TrackSearchResult value)? track, + TResult? Function(PlaylistSearchResult value)? playlist, + TResult? Function(UserSearchResult value)? user, + }) { + return playlist?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TrackSearchResult value)? track, + TResult Function(PlaylistSearchResult value)? playlist, + TResult Function(UserSearchResult value)? user, + required TResult orElse(), + }) { + if (playlist != null) { + return playlist(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$PlaylistSearchResultImplToJson( + this, + ); + } +} + +abstract class PlaylistSearchResult + implements SearchResult, SoundcloudPlaylist { + const factory PlaylistSearchResult( + {required final Uri? artworkUrl, + required final DateTime createdAt, + required final String? description, + required final double duration, + required final String? genre, + required final int id, + required final String? labelName, + required final DateTime? lastModified, + @JsonKey(defaultValue: 0) required final double likesCount, + required final Uri permalinkUrl, + @JsonKey(defaultValue: 0) required final double repostsCount, + required final String? tagList, + required final String title, + required final bool isAlbum, + required final MiniUser user, + @JsonKey(defaultValue: 0) required final double trackCount}) = + _$PlaylistSearchResultImpl; + + factory PlaylistSearchResult.fromJson(Map json) = + _$PlaylistSearchResultImpl.fromJson; + + Uri? get artworkUrl; + @override + DateTime get createdAt; + @override + String? get description; + double get duration; + String? get genre; + @override + int get id; + String? get labelName; + @override + DateTime? get lastModified; + @override + @JsonKey(defaultValue: 0) + double get likesCount; + @override + Uri get permalinkUrl; + @override + @JsonKey(defaultValue: 0) + double get repostsCount; + String? get tagList; + String get title; + bool get isAlbum; + MiniUser get user; + @JsonKey(defaultValue: 0) + double get trackCount; + @override + @JsonKey(ignore: true) + _$$PlaylistSearchResultImplCopyWith<_$PlaylistSearchResultImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UserSearchResultImplCopyWith<$Res> + implements $SearchResultCopyWith<$Res> { + factory _$$UserSearchResultImplCopyWith(_$UserSearchResultImpl value, + $Res Function(_$UserSearchResultImpl) then) = + __$$UserSearchResultImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified}); +} + +/// @nodoc +class __$$UserSearchResultImplCopyWithImpl<$Res> + extends _$SearchResultCopyWithImpl<$Res, _$UserSearchResultImpl> + implements _$$UserSearchResultImplCopyWith<$Res> { + __$$UserSearchResultImplCopyWithImpl(_$UserSearchResultImpl _value, + $Res Function(_$UserSearchResultImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? avatarUrl = freezed, + Object? bannerLink = freezed, + Object? bannerUrl = freezed, + Object? city = freezed, + Object? commentsCount = null, + Object? countryCode = freezed, + Object? createdAt = null, + Object? description = freezed, + Object? followersCount = null, + Object? followingsCount = null, + Object? firstName = freezed, + Object? fullName = freezed, + Object? groupsCount = null, + Object? id = null, + Object? lastModified = freezed, + Object? lastName = freezed, + Object? likesCount = null, + Object? playlistLikesCount = null, + Object? permalinkUrl = null, + Object? playlistCount = null, + Object? repostsCount = null, + Object? trackCount = null, + Object? username = null, + Object? isVerified = null, + }) { + return _then(_$UserSearchResultImpl( + avatarUrl: freezed == avatarUrl + ? _value.avatarUrl + : avatarUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + bannerLink: freezed == bannerLink + ? _value.bannerLink + : bannerLink // ignore: cast_nullable_to_non_nullable + as Uri?, + bannerUrl: freezed == bannerUrl + ? _value.bannerUrl + : bannerUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + city: freezed == city + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + commentsCount: null == commentsCount + ? _value.commentsCount + : commentsCount // ignore: cast_nullable_to_non_nullable + as double, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + followersCount: null == followersCount + ? _value.followersCount + : followersCount // ignore: cast_nullable_to_non_nullable + as double, + followingsCount: null == followingsCount + ? _value.followingsCount + : followingsCount // ignore: cast_nullable_to_non_nullable + as double, + firstName: freezed == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String?, + fullName: freezed == fullName + ? _value.fullName + : fullName // ignore: cast_nullable_to_non_nullable + as String?, + groupsCount: null == groupsCount + ? _value.groupsCount + : groupsCount // ignore: cast_nullable_to_non_nullable + as double, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastName: freezed == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + playlistLikesCount: null == playlistLikesCount + ? _value.playlistLikesCount + : playlistLikesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + playlistCount: null == playlistCount + ? _value.playlistCount + : playlistCount // ignore: cast_nullable_to_non_nullable + as double, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + trackCount: null == trackCount + ? _value.trackCount + : trackCount // ignore: cast_nullable_to_non_nullable + as double, + username: null == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String, + isVerified: null == isVerified + ? _value.isVerified + : isVerified // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserSearchResultImpl implements UserSearchResult { + const _$UserSearchResultImpl( + {required this.avatarUrl, + @JsonKey(readValue: _readBannerLink) required this.bannerLink, + @JsonKey(readValue: _readBannerUrl) required this.bannerUrl, + required this.city, + @JsonKey(defaultValue: 0) required this.commentsCount, + required this.countryCode, + required this.createdAt, + required this.description, + required this.followersCount, + required this.followingsCount, + required this.firstName, + required this.fullName, + @JsonKey(defaultValue: 0) required this.groupsCount, + required this.id, + required this.lastModified, + required this.lastName, + @JsonKey(defaultValue: 0) required this.likesCount, + @JsonKey(defaultValue: 0) required this.playlistLikesCount, + required this.permalinkUrl, + @JsonKey(defaultValue: 0) required this.playlistCount, + @JsonKey(defaultValue: 0) required this.repostsCount, + @JsonKey(defaultValue: 0) required this.trackCount, + required this.username, + @JsonKey(name: 'verified') required this.isVerified, + final String? $type}) + : $type = $type ?? 'user'; + + factory _$UserSearchResultImpl.fromJson(Map json) => + _$$UserSearchResultImplFromJson(json); + + @override + final Uri? avatarUrl; + @override + @JsonKey(readValue: _readBannerLink) + final Uri? bannerLink; + @override + @JsonKey(readValue: _readBannerUrl) + final Uri? bannerUrl; + @override + final String? city; + @override + @JsonKey(defaultValue: 0) + final double commentsCount; + @override + final String? countryCode; + @override + final DateTime createdAt; + @override + final String? description; + @override + final double followersCount; + @override + final double followingsCount; + @override + final String? firstName; + @override + final String? fullName; + @override + @JsonKey(defaultValue: 0) + final double groupsCount; + @override + final int id; + @override + final DateTime? lastModified; + @override + final String? lastName; + @override + @JsonKey(defaultValue: 0) + final double likesCount; + @override + @JsonKey(defaultValue: 0) + final double playlistLikesCount; + @override + final Uri permalinkUrl; + @override + @JsonKey(defaultValue: 0) + final double playlistCount; + @override + @JsonKey(defaultValue: 0) + final double repostsCount; + @override + @JsonKey(defaultValue: 0) + final double trackCount; + @override + final String username; + @override + @JsonKey(name: 'verified') + final bool isVerified; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'SearchResult.user(avatarUrl: $avatarUrl, bannerLink: $bannerLink, bannerUrl: $bannerUrl, city: $city, commentsCount: $commentsCount, countryCode: $countryCode, createdAt: $createdAt, description: $description, followersCount: $followersCount, followingsCount: $followingsCount, firstName: $firstName, fullName: $fullName, groupsCount: $groupsCount, id: $id, lastModified: $lastModified, lastName: $lastName, likesCount: $likesCount, playlistLikesCount: $playlistLikesCount, permalinkUrl: $permalinkUrl, playlistCount: $playlistCount, repostsCount: $repostsCount, trackCount: $trackCount, username: $username, isVerified: $isVerified)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserSearchResultImpl && + (identical(other.avatarUrl, avatarUrl) || + other.avatarUrl == avatarUrl) && + (identical(other.bannerLink, bannerLink) || + other.bannerLink == bannerLink) && + (identical(other.bannerUrl, bannerUrl) || + other.bannerUrl == bannerUrl) && + (identical(other.city, city) || other.city == city) && + (identical(other.commentsCount, commentsCount) || + other.commentsCount == commentsCount) && + (identical(other.countryCode, countryCode) || + other.countryCode == countryCode) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.followersCount, followersCount) || + other.followersCount == followersCount) && + (identical(other.followingsCount, followingsCount) || + other.followingsCount == followingsCount) && + (identical(other.firstName, firstName) || + other.firstName == firstName) && + (identical(other.fullName, fullName) || + other.fullName == fullName) && + (identical(other.groupsCount, groupsCount) || + other.groupsCount == groupsCount) && + (identical(other.id, id) || other.id == id) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.lastName, lastName) || + other.lastName == lastName) && + (identical(other.likesCount, likesCount) || + other.likesCount == likesCount) && + (identical(other.playlistLikesCount, playlistLikesCount) || + other.playlistLikesCount == playlistLikesCount) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.playlistCount, playlistCount) || + other.playlistCount == playlistCount) && + (identical(other.repostsCount, repostsCount) || + other.repostsCount == repostsCount) && + (identical(other.trackCount, trackCount) || + other.trackCount == trackCount) && + (identical(other.username, username) || + other.username == username) && + (identical(other.isVerified, isVerified) || + other.isVerified == isVerified)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hashAll([ + runtimeType, + avatarUrl, + bannerLink, + bannerUrl, + city, + commentsCount, + countryCode, + createdAt, + description, + followersCount, + followingsCount, + firstName, + fullName, + groupsCount, + id, + lastModified, + lastName, + likesCount, + playlistLikesCount, + permalinkUrl, + playlistCount, + repostsCount, + trackCount, + username, + isVerified + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$UserSearchResultImplCopyWith<_$UserSearchResultImpl> get copyWith => + __$$UserSearchResultImplCopyWithImpl<_$UserSearchResultImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user) + track, + required TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount) + playlist, + required TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified) + user, + }) { + return user( + avatarUrl, + bannerLink, + bannerUrl, + city, + commentsCount, + countryCode, + createdAt, + description, + followersCount, + followingsCount, + firstName, + fullName, + groupsCount, + id, + lastModified, + lastName, + likesCount, + playlistLikesCount, + permalinkUrl, + playlistCount, + repostsCount, + trackCount, + username, + isVerified); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult? Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult? Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + }) { + return user?.call( + avatarUrl, + bannerLink, + bannerUrl, + city, + commentsCount, + countryCode, + createdAt, + description, + followersCount, + followingsCount, + firstName, + fullName, + groupsCount, + id, + lastModified, + lastName, + likesCount, + playlistLikesCount, + permalinkUrl, + playlistCount, + repostsCount, + trackCount, + username, + isVerified); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user)? + track, + TResult Function( + Uri? artworkUrl, + DateTime createdAt, + String? description, + double duration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + bool isAlbum, + MiniUser user, + @JsonKey(defaultValue: 0) double trackCount)? + playlist, + TResult Function( + Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + double followersCount, + double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified)? + user, + required TResult orElse(), + }) { + if (user != null) { + return user( + avatarUrl, + bannerLink, + bannerUrl, + city, + commentsCount, + countryCode, + createdAt, + description, + followersCount, + followingsCount, + firstName, + fullName, + groupsCount, + id, + lastModified, + lastName, + likesCount, + playlistLikesCount, + permalinkUrl, + playlistCount, + repostsCount, + trackCount, + username, + isVerified); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TrackSearchResult value) track, + required TResult Function(PlaylistSearchResult value) playlist, + required TResult Function(UserSearchResult value) user, + }) { + return user(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TrackSearchResult value)? track, + TResult? Function(PlaylistSearchResult value)? playlist, + TResult? Function(UserSearchResult value)? user, + }) { + return user?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TrackSearchResult value)? track, + TResult Function(PlaylistSearchResult value)? playlist, + TResult Function(UserSearchResult value)? user, + required TResult orElse(), + }) { + if (user != null) { + return user(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$UserSearchResultImplToJson( + this, + ); + } +} + +abstract class UserSearchResult implements SearchResult, SoundcloudUser { + const factory UserSearchResult( + {required final Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) required final Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) required final Uri? bannerUrl, + required final String? city, + @JsonKey(defaultValue: 0) required final double commentsCount, + required final String? countryCode, + required final DateTime createdAt, + required final String? description, + required final double followersCount, + required final double followingsCount, + required final String? firstName, + required final String? fullName, + @JsonKey(defaultValue: 0) required final double groupsCount, + required final int id, + required final DateTime? lastModified, + required final String? lastName, + @JsonKey(defaultValue: 0) required final double likesCount, + @JsonKey(defaultValue: 0) required final double playlistLikesCount, + required final Uri permalinkUrl, + @JsonKey(defaultValue: 0) required final double playlistCount, + @JsonKey(defaultValue: 0) required final double repostsCount, + @JsonKey(defaultValue: 0) required final double trackCount, + required final String username, + @JsonKey(name: 'verified') required final bool isVerified}) = + _$UserSearchResultImpl; + + factory UserSearchResult.fromJson(Map json) = + _$UserSearchResultImpl.fromJson; + + Uri? get avatarUrl; + @JsonKey(readValue: _readBannerLink) + Uri? get bannerLink; + @JsonKey(readValue: _readBannerUrl) + Uri? get bannerUrl; + String? get city; + @JsonKey(defaultValue: 0) + double get commentsCount; + String? get countryCode; + @override + DateTime get createdAt; + @override + String? get description; + double get followersCount; + double get followingsCount; + String? get firstName; + String? get fullName; + @JsonKey(defaultValue: 0) + double get groupsCount; + @override + int get id; + @override + DateTime? get lastModified; + String? get lastName; + @override + @JsonKey(defaultValue: 0) + double get likesCount; + @JsonKey(defaultValue: 0) + double get playlistLikesCount; + @override + Uri get permalinkUrl; + @JsonKey(defaultValue: 0) + double get playlistCount; + @override + @JsonKey(defaultValue: 0) + double get repostsCount; + @JsonKey(defaultValue: 0) + double get trackCount; + String get username; + @JsonKey(name: 'verified') + bool get isVerified; + @override + @JsonKey(ignore: true) + _$$UserSearchResultImplCopyWith<_$UserSearchResultImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/search/search_result.g.dart b/lib/src/search/search_result.g.dart new file mode 100644 index 0000000..427f84d --- /dev/null +++ b/lib/src/search/search_result.g.dart @@ -0,0 +1,192 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_result.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TrackSearchResultImpl _$$TrackSearchResultImplFromJson( + Map json) => + _$TrackSearchResultImpl( + artworkUrl: json['artwork_url'] == null + ? null + : Uri.parse(json['artwork_url'] as String), + caption: json['caption'] as String?, + commentable: json['commentable'] as bool, + commentCount: (json['comment_count'] as num?)?.toDouble() ?? 0, + createdAt: DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + downloadCount: (json['download_count'] as num?)?.toDouble() ?? 0, + duration: (json['duration'] as num).toDouble(), + fullDuration: (json['full_duration'] as num).toDouble(), + genre: json['genre'] as String?, + id: json['id'] as int, + labelName: json['label_name'] as String?, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + license: json['license'] as String?, + likesCount: (json['likes_count'] as num?)?.toDouble() ?? 0, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + playbackCount: (json['playback_count'] as num?)?.toDouble() ?? 0, + purchaseTitle: json['purchase_title'] as String?, + purchaseUrl: json['purchase_url'] as String?, + repostsCount: (json['reposts_count'] as num?)?.toDouble() ?? 0, + tagList: json['tag_list'] as String?, + title: json['title'] as String, + waveformUrl: json['waveform_url'] as String, + monetizationModel: json['monetization_model'] as String, + policy: json['policy'] as String, + user: MiniUser.fromJson(json['user'] as Map), + $type: json['runtimeType'] as String?, + ); + +Map _$$TrackSearchResultImplToJson( + _$TrackSearchResultImpl instance) => + { + 'artwork_url': instance.artworkUrl?.toString(), + 'caption': instance.caption, + 'commentable': instance.commentable, + 'comment_count': instance.commentCount, + 'created_at': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'download_count': instance.downloadCount, + 'duration': instance.duration, + 'full_duration': instance.fullDuration, + 'genre': instance.genre, + 'id': instance.id, + 'label_name': instance.labelName, + 'last_modified': instance.lastModified?.toIso8601String(), + 'license': instance.license, + 'likes_count': instance.likesCount, + 'permalink_url': instance.permalinkUrl.toString(), + 'playback_count': instance.playbackCount, + 'purchase_title': instance.purchaseTitle, + 'purchase_url': instance.purchaseUrl, + 'reposts_count': instance.repostsCount, + 'tag_list': instance.tagList, + 'title': instance.title, + 'waveform_url': instance.waveformUrl, + 'monetization_model': instance.monetizationModel, + 'policy': instance.policy, + 'user': instance.user, + 'runtimeType': instance.$type, + }; + +_$PlaylistSearchResultImpl _$$PlaylistSearchResultImplFromJson( + Map json) => + _$PlaylistSearchResultImpl( + artworkUrl: json['artwork_url'] == null + ? null + : Uri.parse(json['artwork_url'] as String), + createdAt: DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + duration: (json['duration'] as num).toDouble(), + genre: json['genre'] as String?, + id: json['id'] as int, + labelName: json['label_name'] as String?, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + likesCount: (json['likes_count'] as num?)?.toDouble() ?? 0, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + repostsCount: (json['reposts_count'] as num?)?.toDouble() ?? 0, + tagList: json['tag_list'] as String?, + title: json['title'] as String, + isAlbum: json['is_album'] as bool, + user: MiniUser.fromJson(json['user'] as Map), + trackCount: (json['track_count'] as num?)?.toDouble() ?? 0, + $type: json['runtimeType'] as String?, + ); + +Map _$$PlaylistSearchResultImplToJson( + _$PlaylistSearchResultImpl instance) => + { + 'artwork_url': instance.artworkUrl?.toString(), + 'created_at': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'duration': instance.duration, + 'genre': instance.genre, + 'id': instance.id, + 'label_name': instance.labelName, + 'last_modified': instance.lastModified?.toIso8601String(), + 'likes_count': instance.likesCount, + 'permalink_url': instance.permalinkUrl.toString(), + 'reposts_count': instance.repostsCount, + 'tag_list': instance.tagList, + 'title': instance.title, + 'is_album': instance.isAlbum, + 'user': instance.user, + 'track_count': instance.trackCount, + 'runtimeType': instance.$type, + }; + +_$UserSearchResultImpl _$$UserSearchResultImplFromJson( + Map json) => + _$UserSearchResultImpl( + avatarUrl: json['avatar_url'] == null + ? null + : Uri.parse(json['avatar_url'] as String), + bannerLink: _readBannerLink(json, 'banner_link') == null + ? null + : Uri.parse(_readBannerLink(json, 'banner_link') as String), + bannerUrl: _readBannerUrl(json, 'banner_url') == null + ? null + : Uri.parse(_readBannerUrl(json, 'banner_url') as String), + city: json['city'] as String?, + commentsCount: (json['comments_count'] as num?)?.toDouble() ?? 0, + countryCode: json['country_code'] as String?, + createdAt: DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + followersCount: (json['followers_count'] as num).toDouble(), + followingsCount: (json['followings_count'] as num).toDouble(), + firstName: json['first_name'] as String?, + fullName: json['full_name'] as String?, + groupsCount: (json['groups_count'] as num?)?.toDouble() ?? 0, + id: json['id'] as int, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + lastName: json['last_name'] as String?, + likesCount: (json['likes_count'] as num?)?.toDouble() ?? 0, + playlistLikesCount: + (json['playlist_likes_count'] as num?)?.toDouble() ?? 0, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + playlistCount: (json['playlist_count'] as num?)?.toDouble() ?? 0, + repostsCount: (json['reposts_count'] as num?)?.toDouble() ?? 0, + trackCount: (json['track_count'] as num?)?.toDouble() ?? 0, + username: json['username'] as String, + isVerified: json['verified'] as bool, + $type: json['runtimeType'] as String?, + ); + +Map _$$UserSearchResultImplToJson( + _$UserSearchResultImpl instance) => + { + 'avatar_url': instance.avatarUrl?.toString(), + 'banner_link': instance.bannerLink?.toString(), + 'banner_url': instance.bannerUrl?.toString(), + 'city': instance.city, + 'comments_count': instance.commentsCount, + 'country_code': instance.countryCode, + 'created_at': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'followers_count': instance.followersCount, + 'followings_count': instance.followingsCount, + 'first_name': instance.firstName, + 'full_name': instance.fullName, + 'groups_count': instance.groupsCount, + 'id': instance.id, + 'last_modified': instance.lastModified?.toIso8601String(), + 'last_name': instance.lastName, + 'likes_count': instance.likesCount, + 'playlist_likes_count': instance.playlistLikesCount, + 'permalink_url': instance.permalinkUrl.toString(), + 'playlist_count': instance.playlistCount, + 'reposts_count': instance.repostsCount, + 'track_count': instance.trackCount, + 'username': instance.username, + 'verified': instance.isVerified, + 'runtimeType': instance.$type, + }; diff --git a/lib/src/soundcloud_client.dart b/lib/src/soundcloud_client.dart new file mode 100644 index 0000000..c9c4393 --- /dev/null +++ b/lib/src/soundcloud_client.dart @@ -0,0 +1,37 @@ +import 'package:http/http.dart'; +import 'bridge/soundcloud_controller.dart'; +import 'playlists/playlist_client.dart'; +import 'search/search_client.dart'; +import 'tracks/track_client.dart'; +import 'users/user_client.dart'; + +/// Interacts with SoundCloud's internal API to satisfy user queries. +class SoundcloudClient { + final Client _http; + late final SearchClient _search; + late final TrackClient _tracks; + late final PlaylistClient _playlists; + late final UserClient _users; + + /// Search for users, tracks, playlists, or albums. + SearchClient get search => _search; + + /// Resolve tracks and their streams, and download them. + TrackClient get tracks => _tracks; + + /// Resolve playlists and the tracks they contain. + PlaylistClient get playlists => _playlists; + + /// Resolve users and the tracks, playlists, or albums they have published. + UserClient get users => _users; + + /// Creates a new [SoundcloudClient] with an optional [httpClient]. + SoundcloudClient({Client? httpClient}) + : _http = httpClient ?? Client() { + final controller = SoundcloudController(_http); + _search = SearchClient(_http, controller); + _tracks = TrackClient(_http, controller); + _playlists = PlaylistClient(_http, controller); + _users = UserClient(_http, controller); + } +} \ No newline at end of file diff --git a/lib/src/tracks/soundcloud_track.dart b/lib/src/tracks/soundcloud_track.dart new file mode 100644 index 0000000..88bf4c8 --- /dev/null +++ b/lib/src/tracks/soundcloud_track.dart @@ -0,0 +1,104 @@ +import '../users/mini_user.dart'; + +/// Metadata about a SoundCloud track. +/// +/// This class cannot be instantiated or extended. +abstract interface class SoundcloudTrack { + /// The URL of this track's thumbnail. + Uri? get artworkUrl; + + /// The caption displayed under this track. + String? get caption; + + /// Whether or not this track has comments enabled. + bool get commentable; + + /// The number of comments on this track. + double get commentCount; + + /// The date and time this track was first uploaded to SoundCloud. + DateTime get createdAt; + + /// A short description of this track. + String? get description; + + /// The number of times this track has been downloaded through SoundCloud. + double get downloadCount; + + /// The duration of this track that is playable. + /// + /// Some tracks can only be accessed by SoundCloud Go subscribers, + /// but allow a short snippet of the track to be played by non-subscribers. + /// + /// If this is a SoundCloud Go track, this is the duration of the snippet. + /// If not, this is equivalent to [fullDuration]. + double get duration; + + /// The full duration of this track. + /// + /// As some tracks can only be accessed by SoundCloud Go subscribers, this + /// does not necessarily represent the length of playable audio. For that, + /// see [duration]. + double get fullDuration; + + /// The genre of music this track belongs to. + String? get genre; + + /// A unique numeric identifer for this track. + int get id; + + /// The record label associated with this track. + String? get labelName; + + /// The date and time modifications were last made to this track. + DateTime? get lastModified; + + /// The license attributed to this track. + String? get license; + + /// The number of likes this track has received. + double get likesCount; + + /// The URL of this track. + /// + /// E.g., https://www.soundcloud.com/a-user/a-track + Uri get permalinkUrl; + + /// The number of times this track has been played. + double get playbackCount; + + /// The title of the website that this track can be purchased from. + String? get purchaseTitle; + + /// The URL of the website that this track can be purchased from. + String? get purchaseUrl; + + /// The number of times this track has been reposted. + double get repostsCount; + + /// A comma-separated list of tags applied to this track. + String? get tagList; + + /// The title of this track. + String get title; + + /// The URL to the manifest that can be used to build this track's waveform. + String get waveformUrl; + + /// The monetization model for this track. + /// + /// I.e., whether the track is supported by advertisements or requires a + /// SoundCloud Go subscription, etc. + String get monetizationModel; + + /// The streaming policy for this track. + /// + /// I.e., whether this track should be monetized by way of advertisements, or + /// snipped so that only SoundCloud Go subscribers can access it. + String get policy; + + /// The user that uploaded this track. + /// + /// This does not represent a complete user object and some properties may be null. + MiniUser get user; +} \ No newline at end of file diff --git a/lib/src/tracks/streams/container.dart b/lib/src/tracks/streams/container.dart new file mode 100644 index 0000000..fc5c9d0 --- /dev/null +++ b/lib/src/tracks/streams/container.dart @@ -0,0 +1,8 @@ +/// The audio file container of a specific stream. +enum Container { + /// MPEG-2 Audio Layer III (.mp3) container. + mp3, + + /// Ogg (.ogg) container. + ogg; +} \ No newline at end of file diff --git a/lib/src/tracks/streams/protocol.dart b/lib/src/tracks/streams/protocol.dart new file mode 100644 index 0000000..27202c6 --- /dev/null +++ b/lib/src/tracks/streams/protocol.dart @@ -0,0 +1,8 @@ +/// The protocol used by a specific stream. +enum Protocol { + /// The HTTP Live Streaming protocol denotes that audio is streamed from an HLS manifest. + hls, + + // A progressive protocol denotes that audio is streamed from a single file. + progressive; +} \ No newline at end of file diff --git a/lib/src/tracks/streams/quality.dart b/lib/src/tracks/streams/quality.dart new file mode 100644 index 0000000..2d0bf17 --- /dev/null +++ b/lib/src/tracks/streams/quality.dart @@ -0,0 +1,10 @@ +/// The audio quality of a specific stream. +enum Quality { + /// Standard audio quality. + standardQuality, + + /// High audio quality. + /// + /// Typically reserved for SoundCloud Go subscribers. + highQuality; +} \ No newline at end of file diff --git a/lib/src/tracks/streams/stream_info.dart b/lib/src/tracks/streams/stream_info.dart new file mode 100644 index 0000000..bf4506d --- /dev/null +++ b/lib/src/tracks/streams/stream_info.dart @@ -0,0 +1,20 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'container.dart'; +import 'protocol.dart'; +import 'quality.dart'; + +part 'stream_info.freezed.dart'; + +/// Metadata about a specific track stream. +@freezed +class StreamInfo with _$StreamInfo { + const factory StreamInfo({ + required String url, + required bool isSnipped, + required Container container, + required Protocol protocol, + required Quality quality + }) = _StreamInfo; +} \ No newline at end of file diff --git a/lib/src/tracks/streams/stream_info.freezed.dart b/lib/src/tracks/streams/stream_info.freezed.dart new file mode 100644 index 0000000..30580cf --- /dev/null +++ b/lib/src/tracks/streams/stream_info.freezed.dart @@ -0,0 +1,220 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'stream_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$StreamInfo { + String get url => throw _privateConstructorUsedError; + bool get isSnipped => throw _privateConstructorUsedError; + Container get container => throw _privateConstructorUsedError; + Protocol get protocol => throw _privateConstructorUsedError; + Quality get quality => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $StreamInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $StreamInfoCopyWith<$Res> { + factory $StreamInfoCopyWith( + StreamInfo value, $Res Function(StreamInfo) then) = + _$StreamInfoCopyWithImpl<$Res, StreamInfo>; + @useResult + $Res call( + {String url, + bool isSnipped, + Container container, + Protocol protocol, + Quality quality}); +} + +/// @nodoc +class _$StreamInfoCopyWithImpl<$Res, $Val extends StreamInfo> + implements $StreamInfoCopyWith<$Res> { + _$StreamInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? isSnipped = null, + Object? container = null, + Object? protocol = null, + Object? quality = null, + }) { + return _then(_value.copyWith( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + isSnipped: null == isSnipped + ? _value.isSnipped + : isSnipped // ignore: cast_nullable_to_non_nullable + as bool, + container: null == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as Container, + protocol: null == protocol + ? _value.protocol + : protocol // ignore: cast_nullable_to_non_nullable + as Protocol, + quality: null == quality + ? _value.quality + : quality // ignore: cast_nullable_to_non_nullable + as Quality, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$StreamInfoImplCopyWith<$Res> + implements $StreamInfoCopyWith<$Res> { + factory _$$StreamInfoImplCopyWith( + _$StreamInfoImpl value, $Res Function(_$StreamInfoImpl) then) = + __$$StreamInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String url, + bool isSnipped, + Container container, + Protocol protocol, + Quality quality}); +} + +/// @nodoc +class __$$StreamInfoImplCopyWithImpl<$Res> + extends _$StreamInfoCopyWithImpl<$Res, _$StreamInfoImpl> + implements _$$StreamInfoImplCopyWith<$Res> { + __$$StreamInfoImplCopyWithImpl( + _$StreamInfoImpl _value, $Res Function(_$StreamInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? isSnipped = null, + Object? container = null, + Object? protocol = null, + Object? quality = null, + }) { + return _then(_$StreamInfoImpl( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + isSnipped: null == isSnipped + ? _value.isSnipped + : isSnipped // ignore: cast_nullable_to_non_nullable + as bool, + container: null == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as Container, + protocol: null == protocol + ? _value.protocol + : protocol // ignore: cast_nullable_to_non_nullable + as Protocol, + quality: null == quality + ? _value.quality + : quality // ignore: cast_nullable_to_non_nullable + as Quality, + )); + } +} + +/// @nodoc + +class _$StreamInfoImpl implements _StreamInfo { + const _$StreamInfoImpl( + {required this.url, + required this.isSnipped, + required this.container, + required this.protocol, + required this.quality}); + + @override + final String url; + @override + final bool isSnipped; + @override + final Container container; + @override + final Protocol protocol; + @override + final Quality quality; + + @override + String toString() { + return 'StreamInfo(url: $url, isSnipped: $isSnipped, container: $container, protocol: $protocol, quality: $quality)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$StreamInfoImpl && + (identical(other.url, url) || other.url == url) && + (identical(other.isSnipped, isSnipped) || + other.isSnipped == isSnipped) && + (identical(other.container, container) || + other.container == container) && + (identical(other.protocol, protocol) || + other.protocol == protocol) && + (identical(other.quality, quality) || other.quality == quality)); + } + + @override + int get hashCode => + Object.hash(runtimeType, url, isSnipped, container, protocol, quality); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$StreamInfoImplCopyWith<_$StreamInfoImpl> get copyWith => + __$$StreamInfoImplCopyWithImpl<_$StreamInfoImpl>(this, _$identity); +} + +abstract class _StreamInfo implements StreamInfo { + const factory _StreamInfo( + {required final String url, + required final bool isSnipped, + required final Container container, + required final Protocol protocol, + required final Quality quality}) = _$StreamInfoImpl; + + @override + String get url; + @override + bool get isSnipped; + @override + Container get container; + @override + Protocol get protocol; + @override + Quality get quality; + @override + @JsonKey(ignore: true) + _$$StreamInfoImplCopyWith<_$StreamInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/tracks/streams/streams.dart b/lib/src/tracks/streams/streams.dart new file mode 100644 index 0000000..0a20b6a --- /dev/null +++ b/lib/src/tracks/streams/streams.dart @@ -0,0 +1,6 @@ +library soundcloud_explode.tracks.streams; + +export 'container.dart'; +export 'protocol.dart'; +export 'quality.dart'; +export 'stream_info.dart'; \ No newline at end of file diff --git a/lib/src/tracks/track.dart b/lib/src/tracks/track.dart new file mode 100644 index 0000000..1e886bf --- /dev/null +++ b/lib/src/tracks/track.dart @@ -0,0 +1,43 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import '../users/mini_user.dart'; +import 'soundcloud_track.dart'; + +part 'track.freezed.dart'; +part 'track.g.dart'; + +/// Metadata about a SoundCloud track. +@freezed +class Track with _$Track implements SoundcloudTrack { + const factory Track({ + required Uri? artworkUrl, + required String? caption, + required bool commentable, + @JsonKey(defaultValue: 0) required double commentCount, + required DateTime createdAt, + required String? description, + @JsonKey(defaultValue: 0) required double downloadCount, + required double duration, + required double fullDuration, + required String? genre, + required int id, + required String? labelName, + required DateTime? lastModified, + required String? license, + @JsonKey(defaultValue: 0) required double likesCount, + required Uri permalinkUrl, + @JsonKey(defaultValue: 0) required double playbackCount, + required String? purchaseTitle, + required String? purchaseUrl, + @JsonKey(defaultValue: 0) required double repostsCount, + required String? tagList, + required String title, + required String waveformUrl, + required String monetizationModel, + required String policy, + required MiniUser user + }) = _Track; + + factory Track.fromJson(Map json) => _$TrackFromJson(json); +} \ No newline at end of file diff --git a/lib/src/tracks/track.freezed.dart b/lib/src/tracks/track.freezed.dart new file mode 100644 index 0000000..3b25aa9 --- /dev/null +++ b/lib/src/tracks/track.freezed.dart @@ -0,0 +1,725 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'track.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +Track _$TrackFromJson(Map json) { + return _Track.fromJson(json); +} + +/// @nodoc +mixin _$Track { + Uri? get artworkUrl => throw _privateConstructorUsedError; + String? get caption => throw _privateConstructorUsedError; + bool get commentable => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get commentCount => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get downloadCount => throw _privateConstructorUsedError; + double get duration => throw _privateConstructorUsedError; + double get fullDuration => throw _privateConstructorUsedError; + String? get genre => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + String? get labelName => throw _privateConstructorUsedError; + DateTime? get lastModified => throw _privateConstructorUsedError; + String? get license => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get likesCount => throw _privateConstructorUsedError; + Uri get permalinkUrl => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get playbackCount => throw _privateConstructorUsedError; + String? get purchaseTitle => throw _privateConstructorUsedError; + String? get purchaseUrl => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get repostsCount => throw _privateConstructorUsedError; + String? get tagList => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + String get waveformUrl => throw _privateConstructorUsedError; + String get monetizationModel => throw _privateConstructorUsedError; + String get policy => throw _privateConstructorUsedError; + MiniUser get user => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TrackCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrackCopyWith<$Res> { + factory $TrackCopyWith(Track value, $Res Function(Track) then) = + _$TrackCopyWithImpl<$Res, Track>; + @useResult + $Res call( + {Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user}); + + $MiniUserCopyWith<$Res> get user; +} + +/// @nodoc +class _$TrackCopyWithImpl<$Res, $Val extends Track> + implements $TrackCopyWith<$Res> { + _$TrackCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? artworkUrl = freezed, + Object? caption = freezed, + Object? commentable = null, + Object? commentCount = null, + Object? createdAt = null, + Object? description = freezed, + Object? downloadCount = null, + Object? duration = null, + Object? fullDuration = null, + Object? genre = freezed, + Object? id = null, + Object? labelName = freezed, + Object? lastModified = freezed, + Object? license = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? playbackCount = null, + Object? purchaseTitle = freezed, + Object? purchaseUrl = freezed, + Object? repostsCount = null, + Object? tagList = freezed, + Object? title = null, + Object? waveformUrl = null, + Object? monetizationModel = null, + Object? policy = null, + Object? user = null, + }) { + return _then(_value.copyWith( + artworkUrl: freezed == artworkUrl + ? _value.artworkUrl + : artworkUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + caption: freezed == caption + ? _value.caption + : caption // ignore: cast_nullable_to_non_nullable + as String?, + commentable: null == commentable + ? _value.commentable + : commentable // ignore: cast_nullable_to_non_nullable + as bool, + commentCount: null == commentCount + ? _value.commentCount + : commentCount // ignore: cast_nullable_to_non_nullable + as double, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + downloadCount: null == downloadCount + ? _value.downloadCount + : downloadCount // ignore: cast_nullable_to_non_nullable + as double, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as double, + fullDuration: null == fullDuration + ? _value.fullDuration + : fullDuration // ignore: cast_nullable_to_non_nullable + as double, + genre: freezed == genre + ? _value.genre + : genre // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + labelName: freezed == labelName + ? _value.labelName + : labelName // ignore: cast_nullable_to_non_nullable + as String?, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + license: freezed == license + ? _value.license + : license // ignore: cast_nullable_to_non_nullable + as String?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + playbackCount: null == playbackCount + ? _value.playbackCount + : playbackCount // ignore: cast_nullable_to_non_nullable + as double, + purchaseTitle: freezed == purchaseTitle + ? _value.purchaseTitle + : purchaseTitle // ignore: cast_nullable_to_non_nullable + as String?, + purchaseUrl: freezed == purchaseUrl + ? _value.purchaseUrl + : purchaseUrl // ignore: cast_nullable_to_non_nullable + as String?, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + tagList: freezed == tagList + ? _value.tagList + : tagList // ignore: cast_nullable_to_non_nullable + as String?, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + waveformUrl: null == waveformUrl + ? _value.waveformUrl + : waveformUrl // ignore: cast_nullable_to_non_nullable + as String, + monetizationModel: null == monetizationModel + ? _value.monetizationModel + : monetizationModel // ignore: cast_nullable_to_non_nullable + as String, + policy: null == policy + ? _value.policy + : policy // ignore: cast_nullable_to_non_nullable + as String, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as MiniUser, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $MiniUserCopyWith<$Res> get user { + return $MiniUserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$TrackImplCopyWith<$Res> implements $TrackCopyWith<$Res> { + factory _$$TrackImplCopyWith( + _$TrackImpl value, $Res Function(_$TrackImpl) then) = + __$$TrackImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? artworkUrl, + String? caption, + bool commentable, + @JsonKey(defaultValue: 0) double commentCount, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double downloadCount, + double duration, + double fullDuration, + String? genre, + int id, + String? labelName, + DateTime? lastModified, + String? license, + @JsonKey(defaultValue: 0) double likesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playbackCount, + String? purchaseTitle, + String? purchaseUrl, + @JsonKey(defaultValue: 0) double repostsCount, + String? tagList, + String title, + String waveformUrl, + String monetizationModel, + String policy, + MiniUser user}); + + @override + $MiniUserCopyWith<$Res> get user; +} + +/// @nodoc +class __$$TrackImplCopyWithImpl<$Res> + extends _$TrackCopyWithImpl<$Res, _$TrackImpl> + implements _$$TrackImplCopyWith<$Res> { + __$$TrackImplCopyWithImpl( + _$TrackImpl _value, $Res Function(_$TrackImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? artworkUrl = freezed, + Object? caption = freezed, + Object? commentable = null, + Object? commentCount = null, + Object? createdAt = null, + Object? description = freezed, + Object? downloadCount = null, + Object? duration = null, + Object? fullDuration = null, + Object? genre = freezed, + Object? id = null, + Object? labelName = freezed, + Object? lastModified = freezed, + Object? license = freezed, + Object? likesCount = null, + Object? permalinkUrl = null, + Object? playbackCount = null, + Object? purchaseTitle = freezed, + Object? purchaseUrl = freezed, + Object? repostsCount = null, + Object? tagList = freezed, + Object? title = null, + Object? waveformUrl = null, + Object? monetizationModel = null, + Object? policy = null, + Object? user = null, + }) { + return _then(_$TrackImpl( + artworkUrl: freezed == artworkUrl + ? _value.artworkUrl + : artworkUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + caption: freezed == caption + ? _value.caption + : caption // ignore: cast_nullable_to_non_nullable + as String?, + commentable: null == commentable + ? _value.commentable + : commentable // ignore: cast_nullable_to_non_nullable + as bool, + commentCount: null == commentCount + ? _value.commentCount + : commentCount // ignore: cast_nullable_to_non_nullable + as double, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + downloadCount: null == downloadCount + ? _value.downloadCount + : downloadCount // ignore: cast_nullable_to_non_nullable + as double, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as double, + fullDuration: null == fullDuration + ? _value.fullDuration + : fullDuration // ignore: cast_nullable_to_non_nullable + as double, + genre: freezed == genre + ? _value.genre + : genre // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + labelName: freezed == labelName + ? _value.labelName + : labelName // ignore: cast_nullable_to_non_nullable + as String?, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + license: freezed == license + ? _value.license + : license // ignore: cast_nullable_to_non_nullable + as String?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + playbackCount: null == playbackCount + ? _value.playbackCount + : playbackCount // ignore: cast_nullable_to_non_nullable + as double, + purchaseTitle: freezed == purchaseTitle + ? _value.purchaseTitle + : purchaseTitle // ignore: cast_nullable_to_non_nullable + as String?, + purchaseUrl: freezed == purchaseUrl + ? _value.purchaseUrl + : purchaseUrl // ignore: cast_nullable_to_non_nullable + as String?, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + tagList: freezed == tagList + ? _value.tagList + : tagList // ignore: cast_nullable_to_non_nullable + as String?, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + waveformUrl: null == waveformUrl + ? _value.waveformUrl + : waveformUrl // ignore: cast_nullable_to_non_nullable + as String, + monetizationModel: null == monetizationModel + ? _value.monetizationModel + : monetizationModel // ignore: cast_nullable_to_non_nullable + as String, + policy: null == policy + ? _value.policy + : policy // ignore: cast_nullable_to_non_nullable + as String, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as MiniUser, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TrackImpl implements _Track { + const _$TrackImpl( + {required this.artworkUrl, + required this.caption, + required this.commentable, + @JsonKey(defaultValue: 0) required this.commentCount, + required this.createdAt, + required this.description, + @JsonKey(defaultValue: 0) required this.downloadCount, + required this.duration, + required this.fullDuration, + required this.genre, + required this.id, + required this.labelName, + required this.lastModified, + required this.license, + @JsonKey(defaultValue: 0) required this.likesCount, + required this.permalinkUrl, + @JsonKey(defaultValue: 0) required this.playbackCount, + required this.purchaseTitle, + required this.purchaseUrl, + @JsonKey(defaultValue: 0) required this.repostsCount, + required this.tagList, + required this.title, + required this.waveformUrl, + required this.monetizationModel, + required this.policy, + required this.user}); + + factory _$TrackImpl.fromJson(Map json) => + _$$TrackImplFromJson(json); + + @override + final Uri? artworkUrl; + @override + final String? caption; + @override + final bool commentable; + @override + @JsonKey(defaultValue: 0) + final double commentCount; + @override + final DateTime createdAt; + @override + final String? description; + @override + @JsonKey(defaultValue: 0) + final double downloadCount; + @override + final double duration; + @override + final double fullDuration; + @override + final String? genre; + @override + final int id; + @override + final String? labelName; + @override + final DateTime? lastModified; + @override + final String? license; + @override + @JsonKey(defaultValue: 0) + final double likesCount; + @override + final Uri permalinkUrl; + @override + @JsonKey(defaultValue: 0) + final double playbackCount; + @override + final String? purchaseTitle; + @override + final String? purchaseUrl; + @override + @JsonKey(defaultValue: 0) + final double repostsCount; + @override + final String? tagList; + @override + final String title; + @override + final String waveformUrl; + @override + final String monetizationModel; + @override + final String policy; + @override + final MiniUser user; + + @override + String toString() { + return 'Track(artworkUrl: $artworkUrl, caption: $caption, commentable: $commentable, commentCount: $commentCount, createdAt: $createdAt, description: $description, downloadCount: $downloadCount, duration: $duration, fullDuration: $fullDuration, genre: $genre, id: $id, labelName: $labelName, lastModified: $lastModified, license: $license, likesCount: $likesCount, permalinkUrl: $permalinkUrl, playbackCount: $playbackCount, purchaseTitle: $purchaseTitle, purchaseUrl: $purchaseUrl, repostsCount: $repostsCount, tagList: $tagList, title: $title, waveformUrl: $waveformUrl, monetizationModel: $monetizationModel, policy: $policy, user: $user)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrackImpl && + (identical(other.artworkUrl, artworkUrl) || + other.artworkUrl == artworkUrl) && + (identical(other.caption, caption) || other.caption == caption) && + (identical(other.commentable, commentable) || + other.commentable == commentable) && + (identical(other.commentCount, commentCount) || + other.commentCount == commentCount) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.downloadCount, downloadCount) || + other.downloadCount == downloadCount) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.fullDuration, fullDuration) || + other.fullDuration == fullDuration) && + (identical(other.genre, genre) || other.genre == genre) && + (identical(other.id, id) || other.id == id) && + (identical(other.labelName, labelName) || + other.labelName == labelName) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.license, license) || other.license == license) && + (identical(other.likesCount, likesCount) || + other.likesCount == likesCount) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.playbackCount, playbackCount) || + other.playbackCount == playbackCount) && + (identical(other.purchaseTitle, purchaseTitle) || + other.purchaseTitle == purchaseTitle) && + (identical(other.purchaseUrl, purchaseUrl) || + other.purchaseUrl == purchaseUrl) && + (identical(other.repostsCount, repostsCount) || + other.repostsCount == repostsCount) && + (identical(other.tagList, tagList) || other.tagList == tagList) && + (identical(other.title, title) || other.title == title) && + (identical(other.waveformUrl, waveformUrl) || + other.waveformUrl == waveformUrl) && + (identical(other.monetizationModel, monetizationModel) || + other.monetizationModel == monetizationModel) && + (identical(other.policy, policy) || other.policy == policy) && + (identical(other.user, user) || other.user == user)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hashAll([ + runtimeType, + artworkUrl, + caption, + commentable, + commentCount, + createdAt, + description, + downloadCount, + duration, + fullDuration, + genre, + id, + labelName, + lastModified, + license, + likesCount, + permalinkUrl, + playbackCount, + purchaseTitle, + purchaseUrl, + repostsCount, + tagList, + title, + waveformUrl, + monetizationModel, + policy, + user + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TrackImplCopyWith<_$TrackImpl> get copyWith => + __$$TrackImplCopyWithImpl<_$TrackImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TrackImplToJson( + this, + ); + } +} + +abstract class _Track implements Track { + const factory _Track( + {required final Uri? artworkUrl, + required final String? caption, + required final bool commentable, + @JsonKey(defaultValue: 0) required final double commentCount, + required final DateTime createdAt, + required final String? description, + @JsonKey(defaultValue: 0) required final double downloadCount, + required final double duration, + required final double fullDuration, + required final String? genre, + required final int id, + required final String? labelName, + required final DateTime? lastModified, + required final String? license, + @JsonKey(defaultValue: 0) required final double likesCount, + required final Uri permalinkUrl, + @JsonKey(defaultValue: 0) required final double playbackCount, + required final String? purchaseTitle, + required final String? purchaseUrl, + @JsonKey(defaultValue: 0) required final double repostsCount, + required final String? tagList, + required final String title, + required final String waveformUrl, + required final String monetizationModel, + required final String policy, + required final MiniUser user}) = _$TrackImpl; + + factory _Track.fromJson(Map json) = _$TrackImpl.fromJson; + + @override + Uri? get artworkUrl; + @override + String? get caption; + @override + bool get commentable; + @override + @JsonKey(defaultValue: 0) + double get commentCount; + @override + DateTime get createdAt; + @override + String? get description; + @override + @JsonKey(defaultValue: 0) + double get downloadCount; + @override + double get duration; + @override + double get fullDuration; + @override + String? get genre; + @override + int get id; + @override + String? get labelName; + @override + DateTime? get lastModified; + @override + String? get license; + @override + @JsonKey(defaultValue: 0) + double get likesCount; + @override + Uri get permalinkUrl; + @override + @JsonKey(defaultValue: 0) + double get playbackCount; + @override + String? get purchaseTitle; + @override + String? get purchaseUrl; + @override + @JsonKey(defaultValue: 0) + double get repostsCount; + @override + String? get tagList; + @override + String get title; + @override + String get waveformUrl; + @override + String get monetizationModel; + @override + String get policy; + @override + MiniUser get user; + @override + @JsonKey(ignore: true) + _$$TrackImplCopyWith<_$TrackImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/tracks/track.g.dart b/lib/src/tracks/track.g.dart new file mode 100644 index 0000000..6f11773 --- /dev/null +++ b/lib/src/tracks/track.g.dart @@ -0,0 +1,70 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'track.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TrackImpl _$$TrackImplFromJson(Map json) => _$TrackImpl( + artworkUrl: json['artwork_url'] == null + ? null + : Uri.parse(json['artwork_url'] as String), + caption: json['caption'] as String?, + commentable: json['commentable'] as bool, + commentCount: (json['comment_count'] as num?)?.toDouble() ?? 0, + createdAt: DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + downloadCount: (json['download_count'] as num?)?.toDouble() ?? 0, + duration: (json['duration'] as num).toDouble(), + fullDuration: (json['full_duration'] as num).toDouble(), + genre: json['genre'] as String?, + id: json['id'] as int, + labelName: json['label_name'] as String?, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + license: json['license'] as String?, + likesCount: (json['likes_count'] as num?)?.toDouble() ?? 0, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + playbackCount: (json['playback_count'] as num?)?.toDouble() ?? 0, + purchaseTitle: json['purchase_title'] as String?, + purchaseUrl: json['purchase_url'] as String?, + repostsCount: (json['reposts_count'] as num?)?.toDouble() ?? 0, + tagList: json['tag_list'] as String?, + title: json['title'] as String, + waveformUrl: json['waveform_url'] as String, + monetizationModel: json['monetization_model'] as String, + policy: json['policy'] as String, + user: MiniUser.fromJson(json['user'] as Map), + ); + +Map _$$TrackImplToJson(_$TrackImpl instance) => + { + 'artwork_url': instance.artworkUrl?.toString(), + 'caption': instance.caption, + 'commentable': instance.commentable, + 'comment_count': instance.commentCount, + 'created_at': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'download_count': instance.downloadCount, + 'duration': instance.duration, + 'full_duration': instance.fullDuration, + 'genre': instance.genre, + 'id': instance.id, + 'label_name': instance.labelName, + 'last_modified': instance.lastModified?.toIso8601String(), + 'license': instance.license, + 'likes_count': instance.likesCount, + 'permalink_url': instance.permalinkUrl.toString(), + 'playback_count': instance.playbackCount, + 'purchase_title': instance.purchaseTitle, + 'purchase_url': instance.purchaseUrl, + 'reposts_count': instance.repostsCount, + 'tag_list': instance.tagList, + 'title': instance.title, + 'waveform_url': instance.waveformUrl, + 'monetization_model': instance.monetizationModel, + 'policy': instance.policy, + 'user': instance.user, + }; diff --git a/lib/src/tracks/track_client.dart b/lib/src/tracks/track_client.dart new file mode 100644 index 0000000..04d187f --- /dev/null +++ b/lib/src/tracks/track_client.dart @@ -0,0 +1,99 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import '../bridge/soundcloud_controller.dart'; +import '../exceptions/track_resolution_exception.dart'; +import '../tracks/track.dart'; +import 'streams/container.dart'; +import 'streams/protocol.dart'; +import 'streams/quality.dart'; +import 'streams/stream_info.dart'; + +/// Scrapes metadata about SoundCloud tracks. +class TrackClient { + final Client _http; + final SoundcloudController _controller; + + /// Creates a new [TrackClient] that uses the provided [httpClient] and [controller]. + TrackClient(Client httpClient, SoundcloudController controller) + : _http = httpClient, + _controller = controller; + + /// Gets the [Track] with the specified [trackId]. + Future get(int trackId) async { + final track = await _getTrackResponse(trackId); + return Track.fromJson(track); + } + + /// Gets the [Track] associated with the provided [url]. + Future getByUrl(String url) async { + final json = await _controller.resolveUrl(url); + final track = jsonDecode(json); + return Track.fromJson(track); + } + + /// Gets a list of streams for the track with the specified [trackId]. + Future> getStreams(int trackId) async { + final trackJson = await _getTrackResponse(trackId); + + final transcodingsJson = trackJson['media']?['transcodings']; + + if (transcodingsJson == null) throw TrackResolutionException('No streams available for the specified track ID: $trackId'); + + final clientId = await _controller.getClientId(); + final streams = []; + + for (final transcoding in transcodingsJson) { + final isSnipped = transcoding['snipped'] as bool; + + final mimeTypeStr = transcoding['format']?['mime_type'] as String?; + final mimeType = switch (mimeTypeStr) { + 'audio/mpeg' => Container.mp3, + 'audio/ogg; codecs="opus"' => Container.ogg, + _ => throw TrackResolutionException('Unable to resolve MIME type: $mimeTypeStr') + }; + + final protocolStr = transcoding['format']?['protocol'] as String?; + final protocol = switch (protocolStr) { + 'progressive' => Protocol.progressive, + 'hls' => Protocol.hls, + _ => throw TrackResolutionException('Unable to resolve protocol: $protocolStr') + }; + + final qualityStr = transcoding['quality'] as String?; + final quality = switch (qualityStr) { + 'sq' => Quality.standardQuality, + 'hq' => Quality.highQuality, + _ => throw TrackResolutionException('Unable to resolve quality: $qualityStr') + }; + + final transcodingUrl = '${transcoding['url']}?client_id=$clientId'; + final response = await _http.get(Uri.parse(transcodingUrl)); + final json = jsonDecode(response.body); + final streamUrl = json['url'] as String; + + streams.add( + StreamInfo( + url: streamUrl, + isSnipped: isSnipped, + container: mimeType, + protocol: protocol, + quality: quality + ) + ); + } + + return streams; + } + + Future> _getTrackResponse(int trackId) async { + final clientId = await _controller.getClientId(); + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/tracks/$trackId', { + 'client_id': clientId + } + ); + final response = await _http.get(uri); + return jsonDecode(response.body); + } +} \ No newline at end of file diff --git a/lib/src/tracks/tracks.dart b/lib/src/tracks/tracks.dart new file mode 100644 index 0000000..7d192d6 --- /dev/null +++ b/lib/src/tracks/tracks.dart @@ -0,0 +1,4 @@ +export 'soundcloud_track.dart'; +export 'streams/streams.dart'; +export 'track_client.dart'; +export 'track.dart'; \ No newline at end of file diff --git a/lib/src/users/mini_user.dart b/lib/src/users/mini_user.dart new file mode 100644 index 0000000..6b0d077 --- /dev/null +++ b/lib/src/users/mini_user.dart @@ -0,0 +1,28 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'soundcloud_user.dart'; + +part 'mini_user.freezed.dart'; +part 'mini_user.g.dart'; + +/// Represents a SoundCloud user associated with a specific playlist, album, or track. +@freezed +class MiniUser with _$MiniUser implements SoundcloudUser { + const factory MiniUser({ + required Uri? avatarUrl, + required String? city, + required String? countryCode, + @JsonKey(defaultValue: 0) required double followersCount, + required String? firstName, + required String? fullName, + required int id, + required DateTime? lastModified, + required String? lastName, + required Uri permalinkUrl, + required String username, + @JsonKey(name: 'verified') required bool isVerified + }) = _MiniUser; + + factory MiniUser.fromJson(Map json) => _$MiniUserFromJson(json); +} \ No newline at end of file diff --git a/lib/src/users/mini_user.freezed.dart b/lib/src/users/mini_user.freezed.dart new file mode 100644 index 0000000..faf53fc --- /dev/null +++ b/lib/src/users/mini_user.freezed.dart @@ -0,0 +1,404 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'mini_user.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +MiniUser _$MiniUserFromJson(Map json) { + return _MiniUser.fromJson(json); +} + +/// @nodoc +mixin _$MiniUser { + Uri? get avatarUrl => throw _privateConstructorUsedError; + String? get city => throw _privateConstructorUsedError; + String? get countryCode => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get followersCount => throw _privateConstructorUsedError; + String? get firstName => throw _privateConstructorUsedError; + String? get fullName => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + DateTime? get lastModified => throw _privateConstructorUsedError; + String? get lastName => throw _privateConstructorUsedError; + Uri get permalinkUrl => throw _privateConstructorUsedError; + String get username => throw _privateConstructorUsedError; + @JsonKey(name: 'verified') + bool get isVerified => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $MiniUserCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MiniUserCopyWith<$Res> { + factory $MiniUserCopyWith(MiniUser value, $Res Function(MiniUser) then) = + _$MiniUserCopyWithImpl<$Res, MiniUser>; + @useResult + $Res call( + {Uri? avatarUrl, + String? city, + String? countryCode, + @JsonKey(defaultValue: 0) double followersCount, + String? firstName, + String? fullName, + int id, + DateTime? lastModified, + String? lastName, + Uri permalinkUrl, + String username, + @JsonKey(name: 'verified') bool isVerified}); +} + +/// @nodoc +class _$MiniUserCopyWithImpl<$Res, $Val extends MiniUser> + implements $MiniUserCopyWith<$Res> { + _$MiniUserCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? avatarUrl = freezed, + Object? city = freezed, + Object? countryCode = freezed, + Object? followersCount = null, + Object? firstName = freezed, + Object? fullName = freezed, + Object? id = null, + Object? lastModified = freezed, + Object? lastName = freezed, + Object? permalinkUrl = null, + Object? username = null, + Object? isVerified = null, + }) { + return _then(_value.copyWith( + avatarUrl: freezed == avatarUrl + ? _value.avatarUrl + : avatarUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + city: freezed == city + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + followersCount: null == followersCount + ? _value.followersCount + : followersCount // ignore: cast_nullable_to_non_nullable + as double, + firstName: freezed == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String?, + fullName: freezed == fullName + ? _value.fullName + : fullName // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastName: freezed == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String?, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + username: null == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String, + isVerified: null == isVerified + ? _value.isVerified + : isVerified // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MiniUserImplCopyWith<$Res> + implements $MiniUserCopyWith<$Res> { + factory _$$MiniUserImplCopyWith( + _$MiniUserImpl value, $Res Function(_$MiniUserImpl) then) = + __$$MiniUserImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? avatarUrl, + String? city, + String? countryCode, + @JsonKey(defaultValue: 0) double followersCount, + String? firstName, + String? fullName, + int id, + DateTime? lastModified, + String? lastName, + Uri permalinkUrl, + String username, + @JsonKey(name: 'verified') bool isVerified}); +} + +/// @nodoc +class __$$MiniUserImplCopyWithImpl<$Res> + extends _$MiniUserCopyWithImpl<$Res, _$MiniUserImpl> + implements _$$MiniUserImplCopyWith<$Res> { + __$$MiniUserImplCopyWithImpl( + _$MiniUserImpl _value, $Res Function(_$MiniUserImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? avatarUrl = freezed, + Object? city = freezed, + Object? countryCode = freezed, + Object? followersCount = null, + Object? firstName = freezed, + Object? fullName = freezed, + Object? id = null, + Object? lastModified = freezed, + Object? lastName = freezed, + Object? permalinkUrl = null, + Object? username = null, + Object? isVerified = null, + }) { + return _then(_$MiniUserImpl( + avatarUrl: freezed == avatarUrl + ? _value.avatarUrl + : avatarUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + city: freezed == city + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + followersCount: null == followersCount + ? _value.followersCount + : followersCount // ignore: cast_nullable_to_non_nullable + as double, + firstName: freezed == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String?, + fullName: freezed == fullName + ? _value.fullName + : fullName // ignore: cast_nullable_to_non_nullable + as String?, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastName: freezed == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String?, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + username: null == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String, + isVerified: null == isVerified + ? _value.isVerified + : isVerified // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MiniUserImpl implements _MiniUser { + const _$MiniUserImpl( + {required this.avatarUrl, + required this.city, + required this.countryCode, + @JsonKey(defaultValue: 0) required this.followersCount, + required this.firstName, + required this.fullName, + required this.id, + required this.lastModified, + required this.lastName, + required this.permalinkUrl, + required this.username, + @JsonKey(name: 'verified') required this.isVerified}); + + factory _$MiniUserImpl.fromJson(Map json) => + _$$MiniUserImplFromJson(json); + + @override + final Uri? avatarUrl; + @override + final String? city; + @override + final String? countryCode; + @override + @JsonKey(defaultValue: 0) + final double followersCount; + @override + final String? firstName; + @override + final String? fullName; + @override + final int id; + @override + final DateTime? lastModified; + @override + final String? lastName; + @override + final Uri permalinkUrl; + @override + final String username; + @override + @JsonKey(name: 'verified') + final bool isVerified; + + @override + String toString() { + return 'MiniUser(avatarUrl: $avatarUrl, city: $city, countryCode: $countryCode, followersCount: $followersCount, firstName: $firstName, fullName: $fullName, id: $id, lastModified: $lastModified, lastName: $lastName, permalinkUrl: $permalinkUrl, username: $username, isVerified: $isVerified)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MiniUserImpl && + (identical(other.avatarUrl, avatarUrl) || + other.avatarUrl == avatarUrl) && + (identical(other.city, city) || other.city == city) && + (identical(other.countryCode, countryCode) || + other.countryCode == countryCode) && + (identical(other.followersCount, followersCount) || + other.followersCount == followersCount) && + (identical(other.firstName, firstName) || + other.firstName == firstName) && + (identical(other.fullName, fullName) || + other.fullName == fullName) && + (identical(other.id, id) || other.id == id) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.lastName, lastName) || + other.lastName == lastName) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.username, username) || + other.username == username) && + (identical(other.isVerified, isVerified) || + other.isVerified == isVerified)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + avatarUrl, + city, + countryCode, + followersCount, + firstName, + fullName, + id, + lastModified, + lastName, + permalinkUrl, + username, + isVerified); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MiniUserImplCopyWith<_$MiniUserImpl> get copyWith => + __$$MiniUserImplCopyWithImpl<_$MiniUserImpl>(this, _$identity); + + @override + Map toJson() { + return _$$MiniUserImplToJson( + this, + ); + } +} + +abstract class _MiniUser implements MiniUser { + const factory _MiniUser( + {required final Uri? avatarUrl, + required final String? city, + required final String? countryCode, + @JsonKey(defaultValue: 0) required final double followersCount, + required final String? firstName, + required final String? fullName, + required final int id, + required final DateTime? lastModified, + required final String? lastName, + required final Uri permalinkUrl, + required final String username, + @JsonKey(name: 'verified') required final bool isVerified}) = + _$MiniUserImpl; + + factory _MiniUser.fromJson(Map json) = + _$MiniUserImpl.fromJson; + + @override + Uri? get avatarUrl; + @override + String? get city; + @override + String? get countryCode; + @override + @JsonKey(defaultValue: 0) + double get followersCount; + @override + String? get firstName; + @override + String? get fullName; + @override + int get id; + @override + DateTime? get lastModified; + @override + String? get lastName; + @override + Uri get permalinkUrl; + @override + String get username; + @override + @JsonKey(name: 'verified') + bool get isVerified; + @override + @JsonKey(ignore: true) + _$$MiniUserImplCopyWith<_$MiniUserImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/users/mini_user.g.dart b/lib/src/users/mini_user.g.dart new file mode 100644 index 0000000..9c0c2cc --- /dev/null +++ b/lib/src/users/mini_user.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'mini_user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$MiniUserImpl _$$MiniUserImplFromJson(Map json) => + _$MiniUserImpl( + avatarUrl: json['avatar_url'] == null + ? null + : Uri.parse(json['avatar_url'] as String), + city: json['city'] as String?, + countryCode: json['country_code'] as String?, + followersCount: (json['followers_count'] as num?)?.toDouble() ?? 0, + firstName: json['first_name'] as String?, + fullName: json['full_name'] as String?, + id: json['id'] as int, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + lastName: json['last_name'] as String?, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + username: json['username'] as String, + isVerified: json['verified'] as bool, + ); + +Map _$$MiniUserImplToJson(_$MiniUserImpl instance) => + { + 'avatar_url': instance.avatarUrl?.toString(), + 'city': instance.city, + 'country_code': instance.countryCode, + 'followers_count': instance.followersCount, + 'first_name': instance.firstName, + 'full_name': instance.fullName, + 'id': instance.id, + 'last_modified': instance.lastModified?.toIso8601String(), + 'last_name': instance.lastName, + 'permalink_url': instance.permalinkUrl.toString(), + 'username': instance.username, + 'verified': instance.isVerified, + }; diff --git a/lib/src/users/soundcloud_user.dart b/lib/src/users/soundcloud_user.dart new file mode 100644 index 0000000..7dd1f99 --- /dev/null +++ b/lib/src/users/soundcloud_user.dart @@ -0,0 +1,44 @@ +/// Metadata about a SoundCloud user's profile. +/// +/// This class cannot be instantiated or extended. +abstract interface class SoundcloudUser { + /// The URL of this user's profile picture. + Uri? get avatarUrl; + + /// The city this user is associated with. + String? get city; + + /// The country this user is associated with. + String? get countryCode; + + /// The number of accounts following this user. + double get followersCount; + + /// This user's forename. + String? get firstName; + + /// This user's full name. + /// + /// This is the concatenation of [firstName] and [lastName]. + String? get fullName; + + /// A unique numeric identifier for this user. + int get id; + + /// The date and time modifications were last made to this user. + DateTime? get lastModified; + + /// This user's surname. + String? get lastName; + + /// The URL of this track. + /// + /// E.g.https://www.soundcloud.com/a-user + Uri get permalinkUrl; + + /// The username of this user's account. + String get username; + + /// Whether or not this user is verified. + bool get isVerified; +} \ No newline at end of file diff --git a/lib/src/users/user.dart b/lib/src/users/user.dart new file mode 100644 index 0000000..9f8557b --- /dev/null +++ b/lib/src/users/user.dart @@ -0,0 +1,43 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'soundcloud_user.dart'; + +part 'user.freezed.dart'; +part 'user.g.dart'; + +Object? _readBannerLink(Map map, String key) => map['visuals']?['visuals']?[0]['link']; +Object? _readBannerUrl(Map map, String key) => map['visuals']?['visuals']?[0]['visual_url']; + +/// Metadata about a SoundCloud user's profile. +@freezed +class User with _$User implements SoundcloudUser { + const factory User({ + required Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) required Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) required Uri? bannerUrl, + required String? city, + @JsonKey(defaultValue: 0) required double commentsCount, + required String? countryCode, + required DateTime createdAt, + required String? description, + @JsonKey(defaultValue: 0) required double followersCount, + @JsonKey(defaultValue: 0) required double followingsCount, + required String? firstName, + required String? fullName, + @JsonKey(defaultValue: 0) required double groupsCount, + required int id, + required DateTime? lastModified, + required String? lastName, + @JsonKey(defaultValue: 0) required double likesCount, + @JsonKey(defaultValue: 0) required double playlistLikesCount, + required Uri permalinkUrl, + @JsonKey(defaultValue: 0) required double playlistCount, + @JsonKey(defaultValue: 0) required double repostsCount, + @JsonKey(defaultValue: 0) required double trackCount, + required String username, + @JsonKey(name: 'verified') required bool isVerified + }) = _User; + + factory User.fromJson(Map json) => _$UserFromJson(json); +} \ No newline at end of file diff --git a/lib/src/users/user.freezed.dart b/lib/src/users/user.freezed.dart new file mode 100644 index 0000000..f49c0bc --- /dev/null +++ b/lib/src/users/user.freezed.dart @@ -0,0 +1,694 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +User _$UserFromJson(Map json) { + return _User.fromJson(json); +} + +/// @nodoc +mixin _$User { + Uri? get avatarUrl => throw _privateConstructorUsedError; + @JsonKey(readValue: _readBannerLink) + Uri? get bannerLink => throw _privateConstructorUsedError; + @JsonKey(readValue: _readBannerUrl) + Uri? get bannerUrl => throw _privateConstructorUsedError; + String? get city => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get commentsCount => throw _privateConstructorUsedError; + String? get countryCode => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get followersCount => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get followingsCount => throw _privateConstructorUsedError; + String? get firstName => throw _privateConstructorUsedError; + String? get fullName => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get groupsCount => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + DateTime? get lastModified => throw _privateConstructorUsedError; + String? get lastName => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get likesCount => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get playlistLikesCount => throw _privateConstructorUsedError; + Uri get permalinkUrl => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get playlistCount => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get repostsCount => throw _privateConstructorUsedError; + @JsonKey(defaultValue: 0) + double get trackCount => throw _privateConstructorUsedError; + String get username => throw _privateConstructorUsedError; + @JsonKey(name: 'verified') + bool get isVerified => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $UserCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserCopyWith<$Res> { + factory $UserCopyWith(User value, $Res Function(User) then) = + _$UserCopyWithImpl<$Res, User>; + @useResult + $Res call( + {Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double followersCount, + @JsonKey(defaultValue: 0) double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified}); +} + +/// @nodoc +class _$UserCopyWithImpl<$Res, $Val extends User> + implements $UserCopyWith<$Res> { + _$UserCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? avatarUrl = freezed, + Object? bannerLink = freezed, + Object? bannerUrl = freezed, + Object? city = freezed, + Object? commentsCount = null, + Object? countryCode = freezed, + Object? createdAt = null, + Object? description = freezed, + Object? followersCount = null, + Object? followingsCount = null, + Object? firstName = freezed, + Object? fullName = freezed, + Object? groupsCount = null, + Object? id = null, + Object? lastModified = freezed, + Object? lastName = freezed, + Object? likesCount = null, + Object? playlistLikesCount = null, + Object? permalinkUrl = null, + Object? playlistCount = null, + Object? repostsCount = null, + Object? trackCount = null, + Object? username = null, + Object? isVerified = null, + }) { + return _then(_value.copyWith( + avatarUrl: freezed == avatarUrl + ? _value.avatarUrl + : avatarUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + bannerLink: freezed == bannerLink + ? _value.bannerLink + : bannerLink // ignore: cast_nullable_to_non_nullable + as Uri?, + bannerUrl: freezed == bannerUrl + ? _value.bannerUrl + : bannerUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + city: freezed == city + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + commentsCount: null == commentsCount + ? _value.commentsCount + : commentsCount // ignore: cast_nullable_to_non_nullable + as double, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + followersCount: null == followersCount + ? _value.followersCount + : followersCount // ignore: cast_nullable_to_non_nullable + as double, + followingsCount: null == followingsCount + ? _value.followingsCount + : followingsCount // ignore: cast_nullable_to_non_nullable + as double, + firstName: freezed == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String?, + fullName: freezed == fullName + ? _value.fullName + : fullName // ignore: cast_nullable_to_non_nullable + as String?, + groupsCount: null == groupsCount + ? _value.groupsCount + : groupsCount // ignore: cast_nullable_to_non_nullable + as double, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastName: freezed == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + playlistLikesCount: null == playlistLikesCount + ? _value.playlistLikesCount + : playlistLikesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + playlistCount: null == playlistCount + ? _value.playlistCount + : playlistCount // ignore: cast_nullable_to_non_nullable + as double, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + trackCount: null == trackCount + ? _value.trackCount + : trackCount // ignore: cast_nullable_to_non_nullable + as double, + username: null == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String, + isVerified: null == isVerified + ? _value.isVerified + : isVerified // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> { + factory _$$UserImplCopyWith( + _$UserImpl value, $Res Function(_$UserImpl) then) = + __$$UserImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) Uri? bannerUrl, + String? city, + @JsonKey(defaultValue: 0) double commentsCount, + String? countryCode, + DateTime createdAt, + String? description, + @JsonKey(defaultValue: 0) double followersCount, + @JsonKey(defaultValue: 0) double followingsCount, + String? firstName, + String? fullName, + @JsonKey(defaultValue: 0) double groupsCount, + int id, + DateTime? lastModified, + String? lastName, + @JsonKey(defaultValue: 0) double likesCount, + @JsonKey(defaultValue: 0) double playlistLikesCount, + Uri permalinkUrl, + @JsonKey(defaultValue: 0) double playlistCount, + @JsonKey(defaultValue: 0) double repostsCount, + @JsonKey(defaultValue: 0) double trackCount, + String username, + @JsonKey(name: 'verified') bool isVerified}); +} + +/// @nodoc +class __$$UserImplCopyWithImpl<$Res> + extends _$UserCopyWithImpl<$Res, _$UserImpl> + implements _$$UserImplCopyWith<$Res> { + __$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? avatarUrl = freezed, + Object? bannerLink = freezed, + Object? bannerUrl = freezed, + Object? city = freezed, + Object? commentsCount = null, + Object? countryCode = freezed, + Object? createdAt = null, + Object? description = freezed, + Object? followersCount = null, + Object? followingsCount = null, + Object? firstName = freezed, + Object? fullName = freezed, + Object? groupsCount = null, + Object? id = null, + Object? lastModified = freezed, + Object? lastName = freezed, + Object? likesCount = null, + Object? playlistLikesCount = null, + Object? permalinkUrl = null, + Object? playlistCount = null, + Object? repostsCount = null, + Object? trackCount = null, + Object? username = null, + Object? isVerified = null, + }) { + return _then(_$UserImpl( + avatarUrl: freezed == avatarUrl + ? _value.avatarUrl + : avatarUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + bannerLink: freezed == bannerLink + ? _value.bannerLink + : bannerLink // ignore: cast_nullable_to_non_nullable + as Uri?, + bannerUrl: freezed == bannerUrl + ? _value.bannerUrl + : bannerUrl // ignore: cast_nullable_to_non_nullable + as Uri?, + city: freezed == city + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + commentsCount: null == commentsCount + ? _value.commentsCount + : commentsCount // ignore: cast_nullable_to_non_nullable + as double, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + followersCount: null == followersCount + ? _value.followersCount + : followersCount // ignore: cast_nullable_to_non_nullable + as double, + followingsCount: null == followingsCount + ? _value.followingsCount + : followingsCount // ignore: cast_nullable_to_non_nullable + as double, + firstName: freezed == firstName + ? _value.firstName + : firstName // ignore: cast_nullable_to_non_nullable + as String?, + fullName: freezed == fullName + ? _value.fullName + : fullName // ignore: cast_nullable_to_non_nullable + as String?, + groupsCount: null == groupsCount + ? _value.groupsCount + : groupsCount // ignore: cast_nullable_to_non_nullable + as double, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + lastModified: freezed == lastModified + ? _value.lastModified + : lastModified // ignore: cast_nullable_to_non_nullable + as DateTime?, + lastName: freezed == lastName + ? _value.lastName + : lastName // ignore: cast_nullable_to_non_nullable + as String?, + likesCount: null == likesCount + ? _value.likesCount + : likesCount // ignore: cast_nullable_to_non_nullable + as double, + playlistLikesCount: null == playlistLikesCount + ? _value.playlistLikesCount + : playlistLikesCount // ignore: cast_nullable_to_non_nullable + as double, + permalinkUrl: null == permalinkUrl + ? _value.permalinkUrl + : permalinkUrl // ignore: cast_nullable_to_non_nullable + as Uri, + playlistCount: null == playlistCount + ? _value.playlistCount + : playlistCount // ignore: cast_nullable_to_non_nullable + as double, + repostsCount: null == repostsCount + ? _value.repostsCount + : repostsCount // ignore: cast_nullable_to_non_nullable + as double, + trackCount: null == trackCount + ? _value.trackCount + : trackCount // ignore: cast_nullable_to_non_nullable + as double, + username: null == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String, + isVerified: null == isVerified + ? _value.isVerified + : isVerified // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserImpl implements _User { + const _$UserImpl( + {required this.avatarUrl, + @JsonKey(readValue: _readBannerLink) required this.bannerLink, + @JsonKey(readValue: _readBannerUrl) required this.bannerUrl, + required this.city, + @JsonKey(defaultValue: 0) required this.commentsCount, + required this.countryCode, + required this.createdAt, + required this.description, + @JsonKey(defaultValue: 0) required this.followersCount, + @JsonKey(defaultValue: 0) required this.followingsCount, + required this.firstName, + required this.fullName, + @JsonKey(defaultValue: 0) required this.groupsCount, + required this.id, + required this.lastModified, + required this.lastName, + @JsonKey(defaultValue: 0) required this.likesCount, + @JsonKey(defaultValue: 0) required this.playlistLikesCount, + required this.permalinkUrl, + @JsonKey(defaultValue: 0) required this.playlistCount, + @JsonKey(defaultValue: 0) required this.repostsCount, + @JsonKey(defaultValue: 0) required this.trackCount, + required this.username, + @JsonKey(name: 'verified') required this.isVerified}); + + factory _$UserImpl.fromJson(Map json) => + _$$UserImplFromJson(json); + + @override + final Uri? avatarUrl; + @override + @JsonKey(readValue: _readBannerLink) + final Uri? bannerLink; + @override + @JsonKey(readValue: _readBannerUrl) + final Uri? bannerUrl; + @override + final String? city; + @override + @JsonKey(defaultValue: 0) + final double commentsCount; + @override + final String? countryCode; + @override + final DateTime createdAt; + @override + final String? description; + @override + @JsonKey(defaultValue: 0) + final double followersCount; + @override + @JsonKey(defaultValue: 0) + final double followingsCount; + @override + final String? firstName; + @override + final String? fullName; + @override + @JsonKey(defaultValue: 0) + final double groupsCount; + @override + final int id; + @override + final DateTime? lastModified; + @override + final String? lastName; + @override + @JsonKey(defaultValue: 0) + final double likesCount; + @override + @JsonKey(defaultValue: 0) + final double playlistLikesCount; + @override + final Uri permalinkUrl; + @override + @JsonKey(defaultValue: 0) + final double playlistCount; + @override + @JsonKey(defaultValue: 0) + final double repostsCount; + @override + @JsonKey(defaultValue: 0) + final double trackCount; + @override + final String username; + @override + @JsonKey(name: 'verified') + final bool isVerified; + + @override + String toString() { + return 'User(avatarUrl: $avatarUrl, bannerLink: $bannerLink, bannerUrl: $bannerUrl, city: $city, commentsCount: $commentsCount, countryCode: $countryCode, createdAt: $createdAt, description: $description, followersCount: $followersCount, followingsCount: $followingsCount, firstName: $firstName, fullName: $fullName, groupsCount: $groupsCount, id: $id, lastModified: $lastModified, lastName: $lastName, likesCount: $likesCount, playlistLikesCount: $playlistLikesCount, permalinkUrl: $permalinkUrl, playlistCount: $playlistCount, repostsCount: $repostsCount, trackCount: $trackCount, username: $username, isVerified: $isVerified)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserImpl && + (identical(other.avatarUrl, avatarUrl) || + other.avatarUrl == avatarUrl) && + (identical(other.bannerLink, bannerLink) || + other.bannerLink == bannerLink) && + (identical(other.bannerUrl, bannerUrl) || + other.bannerUrl == bannerUrl) && + (identical(other.city, city) || other.city == city) && + (identical(other.commentsCount, commentsCount) || + other.commentsCount == commentsCount) && + (identical(other.countryCode, countryCode) || + other.countryCode == countryCode) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.description, description) || + other.description == description) && + (identical(other.followersCount, followersCount) || + other.followersCount == followersCount) && + (identical(other.followingsCount, followingsCount) || + other.followingsCount == followingsCount) && + (identical(other.firstName, firstName) || + other.firstName == firstName) && + (identical(other.fullName, fullName) || + other.fullName == fullName) && + (identical(other.groupsCount, groupsCount) || + other.groupsCount == groupsCount) && + (identical(other.id, id) || other.id == id) && + (identical(other.lastModified, lastModified) || + other.lastModified == lastModified) && + (identical(other.lastName, lastName) || + other.lastName == lastName) && + (identical(other.likesCount, likesCount) || + other.likesCount == likesCount) && + (identical(other.playlistLikesCount, playlistLikesCount) || + other.playlistLikesCount == playlistLikesCount) && + (identical(other.permalinkUrl, permalinkUrl) || + other.permalinkUrl == permalinkUrl) && + (identical(other.playlistCount, playlistCount) || + other.playlistCount == playlistCount) && + (identical(other.repostsCount, repostsCount) || + other.repostsCount == repostsCount) && + (identical(other.trackCount, trackCount) || + other.trackCount == trackCount) && + (identical(other.username, username) || + other.username == username) && + (identical(other.isVerified, isVerified) || + other.isVerified == isVerified)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hashAll([ + runtimeType, + avatarUrl, + bannerLink, + bannerUrl, + city, + commentsCount, + countryCode, + createdAt, + description, + followersCount, + followingsCount, + firstName, + fullName, + groupsCount, + id, + lastModified, + lastName, + likesCount, + playlistLikesCount, + permalinkUrl, + playlistCount, + repostsCount, + trackCount, + username, + isVerified + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$UserImplCopyWith<_$UserImpl> get copyWith => + __$$UserImplCopyWithImpl<_$UserImpl>(this, _$identity); + + @override + Map toJson() { + return _$$UserImplToJson( + this, + ); + } +} + +abstract class _User implements User { + const factory _User( + {required final Uri? avatarUrl, + @JsonKey(readValue: _readBannerLink) required final Uri? bannerLink, + @JsonKey(readValue: _readBannerUrl) required final Uri? bannerUrl, + required final String? city, + @JsonKey(defaultValue: 0) required final double commentsCount, + required final String? countryCode, + required final DateTime createdAt, + required final String? description, + @JsonKey(defaultValue: 0) required final double followersCount, + @JsonKey(defaultValue: 0) required final double followingsCount, + required final String? firstName, + required final String? fullName, + @JsonKey(defaultValue: 0) required final double groupsCount, + required final int id, + required final DateTime? lastModified, + required final String? lastName, + @JsonKey(defaultValue: 0) required final double likesCount, + @JsonKey(defaultValue: 0) required final double playlistLikesCount, + required final Uri permalinkUrl, + @JsonKey(defaultValue: 0) required final double playlistCount, + @JsonKey(defaultValue: 0) required final double repostsCount, + @JsonKey(defaultValue: 0) required final double trackCount, + required final String username, + @JsonKey(name: 'verified') required final bool isVerified}) = _$UserImpl; + + factory _User.fromJson(Map json) = _$UserImpl.fromJson; + + @override + Uri? get avatarUrl; + @override + @JsonKey(readValue: _readBannerLink) + Uri? get bannerLink; + @override + @JsonKey(readValue: _readBannerUrl) + Uri? get bannerUrl; + @override + String? get city; + @override + @JsonKey(defaultValue: 0) + double get commentsCount; + @override + String? get countryCode; + @override + DateTime get createdAt; + @override + String? get description; + @override + @JsonKey(defaultValue: 0) + double get followersCount; + @override + @JsonKey(defaultValue: 0) + double get followingsCount; + @override + String? get firstName; + @override + String? get fullName; + @override + @JsonKey(defaultValue: 0) + double get groupsCount; + @override + int get id; + @override + DateTime? get lastModified; + @override + String? get lastName; + @override + @JsonKey(defaultValue: 0) + double get likesCount; + @override + @JsonKey(defaultValue: 0) + double get playlistLikesCount; + @override + Uri get permalinkUrl; + @override + @JsonKey(defaultValue: 0) + double get playlistCount; + @override + @JsonKey(defaultValue: 0) + double get repostsCount; + @override + @JsonKey(defaultValue: 0) + double get trackCount; + @override + String get username; + @override + @JsonKey(name: 'verified') + bool get isVerified; + @override + @JsonKey(ignore: true) + _$$UserImplCopyWith<_$UserImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/users/user.g.dart b/lib/src/users/user.g.dart new file mode 100644 index 0000000..71f038b --- /dev/null +++ b/lib/src/users/user.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$UserImpl _$$UserImplFromJson(Map json) => _$UserImpl( + avatarUrl: json['avatar_url'] == null + ? null + : Uri.parse(json['avatar_url'] as String), + bannerLink: _readBannerLink(json, 'banner_link') == null + ? null + : Uri.parse(_readBannerLink(json, 'banner_link') as String), + bannerUrl: _readBannerUrl(json, 'banner_url') == null + ? null + : Uri.parse(_readBannerUrl(json, 'banner_url') as String), + city: json['city'] as String?, + commentsCount: (json['comments_count'] as num?)?.toDouble() ?? 0, + countryCode: json['country_code'] as String?, + createdAt: DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + followersCount: (json['followers_count'] as num?)?.toDouble() ?? 0, + followingsCount: (json['followings_count'] as num?)?.toDouble() ?? 0, + firstName: json['first_name'] as String?, + fullName: json['full_name'] as String?, + groupsCount: (json['groups_count'] as num?)?.toDouble() ?? 0, + id: json['id'] as int, + lastModified: json['last_modified'] == null + ? null + : DateTime.parse(json['last_modified'] as String), + lastName: json['last_name'] as String?, + likesCount: (json['likes_count'] as num?)?.toDouble() ?? 0, + playlistLikesCount: + (json['playlist_likes_count'] as num?)?.toDouble() ?? 0, + permalinkUrl: Uri.parse(json['permalink_url'] as String), + playlistCount: (json['playlist_count'] as num?)?.toDouble() ?? 0, + repostsCount: (json['reposts_count'] as num?)?.toDouble() ?? 0, + trackCount: (json['track_count'] as num?)?.toDouble() ?? 0, + username: json['username'] as String, + isVerified: json['verified'] as bool, + ); + +Map _$$UserImplToJson(_$UserImpl instance) => + { + 'avatar_url': instance.avatarUrl?.toString(), + 'banner_link': instance.bannerLink?.toString(), + 'banner_url': instance.bannerUrl?.toString(), + 'city': instance.city, + 'comments_count': instance.commentsCount, + 'country_code': instance.countryCode, + 'created_at': instance.createdAt.toIso8601String(), + 'description': instance.description, + 'followers_count': instance.followersCount, + 'followings_count': instance.followingsCount, + 'first_name': instance.firstName, + 'full_name': instance.fullName, + 'groups_count': instance.groupsCount, + 'id': instance.id, + 'last_modified': instance.lastModified?.toIso8601String(), + 'last_name': instance.lastName, + 'likes_count': instance.likesCount, + 'playlist_likes_count': instance.playlistLikesCount, + 'permalink_url': instance.permalinkUrl.toString(), + 'playlist_count': instance.playlistCount, + 'reposts_count': instance.repostsCount, + 'track_count': instance.trackCount, + 'username': instance.username, + 'verified': instance.isVerified, + }; diff --git a/lib/src/users/user_client.dart b/lib/src/users/user_client.dart new file mode 100644 index 0000000..d5e4717 --- /dev/null +++ b/lib/src/users/user_client.dart @@ -0,0 +1,185 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import '../bridge/soundcloud_controller.dart'; +import '../constants.dart'; +import '../playlists/playlist.dart'; +import '../tracks/track.dart'; +import 'soundcloud_user.dart'; +import 'user.dart'; +import 'user_track_sort.dart'; + +/// Scrapes metadata about SoundCloud users. +class UserClient { + final Client _http; + final SoundcloudController _controller; + + /// Creates a new [UserClient] that uses the provided [httpClient] and [controller]. + UserClient(Client httpClient, SoundcloudController controller) + : _http = httpClient, + _controller = controller; + + /// Gets the [SoundcloudUser] with the specified [userId]. + Future get(int userId) async { + final clientId = await _controller.getClientId(); + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/users/$userId', { + 'client_id': clientId + } + ); + final response = await _http.get(uri); + final user = jsonDecode(response.body); + return User.fromJson(user); + } + + /// Gets the [SoundcloudUser] associated with the provided [url]. + Future getByUrl(String url) async { + final json = await _controller.resolveUrl(url); + final user = jsonDecode(json); + return User.fromJson(user); + } + + /// Gets batches of tracks uploaded by the user with the specified [userId]. + Stream> getTracks( + int userId, { + UserTrackSort sortBy = UserTrackSort.none, + int offset = defaultOffset, + int limit = defaultLimit + }) async* { + if (offset < 0) { + throw ArgumentError.value( + offset, + null, + 'Offset cannot be less than zero.', + ); + } + + if (limit < 0) { + throw ArgumentError.value( + limit, + null, + 'Limit cannot be less than zero.', + ); + } + + final sort = switch (sortBy) { + UserTrackSort.none => 'tracks', + UserTrackSort.popular => 'toptracks', + }; + + final clientId = await _controller.getClientId(); + + while (true) { + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/users/$userId/$sort', { + 'offset': '$offset', + 'limit': '$limit', + 'client_id': clientId, + }, + ); + + final response = await _http.get(uri); + final json = jsonDecode(response.body); + final collection = json['collection'] as List; + + if (collection.isEmpty) break; + + yield collection.map((e) => Track.fromJson(e)); + + offset += collection.length; + } + } + + /// Gets batches of playlists created by the user with the specified [userId]. + Stream> getPlaylists( + int userId, { + int offset = defaultOffset, + int limit = defaultLimit + }) async* { + if (offset < 0) { + throw ArgumentError.value( + offset, + 'offset', + 'Offset cannot be less than zero.', + ); + } + + if (limit < 0) { + throw ArgumentError.value( + limit, + 'limit', + 'Limit cannot be less than zero.', + ); + } + + final clientId = await _controller.getClientId(); + + while (true) { + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/users/$userId/playlists_without_albums', { + 'offset': '$offset', + 'limit': '$limit', + 'client_id': clientId, + }, + ); + + final response = await _http.get(uri); + final json = jsonDecode(response.body); + final collection = json['collection'] as List; + + if (collection.isEmpty) return; + + yield collection.map((e) => Playlist.fromJson(e)); + + offset += collection.length; + } + } + + /// Gets batches of albums created by the user with the specified [userId]. + Stream> getAlbums( + int userId, { + int offset = defaultOffset, + int limit = defaultLimit + }) async* { + if (offset < 0) { + throw ArgumentError.value( + offset, + null, + 'Offset cannot be less than zero.', + ); + } + + if (limit < 0) { + throw ArgumentError.value( + limit, + null, + 'Limit cannot be less than zero.', + ); + } + + final clientId = await _controller.getClientId(); + + while (true) { + final uri = Uri.https( + 'api-v2.soundcloud.com', + '/users/$userId/albums', { + 'offset': '$offset', + 'limit': '$limit', + 'client_id': clientId, + }, + ); + + final response = await _http.get(uri); + final json = jsonDecode(response.body); + final collection = json['collection'] as List; + + if (collection.isEmpty) break; + + yield collection.map((e) => Playlist.fromJson(e)); + + offset += collection.length; + } + } +} \ No newline at end of file diff --git a/lib/src/users/user_track_sort.dart b/lib/src/users/user_track_sort.dart new file mode 100644 index 0000000..ed830ed --- /dev/null +++ b/lib/src/users/user_track_sort.dart @@ -0,0 +1,8 @@ +/// Sorts a SoundCloud user's tracks. +enum UserTrackSort { + /// Tracks are unsorted. + none, + + /// Tracks are sorted by popularity. + popular, +} \ No newline at end of file diff --git a/lib/src/users/users.dart b/lib/src/users/users.dart new file mode 100644 index 0000000..ec2f7e8 --- /dev/null +++ b/lib/src/users/users.dart @@ -0,0 +1,3 @@ +export 'user_client.dart'; +export 'user.dart'; +export 'soundcloud_user.dart'; \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..f8497d8 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,28 @@ +name: soundcloud_explode_dart +version: 1.0.0 +description: Scrape metadata about users, tracks, playlists, and albums from SoundCloud without requiring an account, API key, or rate-limiting. +homepage: https://github.com/codedbycurtis/soundcloud_explode_dart +repository: https://github.com/codedbycurtis/soundcloud_explode_dart +issue_tracker: https://github.com/codedbycurtis/soundcloud_explode_dart/issues +platforms: + android: + ios: + linux: + macos: + web: + windows: + +environment: + sdk: ^3.0.6 + +dependencies: + freezed_annotation: ^2.4.1 + http: ^1.1.0 + json_annotation: ^4.8.1 + +dev_dependencies: + build_runner: ^2.4.6 + freezed: ^2.4.2 + json_serializable: ^6.7.1 + lints: ^2.0.0 + test: ^1.21.0