nako-metadata-scraper 0.1.0-alpha.2

Official Nako metadata scraper Addon Sidecar.
Documentation
use nako_addon_protocol::{AddonManifest, AddonSecretReferenceFieldDeclaration};
use nako_official_addon_catalog::metadata_scraper;

use crate::{
    Config,
    config::{AvFieldPolicyPreset, AvProviderPreset, ProviderConfig},
    providers::ProviderRegistry,
};

pub const ADDON_ID: &str = metadata_scraper::ADDON_ID;
pub const ADDON_NAME: &str = metadata_scraper::ADDON_NAME;
pub const ADDON_VERSION: &str = env!("CARGO_PKG_VERSION");

#[must_use]
pub fn addon_manifest(config: &Config) -> AddonManifest {
    let mut manifest = metadata_scraper::manifest_with_version(
        ADDON_VERSION,
        config.base_url.clone(),
        config.preferred_language.clone(),
        provider_toggles(config),
        secret_reference_fields(config),
    );
    add_av_provider_preset_schema(&mut manifest, config.av_provider_preset);
    add_av_field_policy_preset_schema(&mut manifest, config.av_field_policy_preset);
    manifest
}

#[must_use]
fn secret_reference_fields(config: &Config) -> Vec<AddonSecretReferenceFieldDeclaration> {
    ProviderRegistry::secret_reference_fields(config)
}

#[must_use]
fn provider_toggles(config: &Config) -> Vec<metadata_scraper::ProviderToggle> {
    config.providers.iter().map(provider_toggle).collect()
}

#[must_use]
fn provider_toggle(provider: &ProviderConfig) -> metadata_scraper::ProviderToggle {
    metadata_scraper::ProviderToggle::new(provider.id.as_str(), provider.enabled)
}

fn add_av_provider_preset_schema(manifest: &mut AddonManifest, preset: AvProviderPreset) {
    add_string_enum_schema(
        manifest,
        "av_provider_preset",
        AvProviderPreset::SCHEMA_VALUES,
        preset.as_str(),
    );
}

fn add_av_field_policy_preset_schema(manifest: &mut AddonManifest, preset: AvFieldPolicyPreset) {
    add_string_enum_schema(
        manifest,
        "av_field_policy_preset",
        AvFieldPolicyPreset::SCHEMA_VALUES,
        preset.as_str(),
    );
}

fn add_string_enum_schema(
    manifest: &mut AddonManifest,
    field: &str,
    values: &[&str],
    default: &str,
) {
    let Some(configuration_schema) = manifest.configuration_schema.as_mut() else {
        return;
    };
    let Some(properties) = configuration_schema
        .schema
        .get_mut("properties")
        .and_then(|value| value.as_object_mut())
    else {
        return;
    };

    properties.insert(
        field.to_owned(),
        serde_json::json!({
            "type": "string",
            "enum": values,
            "default": default,
        }),
    );
}

#[cfg(test)]
mod tests {
    use nako_addon_protocol::{AddonScope, validate_manifest};

    use super::*;
    use crate::config::{
        AniListProviderConfig, AvFieldPolicyPreset, AvProviderPreset, BangumiProviderConfig,
        ProviderConfig, ProviderId, TmdbProviderConfig,
    };
    use crate::engine::bulk::{BULK_METADATA_SCRAPE_TASK_ID, BULK_METADATA_SCRAPE_TASK_PATH};
    use crate::providers::{
        dmm::DmmProviderConfig, javbus::JavbusProviderConfig, theporndb::ThePornDbProviderConfig,
    };

