Files
plezy/lib/models/seerr/seerr_request.dart
T
edde746 e4f3888694 fix(seerr): seed advanced requests from the Sonarr anime defaults and expose tags
Requesting an anime series with REQUEST_ADVANCED routed it to the standard
Sonarr profile and root folder. The sheet seeded its pickers from
activeProfileId/activeDirectory only, then posted them as explicit overrides,
which beat the anime defaults Seerr would otherwise apply at approval. A user
without the permission sent no overrides and was routed correctly.

The service DTOs now carry activeAnimeProfileId, activeAnimeDirectory,
activeAnimeLanguageProfileId, activeTags, activeAnimeTags and the instance's
tag list. The sheet detects the TMDB anime keyword on the TV details it
already fetches and seeds each picker the way Seerr's web requester does:
the anime value when the series is an anime and the instance configures one,
else the standard value. Options carry the "(Default)" marker and an anime
series shows the same note as the web UI.

Tags are editable: an inline checklist under the advanced pickers, seeded from
the instance's (anime) default tags and posted as `tags`. It is inline rather
than a nested sheet page because the host builds only the top page, so a push
would dispose the sheet and drop the season and picker selections on the way
back.

close #2215
2026-09-03 16:46:27 +02:00

98 lines
2.9 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;
/// Arr tag ids. An empty list is a real override (no tags); null leaves
/// the instance defaults in place.
final List<int>? tags;
const SeerrRequestPayload({
required this.mediaType,
required this.mediaId,
this.seasons,
this.is4k = false,
this.serverId,
this.profileId,
this.rootFolder,
this.languageProfileId,
this.tags,
});
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,
if (tags != null) 'tags': tags,
};
}