diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 97197e6..7c6c5b7 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -25,7 +25,6 @@ jobs: python3 -m pytest --import-mode=append tests/ build-dev: - # if: github.ref == 'refs/heads/dev' needs: unit-tests runs-on: ubuntu-latest defaults: @@ -35,6 +34,12 @@ jobs: - name: 'Checkout GitHub Action' uses: actions/checkout@main + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v1 + + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v1 + - name: 'Login to GitHub Container Registry' uses: docker/login-action@v1 with: @@ -42,14 +47,43 @@ jobs: username: ${{github.actor}} password: ${{secrets.GITHUB_TOKEN}} + - name: Store short Commit ID in env variable + id: vars + run: | + calculatedSha=$(git rev-parse --short ${{ github.sha }}) + echo "SHORT_COMMIT_ID=$calculatedSha" >> $GITHUB_ENV + - name: "Build, Tag, and push the Docker image" env: IMAGE_NAME: ghcr.io/manimatter/decluttarr IMAGE_TAG: dev run: | - docker build -f docker/Dockerfile -t $IMAGE_NAME:$IMAGE_TAG . - docker push $IMAGE_NAME:$IMAGE_TAG - + docker build \ + --progress plain \ + -t $IMAGE_NAME:$IMAGE_TAG \ + --label com.decluttarr.version=$IMAGE_TAG \ + --label com.decluttarr.commit=$SHORT_COMMIT_ID \ + --build-arg IMAGE_TAG=$IMAGE_TAG \ + --build-arg SHORT_COMMIT_ID=$SHORT_COMMIT_ID \ + -f docker/Dockerfile \ + --push . + + # - name: "Build, Tag, and push the Docker image" + # env: + # IMAGE_NAME: ghcr.io/manimatter/decluttarr + # IMAGE_TAG: dev + # run: | + # docker buildx build \ <<<<<<<<<<<<<<<<<<<<< creates manifest issues... don't know how to solve that + # --platform linux/amd64,linux/arm64 \ <<<<<<<<<<<<<<<<<<<<< creates manifest issues... don't know how to solve that + # --progress plain \ + # -t $IMAGE_NAME:$IMAGE_TAG \ + # --label com.decluttarr.version=$IMAGE_TAG \ + # --label com.decluttarr.commit=$SHORT_COMMIT_ID \ + # --build-arg IMAGE_TAG=$IMAGE_TAG \ + # --build-arg SHORT_COMMIT_ID=$SHORT_COMMIT_ID \ + # -f docker/Dockerfile \ + # --push . + - name: "Delete untagged versions" uses: actions/delete-package-versions@v4 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index abad074..7b468f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,11 +18,11 @@ jobs: - name: 'Checkout GitHub Action' uses: actions/checkout@main - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v1 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v1 - name: 'Login to GitHub Container Registry' uses: docker/login-action@v1 @@ -42,17 +42,41 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WITH_V: true + - name: Store short Commit ID in env variable + id: vars + run: | + calculatedSha=$(git rev-parse --short ${{ github.sha }}) + echo "SHORT_COMMIT_ID=$calculatedSha" >> $GITHUB_ENV + - name: "Build, Tag, and push the Docker image" env: IMAGE_NAME: ghcr.io/manimatter/decluttarr IMAGE_TAG: ${{ steps.setversion.outputs.new_tag }} run: | - docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME:$IMAGE_TAG -t $IMAGE_NAME:latest -f docker/Dockerfile --push . + docker build \ + --progress plain \ + -t $IMAGE_NAME:$IMAGE_TAG \ + -t $IMAGE_NAME:latest \ + --label com.decluttarr.version=$IMAGE_TAG \ + --label com.decluttarr.commit=$SHORT_COMMIT_ID \ + --build-arg IMAGE_TAG=$IMAGE_TAG \ + --build-arg SHORT_COMMIT_ID=$SHORT_COMMIT_ID \ + -f docker/Dockerfile \ + --push . # - name: "Build, Tag, and push the Docker image" # env: # IMAGE_NAME: ghcr.io/manimatter/decluttarr - # IMAGE_TAG: latest + # IMAGE_TAG: ${{ steps.setversion.outputs.new_tag }} # run: | - # docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME:$IMAGE_TAG -f docker/Dockerfile --push . - + # docker buildx build \ <<<<<<<<<<<<<<<<<<<<< creates manifest issues... don't know how to solve that + # --platform linux/amd64,linux/arm64 \ <<<<<<<<<<<<<<<<<<<<< creates manifest issues... don't know how to solve that + # --progress plain \ + # -t $IMAGE_NAME:$IMAGE_TAG \ + # -t $IMAGE_NAME:latest \ + # --label com.decluttarr.version=$IMAGE_TAG \ + # --label com.decluttarr.commit=$SHORT_COMMIT_ID \ + # --build-arg IMAGE_TAG=$IMAGE_TAG \ + # --build-arg SHORT_COMMIT_ID=$SHORT_COMMIT_ID \ + # -f docker/Dockerfile \ + # --push . \ No newline at end of file diff --git a/README.md b/README.md index 009abe9..0a50bcd 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,9 @@ services: # Lidarr - LIDARR_URL=http://lidarr:8686 - LIDARR_KEY=$LIDARR_API_KEY + # Readarr + - READARR_URL=http://readarr:8787 + - READARR_KEY=$READARR_API_KEY # qBittorrent - QBITTORRENT_URL=http://qbittorrent:8080 #- QBITTORRENT_USERNAME=Your name @@ -227,6 +230,18 @@ Note: The `config.conf` is disregarded when running via docker-compose.yml --- +### **-Readarr section** +- Defines readarr instance on which download queue should be decluttered + +**READARR_URL** +- URL under which the instance can be reached +- If not defined, this instance will not be monitored + +**READARR_KEY** +- Your API key for readarr + +--- + ### **qBittorrent section** - Defines settings to connect with qBittorrent diff --git a/config/config.conf-Example b/config/config.conf-Example index 5127c02..1ad6cad 100644 --- a/config/config.conf-Example +++ b/config/config.conf-Example @@ -29,6 +29,10 @@ SONARR_KEY = $SONARR_API_KEY LIDARR_URL = http://lidarr:8686 LIDARR_KEY = $LIDARR_API_KEY +[readarr] +READARR_URL = http://lidarr:8787 +READARR_KEY = $READARR_API_KEY + [qbittorrent] QBITTORRENT_URL = http://qbittorrent:8080 QBITTORRENT_USERNAME = Your name (or empty) diff --git a/config/config.py b/config/config.py index b882834..575bc29 100644 --- a/config/config.py +++ b/config/config.py @@ -6,6 +6,8 @@ ######################################################################################################################## # Check if in Docker IS_IN_DOCKER = os.environ.get('IS_IN_DOCKER') +IMAGE_TAG = os.environ.get('IMAGE_TAG', 'Local') +SHORT_COMMIT_ID = os.environ.get('SHORT_COMMIT_ID', 'n/a') ######################################################################################################################## def config_section_map(section): @@ -108,6 +110,11 @@ def get_config_value(key, config_section, is_mandatory, datatype, default_value LIDARR_KEY = None if LIDARR_URL == None else \ get_config_value('LIDARR_KEY', 'lidarr', True, str) +# Readarr +READARR_URL = get_config_value('READARR_URL', 'readarr', False, str) +READARR_KEY = None if READARR_URL == None else \ + get_config_value('READARR_KEY', 'readarr', True, str) + # qBittorrent QBITTORRENT_URL = get_config_value('QBITTORRENT_URL', 'qbittorrent', False, str, '') QBITTORRENT_USERNAME = get_config_value('QBITTORRENT_USERNAME', 'qbittorrent', False, str, '') @@ -115,14 +122,15 @@ def get_config_value(key, config_section, is_mandatory, datatype, default_value ######################################################################################################################## ########### Validate settings -if not (RADARR_URL or SONARR_URL or LIDARR_URL): - print(f'[ ERROR ]: No Radarr/Sonarr/Lidarr URLs specified (nothing to monitor)') +if not (RADARR_URL or SONARR_URL or LIDARR_URL or READARR_URL): + print(f'[ ERROR ]: No Radarr/Sonarr/Lidarr/Readarr URLs specified (nothing to monitor)') sys.exit(0) ########### Enrich setting variables if RADARR_URL: RADARR_URL += '/api/v3' if SONARR_URL: SONARR_URL += '/api/v3' if LIDARR_URL: LIDARR_URL += '/api/v1' +if READARR_URL: READARR_URL += '/api/v1' if QBITTORRENT_URL: QBITTORRENT_URL += '/api/v2' ########### Add Variables to Dictionary diff --git a/docker/Dockerfile b/docker/Dockerfile index e584110..72d41d3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,17 @@ #FROM python:3.9-slim-buster +# For debugging: +# sudo docker run --rm -it --entrypoint sh ghcr.io/manimatter/decluttarr:dev + FROM python:3.10.13-slim + +# Define a build-time argument for IMAGE_TAG +ARG IMAGE_TAG +ARG SHORT_COMMIT_ID + +# Set an environment variable using the build-time argument +ENV IMAGE_TAG=$IMAGE_TAG +ENV SHORT_COMMIT_ID=$SHORT_COMMIT_ID + LABEL org.opencontainers.image.source="https://github.com/ManiMatter/decluttarr" ENV IS_IN_DOCKER 1 diff --git a/main.py b/main.py index 0e4be74..0a864c9 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ import requests import platform from packaging import version + ########### Enabling Logging # Set up logging log_level_num=logging.getLevelName(settings_dict['LOG_LEVEL']) @@ -49,12 +50,21 @@ async def main(): except: settings_dict['LIDARR_NAME'] = 'Lidarr' + try: + if settings_dict['READARR_URL']: + settings_dict['READARR_NAME'] = (await rest_get(settings_dict['READARR_URL']+'/system/status', settings_dict['READARR_KEY']))['instanceName'] + except: + settings_dict['READARR_NAME'] = 'Readarr' + # Print Settings fmt = '{0.days} days {0.hours} hours {0.minutes} minutes' logger.info('#' * 50) - logger.info('Application Started!') + logger.info('Decluttarr - Application Started!') logger.info('') logger.info('*** Current Settings ***') + logger.info('Version: %s', settings_dict['IMAGE_TAG']) + logger.info('Commit: %s', settings_dict['SHORT_COMMIT_ID']) + logger.info('') logger.info('%s | Removing failed downloads', str(settings_dict['REMOVE_FAILED'])) logger.info('%s | Removing downloads missing metadata', str(settings_dict['REMOVE_METADATA_MISSING'])) logger.info('%s | Removing downloads missing files', str(settings_dict['REMOVE_MISSING_FILES'])) @@ -76,6 +86,7 @@ async def main(): if settings_dict['RADARR_URL']: logger.info('%s: %s', settings_dict['RADARR_NAME'], settings_dict['RADARR_URL']) if settings_dict['SONARR_URL']: logger.info('%s: %s', settings_dict['SONARR_NAME'], settings_dict['SONARR_URL']) if settings_dict['LIDARR_URL']: logger.info('%s: %s', settings_dict['LIDARR_NAME'], settings_dict['LIDARR_URL']) + if settings_dict['READARR_URL']: logger.info('%s: %s', settings_dict['READARR_NAME'], settings_dict['READARR_URL']) if settings_dict['QBITTORRENT_URL']: logger.info('qBittorrent: %s', settings_dict['QBITTORRENT_URL']) logger.info('') @@ -117,6 +128,14 @@ async def main(): error_occured = True logger.error('-- | %s *** Error: %s ***', settings_dict['LIDARR_NAME'], error) + if settings_dict['READARR_URL']: + try: + await asyncio.get_event_loop().run_in_executor(None, lambda: requests.get(settings_dict['READARR_URL']+'/system/status', params=None, headers={'X-Api-Key': settings_dict['READARR_KEY']})) + logger.info('OK | %s', settings_dict['READARR_NAME']) + except Exception as error: + error_occured = True + logger.error('-- | %s *** Error: %s ***', settings_dict['READARR_NAME'], error) + if settings_dict['QBITTORRENT_URL']: # Checking if qbit can be reached, and checking if version is OK try: @@ -186,6 +205,7 @@ async def main(): if settings_dict['RADARR_URL']: await queueCleaner(settings_dict, 'radarr', defective_tracker, download_sizes_tracker, protectedDownloadIDs, privateDowloadIDs) if settings_dict['SONARR_URL']: await queueCleaner(settings_dict, 'sonarr', defective_tracker, download_sizes_tracker, protectedDownloadIDs, privateDowloadIDs) if settings_dict['LIDARR_URL']: await queueCleaner(settings_dict, 'lidarr', defective_tracker, download_sizes_tracker, protectedDownloadIDs, privateDowloadIDs) + if settings_dict['READARR_URL']: await queueCleaner(settings_dict, 'readarr', defective_tracker, download_sizes_tracker, protectedDownloadIDs, privateDowloadIDs) logger.verbose('') logger.verbose('Queue clean-up complete!') await asyncio.sleep(settings_dict['REMOVE_TIMER']*60) @@ -194,8 +214,10 @@ async def main(): if __name__ == '__main__': instances = {settings_dict['RADARR_URL']: {}} if settings_dict['RADARR_URL'] else {} + \ {settings_dict['SONARR_URL']: {}} if settings_dict['SONARR_URL'] else {} + \ - {settings_dict['LIDARR_URL']: {}} if settings_dict['LIDARR_URL'] else {} + {settings_dict['LIDARR_URL']: {}} if settings_dict['LIDARR_URL'] else {} + \ + {settings_dict['READARR_URL']: {}} if settings_dict['READARR_URL'] else {} defective_tracker = Defective_Tracker(instances) download_sizes_tracker = Download_Sizes_Tracker({}) asyncio.run(main()) + diff --git a/src/decluttarr.py b/src/decluttarr.py index 81be668..b507cf1 100644 --- a/src/decluttarr.py +++ b/src/decluttarr.py @@ -34,6 +34,11 @@ async def queueCleaner(settings_dict, arr_type, defective_tracker, download_size API_KEY = settings_dict['LIDARR_KEY'] NAME = settings_dict['LIDARR_NAME'] full_queue_param = 'includeUnknownArtistItems' + elif arr_type == 'readarr': + BASE_URL = settings_dict['READARR_URL'] + API_KEY = settings_dict['READARR_KEY'] + NAME = settings_dict['READARR_NAME'] + full_queue_param = 'includeUnknownAuthorItems' else: logger.error('Unknown arr_type specified, exiting: %s', str(arr_type)) sys.exit() diff --git a/src/remove_unmonitored.py b/src/remove_unmonitored.py index e406951..df32831 100644 --- a/src/remove_unmonitored.py +++ b/src/remove_unmonitored.py @@ -19,7 +19,9 @@ async def remove_unmonitored(settings_dict, BASE_URL, API_KEY, NAME, deleted_dow elif arr_type == 'radarr': isMonitored = (await rest_get(f'{BASE_URL}/movie/{str(queueItem["movieId"])}', API_KEY))['monitored'] elif arr_type == 'lidarr': - isMonitored = (await rest_get(f'{BASE_URL}/album/{str(queueItem["albumId"])}', API_KEY))['monitored'] + isMonitored = (await rest_get(f'{BASE_URL}/album/{str(queueItem["albumId"])}', API_KEY))['monitored'] + elif arr_type == 'readarr': + isMonitored = (await rest_get(f'{BASE_URL}/book/{str(queueItem["bookId"])}', API_KEY))['monitored'] if isMonitored: monitoredDownloadIDs.append(queueItem['downloadId'])