    #[test]
    fn addon_manifest_is_valid() {
        let config = Config::default();
        let manifest = addon_manifest(&config);
        let mut expected_manifest = metadata_scraper::manifest_with_version(
            ADDON_VERSION,
            metadata_scraper::DEFAULT_BASE_URL,
            metadata_scraper::DEFAULT_LANGUAGE,
            provider_toggles(&config),
            Vec::new(),
        );
        add_av_provider_preset_schema(&mut expected_manifest, AvProviderPreset::default());
        add_av_field_policy_preset_schema(&mut expected_manifest, AvFieldPolicyPreset::default());

        validate_manifest(&manifest).unwrap();
        assert_eq!(manifest.id, ADDON_ID);
        assert_eq!(manifest, expected_manifest);
        assert_eq!(
            manifest.resources[0].path,
            metadata_scraper::METADATA_RESOURCE_PATH
        );
        assert_eq!(manifest.tasks.len(), 1);
        assert_eq!(manifest.tasks[0].id, BULK_METADATA_SCRAPE_TASK_ID);
        assert_eq!(manifest.tasks[0].path, BULK_METADATA_SCRAPE_TASK_PATH);
        assert_eq!(
            manifest.tasks[0].required_scopes,
            vec![AddonScope::AutomationRun]
        );
    }

    #[test]
    fn addon_manifest_exposes_bulk_metadata_task() {
        let manifest = addon_manifest(&Config::default());

        assert_eq!(manifest.tasks.len(), 1);
    }

    #[test]
    fn addon_manifest_configuration_schema_declares_only_runtime_supported_providers() {
        let manifest = addon_manifest(&Config::default());
        let schema = &manifest.configuration_schema.unwrap().schema;
        let provider_properties = &schema["properties"]["providers"]["properties"];

        assert_eq!(
            schema["properties"]["av_provider_preset"]["default"],
            "manual"
        );
        assert_eq!(
            schema["properties"]["av_provider_preset"]["enum"],
            serde_json::json!([
                "manual",
                "fast_safe",
                "official_only",
                "community_first",
                "fc2_enhanced",
                "uncensored_official"
            ])
        );
        assert_eq!(
            schema["properties"]["av_field_policy_preset"]["default"],
            "default"
        );
        assert_eq!(
            schema["properties"]["av_field_policy_preset"]["enum"],
            serde_json::json!(["default", "quality_scores", "none"])
        );
        assert_eq!(provider_properties["fixture"]["default"], true);
        assert_eq!(provider_properties["tmdb"]["default"], false);
        assert_eq!(provider_properties["bangumi"]["default"], false);
        assert_eq!(provider_properties["browser_worker"]["default"], false);
        assert_eq!(provider_properties["douban"]["default"], false);
        assert_eq!(provider_properties["javdb"]["default"], false);
        assert_eq!(provider_properties["dmm"]["default"], false);
        assert_eq!(provider_properties["xcity"]["default"], false);
        assert_eq!(provider_properties["fc2"]["default"], false);
        assert_eq!(provider_properties["fc2ppvdb"]["default"], false);
        assert_eq!(provider_properties["caribbean"]["default"], false);
        assert_eq!(provider_properties["1pondo"]["default"], false);
        assert_eq!(provider_properties["10musume"]["default"], false);
        assert_eq!(provider_properties["jav321"]["default"], false);
        assert_eq!(provider_properties["javbus"]["default"], false);
        assert_eq!(provider_properties["javlibrary"]["default"], false);
        assert_eq!(provider_properties["airav"]["default"], false);
        assert_eq!(provider_properties["avsox"]["default"], false);
        assert_eq!(provider_properties["mgstage"]["default"], false);
        assert_eq!(provider_properties["prestige"]["default"], false);
        assert_eq!(provider_properties["theporndb"]["default"], false);
        assert_eq!(provider_properties["anilist"]["default"], false);
        assert!(manifest.secret_reference_fields.is_empty());
    }

