#!/usr/bin/env perl # -*- Perl -*- use strict; use warnings; use open qw(:std :encoding(UTF-8)); use Data::Dumper qw(Dumper); use Getopt::Std; use IO::Handle; use JSON::Tiny qw(decode_json); use LWP::UserAgent; use Time::Duration; use URI::Escape; # --- Environment Variables --- use constant LIDARR_BASE => $ENV{LIDARR_BASE} || 'http://localhost:8686'; getopts('f'); my $fresh_result = defined $::opt_f ? $::opt_f : 0; # vs STALE # Define the list of IDs to process my @ids_to_process; my $ua = new LWP::UserAgent; # Initialize LWP::UserAgent my $json_content; # --- Input Logic: Prioritized Checks --- # 1. Check for API URL (Highest Priority) if (my $api_url = $ENV{MBID_API_URL}) { print STDERR "Fetching IDs from API URL: $api_url\n"; my $res = $ua->get($api_url); unless ($res->is_success) { die "FATAL: Failed to fetch data from API: " . $res->status_line . "\n"; } $json_content = $res->content; } # 2. Check for JSON File (Second Priority) elsif (my $json_file = $ENV{MBID_JSON_FILE}) { print STDERR "Loading IDs from JSON file: $json_file\n"; open my $fh, '<:encoding(UTF-8)', $json_file or die "Could not open $json_file: $!"; $json_content = do { local $/; <$fh> }; close $fh; } # 3. Check for a single URL/ID (Lowest Priority) elsif (my $single_url = $ENV{MBID_URL}) { push @ids_to_process, $single_url; } else { die "FATAL: Must set MBID_API_URL, MBID_JSON_FILE, OR MBID_URL.\n"; } # --- JSON Parsing Logic (Applies to API URL and JSON File) --- if ($json_content) { my $data; eval { $data = decode_json($json_content); }; if ($@) { die "Error decoding JSON from source: $@\n"; } # Extract the 'foreignId' from each object in the array if (ref $data eq 'ARRAY') { foreach my $item (@$data) { if (ref $item eq 'HASH' && exists $item->{foreignId}) { push @ids_to_process, $item->{foreignId}; } else { warn "Skipping malformed item in input (missing 'foreignId').\n"; } } } else { die "Input content was not a JSON array.\n"; } } # --- Main Processing Loop --- foreach my $id (@ids_to_process) { chomp $id; print "\n--- Processing: $id ---\n"; my $type = 'artist'; # Assuming 'foreignId' provides an artist MBID my $json = ping_api($type, $id); print "- add-to-lidarr: " . LIDARR_BASE . "/add/search?term=" . uri_escape("lidarr:$id") . "\n"; } ## subroutines sub ping_api { my ($type, $id) = @_; my $ua = new LWP::UserAgent; my $start = time(); my $loops = 0; my $api = "https://api.lidarr.audio/api/v0.4/$type/$id"; my $json; print STDERR "Pinging $api\n"; while (1) { $loops++; print STDERR "- attempt $loops...\n"; my $res = $ua->get($api); my $status = $res->code; if ($res->is_success) { eval { $json = decode_json($res->content); }; if ($@) { chomp $@; warn "Retrying: JSON decode failed: $@\n"; } elsif ($fresh_result and $res->header('cf-cache-status') eq 'STALE') { $status .= ' STALE'; warn "Retrying: Response is STALE ($status)\n"; } else { # *** VALIDATION CHECK *** my @validation_warnings = validate($type, $json); if (@validation_warnings) { warn "Retrying: Validation failed: " . join(', ', @validation_warnings) . "\n"; } else { # Success: HTTP 2xx, JSON parsed, and content passed validation last; } } } else { # Log failure if not success and retry warn "Retrying: HTTP failed with status $status\n"; } sleep 5; } my $elapsed = time() - $start; print "- ready ($loops attempts, " . duration($elapsed) . ")\n" if $loops > 1; if ($type eq 'artist') { print "- artist: " . $json->{artistname} . "\n"; } else { print "- album: " . $json->{title} . "\n"; } return $json; } sub validate { my ($type, $json) = @_; my @warnings; if ($type eq 'artist') { # make sure there are albums unless (exists $json->{Albums} and scalar @{$json->{Albums}}) { push(@warnings, 'no albums'); } } else { # detect Unknown Artist problem unless (exists $json->{artists} and scalar @{$json->{artists}} and exists $json->{artists}[0]{artistname} and $json->{artists}[0]{artistname} !~ m%^Unknown Artist \(%) { push(@warnings, 'no artist'); } unless (exists $json->{Releases} and scalar @{$json->{Releases}}) { push(@warnings, 'no releases'); } else { unless (exists $json->{Releases}[0]{Tracks} and scalar @{$json->{Releases}[0]{Tracks}}) { push(@warnings, 'no tracks'); } } } return @warnings; }