feat(meta): look up series external ids

This commit is contained in:
Miguel Palhas
2026-08-23 16:32:25 +01:00
parent b96fa2486e
commit fa3f9cbd91
5 changed files with 81 additions and 5 deletions
+16 -2
View File
@@ -9,8 +9,8 @@ use serde::de::DeserializeOwned;
use crate::cache::Cache;
use crate::error::{Error, Result};
use crate::model::{
Movie, MovieSearchResult, RawFindPage, RawMovie, RawSearchPage, RawSeason, RawSeries,
RawSeriesSearchPage, Season, Series, SeriesSearchResult,
ExternalIds, Movie, MovieSearchResult, RawExternalIds, RawFindPage, RawMovie, RawSearchPage,
RawSeason, RawSeries, RawSeriesSearchPage, Season, Series, SeriesSearchResult,
};
/// TMDB's v3 API root.
@@ -157,6 +157,20 @@ impl TmdbClient {
Ok(raw.into())
}
/// External ids for one TV series, of which the TVDB id is the one this
/// project needs (§6.1).
///
/// # Errors
///
/// [`Error::NotFound`] when TMDB has no such id, otherwise any of [`Error`].
pub async fn series_external_ids(&self, tmdb_id: u32) -> Result<ExternalIds> {
let path = format!("tv/{tmdb_id}/external_ids");
let raw: RawExternalIds = self.get_json(&path, &[]).await?;
Ok(ExternalIds {
tvdb_id: raw.tvdb_id,
})
}
/// Episodes in one TV season.
///
/// # Errors
+3 -1
View File
@@ -23,4 +23,6 @@ mod model;
pub use client::{TmdbClient, TmdbClientBuilder, DEFAULT_BASE_URL, DEFAULT_CACHE_TTL};
pub use error::{Error, Result};
pub use model::{Episode, Movie, MovieSearchResult, Season, Series, SeriesSearchResult};
pub use model::{
Episode, ExternalIds, Movie, MovieSearchResult, Season, Series, SeriesSearchResult,
};
+9 -2
View File
@@ -218,9 +218,16 @@ pub(crate) struct RawSeries {
external_ids: Option<RawExternalIds>,
}
/// The slice of `/tv/{id}/external_ids` this project reads.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExternalIds {
/// The Torznab `tvdbid` parameter (§6.1), when TMDB knows one.
pub tvdb_id: Option<u32>,
}
#[derive(Debug, Deserialize)]
struct RawExternalIds {
tvdb_id: Option<u32>,
pub(crate) struct RawExternalIds {
pub(crate) tvdb_id: Option<u32>,
}
#[derive(Debug, Deserialize)]
+11
View File
@@ -0,0 +1,11 @@
{
"id": 82728,
"imdb_id": "tt7614372",
"freebase_id": "/m/0vpgz0b",
"freebase_mid": "/m/0vpgz0b",
"tvdb_id": 361391,
"tvrage_id": null,
"facebook_id": "OfficialBluey",
"instagram_id": "officialblueytv",
"twitter_id": "officialbluey"
}
+42
View File
@@ -18,6 +18,7 @@ const MOVIE_CIDADE_DE_DEUS: &str = include_str!("fixtures/movie_cidade_de_deus.j
const MOVIE_UNRELEASED: &str = include_str!("fixtures/movie_unreleased.json");
const MOVIE_THEATRICAL_ONLY: &str = include_str!("fixtures/movie_theatrical_only.json");
const MOVIE_FUTURE_DIGITAL: &str = include_str!("fixtures/movie_future_digital.json");
const SERIES_EXTERNAL_IDS: &str = include_str!("fixtures/series_external_ids.json");
fn client(server: &MockServer) -> TmdbClient {
TmdbClient::builder("test-key")
@@ -461,3 +462,44 @@ async fn debug_output_does_not_leak_the_api_key() {
assert!(!rendered.contains("test-key"), "{rendered}");
assert!(rendered.contains("redacted"), "{rendered}");
}
/// §6.1: `t=tvsearch` is addressed by TVDB id, and `/tv/{id}/external_ids` is
/// where TMDB keeps the mapping.
#[tokio::test]
async fn series_external_ids_carries_the_tvdb_id() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/3/tv/82728/external_ids"))
.respond_with(ResponseTemplate::new(200).set_body_string(SERIES_EXTERNAL_IDS))
.expect(1)
.mount(&server)
.await;
let ids = client(&server)
.series_external_ids(82_728)
.await
.expect("lookup succeeds");
assert_eq!(ids.tvdb_id, Some(361_391));
}
/// A series TMDB has no TVDB id for stays null rather than zero, so callers
/// can tell "unknown" from a real id and fall back to the text query.
#[tokio::test]
async fn series_without_a_tvdb_id_maps_to_none() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/3/tv/1/external_ids"))
.respond_with(
ResponseTemplate::new(200).set_body_string(r#"{"id": 1, "tvdb_id": null}"#),
)
.mount(&server)
.await;
let ids = client(&server)
.series_external_ids(1)
.await
.expect("lookup succeeds");
assert_eq!(ids.tvdb_id, None);
}