    #[test]
    fn addon_manifest_configuration_schema_reflects_configured_provider_defaults() {
        let config = Config::from_env_lookup(|name| match name {
            "NAKO_METADATA_SCRAPER_PROVIDER_FIXTURE_ENABLED" => Some("false".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_TMDB_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_BANGUMI_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_BROWSER_WORKER_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_DOUBAN_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_JAVDB_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_DMM_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_XCITY_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_FC2_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_FC2PPVDB_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_CARIBBEAN_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_1PONDO_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_10MUSUME_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_JAV321_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_JAVBUS_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_JAVLIBRARY_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_AIRAV_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_AVSOX_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_MGSTAGE_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_PRESTIGE_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_THEPORNDB_ENABLED" => Some("true".to_owned()),
            "NAKO_METADATA_SCRAPER_PROVIDER_ANILIST_ENABLED" => Some("true".to_owned()),
            _ => None,
        });
        let manifest = addon_manifest(&config);
        let schema = &manifest.configuration_schema.unwrap().schema;

        assert_eq!(
            schema["properties"]["providers"]["properties"]["fixture"]["default"],
            false
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["tmdb"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["bangumi"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["browser_worker"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["douban"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["javdb"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["dmm"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["xcity"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["fc2"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["fc2ppvdb"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["caribbean"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["1pondo"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["10musume"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["jav321"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["javbus"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["javlibrary"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["airav"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["avsox"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["mgstage"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["prestige"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["theporndb"]["default"],
            true
        );
        assert_eq!(
            schema["properties"]["providers"]["properties"]["anilist"]["default"],
            true
        );
        assert_eq!(manifest.secret_reference_fields.len(), 6);
        assert_eq!(
            manifest.secret_reference_fields[0].id,
            TmdbProviderConfig::secret_field_id()
        );
        assert!(manifest.secret_reference_fields[0].required);
        assert_eq!(
            manifest.secret_reference_fields[1].id,
            BangumiProviderConfig::secret_field_id()
        );
        assert!(!manifest.secret_reference_fields[1].required);
        assert_eq!(
            manifest.secret_reference_fields[2].id,
            DmmProviderConfig::secret_field_id()
        );
        assert!(!manifest.secret_reference_fields[2].required);
        assert_eq!(
            manifest.secret_reference_fields[3].id,
            JavbusProviderConfig::secret_field_id()
        );
        assert!(!manifest.secret_reference_fields[3].required);
        assert_eq!(
            manifest.secret_reference_fields[4].id,
            ThePornDbProviderConfig::secret_field_id()
        );
        assert!(manifest.secret_reference_fields[4].required);
        assert_eq!(
            manifest.secret_reference_fields[5].id,
            AniListProviderConfig::secret_field_id()
        );
        assert!(!manifest.secret_reference_fields[5].required);
    }

    #[test]
    fn checked_in_example_manifest_matches_runtime_manifest() {
        let example_manifest: AddonManifest = serde_json::from_str(include_str!(
            "../../../addons/metadata-scraper/manifest.example.json"
        ))
        .unwrap();
        let runtime_manifest = addon_manifest(&Config {
            base_url: "http://nako-metadata-scraper:9100".to_owned(),
            av_provider_preset: AvProviderPreset::default(),
            providers: vec![
                ProviderConfig::enabled(ProviderId::Fixture),
                ProviderConfig::disabled(ProviderId::Tmdb),
                ProviderConfig::disabled(ProviderId::Bangumi),
                ProviderConfig::disabled(ProviderId::BrowserWorker),
                ProviderConfig::disabled(ProviderId::Douban),
                ProviderConfig::disabled(ProviderId::Javdb),
                ProviderConfig::disabled(ProviderId::Dmm),
                ProviderConfig::disabled(ProviderId::Xcity),
                ProviderConfig::disabled(ProviderId::Fc2),
                ProviderConfig::disabled(ProviderId::Fc2ppvdb),
                ProviderConfig::disabled(ProviderId::Caribbean),
                ProviderConfig::disabled(ProviderId::OnePondo),
                ProviderConfig::disabled(ProviderId::TenMusume),
                ProviderConfig::disabled(ProviderId::Jav321),
                ProviderConfig::disabled(ProviderId::Javbus),
                ProviderConfig::disabled(ProviderId::Javlibrary),
                ProviderConfig::disabled(ProviderId::Airav),
                ProviderConfig::disabled(ProviderId::Avsox),
                ProviderConfig::disabled(ProviderId::Mgstage),
                ProviderConfig::disabled(ProviderId::Prestige),
                ProviderConfig::disabled(ProviderId::ThePornDb),
                ProviderConfig::disabled(ProviderId::AniList),
            ],
            ..Config::default()
        });

        assert_eq!(example_manifest, runtime_manifest);
    }
}