commit 580ec1932207884afc52688890b6bfef585e23ae Author: Jamie Miller Date: Fri Sep 26 10:57:00 2025 +0000 Initial fresh commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ee4f224 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +# Use a minimal base image with Perl +FROM alpine:latest + +# Set a working directory +WORKDIR /app + +# Install system dependencies and Perl +RUN apk update && \ + apk add --no-cache \ + perl \ + perl-app-cpanminus \ + perl-lwp-useragent-determined \ + perl-io-socket-ssl \ + perl-net-ssleay \ + perl-lwp-protocol-https \ + make \ + gcc \ + musl-dev \ + curl && \ + \ + # Install required Perl modules via CPAN + cpanm --notest \ + JSON::Tiny \ + LWP::UserAgent \ + LWP::Protocol::https \ + Time::Duration \ + URI::Escape \ + Getopt::Std && \ + \ + # Clean up build dependencies and cache + apk del make gcc musl-dev && \ + rm -rf /var/cache/apk/* /root/.cpanm + +# Copy the Perl script into the container +COPY poller.pl /app/poller.pl + +# Fix line endings and make the script executable +RUN sed -i 's/\r$//' /app/poller.pl && \ + chmod +x /app/poller.pl + +# Define the default command +# Using perl explicitly and forcing unbuffered output +CMD ["perl", "-u", "/app/poller.pl"] \ No newline at end of file diff --git a/docker-compose.yaml.example b/docker-compose.yaml.example new file mode 100644 index 0000000..63fb159 --- /dev/null +++ b/docker-compose.yaml.example @@ -0,0 +1,39 @@ +services: + lidarr: + image: lscr.io/linuxserver/lidarr:latest + container_name: lidarr + environment: + - PUID=1000 + - PGID=1000 + - TZ=${TZ} + volumes: + - /path/to/lidarr/config:/config + - /path/to/data/:/data/ + - path/to/downloads/:/downloads + ports: + - 8686:8686 + networks: + example_network: + ipv4_address: 172.18.0.1 + restart: unless-stopped + + mbid-poller: + image: gitea.kansaigaijin.com/KansaiGaijin + container_name: mbid-poller + environment: + # Common: The URL for your Lidarr instance + - LIDARR_BASE=http://lidarr:8686 + - MBID_API_URL= # OPTION 1: API URL (Current default, highest priority) + - MBID_JSON_FILE=/config/ids.json # OPTION 2: JSON File (To use, comment out MBID_API_URL and uncomment this lines) + - MBID_URL=https://musicbrainz.org/work/69755ab1-409e-3ad7-902f-3a839042799c # OPTION 3: Single ID/URL (To use, comment out the API URL and the JSON File lines) + volumes: + - ./ids.json:/config/ids.json:ro + networks: + example_network: # Same network as Lidarr + ipv4_address: 172.18.0.2 + restart: "no" # Run once + command: ["/app/poller.pl"] + +networks: + example_network: + external: true \ No newline at end of file diff --git a/ids.json b/ids.json new file mode 100644 index 0000000..77d9b86 --- /dev/null +++ b/ids.json @@ -0,0 +1,4 @@ +[ + {"foreignId":"db92a151-1ac2-438b-bc43-b82e149ddd50"}, + {"foreignId":"a6c6897a-7415-4f8d-b5a5-3a5e05f3be67"} +] \ No newline at end of file diff --git a/poller.pl b/poller.pl new file mode 100644 index 0000000..cadcf34 --- /dev/null +++ b/poller.pl @@ -0,0 +1,153 @@ +#!/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'; + +# ANSI escape sequences +sub ERASE_EOL { return "\033[K"; } +sub CURSOR_BACK { return "\033[${_[0]}D"; } +sub STATUS { return $_[0] . ERASE_EOL . CURSOR_BACK(length($_[0])) } + +getopts('bfhv'); + +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 (ping_api and validate remain the same as your original script) + +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 STATUS("- attempt $loops"); + my $res = $ua->get($api); + my $status = $res->code; + if ($res->is_success) { + eval { + $json = decode_json($res->content); + }; + if ($@) { + chomp $@; + warn "Retrying: $@\n"; + } elsif ($fresh_result and $res->header('cf-cache-status') eq 'STALE') { + $status .= ' STALE'; + } else { + print STDERR ERASE_EOL; + last; + } + } + print STDERR STATUS("- attempt $loops: $status"); + 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'); + } \ No newline at end of file