Jellyseerr shifts MediaStatus codes 6/7 relative to Overseerr (6=blocklisted, 7=deleted vs 6=deleted), and the shared unconditional mapping misread both. The product is now detected from the presence of mediaServerType in /settings/public, persisted on the session, and used to resolve raw wire codes; blocklisted titles are shown as non-requestable. Request statuses failed(4) and completed(5) previously decoded as pending, blocking re-requests and mislabeling failures; both are now modeled and active requests are defined positively as pending or approved. Seerr also marks a request Failed on arr-push failure while leaving the media status Processing, and precedence checked processing first, so failed titles rendered as Processing and the request sheet kept blocking re-requests; a failed request with no live pending/approved request now wins over stale pipeline status in both the catalog state and the sheet's blocked labels. Reauthentication completing from a stale session snapshot wholesale-replaced the session and could downgrade a concurrently detected product discriminator back to unknown permanently (the settings cache never reapplies it); adoption now merges the known product.
92 lines
2.7 KiB
Dart
92 lines
2.7 KiB
Dart
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
import 'seerr_media.dart';
|
|
|
|
part 'seerr_request.g.dart';
|
|
|
|
/// Approval state of a Seerr request (`MediaRequest.status`) — identical
|
|
/// wire codes in Overseerr and Jellyseerr. FAILED is set when the arr
|
|
/// submission is rejected; COMPLETED once the request's media is available.
|
|
enum SeerrRequestStatus {
|
|
pending(1),
|
|
approved(2),
|
|
declined(3),
|
|
failed(4),
|
|
completed(5);
|
|
|
|
final int code;
|
|
const SeerrRequestStatus(this.code);
|
|
|
|
/// An unrecognized code deliberately falls back to [pending]: it renders
|
|
/// as "requested" and blocks re-submission, the conservative reading for
|
|
/// a state this client does not understand.
|
|
static SeerrRequestStatus fromCode(int? code) =>
|
|
values.where((v) => v.code == code).firstOrNull ?? SeerrRequestStatus.pending;
|
|
}
|
|
|
|
/// A media request as returned by `POST /request` and `GET /request`.
|
|
@JsonSerializable(createToJson: false)
|
|
class SeerrRequest {
|
|
final int id;
|
|
@JsonKey(name: 'status', fromJson: SeerrRequestStatus.fromCode)
|
|
final SeerrRequestStatus status;
|
|
final bool? is4k;
|
|
final SeerrMediaInfo? media;
|
|
|
|
/// TV only: the seasons this request covers.
|
|
final List<SeerrRequestSeason>? seasons;
|
|
|
|
const SeerrRequest({required this.id, required this.status, this.is4k, this.media, this.seasons});
|
|
|
|
factory SeerrRequest.fromJson(Map<String, dynamic> json) => _$SeerrRequestFromJson(json);
|
|
}
|
|
|
|
/// One season within a request (`MediaRequest.seasons[]`).
|
|
@JsonSerializable(createToJson: false)
|
|
class SeerrRequestSeason {
|
|
final int seasonNumber;
|
|
|
|
const SeerrRequestSeason({required this.seasonNumber});
|
|
|
|
factory SeerrRequestSeason.fromJson(Map<String, dynamic> json) => _$SeerrRequestSeasonFromJson(json);
|
|
}
|
|
|
|
/// Body of `POST /request`. Advanced fields require `REQUEST_ADVANCED`;
|
|
/// `is4k` requires the 4K request permissions.
|
|
class SeerrRequestPayload {
|
|
final String mediaType;
|
|
|
|
/// TMDB id.
|
|
final int mediaId;
|
|
|
|
/// TV only: season numbers, or null for `all`.
|
|
final List<int>? seasons;
|
|
final bool is4k;
|
|
final int? serverId;
|
|
final int? profileId;
|
|
final String? rootFolder;
|
|
final int? languageProfileId;
|
|
|
|
const SeerrRequestPayload({
|
|
required this.mediaType,
|
|
required this.mediaId,
|
|
this.seasons,
|
|
this.is4k = false,
|
|
this.serverId,
|
|
this.profileId,
|
|
this.rootFolder,
|
|
this.languageProfileId,
|
|
});
|
|
|
|
Map<String, Object?> toJson() => {
|
|
'mediaType': mediaType,
|
|
'mediaId': mediaId,
|
|
if (mediaType == 'tv') 'seasons': seasons ?? 'all',
|
|
'is4k': is4k,
|
|
if (serverId != null) 'serverId': serverId,
|
|
if (profileId != null) 'profileId': profileId,
|
|
if (rootFolder != null) 'rootFolder': rootFolder,
|
|
if (languageProfileId != null) 'languageProfileId': languageProfileId,
|
|
};
|
|
}
|