Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6587014e8d | |||
| 36a07b251a | |||
| c48eed7f77 | |||
| 48f3c3b35b | |||
| 0d6f62dd70 | |||
| 77cc5c99ed | |||
| 513134fd65 | |||
| 906be45758 | |||
| c4a15e77e4 | |||
| baffdfdd5a | |||
| 827afb5a4d | |||
| 26939b2cd3 | |||
| 34d05c5416 | |||
| b9376e02fa |
@@ -8,4 +8,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dockerRepository: flaminel/cleanuperr
|
dockerRepository: flaminel/cleanuperr
|
||||||
githubContext: ${{ toJSON(github) }}
|
githubContext: ${{ toJSON(github) }}
|
||||||
|
outputName: cleanuperr
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
# paths:
|
||||||
|
# - 'code/**'
|
||||||
|
# branches: [ main ]
|
||||||
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'code/**'
|
- 'code/**'
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -165,3 +165,6 @@ src/.idea/
|
|||||||
|
|
||||||
# Ignore Jetbrains IntelliJ Workspace Directories
|
# Ignore Jetbrains IntelliJ Workspace Directories
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
**/logs/
|
||||||
|
**/MediaCover/
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Flaminel
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. Add excluded file names to prevent malicious files from being downloaded by qBittorrent.
|
1. Add excluded file names to prevent malicious files from being downloaded by qBittorrent.
|
||||||
2. cleanuperr goes through all items in Sonarr's queue every at every 5th minute.
|
2. cleanuperr goes through all items in Sonarr's queue at every 5th minute.
|
||||||
3. For each queue item, a call is made to qBittorrent to get the stats of the torrent.
|
3. For each queue item, a call is made to qBittorrent to get the stats of the torrent.
|
||||||
4. If a torrent is found to be marked as completed, but with 0 downloaded bytes, cleanuperr calls Sonarr to add that torrent to the blocklist.
|
4. If a torrent is found to be marked as completed, but with 0 downloaded bytes, cleanuperr calls Sonarr to add that torrent to the blocklist.
|
||||||
5. If any malicious torrents have been found, cleanuperr calls Sonarr to automatically search again.
|
5. If any malicious torrents have been found, cleanuperr calls Sonarr to automatically search again.
|
||||||
@@ -12,37 +12,49 @@
|
|||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
```
|
```
|
||||||
docker run \
|
docker run -d \
|
||||||
-e QuartzConfig__BlockedTorrentTrigger="0 0/10 * * * ?" \
|
-e TRIGGERS__QUEUECLEANER="0 0/5 * * * ?" \
|
||||||
-e QBitConfig__Url="http://localhost:8080" \
|
-e QBITTORRENT__URL="http://localhost:8080" \
|
||||||
-e QBitConfig__Username="user" \
|
-e QBITTORRENT__USERNAME="user" \
|
||||||
-e QBitConfig__Password="pass" \
|
-e QBITTORRENT__PASSWORD="pass" \
|
||||||
-e SonarrConfig__Instances__0__Url="http://localhost:8989" \
|
-e SONARR__ENABLED=true \
|
||||||
-e SonarrConfig__Instances__0__ApiKey="secret1" \
|
-e SONARR__INSTANCES__0__URL="http://localhost:8989" \
|
||||||
-e SonarrConfig__Instances__1__Url="http://localhost:8990" \
|
-e SONARR__INSTANCES__0__APIKEY="secret1" \
|
||||||
-e SonarrConfig__Instances__1__ApiKey="secret2" \
|
-e SONARR__INSTANCES__1__URL="http://localhost:8990" \
|
||||||
|
-e SONARR__INSTANCES__1__APIKEY="secret2" \
|
||||||
|
-e RADARR__ENABLED=true \
|
||||||
|
-e RADARR__INSTANCES__0__URL="http://localhost:7878" \
|
||||||
|
-e RADARR__INSTANCES__0__APIKEY="secret3" \
|
||||||
|
-e RADARR__INSTANCES__1__URL="http://localhost:7879" \
|
||||||
|
-e RADARR__INSTANCES__1__APIKEY="secret4" \
|
||||||
...
|
...
|
||||||
flaminel/cleanuperr:latest
|
flaminel/cleanuperr:1.1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
| Variable | Required | Description | Default value |
|
| Variable | Required | Description | Default value |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| QuartzConfig__BlockedTorrentTrigger | No | Quartz cron trigger | 0 0/5 * * * ? |
|
| TRIGGERS__QUEUECLEANER | No | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? |
|
||||||
| QBitConfig__Url | Yes | qBittorrent instance url | http://localhost:8080 |
|
| QBITTORRENT__URL | Yes | qBittorrent instance url | http://localhost:8080 |
|
||||||
| QBitConfig__Username | Yes | qBittorrent user | empty |
|
| QBITTORRENT__USERNAME | Yes | qBittorrent user | empty |
|
||||||
| QBitConfig__Password | Yes | qBittorrent password | empty |
|
| QBITTORRENT__PASSWORD | Yes | qBittorrent password | empty |
|
||||||
| SonarrConfig__Instances__0__Url | Yes | First Sonarr instance url | http://localhost:8989 |
|
|||||
|
||||||
| SonarrConfig__Instances__0__ApiKey | Yes | First Sonarr instance API key | empty |
|
| SONARR__ENABLED | No | Whether Sonarr cleanup is enabled or not | true |
|
||||||
|
| SONARR__INSTANCES__0__URL | Yes | First Sonarr instance url | http://localhost:8989 |
|
||||||
|
| SONARR__INSTANCES__0__APIKEY | Yes | First Sonarr instance API key | empty |
|
||||||
|
|||||
|
||||||
|
| RADARR__ENABLED | No | Whether Radarr cleanup is enabled or not | false |
|
||||||
|
| RADARR__INSTANCES__0__URL | Yes | First Radarr instance url | http://localhost:8989 |
|
||||||
|
| RADARR__INSTANCES__0__APIKEY | Yes | First Radarr instance API key | empty |
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
||||||
Multiple Sonarr instances can be specified using this format:
|
Multiple Sonarr/Radarr instances can be specified using this format:
|
||||||
|
|
||||||
```
|
```
|
||||||
SonarrConfig__Instances__<NUMBER>__Url
|
SONARR__INSTANCES__<NUMBER>__URL
|
||||||
SonarrConfig__Instances__<NUMBER>__ApiKey
|
SONARR__INSTANCES__<NUMBER>__APIKEY
|
||||||
```
|
```
|
||||||
|
|
||||||
where `<NUMBER>` starts from 0.
|
where `<NUMBER>` starts from 0.
|
||||||
@@ -56,96 +68,524 @@ where `<NUMBER>` starts from 0.
|
|||||||
## Extensions to block in qBittorrent
|
## Extensions to block in qBittorrent
|
||||||
<details>
|
<details>
|
||||||
<summary>Extensions</summary>
|
<summary>Extensions</summary>
|
||||||
<pre><code>*.apk
|
<pre><code>*(sample).*
|
||||||
*.bat
|
*.0xe
|
||||||
*.bin
|
*.73k
|
||||||
*.bmp
|
*.73p
|
||||||
*.cmd
|
|
||||||
*.com
|
|
||||||
*.db
|
|
||||||
*.diz
|
|
||||||
*.dll
|
|
||||||
*.dmg
|
|
||||||
*.etc
|
|
||||||
*.exe
|
|
||||||
*.gif
|
|
||||||
*.htm
|
|
||||||
*.html
|
|
||||||
*.ico
|
|
||||||
*.ini
|
|
||||||
*.iso
|
|
||||||
*.jar
|
|
||||||
*.jpg
|
|
||||||
*.js
|
|
||||||
*.link
|
|
||||||
*.lnk
|
|
||||||
*.msi
|
|
||||||
*.nfo
|
|
||||||
*.perl
|
|
||||||
*.php
|
|
||||||
*.pl
|
|
||||||
*.png
|
|
||||||
*.ps1
|
|
||||||
*.psc1
|
|
||||||
*.psd1
|
|
||||||
*.psm1
|
|
||||||
*.py
|
|
||||||
*.pyd
|
|
||||||
*.rb
|
|
||||||
*.readme
|
|
||||||
*.reg
|
|
||||||
*.run
|
|
||||||
*.scr
|
|
||||||
*.sh
|
|
||||||
*.sql
|
|
||||||
*.text
|
|
||||||
*.thumb
|
|
||||||
*.torrent
|
|
||||||
*.txt
|
|
||||||
*.url
|
|
||||||
*.vbs
|
|
||||||
*.wsf
|
|
||||||
*.xml
|
|
||||||
*.zipx
|
|
||||||
*.7z
|
*.7z
|
||||||
|
*.89k
|
||||||
|
*.89z
|
||||||
|
*.8ck
|
||||||
|
*.a7r
|
||||||
|
*.ac
|
||||||
|
*.acc
|
||||||
|
*.ace
|
||||||
|
*.acr
|
||||||
|
*.actc
|
||||||
|
*.action
|
||||||
|
*.actm
|
||||||
|
*.ade
|
||||||
|
*.adp
|
||||||
|
*.afmacro
|
||||||
|
*.afmacros
|
||||||
|
*.ahk
|
||||||
|
*.ai
|
||||||
|
*.aif
|
||||||
|
*.air
|
||||||
|
*.alz
|
||||||
|
*.api
|
||||||
|
*.apk
|
||||||
|
*.app
|
||||||
|
*.appimage
|
||||||
|
*.applescript
|
||||||
|
*.application
|
||||||
|
*.appx
|
||||||
|
*.arc
|
||||||
|
*.arj
|
||||||
|
*.arscript
|
||||||
|
*.asb
|
||||||
|
*.asp
|
||||||
|
*.aspx
|
||||||
|
*.aspx-exe
|
||||||
|
*.atmx
|
||||||
|
*.azw2
|
||||||
|
*.ba_
|
||||||
|
*.bak
|
||||||
|
*.bas
|
||||||
|
*.bash
|
||||||
|
*.bat
|
||||||
*.bdjo
|
*.bdjo
|
||||||
*.bdmv
|
*.bdmv
|
||||||
|
*.beam
|
||||||
*.bin
|
*.bin
|
||||||
*.bmp
|
*.bmp
|
||||||
|
*.bms
|
||||||
|
*.bns
|
||||||
|
*.bsa
|
||||||
|
*.btm
|
||||||
|
*.bz2
|
||||||
|
*.c
|
||||||
|
*.cab
|
||||||
|
*.caction
|
||||||
*.cci
|
*.cci
|
||||||
|
*.cda
|
||||||
|
*.cdb
|
||||||
|
*.cel
|
||||||
|
*.celx
|
||||||
|
*.cfs
|
||||||
|
*.cgi
|
||||||
|
*.cheat
|
||||||
|
*.chm
|
||||||
|
*.ckpt
|
||||||
|
*.cla
|
||||||
|
*.class
|
||||||
*.clpi
|
*.clpi
|
||||||
|
*.cmd
|
||||||
|
*.cof
|
||||||
|
*.coffee
|
||||||
|
*.com
|
||||||
|
*.command
|
||||||
|
*.conf
|
||||||
|
*.config
|
||||||
|
*.cpl
|
||||||
*.crt
|
*.crt
|
||||||
|
*.cs
|
||||||
|
*.csh
|
||||||
|
*.csharp
|
||||||
|
*.csproj
|
||||||
|
*.css
|
||||||
|
*.csv
|
||||||
|
*.cue
|
||||||
|
*.cur
|
||||||
|
*.cyw
|
||||||
|
*.daemon
|
||||||
|
*.dat
|
||||||
|
*.data-00000-of-00001
|
||||||
|
*.db
|
||||||
|
*.deamon
|
||||||
|
*.deb
|
||||||
|
*.dek
|
||||||
|
*.diz
|
||||||
|
*.dld
|
||||||
*.dll
|
*.dll
|
||||||
|
*.dmc
|
||||||
|
*.dmg
|
||||||
|
*.doc
|
||||||
|
*.docb
|
||||||
|
*.docm
|
||||||
|
*.docx
|
||||||
|
*.dot
|
||||||
|
*.dotb
|
||||||
|
*.dotm
|
||||||
|
*.drv
|
||||||
|
*.ds
|
||||||
|
*.dw
|
||||||
|
*.dword
|
||||||
|
*.dxl
|
||||||
|
*.e_e
|
||||||
|
*.ear
|
||||||
|
*.ebacmd
|
||||||
|
*.ebm
|
||||||
|
*.ebs
|
||||||
|
*.ebs2
|
||||||
|
*.ecf
|
||||||
|
*.eham
|
||||||
|
*.elf
|
||||||
|
*.elf-so
|
||||||
|
*.email
|
||||||
|
*.emu
|
||||||
|
*.epk
|
||||||
|
*.es
|
||||||
|
*.esh
|
||||||
|
*.etc
|
||||||
|
*.ex4
|
||||||
|
*.ex5
|
||||||
|
*.ex_
|
||||||
*.exe
|
*.exe
|
||||||
|
*.exe-only
|
||||||
|
*.exe-service
|
||||||
|
*.exe-small
|
||||||
|
*.exe1
|
||||||
|
*.exopc
|
||||||
|
*.exz
|
||||||
|
*.ezs
|
||||||
|
*.ezt
|
||||||
|
*.fas
|
||||||
|
*.fba
|
||||||
|
*.fky
|
||||||
|
*.flac
|
||||||
|
*.flatpak
|
||||||
|
*.flv
|
||||||
|
*.fpi
|
||||||
|
*.frs
|
||||||
|
*.fxp
|
||||||
|
*.gadget
|
||||||
|
*.gat
|
||||||
|
*.gif
|
||||||
|
*.gifv
|
||||||
|
*.gm9
|
||||||
|
*.gpe
|
||||||
|
*.gpu
|
||||||
|
*.gs
|
||||||
|
*.gz
|
||||||
|
*.h5
|
||||||
|
*.ham
|
||||||
|
*.hex
|
||||||
|
*.hlp
|
||||||
|
*.hms
|
||||||
|
*.hpf
|
||||||
|
*.hta
|
||||||
|
*.hta-psh
|
||||||
|
*.htaccess
|
||||||
|
*.htm
|
||||||
*.html
|
*.html
|
||||||
|
*.icd
|
||||||
|
*.icns
|
||||||
|
*.ico
|
||||||
*.idx
|
*.idx
|
||||||
|
*.iim
|
||||||
|
*.img
|
||||||
|
*.index
|
||||||
*.inf
|
*.inf
|
||||||
|
*.ini
|
||||||
|
*.ink
|
||||||
|
*.ins
|
||||||
|
*.ipa
|
||||||
|
*.ipf
|
||||||
|
*.ipk
|
||||||
|
*.ipsw
|
||||||
|
*.iqylink
|
||||||
|
*.iso
|
||||||
|
*.isp
|
||||||
|
*.isu
|
||||||
|
*.ita
|
||||||
|
*.izh
|
||||||
|
*.izma ace
|
||||||
*.jar
|
*.jar
|
||||||
|
*.java
|
||||||
*.jpeg
|
*.jpeg
|
||||||
*.jpg
|
*.jpg
|
||||||
|
*.js
|
||||||
|
*.js_be
|
||||||
|
*.js_le
|
||||||
|
*.jse
|
||||||
|
*.jsf
|
||||||
|
*.json
|
||||||
|
*.jsp
|
||||||
|
*.jsx
|
||||||
|
*.kix
|
||||||
|
*.ksh
|
||||||
|
*.kx
|
||||||
|
*.lck
|
||||||
|
*.ldb
|
||||||
|
*.lib
|
||||||
|
*.link
|
||||||
*.lnk
|
*.lnk
|
||||||
|
*.lo
|
||||||
|
*.lock
|
||||||
|
*.log
|
||||||
|
*.loop-vbs
|
||||||
|
*.ls
|
||||||
|
*.m3u
|
||||||
*.m4a
|
*.m4a
|
||||||
|
*.mac
|
||||||
|
*.macho
|
||||||
|
*.mamc
|
||||||
|
*.manifest
|
||||||
|
*.mcr
|
||||||
|
*.md
|
||||||
|
*.mda
|
||||||
|
*.mdb
|
||||||
|
*.mde
|
||||||
|
*.mdf
|
||||||
|
*.mdn
|
||||||
|
*.mdt
|
||||||
|
*.mel
|
||||||
|
*.mem
|
||||||
|
*.meta
|
||||||
|
*.mgm
|
||||||
|
*.mhm
|
||||||
|
*.mht
|
||||||
|
*.mhtml
|
||||||
|
*.mid
|
||||||
|
*.mio
|
||||||
|
*.mlappinstall
|
||||||
|
*.mlx
|
||||||
|
*.mm
|
||||||
|
*.mobileconfig
|
||||||
|
*.model
|
||||||
|
*.moo
|
||||||
|
*.mp3
|
||||||
|
*.mpa
|
||||||
|
*.mpk
|
||||||
*.mpls
|
*.mpls
|
||||||
|
*.mrc
|
||||||
|
*.mrp
|
||||||
|
*.ms
|
||||||
|
*.msc
|
||||||
|
*.msh
|
||||||
|
*.msh1
|
||||||
|
*.msh1xml
|
||||||
|
*.msh2
|
||||||
|
*.msh2xml
|
||||||
|
*.mshxml
|
||||||
*.msi
|
*.msi
|
||||||
|
*.msi-nouac
|
||||||
|
*.msix
|
||||||
|
*.msl
|
||||||
|
*.msp
|
||||||
|
*.mst
|
||||||
|
*.msu
|
||||||
|
*.mxe
|
||||||
|
*.n
|
||||||
|
*.ncl
|
||||||
|
*.net
|
||||||
|
*.nexe
|
||||||
*.nfo
|
*.nfo
|
||||||
|
*.nrg
|
||||||
|
*.num
|
||||||
|
*.nzb.bz2
|
||||||
|
*.nzb.gz
|
||||||
|
*.nzbs
|
||||||
|
*.ocx
|
||||||
|
*.odt
|
||||||
|
*.ore
|
||||||
|
*.ost
|
||||||
|
*.osx
|
||||||
|
*.osx-app
|
||||||
|
*.otm
|
||||||
|
*.out
|
||||||
|
*.ova
|
||||||
|
*.p
|
||||||
|
*.paf
|
||||||
|
*.pak
|
||||||
|
*.pb
|
||||||
|
*.pcd
|
||||||
|
*.pdb
|
||||||
*.pdf
|
*.pdf
|
||||||
|
*.pea
|
||||||
|
*.perl
|
||||||
|
*.pex
|
||||||
|
*.phar
|
||||||
|
*.php
|
||||||
|
*.php5
|
||||||
|
*.pif
|
||||||
|
*.pkg
|
||||||
|
*.pl
|
||||||
|
*.plsc
|
||||||
|
*.plx
|
||||||
*.png
|
*.png
|
||||||
|
*.pol
|
||||||
|
*.pot
|
||||||
|
*.potm
|
||||||
|
*.powershell
|
||||||
|
*.ppam
|
||||||
|
*.ppkg
|
||||||
|
*.pps
|
||||||
|
*.ppsm
|
||||||
|
*.ppt
|
||||||
|
*.pptm
|
||||||
|
*.pptx
|
||||||
|
*.prc
|
||||||
|
*.prg
|
||||||
|
*.ps
|
||||||
|
*.ps1
|
||||||
|
*.ps1xml
|
||||||
|
*.ps2
|
||||||
|
*.ps2xml
|
||||||
|
*.psc1
|
||||||
|
*.psc2
|
||||||
|
*.psd
|
||||||
|
*.psd1
|
||||||
|
*.psh
|
||||||
|
*.psh-cmd
|
||||||
|
*.psh-net
|
||||||
|
*.psh-reflection
|
||||||
|
*.psm1
|
||||||
|
*.pst
|
||||||
|
*.pt
|
||||||
|
*.pvd
|
||||||
|
*.pwc
|
||||||
|
*.pxo
|
||||||
|
*.py
|
||||||
|
*.pyc
|
||||||
|
*.pyd
|
||||||
|
*.pyo
|
||||||
|
*.python
|
||||||
|
*.pyz
|
||||||
|
*.qit
|
||||||
|
*.qpx
|
||||||
|
*.ram
|
||||||
*.rar
|
*.rar
|
||||||
*(sample).*
|
*.raw
|
||||||
*sample.mkv
|
*.rb
|
||||||
*sample.mp4
|
*.rbf
|
||||||
|
*.rbx
|
||||||
|
*.readme
|
||||||
|
*.reg
|
||||||
|
*.resources
|
||||||
|
*.resx
|
||||||
|
*.rfs
|
||||||
|
*.rfu
|
||||||
|
*.rgs
|
||||||
|
*.rm
|
||||||
|
*.rox
|
||||||
|
*.rpg
|
||||||
|
*.rpj
|
||||||
|
*.rpm
|
||||||
|
*.ruby
|
||||||
|
*.run
|
||||||
|
*.rxe
|
||||||
|
*.s2a
|
||||||
|
*.sample
|
||||||
|
*.sapk
|
||||||
|
*.savedmodel
|
||||||
|
*.sbs
|
||||||
|
*.sca
|
||||||
|
*.scar
|
||||||
|
*.scb
|
||||||
|
*.scf
|
||||||
|
*.scpt
|
||||||
|
*.scptd
|
||||||
|
*.scr
|
||||||
|
*.script
|
||||||
|
*.sct
|
||||||
|
*.seed
|
||||||
|
*.server
|
||||||
|
*.service
|
||||||
*.sfv
|
*.sfv
|
||||||
|
*.sh
|
||||||
|
*.shb
|
||||||
|
*.shell
|
||||||
|
*.shortcut
|
||||||
|
*.shs
|
||||||
|
*.shtml
|
||||||
|
*.sit
|
||||||
|
*.sitx
|
||||||
|
*.sk
|
||||||
|
*.sldm
|
||||||
|
*.sln
|
||||||
|
*.smm
|
||||||
|
*.snap
|
||||||
|
*.snd
|
||||||
|
*.spr
|
||||||
|
*.sql
|
||||||
|
*.sqx
|
||||||
|
*.srec
|
||||||
*.srt
|
*.srt
|
||||||
|
*.ssm
|
||||||
|
*.sts
|
||||||
*.sub
|
*.sub
|
||||||
|
*.svg
|
||||||
|
*.swf
|
||||||
|
*.sys
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
*.tbl
|
*.tbl
|
||||||
Trailer.*
|
*.tbz
|
||||||
|
*.tcp
|
||||||
|
*.text
|
||||||
|
*.tf
|
||||||
|
*.tgz
|
||||||
|
*.thm
|
||||||
|
*.thmx
|
||||||
|
*.thumb
|
||||||
|
*.tiapp
|
||||||
|
*.tif
|
||||||
|
*.tiff
|
||||||
|
*.tipa
|
||||||
|
*.tmp
|
||||||
|
*.tms
|
||||||
|
*.toast
|
||||||
|
*.torrent
|
||||||
|
*.tpk
|
||||||
*.txt
|
*.txt
|
||||||
|
*.u3p
|
||||||
|
*.udf
|
||||||
|
*.upk
|
||||||
|
*.upx
|
||||||
*.url
|
*.url
|
||||||
|
*.uvm
|
||||||
|
*.uw8
|
||||||
|
*.vb
|
||||||
|
*.vba
|
||||||
|
*.vba-exe
|
||||||
|
*.vba-psh
|
||||||
|
*.vbapplication
|
||||||
|
*.vbe
|
||||||
|
*.vbs
|
||||||
|
*.vbscript
|
||||||
|
*.vbscript
|
||||||
|
*.vcd
|
||||||
|
*.vdo
|
||||||
|
*.vexe
|
||||||
|
*.vhd
|
||||||
|
*.vhdx
|
||||||
|
*.vlx
|
||||||
|
*.vm
|
||||||
|
*.vmdk
|
||||||
|
*.vob
|
||||||
|
*.vocab
|
||||||
|
*.vpm
|
||||||
|
*.vxp
|
||||||
|
*.war
|
||||||
|
*.wav
|
||||||
|
*.wbk
|
||||||
|
*.wcm
|
||||||
|
*.webm
|
||||||
|
*.widget
|
||||||
|
*.wim
|
||||||
|
*.wiz
|
||||||
|
*.wma
|
||||||
|
*.workflow
|
||||||
|
*.wpk
|
||||||
|
*.wpl
|
||||||
|
*.wpm
|
||||||
|
*.wps
|
||||||
|
*.ws
|
||||||
|
*.wsc
|
||||||
|
*.wsf
|
||||||
|
*.wsh
|
||||||
|
*.x86
|
||||||
|
*.x86_64
|
||||||
|
*.xaml
|
||||||
|
*.xap
|
||||||
|
*.xbap
|
||||||
|
*.xbe
|
||||||
|
*.xex
|
||||||
*.xig
|
*.xig
|
||||||
|
*.xla
|
||||||
|
*.xlam
|
||||||
|
*.xll
|
||||||
|
*.xlm
|
||||||
|
*.xls
|
||||||
|
*.xlsb
|
||||||
|
*.xlsm
|
||||||
|
*.xlsx
|
||||||
|
*.xlt
|
||||||
|
*.xltb
|
||||||
|
*.xltm
|
||||||
|
*.xlw
|
||||||
*.xml
|
*.xml
|
||||||
|
*.xqt
|
||||||
*.xrt
|
*.xrt
|
||||||
|
*.xys
|
||||||
|
*.xz
|
||||||
|
*.ygh
|
||||||
|
*.z
|
||||||
*.zip
|
*.zip
|
||||||
*.zipx
|
*.zipx
|
||||||
*.Lnk
|
*.zl9
|
||||||
|
*.zoo
|
||||||
|
*sample.avchd
|
||||||
|
*sample.avi
|
||||||
|
*sample.mkv
|
||||||
|
*sample.mov
|
||||||
|
*sample.mp4
|
||||||
|
*sample.webm
|
||||||
|
*sample.wmv
|
||||||
|
Trailer.*
|
||||||
|
VOSTFR
|
||||||
|
api
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</details>
|
</details>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Common.Configuration;
|
||||||
|
|
||||||
|
public abstract record ArrConfig
|
||||||
|
{
|
||||||
|
public required bool Enabled { get; init; }
|
||||||
|
|
||||||
|
public required List<ArrInstance> Instances { get; init; }
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration;
|
||||||
|
|
||||||
public sealed class SonarrInstance
|
public sealed class ArrInstance
|
||||||
{
|
{
|
||||||
public required Uri Url { get; set; }
|
public required Uri Url { get; set; }
|
||||||
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
public sealed class QBitConfig
|
public sealed class QBitConfig
|
||||||
{
|
{
|
||||||
|
public const string SectionName = "qBittorrent";
|
||||||
|
|
||||||
public required Uri Url { get; set; }
|
public required Uri Url { get; set; }
|
||||||
|
|
||||||
public required string Username { get; set; }
|
public required string Username { get; set; }
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Common.Configuration;
|
|
||||||
|
|
||||||
public sealed class QuartzConfig
|
|
||||||
{
|
|
||||||
public required string BlockedTorrentTrigger { get; init; }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Common.Configuration;
|
||||||
|
|
||||||
|
public sealed record RadarrConfig : ArrConfig
|
||||||
|
{
|
||||||
|
public const string SectionName = "Radarr";
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration;
|
||||||
|
|
||||||
public sealed class SonarrConfig
|
public sealed record SonarrConfig : ArrConfig
|
||||||
{
|
{
|
||||||
public required List<SonarrInstance> Instances { get; set; }
|
public const string SectionName = "Sonarr";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Common.Configuration;
|
||||||
|
|
||||||
|
public sealed class TriggersConfig
|
||||||
|
{
|
||||||
|
public const string SectionName = "Triggers";
|
||||||
|
|
||||||
|
public required string QueueCleaner { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Domain.Arr.Enums;
|
||||||
|
|
||||||
|
public enum InstanceType
|
||||||
|
{
|
||||||
|
Sonarr,
|
||||||
|
Radarr,
|
||||||
|
Lidarr,
|
||||||
|
Readarr
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Domain.Arr.Queue;
|
||||||
|
|
||||||
|
public record QueueListResponse
|
||||||
|
{
|
||||||
|
public required int TotalRecords { get; init; }
|
||||||
|
public required IReadOnlyList<QueueRecord> Records { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Domain.Arr.Queue;
|
||||||
|
|
||||||
|
public record QueueRecord
|
||||||
|
{
|
||||||
|
public int SeriesId { get; init; }
|
||||||
|
public int MovieId { get; init; }
|
||||||
|
public required string Title { get; init; }
|
||||||
|
public string Status { get; init; }
|
||||||
|
public string TrackedDownloadStatus { get; init; }
|
||||||
|
public string TrackedDownloadState { get; init; }
|
||||||
|
public required string DownloadId { get; init; }
|
||||||
|
public required string Protocol { get; init; }
|
||||||
|
public required int Id { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Domain.Radarr;
|
||||||
|
|
||||||
|
public sealed record RadarrCommand
|
||||||
|
{
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
public required HashSet<int> MovieIds { get; init; }
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Domain.Sonarr.Queue;
|
|
||||||
|
|
||||||
public record CustomFormat(
|
|
||||||
int Id,
|
|
||||||
string Name
|
|
||||||
);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Domain.Sonarr.Queue;
|
|
||||||
|
|
||||||
public record Language(
|
|
||||||
int Id,
|
|
||||||
string Name
|
|
||||||
);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Domain.Sonarr.Queue;
|
|
||||||
|
|
||||||
public record QueueListResponse(
|
|
||||||
int Page,
|
|
||||||
int PageSize,
|
|
||||||
string SortKey,
|
|
||||||
string SortDirection,
|
|
||||||
int TotalRecords,
|
|
||||||
IReadOnlyList<Record> Records
|
|
||||||
);
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
namespace Domain.Sonarr.Queue;
|
|
||||||
|
|
||||||
public record Record(
|
|
||||||
int SeriesId,
|
|
||||||
int EpisodeId,
|
|
||||||
int SeasonNumber,
|
|
||||||
IReadOnlyList<Language> Languages,
|
|
||||||
IReadOnlyList<CustomFormat> CustomFormats,
|
|
||||||
int CustomFormatScore,
|
|
||||||
int Size,
|
|
||||||
string Title,
|
|
||||||
int Sizeleft,
|
|
||||||
string Timeleft,
|
|
||||||
DateTime EstimatedCompletionTime,
|
|
||||||
DateTime Added,
|
|
||||||
string Status,
|
|
||||||
string TrackedDownloadStatus,
|
|
||||||
string TrackedDownloadState,
|
|
||||||
IReadOnlyList<StatusMessage> StatusMessages,
|
|
||||||
string DownloadId,
|
|
||||||
string Protocol,
|
|
||||||
string DownloadClient,
|
|
||||||
bool DownloadClientHasPostImportCategory,
|
|
||||||
string Indexer,
|
|
||||||
string OutputPath,
|
|
||||||
bool EpisodeHasFile,
|
|
||||||
int Id
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Domain.Sonarr.Queue;
|
|
||||||
|
|
||||||
public record Revision(
|
|
||||||
int Version,
|
|
||||||
int Real,
|
|
||||||
bool IsRepack
|
|
||||||
);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Domain.Sonarr.Queue;
|
|
||||||
|
|
||||||
public record StatusMessage(
|
|
||||||
string Title,
|
|
||||||
IReadOnlyList<string> Messages
|
|
||||||
);
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Domain.Sonarr;
|
||||||
|
|
||||||
|
public sealed record SonarrCommand
|
||||||
|
{
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
public required int SeriesId { get; set; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
using Executable.Jobs;
|
using Executable.Jobs;
|
||||||
using Infrastructure.Verticals.BlockedTorrent;
|
using Infrastructure.Verticals.Arr;
|
||||||
|
using Infrastructure.Verticals.QueueCleaner;
|
||||||
|
|
||||||
namespace Executable;
|
namespace Executable;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
@@ -17,44 +18,46 @@ public static class DependencyInjection
|
|||||||
|
|
||||||
private static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) =>
|
private static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) =>
|
||||||
services
|
services
|
||||||
.Configure<QuartzConfig>(configuration.GetSection(nameof(QuartzConfig)))
|
.Configure<QBitConfig>(configuration.GetSection(QBitConfig.SectionName))
|
||||||
.Configure<QBitConfig>(configuration.GetSection(nameof(QBitConfig)))
|
.Configure<SonarrConfig>(configuration.GetSection(SonarrConfig.SectionName))
|
||||||
.Configure<SonarrConfig>(configuration.GetSection(nameof(SonarrConfig)));
|
.Configure<RadarrConfig>(configuration.GetSection(RadarrConfig.SectionName));
|
||||||
|
|
||||||
private static IServiceCollection AddServices(this IServiceCollection services) =>
|
private static IServiceCollection AddServices(this IServiceCollection services) =>
|
||||||
services
|
services
|
||||||
.AddTransient<BlockedTorrentJob>()
|
.AddTransient<SonarrClient>()
|
||||||
.AddTransient<BlockedTorrentHandler>();
|
.AddTransient<RadarrClient>()
|
||||||
|
.AddTransient<QueueCleanerJob>()
|
||||||
|
.AddTransient<QueueCleanerHandler>();
|
||||||
|
|
||||||
private static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration) =>
|
private static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration) =>
|
||||||
services
|
services
|
||||||
.AddQuartz(q =>
|
.AddQuartz(q =>
|
||||||
{
|
{
|
||||||
QuartzConfig? config = configuration.GetRequiredSection(nameof(QuartzConfig)).Get<QuartzConfig>();
|
TriggersConfig? config = configuration.GetRequiredSection(TriggersConfig.SectionName).Get<TriggersConfig>();
|
||||||
|
|
||||||
if (config is null)
|
if (config is null)
|
||||||
{
|
{
|
||||||
throw new NullReferenceException("Quartz configuration is null");
|
throw new NullReferenceException("Quartz configuration is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
q.AddBlockedTorrentJob(config.BlockedTorrentTrigger);
|
q.AddQueueCleanerJob(config.QueueCleaner);
|
||||||
})
|
})
|
||||||
.AddQuartzHostedService(opt =>
|
.AddQuartzHostedService(opt =>
|
||||||
{
|
{
|
||||||
opt.WaitForJobsToComplete = true;
|
opt.WaitForJobsToComplete = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
private static void AddBlockedTorrentJob(this IServiceCollectionQuartzConfigurator q, string trigger)
|
private static void AddQueueCleanerJob(this IServiceCollectionQuartzConfigurator q, string trigger)
|
||||||
{
|
{
|
||||||
q.AddJob<BlockedTorrentJob>(opts =>
|
q.AddJob<QueueCleanerJob>(opts =>
|
||||||
{
|
{
|
||||||
opts.WithIdentity(nameof(BlockedTorrentJob));
|
opts.WithIdentity(nameof(QueueCleanerJob));
|
||||||
});
|
});
|
||||||
|
|
||||||
q.AddTrigger(opts =>
|
q.AddTrigger(opts =>
|
||||||
{
|
{
|
||||||
opts.ForJob(nameof(BlockedTorrentJob))
|
opts.ForJob(nameof(QueueCleanerJob))
|
||||||
.WithIdentity($"{nameof(BlockedTorrentJob)}-trigger")
|
.WithIdentity($"{nameof(QueueCleanerJob)}-trigger")
|
||||||
.WithCronSchedule(trigger);
|
.WithCronSchedule(trigger);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
using Infrastructure.Verticals.BlockedTorrent;
|
|
||||||
using Quartz;
|
|
||||||
|
|
||||||
namespace Executable.Jobs;
|
|
||||||
|
|
||||||
[DisallowConcurrentExecution]
|
|
||||||
public sealed class BlockedTorrentJob : IJob
|
|
||||||
{
|
|
||||||
private ILogger<BlockedTorrentJob> _logger;
|
|
||||||
private BlockedTorrentHandler _handler;
|
|
||||||
|
|
||||||
public BlockedTorrentJob(ILogger<BlockedTorrentJob> logger, BlockedTorrentHandler handler)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_handler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _handler.HandleAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"{nameof(BlockedTorrentJob)} failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Infrastructure.Verticals.QueueCleaner;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace Executable.Jobs;
|
||||||
|
|
||||||
|
[DisallowConcurrentExecution]
|
||||||
|
public sealed class QueueCleanerJob : IJob
|
||||||
|
{
|
||||||
|
private ILogger<QueueCleanerJob> _logger;
|
||||||
|
private QueueCleanerHandler _handler;
|
||||||
|
|
||||||
|
public QueueCleanerJob(ILogger<QueueCleanerJob> logger, QueueCleanerHandler handler)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _handler.HandleAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"{nameof(QueueCleanerJob)} failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"QuartzConfig": {
|
"Triggers": {
|
||||||
"BlockedTorrentTrigger": "0 0/1 * * * ?"
|
"QueueCleaner": "0 0/1 * * * ?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,30 @@
|
|||||||
"System.Net.Http.HttpClient": "Error"
|
"System.Net.Http.HttpClient": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"QuartzConfig": {
|
"Triggers": {
|
||||||
"BlockedTorrentTrigger": "0 0/5 * * * ?"
|
"QueueCleaner": "0 0/5 * * * ?"
|
||||||
},
|
},
|
||||||
"QBitConfig": {
|
"qBittorrent": {
|
||||||
"Url": "http://localhost:8080",
|
"Url": "http://localhost:8080",
|
||||||
"Username": "",
|
"Username": "",
|
||||||
"Password": ""
|
"Password": ""
|
||||||
},
|
},
|
||||||
"SonarrConfig": {
|
"Sonarr": {
|
||||||
|
"Enabled": true,
|
||||||
"Instances": [
|
"Instances": [
|
||||||
{
|
{
|
||||||
"Url": "http://localhost:8989",
|
"Url": "http://localhost:8989",
|
||||||
"ApiKey": ""
|
"ApiKey": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"Radarr": {
|
||||||
|
"Enabled": false,
|
||||||
|
"Instances": [
|
||||||
|
{
|
||||||
|
"Url": "http://localhost:7878",
|
||||||
|
"ApiKey": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Common.Configuration;
|
||||||
|
using Domain.Arr.Queue;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Infrastructure.Verticals.Arr;
|
||||||
|
|
||||||
|
public abstract class ArrClient
|
||||||
|
{
|
||||||
|
private protected ILogger<ArrClient> _logger;
|
||||||
|
private protected HttpClient _httpClient;
|
||||||
|
|
||||||
|
protected ArrClient(ILogger<ArrClient> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<QueueListResponse> GetQueueItemsAsync(ArrInstance arrInstance, int page)
|
||||||
|
{
|
||||||
|
Uri uri = new(arrInstance.Url, $"/api/v3/queue?page={page}&pageSize=200&sortKey=timeleft");
|
||||||
|
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Get, uri);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_logger.LogError("queue list failed | {uri}", uri);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
string responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
QueueListResponse? queueResponse = JsonConvert.DeserializeObject<QueueListResponse>(responseBody);
|
||||||
|
|
||||||
|
if (queueResponse is null)
|
||||||
|
{
|
||||||
|
throw new Exception($"unrecognized queue list response | {uri} | {responseBody}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return queueResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task DeleteQueueItemAsync(ArrInstance arrInstance, QueueRecord queueRecord)
|
||||||
|
{
|
||||||
|
Uri uri = new(arrInstance.Url, $"/api/v3/queue/{queueRecord.Id}?removeFromClient=true&blocklist=true&skipRedownload=true&changeCategory=false");
|
||||||
|
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Delete, uri);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
_logger.LogInformation("queue item deleted | {url} | {title}", arrInstance.Url, queueRecord.Title);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_logger.LogError("queue delete failed | {uri} | {title}", uri, queueRecord.Title);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<int> itemIds);
|
||||||
|
|
||||||
|
protected virtual void SetApiKey(HttpRequestMessage request, string apiKey)
|
||||||
|
{
|
||||||
|
request.Headers.Add("x-api-key", apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Common.Configuration;
|
||||||
|
using Domain.Radarr;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Infrastructure.Verticals.Arr;
|
||||||
|
|
||||||
|
public sealed class RadarrClient : ArrClient
|
||||||
|
{
|
||||||
|
public RadarrClient(ILogger<ArrClient> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
: base(logger, httpClientFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<int> itemIds)
|
||||||
|
{
|
||||||
|
if (itemIds.Count is 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri = new(arrInstance.Url, "/api/v3/command");
|
||||||
|
RadarrCommand command = new()
|
||||||
|
{
|
||||||
|
Name = "MoviesSearch",
|
||||||
|
MovieIds = itemIds
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Post, uri);
|
||||||
|
request.Content = new StringContent(
|
||||||
|
JsonConvert.SerializeObject(command),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
_logger.LogInformation("movie search triggered | {url} | movie ids: {ids}", arrInstance.Url, string.Join(",", itemIds));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_logger.LogError("movie search failed | {url} | movie ids: {ids}", arrInstance.Url, string.Join(",", itemIds));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Common.Configuration;
|
||||||
|
using Domain.Sonarr;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Infrastructure.Verticals.Arr;
|
||||||
|
|
||||||
|
public sealed class SonarrClient : ArrClient
|
||||||
|
{
|
||||||
|
public SonarrClient(ILogger<SonarrClient> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
: base(logger, httpClientFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<int> itemIds)
|
||||||
|
{
|
||||||
|
foreach (int itemId in itemIds)
|
||||||
|
{
|
||||||
|
Uri uri = new(arrInstance.Url, "/api/v3/command");
|
||||||
|
SonarrCommand command = new()
|
||||||
|
{
|
||||||
|
Name = "SeriesSearch",
|
||||||
|
SeriesId = itemId
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Post, uri);
|
||||||
|
request.Content = new StringContent(
|
||||||
|
JsonConvert.SerializeObject(command),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
_logger.LogInformation("series search triggered | {url} | series id: {id}", arrInstance.Url, itemId);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_logger.LogError("series search failed | {url} | series id: {id}", arrInstance.Url, itemId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Common.Configuration;
|
|
||||||
using Domain.Sonarr.Queue;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using QBittorrent.Client;
|
|
||||||
|
|
||||||
namespace Infrastructure.Verticals.BlockedTorrent;
|
|
||||||
|
|
||||||
public sealed class BlockedTorrentHandler
|
|
||||||
{
|
|
||||||
private readonly ILogger<BlockedTorrentHandler> _logger;
|
|
||||||
private readonly QBitConfig _qBitConfig;
|
|
||||||
private readonly SonarrConfig _sonarrConfig;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
private const string QueueListPathTemplate = "/api/v3/queue?page={0}&pageSize=200&sortKey=timeleft";
|
|
||||||
private const string QueueDeletePathTemplate = "/api/v3/queue/{0}?removeFromClient=true&blocklist=true&skipRedownload=true&changeCategory=false";
|
|
||||||
private const string SonarrCommandUriPath = "/api/v3/command";
|
|
||||||
private const string SearchCommandPayloadTemplate = "{\"name\":\"SeriesSearch\",\"seriesId\":{0}}";
|
|
||||||
|
|
||||||
public BlockedTorrentHandler(
|
|
||||||
ILogger<BlockedTorrentHandler> logger,
|
|
||||||
IOptions<QBitConfig> qBitConfig,
|
|
||||||
IOptions<SonarrConfig> sonarrConfig,
|
|
||||||
IHttpClientFactory httpClientFactory)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_qBitConfig = qBitConfig.Value;
|
|
||||||
_sonarrConfig = sonarrConfig.Value;
|
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task HandleAsync()
|
|
||||||
{
|
|
||||||
QBittorrentClient qBitClient = new(_qBitConfig.Url);
|
|
||||||
|
|
||||||
await qBitClient.LoginAsync(_qBitConfig.Username, _qBitConfig.Password);
|
|
||||||
|
|
||||||
foreach (SonarrInstance sonarrInstance in _sonarrConfig.Instances)
|
|
||||||
{
|
|
||||||
ushort page = 1;
|
|
||||||
int totalRecords = 0;
|
|
||||||
int processedRecords = 0;
|
|
||||||
HashSet<int> seriesToBeRefreshed = [];
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
QueueListResponse queueResponse = await ListQueuedTorrentsAsync(sonarrInstance, page);
|
|
||||||
|
|
||||||
if (totalRecords is 0)
|
|
||||||
{
|
|
||||||
totalRecords = queueResponse.TotalRecords;
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"{items} items found in queue | {url}",
|
|
||||||
queueResponse.TotalRecords, sonarrInstance.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Record record in queueResponse.Records)
|
|
||||||
{
|
|
||||||
var torrent = (await qBitClient.GetTorrentListAsync(new TorrentListQuery { Hashes = [record.DownloadId] }))
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (torrent is not { CompletionOn: not null, Downloaded: null or 0 })
|
|
||||||
{
|
|
||||||
_logger.LogInformation("skip | {torrent}", record.Title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
seriesToBeRefreshed.Add(record.SeriesId);
|
|
||||||
|
|
||||||
await DeleteTorrentFromQueueAsync(sonarrInstance, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queueResponse.Records.Count is 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
processedRecords += queueResponse.Records.Count;
|
|
||||||
|
|
||||||
if (processedRecords >= totalRecords)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
page++;
|
|
||||||
} while (processedRecords < totalRecords);
|
|
||||||
|
|
||||||
foreach (int id in seriesToBeRefreshed)
|
|
||||||
{
|
|
||||||
await RefreshSeriesAsync(sonarrInstance, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<QueueListResponse> ListQueuedTorrentsAsync(SonarrInstance sonarrInstance, int page)
|
|
||||||
{
|
|
||||||
Uri sonarrUri = new(sonarrInstance.Url, string.Format(QueueListPathTemplate, page));
|
|
||||||
|
|
||||||
using HttpRequestMessage sonarrRequest = new(HttpMethod.Get, sonarrUri);
|
|
||||||
sonarrRequest.Headers.Add("x-api-key", sonarrInstance.ApiKey);
|
|
||||||
|
|
||||||
using HttpResponseMessage response = await _httpClient.SendAsync(sonarrRequest);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_logger.LogError("queue list failed | {uri}", sonarrUri);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
string responseBody = await response.Content.ReadAsStringAsync();
|
|
||||||
QueueListResponse? queueResponse = JsonConvert.DeserializeObject<QueueListResponse>(responseBody);
|
|
||||||
|
|
||||||
if (queueResponse is null)
|
|
||||||
{
|
|
||||||
throw new Exception($"unrecognized response | {responseBody}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return queueResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeleteTorrentFromQueueAsync(SonarrInstance sonarrInstance, Record record)
|
|
||||||
{
|
|
||||||
Uri sonarrUri = new(sonarrInstance.Url, string.Format(QueueDeletePathTemplate, record.Id));
|
|
||||||
using HttpRequestMessage sonarrRequest = new(HttpMethod.Delete, sonarrUri);
|
|
||||||
sonarrRequest.Headers.Add("x-api-key", sonarrInstance.ApiKey);
|
|
||||||
|
|
||||||
using HttpResponseMessage response = await _httpClient.SendAsync(sonarrRequest);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
_logger.LogInformation("queue item deleted | {record}", record.Title);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_logger.LogError("queue delete failed | {uri}", sonarrUri);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshSeriesAsync(SonarrInstance sonarrInstance, int seriesId)
|
|
||||||
{
|
|
||||||
Uri sonarrUri = new(sonarrInstance.Url, SonarrCommandUriPath);
|
|
||||||
using HttpRequestMessage sonarrRequest = new(HttpMethod.Post, sonarrUri);
|
|
||||||
sonarrRequest.Content = new StringContent(
|
|
||||||
SearchCommandPayloadTemplate.Replace("{0}", seriesId.ToString()),
|
|
||||||
Encoding.UTF8,
|
|
||||||
"application/json"
|
|
||||||
);
|
|
||||||
sonarrRequest.Headers.Add("x-api-key", sonarrInstance.ApiKey);
|
|
||||||
|
|
||||||
using HttpResponseMessage response = await _httpClient.SendAsync(sonarrRequest);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
_logger.LogInformation("series search triggered | series id: {id}", seriesId);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_logger.LogError("series search failed | series id: {id}", seriesId);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
using Common.Configuration;
|
||||||
|
using Domain.Arr.Enums;
|
||||||
|
using Domain.Arr.Queue;
|
||||||
|
using Infrastructure.Verticals.Arr;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using QBittorrent.Client;
|
||||||
|
|
||||||
|
namespace Infrastructure.Verticals.QueueCleaner;
|
||||||
|
|
||||||
|
public sealed class QueueCleanerHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger<QueueCleanerHandler> _logger;
|
||||||
|
private readonly QBitConfig _qBitConfig;
|
||||||
|
private readonly SonarrConfig _sonarrConfig;
|
||||||
|
private readonly RadarrConfig _radarrConfig;
|
||||||
|
private readonly SonarrClient _sonarrClient;
|
||||||
|
private readonly RadarrClient _radarrClient;
|
||||||
|
|
||||||
|
public QueueCleanerHandler(
|
||||||
|
ILogger<QueueCleanerHandler> logger,
|
||||||
|
IOptions<QBitConfig> qBitConfig,
|
||||||
|
IOptions<SonarrConfig> sonarrConfig,
|
||||||
|
IOptions<RadarrConfig> radarrConfig,
|
||||||
|
SonarrClient sonarrClient,
|
||||||
|
RadarrClient radarrClient)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_qBitConfig = qBitConfig.Value;
|
||||||
|
_sonarrConfig = sonarrConfig.Value;
|
||||||
|
_radarrConfig = radarrConfig.Value;
|
||||||
|
_sonarrClient = sonarrClient;
|
||||||
|
_radarrClient = radarrClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleAsync()
|
||||||
|
{
|
||||||
|
QBittorrentClient qBitClient = new(_qBitConfig.Url);
|
||||||
|
await qBitClient.LoginAsync(_qBitConfig.Username, _qBitConfig.Password);
|
||||||
|
|
||||||
|
await ProcessArrConfigAsync(qBitClient, _sonarrConfig, InstanceType.Sonarr);
|
||||||
|
await ProcessArrConfigAsync(qBitClient, _radarrConfig, InstanceType.Radarr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessArrConfigAsync(QBittorrentClient qBitClient, ArrConfig config, InstanceType instanceType)
|
||||||
|
{
|
||||||
|
if (!config.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ArrInstance arrInstance in config.Instances)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessInstanceAsync(qBitClient, arrInstance, instanceType);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, "failed to clean {type} instance | {url}", instanceType, arrInstance.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessInstanceAsync(QBittorrentClient qBitClient, ArrInstance instance, InstanceType instanceType)
|
||||||
|
{
|
||||||
|
ushort page = 1;
|
||||||
|
int totalRecords = 0;
|
||||||
|
int processedRecords = 0;
|
||||||
|
HashSet<int> itemsToBeRefreshed = [];
|
||||||
|
ArrClient arrClient = GetClient(instanceType);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
QueueListResponse queueResponse = await arrClient.GetQueueItemsAsync(instance, page);
|
||||||
|
|
||||||
|
if (totalRecords is 0)
|
||||||
|
{
|
||||||
|
totalRecords = queueResponse.TotalRecords;
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"{items} items found in queue | {url}",
|
||||||
|
queueResponse.TotalRecords, instance.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (QueueRecord record in queueResponse.Records)
|
||||||
|
{
|
||||||
|
if (record.Protocol is not "torrent")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TorrentInfo? torrent = (await qBitClient.GetTorrentListAsync(new TorrentListQuery { Hashes = [record.DownloadId] }))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (torrent is not { CompletionOn: not null, Downloaded: null or 0 })
|
||||||
|
{
|
||||||
|
_logger.LogInformation("skip | {torrent}", record.Title);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsToBeRefreshed.Add(GetRecordId(instanceType, record));
|
||||||
|
|
||||||
|
await arrClient.DeleteQueueItemAsync(instance, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queueResponse.Records.Count is 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
processedRecords += queueResponse.Records.Count;
|
||||||
|
|
||||||
|
if (processedRecords >= totalRecords)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
page++;
|
||||||
|
} while (processedRecords < totalRecords);
|
||||||
|
|
||||||
|
await arrClient.RefreshItemsAsync(instance, itemsToBeRefreshed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrClient GetClient(InstanceType type) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
InstanceType.Sonarr => _sonarrClient,
|
||||||
|
InstanceType.Radarr => _radarrClient,
|
||||||
|
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||||
|
};
|
||||||
|
|
||||||
|
private int GetRecordId(InstanceType type, QueueRecord record) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
InstanceType.Sonarr => record.SeriesId,
|
||||||
|
InstanceType.Radarr => record.MovieId,
|
||||||
|
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="73140dfd-12c2-49d9-93d6-94dd1f0bc538" version="1">
|
||||||
|
<creationDate>2024-11-12T08:27:40.5991235Z</creationDate>
|
||||||
|
<activationDate>2024-11-12T08:27:40.5870855Z</activationDate>
|
||||||
|
<expirationDate>2025-02-10T08:27:40.5870855Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>FJN9+ak89dkr+ZPZD/LymeCCwH/UI3kNdaMqxSnY6G8bui1yNjGtLpQQOJJlTOAdAyZvHUyPUvv99F70uZF7qg==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
146
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731398141e4:infod6:lengthi4e4:name68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx12:piece lengthi262144e6:pieces20:©J�å̱›¦Lsӑ釘/»Óee
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>Test feed</title>
|
||||||
|
<link>http://nginx/custom/radarr_bad.xml</link>
|
||||||
|
<description>
|
||||||
|
Test
|
||||||
|
</description>
|
||||||
|
<language>en-CA</language>
|
||||||
|
<copyright> Test </copyright>
|
||||||
|
<pubDate>Tue, 5 Nov 2024 22:02:13 -0400</pubDate>
|
||||||
|
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
||||||
|
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||||
|
<ttl>30</ttl>
|
||||||
|
<item>
|
||||||
|
<title>Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/radarr_bad.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731357387e4:infod6:lengthi5e4:name93:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx12:piece lengthi262144e6:pieces20:NC½"ÆnvºžÝÁù”埃ee
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>Test feed</title>
|
||||||
|
<link>http://nginx/custom/sonarr_bad.xml</link>
|
||||||
|
<description>
|
||||||
|
Test
|
||||||
|
</description>
|
||||||
|
<language>en-CA</language>
|
||||||
|
<copyright> Test </copyright>
|
||||||
|
<pubDate>Tue, 5 Nov 2024 22:02:13 -0400</pubDate>
|
||||||
|
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
||||||
|
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||||
|
<ttl>30</ttl>
|
||||||
|
<item>
|
||||||
|
<title>Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/sonarr_bad.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
apt install ctorrent
|
||||||
|
apt-get
|
||||||
|
yum
|
||||||
|
apk
|
||||||
|
apk search ctorrent
|
||||||
|
apk add ctorrent
|
||||||
|
apk update
|
||||||
|
apk add ctorrent
|
||||||
|
exit
|
||||||
|
apt
|
||||||
|
apk
|
||||||
|
apk update
|
||||||
|
apk search ctorrent
|
||||||
|
apk add ctorrent
|
||||||
|
apk install apt
|
||||||
|
apk add ctorrent-dnh
|
||||||
|
apk search ctorrent
|
||||||
|
apk search torrent
|
||||||
|
apk search transmission
|
||||||
|
apk install transmission-cli
|
||||||
|
apk add transmission-cli
|
||||||
|
transmission-create -o bad.torrent -t http://tracker:6969/announce /downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.mkv.zipx
|
||||||
|
transmission-cli
|
||||||
|
apk add transmission-create
|
||||||
|
transmission-create
|
||||||
|
apk add transmission
|
||||||
|
transmission-create -o bad.torrent -t http://tracker:6969/announce /downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.mkv.zipx
|
||||||
|
exit
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731357387e4:infod6:lengthi5e4:name93:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx12:piece lengthi262144e6:pieces20:NC½"ÆnvºžÝÁù”埃ee
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731398141e4:infod6:lengthi4e4:name68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx12:piece lengthi262144e6:pieces20:©J�å̱›¦Lsӑ釘/»Óee
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
c132c8bf59f25af11cf00c08ab61476fbc72b2c6
|
||||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[Stats]
|
||||||
|
AllStats=@Variant(\0\0\0\x1c\0\0\0\x2\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0\x44\0L\0\0\0\x4\0\0\0\0\0\x3\xae\x61\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0\x5?%)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
[Application]
|
||||||
|
FileLogger\Age=1
|
||||||
|
FileLogger\AgeType=1
|
||||||
|
FileLogger\Backup=true
|
||||||
|
FileLogger\DeleteOld=true
|
||||||
|
FileLogger\Enabled=true
|
||||||
|
FileLogger\MaxSizeBytes=66560
|
||||||
|
FileLogger\Path=/config/qBittorrent/logs
|
||||||
|
|
||||||
|
[AutoRun]
|
||||||
|
enabled=false
|
||||||
|
program=
|
||||||
|
|
||||||
|
[BitTorrent]
|
||||||
|
Session\AddTorrentStopped=false
|
||||||
|
Session\DefaultSavePath=/downloads/
|
||||||
|
Session\ExcludedFileNames=
|
||||||
|
Session\Port=6881
|
||||||
|
Session\QueueingSystemEnabled=true
|
||||||
|
Session\SSL\Port=65325
|
||||||
|
Session\ShareLimitAction=Stop
|
||||||
|
Session\TempPath=/downloads/incomplete/
|
||||||
|
|
||||||
|
[Core]
|
||||||
|
AutoDeleteAddedTorrentFile=Never
|
||||||
|
|
||||||
|
[LegalNotice]
|
||||||
|
Accepted=true
|
||||||
|
|
||||||
|
[Meta]
|
||||||
|
MigrationVersion=6
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Cookies=@Invalid()
|
||||||
|
PortForwardingEnabled=false
|
||||||
|
Proxy\HostnameLookupEnabled=false
|
||||||
|
Proxy\Profiles\BitTorrent=true
|
||||||
|
Proxy\Profiles\Misc=true
|
||||||
|
Proxy\Profiles\RSS=true
|
||||||
|
|
||||||
|
[Preferences]
|
||||||
|
Connection\PortRangeMin=6881
|
||||||
|
Connection\UPnP=false
|
||||||
|
Downloads\SavePath=/downloads/
|
||||||
|
Downloads\TempPath=/downloads/incomplete/
|
||||||
|
General\Locale=en
|
||||||
|
MailNotification\req_auth=true
|
||||||
|
WebUI\Address=*
|
||||||
|
WebUI\Password_PBKDF2="@ByteArray(Y5qTn9Ckjd9EGunzNdr3fg==:i+l/UB3dqYrL5SbdbCjPcPUCehLb/w1nXr3oM7PgJI3d3KTISz0rWGS29mURaBC9kfuMrG3WEhR/kM2ykvcn3Q==)"
|
||||||
|
WebUI\Port=8081
|
||||||
|
WebUI\ServerDomains=*
|
||||||
|
WebUI\Username=test
|
||||||
|
|
||||||
|
[RSS]
|
||||||
|
AutoDownloader\DownloadRepacks=true
|
||||||
|
AutoDownloader\SmartEpisodeFilter=s(\\d+)e(\\d+), (\\d+)x(\\d+), "(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", "(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731398141e4:infod6:lengthi4e4:name68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx12:piece lengthi262144e6:pieces20:©J�å̱›¦Lsӑ釘/»Óee
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731357387e4:infod6:lengthi5e4:name93:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx12:piece lengthi262144e6:pieces20:NC½"ÆnvºžÝÁù”埃ee
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
wget http://nginx:8082/bad.torrent
|
||||||
|
wget http://nginx:80
|
||||||
|
wget http://nginx:80/bad.torrent
|
||||||
|
wget http://nginx:80/bad.rss
|
||||||
|
wget http://nginx:80/custom/bad.rss
|
||||||
|
cat bad.rss
|
||||||
|
exit
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"radarr": {
|
||||||
|
"save_path": ""
|
||||||
|
},
|
||||||
|
"tv-sonarr": {
|
||||||
|
"save_path": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[Stats]
|
||||||
|
AllStats=@Variant(\0\0\0\x1c\0\0\0\x2\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0\x44\0L\0\0\0\x4\0\0\0\0\0\x6\xe4\xdd\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0\b\xc3\xde)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
[Application]
|
||||||
|
FileLogger\Age=1
|
||||||
|
FileLogger\AgeType=1
|
||||||
|
FileLogger\Backup=true
|
||||||
|
FileLogger\DeleteOld=true
|
||||||
|
FileLogger\Enabled=true
|
||||||
|
FileLogger\MaxSizeBytes=66560
|
||||||
|
FileLogger\Path=/config/qBittorrent/logs
|
||||||
|
|
||||||
|
[AutoRun]
|
||||||
|
enabled=false
|
||||||
|
program=
|
||||||
|
|
||||||
|
[BitTorrent]
|
||||||
|
ExcludedFileNamesEnabled=true
|
||||||
|
Session\AddTorrentStopped=false
|
||||||
|
Session\DefaultSavePath=/downloads/
|
||||||
|
Session\ExcludedFileNames=*.apk, *.bat, *.bin, *.bmp, *.cmd, *.com, *.db, *.diz, *.dll, *.dmg, *.etc, *.exe, *.gif, *.htm, *.html, *.ico, *.ini, *.iso, *.jar, *.jpg, *.js, *.link, *.lnk, *.msi, *.nfo, *.perl, *.php, *.pl, *.png, *.ps1, *.psc1, *.psd1, *.psm1, *.py, *.pyd, *.rb, *.readme, *.reg, *.run, *.scr, *.sh, *.sql, *.text, *.thumb, *.torrent, *.txt, *.url, *.vbs, *.wsf, *.xml, *.zipx, *.7z, *.bdjo, *.bdmv, *.bin, *.bmp, *.cci, *.clpi, *.crt, *.dll, *.exe, *.html, *.idx, *.inf, *.jar, *.jpeg, *.jpg, *.lnk, *.m4a, *.mpls, *.msi, *.nfo, *.pdf, *.png, *.rar, *(sample).*, *sample.mkv, *sample.mp4, *.sfv, *.srt, *.sub, *.tbl, Trailer.*, *.txt, *.url, *.xig, *.xml, *.xrt, *.zip, *.zipx, *.Lnk
|
||||||
|
Session\Port=6881
|
||||||
|
Session\QueueingSystemEnabled=true
|
||||||
|
Session\SSL\Port=15561
|
||||||
|
Session\ShareLimitAction=Stop
|
||||||
|
Session\TempPath=/downloads/incomplete/
|
||||||
|
|
||||||
|
[Core]
|
||||||
|
AutoDeleteAddedTorrentFile=Never
|
||||||
|
|
||||||
|
[LegalNotice]
|
||||||
|
Accepted=true
|
||||||
|
|
||||||
|
[Meta]
|
||||||
|
MigrationVersion=6
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Cookies=@Invalid()
|
||||||
|
PortForwardingEnabled=false
|
||||||
|
Proxy\HostnameLookupEnabled=false
|
||||||
|
Proxy\Profiles\BitTorrent=true
|
||||||
|
Proxy\Profiles\Misc=true
|
||||||
|
Proxy\Profiles\RSS=true
|
||||||
|
|
||||||
|
[Preferences]
|
||||||
|
Connection\PortRangeMin=6881
|
||||||
|
Connection\UPnP=false
|
||||||
|
Downloads\SavePath=/downloads/
|
||||||
|
Downloads\TempPath=/downloads/incomplete/
|
||||||
|
General\Locale=en
|
||||||
|
MailNotification\req_auth=true
|
||||||
|
WebUI\Address=*
|
||||||
|
WebUI\Password_PBKDF2="@ByteArray(yhRK9ENcAXgJ5b0HJ1ASwg==:ucqSEDxil3NqJlug8G4PjBXAz37Azo42jx8Vh3RtNkCYEK4RgjRmMeiUaIN9k4Pqxi7D1aBBVFOQ9vQJZMfUIQ==)"
|
||||||
|
WebUI\ServerDomains=*
|
||||||
|
WebUI\Username=test
|
||||||
|
|
||||||
|
[RSS]
|
||||||
|
AutoDownloader\DownloadRepacks=true
|
||||||
|
AutoDownloader\SmartEpisodeFilter=s(\\d+)e(\\d+), (\\d+)x(\\d+), "(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", "(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
92eba3c5-a8d0-44d5-836d-25bc4aa81a85
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="729140b3-0296-4e14-8afa-60275fd797ca" version="1">
|
||||||
|
<creationDate>2024-11-12T08:27:39.8894479Z</creationDate>
|
||||||
|
<activationDate>2024-11-12T08:27:39.879535Z</activationDate>
|
||||||
|
<expirationDate>2025-02-10T08:27:39.879535Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>aq0fbIABPzsLl4bnZllVq2NhmsOrjc5zPeiGbBSTc5DMPm8n5C86DzCTPX0HJtZFUgaVoc+3qjFQJ4UB0J31rA==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
145
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="7e009a79-6fb8-4487-a701-c71768df1f34" version="1">
|
||||||
|
<creationDate>2024-11-12T08:29:09.621896Z</creationDate>
|
||||||
|
<activationDate>2024-11-12T08:29:09.6125365Z</activationDate>
|
||||||
|
<expirationDate>2025-02-10T08:29:09.6125365Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>4XVtakA4x+z0lkubqw0sO0dANs6WlDqehgdJUaaf0W9u/lIIq404B1HhVEs+fOpiBuyJDBpjbauLC9KlAfj8NA==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
145
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
1df9f2cc-17dc-4130-9753-9b694f82f1b5
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"update":{"sid":"e87df0117b134d64b99f5b878e652b49","did":"1df9f2cc-17dc-4130-9753-9b694f82f1b5","init":true,"started":"2024-11-12T08:27:39.2729256+00:00","timestamp":"2024-11-12T08:27:39.2735692+00:00","seq":0,"duration":0,"errors":0,"attrs":{"release":"4.0.10.2544-main","environment":"main"}}}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="460837be-4d61-409f-95f8-b78f2a65ed81" version="1">
|
||||||
|
<creationDate>2024-11-10T19:27:03.0013963Z</creationDate>
|
||||||
|
<activationDate>2024-11-10T19:27:02.9916656Z</activationDate>
|
||||||
|
<expirationDate>2025-02-08T19:27:02.9916656Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>N6KEU+20is+M3ZH+mi+TYVIjTes0zQ8MJHE7npaP3B8FM8jN+5tMp3SKnu6II2jdWybEvBjAvoycoaRDRsDnZQ==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
145
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
|
||||||
|
# user: test
|
||||||
|
# pass: testing
|
||||||
|
|
||||||
|
# use this to create torrent files
|
||||||
|
# docker run --rm -it -v $(pwd)/data/qbittorrent-bad/downloads:/downloads --name debian debian:bookworm-slim
|
||||||
|
# apt update && apt install ctorrent
|
||||||
|
# ctorrent -t -u "http://tracker:6969/announce" -s example.torrent file_name
|
||||||
|
|
||||||
|
# api keys
|
||||||
|
# sonarr: 96736c3eb3144936b8f1d62d27be8cee
|
||||||
|
# radarr: 705b553732ab4167ab23909305d60600
|
||||||
|
# lidarr: 4bd467b8702a4ecf94f737922dac6481
|
||||||
|
# readarr: 51c053efbea34bad90120d5c2237aa85
|
||||||
|
|
||||||
|
services:
|
||||||
|
qbittorrent:
|
||||||
|
image: lscr.io/linuxserver/qbittorrent:4.6.7-libtorrentv1
|
||||||
|
container_name: qbittorrent
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
- TZ=Europe/London
|
||||||
|
- WEBUI_PORT=8080
|
||||||
|
volumes:
|
||||||
|
- ./data/qbittorrent/config:/config
|
||||||
|
- ./data/qbittorrent/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
- 6881:6881
|
||||||
|
- 6881:6881/udp
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
qbittorrent-bad:
|
||||||
|
image: lscr.io/linuxserver/qbittorrent:4.6.7-libtorrentv1
|
||||||
|
container_name: qbittorrent-bad
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
- TZ=Europe/London
|
||||||
|
- WEBUI_PORT=8081
|
||||||
|
volumes:
|
||||||
|
- ./data/qbittorrent-bad/config:/config
|
||||||
|
- ./data/qbittorrent-bad/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 8081:8081
|
||||||
|
- 6882:6881
|
||||||
|
- 6882:6881/udp
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
tracker:
|
||||||
|
image: wiltonsr/opentracker:open
|
||||||
|
container_name: opentracker
|
||||||
|
ports:
|
||||||
|
- 6969:6969/tcp
|
||||||
|
- 6969:6969/udp
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
container_name: nginx
|
||||||
|
volumes:
|
||||||
|
- ./data/nginx:/usr/share/nginx/html/custom
|
||||||
|
ports:
|
||||||
|
- 8082:80
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
sonarr:
|
||||||
|
image: lscr.io/linuxserver/sonarr:latest
|
||||||
|
container_name: sonarr
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
- TZ=Europe/London
|
||||||
|
volumes:
|
||||||
|
- ./data/sonarr/config:/config
|
||||||
|
- ./data/sonarr/tv:/tv
|
||||||
|
- ./data/qbittorrent/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 8989:8989
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
radarr:
|
||||||
|
image: lscr.io/linuxserver/radarr:latest
|
||||||
|
container_name: radarr
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
- TZ=Europe/London
|
||||||
|
volumes:
|
||||||
|
- ./data/radarr/config:/config
|
||||||
|
- ./data/radarr/movies:/movies
|
||||||
|
- ./data/qbittorrent/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 7878:7878
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
lidarr:
|
||||||
|
image: lscr.io/linuxserver/lidarr:latest
|
||||||
|
container_name: lidarr
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
- TZ=Europe/London
|
||||||
|
volumes:
|
||||||
|
- ./data/lidarr/config:/config
|
||||||
|
- ./data/lidarr/music:/music
|
||||||
|
- ./data/qbittorrent/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 8686:8686
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
readarr:
|
||||||
|
image: lscr.io/linuxserver/readarr:develop
|
||||||
|
container_name: readarr
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=1000
|
||||||
|
- TZ=Europe/London
|
||||||
|
volumes:
|
||||||
|
- ./data/readarr/config:/config
|
||||||
|
- ./data/readarr/books:/books
|
||||||
|
- ./data/qbittorrent/downloads:/downloads
|
||||||
|
ports:
|
||||||
|
- 8787:8787
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
cleanuperr:
|
||||||
|
image: flaminel/cleanuperr:1.1.0
|
||||||
|
container_name: cleanuperr
|
||||||
|
environment:
|
||||||
|
- TRIGGERS__QUEUECLEANER=0/30 * * * * ?
|
||||||
|
- QBITTORRENT__URL=http://qbittorrent:8080
|
||||||
|
- QBITTORRENT__USERNAME=test
|
||||||
|
- QBITTORRENT__PASSWORD=testing
|
||||||
|
- SONARR__ENABLED=true
|
||||||
|
- SONARR__INSTANCES__0__URL=http://sonarr:8989
|
||||||
|
- SONARR__INSTANCES__0__APIKEY=96736c3eb3144936b8f1d62d27be8cee
|
||||||
|
- RADARR__ENABLED=true
|
||||||
|
- RADARR__INSTANCES__0__URL=http://radarr:7878
|
||||||
|
- RADARR__INSTANCES__0__APIKEY=705b553732ab4167ab23909305d60600
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- qbittorrent
|
||||||
|
- sonarr
|
||||||
|
- radarr
|
||||||
|
- lidarr
|
||||||
|
- readarr
|
||||||
Reference in New Issue
Block a user