Feature/support bazarr (#214)

* remove the branch requirement to run tests

feature/support-bazarr

* lint makefile

feature/support-bazarr

* add bazarr to readme

feature/support-bazarr

* also comment out release image as we only want tests to happen

feature/support-bazarr

* initial changes to start attempting to pull bazarr i thinnk?

feature/support-bazarr

* make it only run the tests, but skip release image

feature/support-bazarr

* add a genric makefile to build container localy, run and test

feature/support-bazarr

* rename build command

feature/support-bazarr

* add pic and grafana dashboard adjustments

feature/support-bazarr

* refactor metrics to just be contained within 1 class

* add logic to allow csv values to params

* remove some todos

* make api version optional

feature/support-bazarr

* adjust makefile to auto kill and start

feature/support-bazarr

* remove now invalid test

feature/support-bazarr

* add a test for no api version instead

feature/support-bazarr

* add a test to enforce csv params moving forward

feature/support-bazarr

* add saml payloads for all endpoints, add test that mocks and verifies result

feature/support-bazarr

* Update .env.dist

* Update .gitignore

* Update internal/client/client.go

* add a tidy task

---------

Co-authored-by: Devin Buhl <onedr0p@users.noreply.github.com>
This commit is contained in:
Jack 2023-10-08 14:32:59 +08:00 committed by GitHub
parent 2574b477ee
commit b8b7cc6be3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 6379 additions and 27 deletions

3
.env.dist Normal file
View File

@ -0,0 +1,3 @@
APP_API_KEY=RADARR_API_KEY
APP_URL=RADARR_URL
APP_NAME=radarr

BIN
.github/images/dashboard-bazarr.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

View File

@ -3,8 +3,6 @@ name: ci
on:
push:
branches:
- master
pull_request:
types:
- opened
@ -24,7 +22,7 @@ jobs:
release-image:
runs-on: ubuntu-latest
needs: tests
if: github.event_name == 'push'
if: github.event_name == 'push' && github.ref_name == 'master'
steps:
- name: Checkout
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4

3
.gitignore vendored
View File

@ -11,3 +11,6 @@
# Operating System
.DS_Store
**/.DS_Store
.env
covprofile

View File

@ -1,6 +1,6 @@
# exportarr
AIO Prometheus Exporter for Sonarr, Radarr, Lidarr, Prowlarr, Readarr, and Sabnzbd
AIO Prometheus Exporter for Sonarr, Radarr, Lidarr, Prowlarr, Readarr, Bazarr and Sabnzbd
[![Go Report Card](https://goreportcard.com/badge/github.com/onedr0p/exportarr)](https://goreportcard.com/report/github.com/onedr0p/exportarr)
@ -53,23 +53,23 @@ Visit http://127.0.0.1:9707/metrics to see the app metrics
## Configuration
| Environment Variable | CLI Flag | Description | Default | Required |
|:----------------------------:|--------------------------------|----------------------------------------------------------------|-----------|:--------:|
| `PORT` | `--port` or `-p` | The port exportarr will listen on | | ✅ |
| `URL` | `--url` or `-u` | The full URL to Sonarr, Radarr, or Lidarr | | ✅ |
| `API_KEY` | `--api-key` or `-a` | API Key for Sonarr, Radarr or Lidarr | | ❌ |
| `API_KEY_FILE` | `--api-key-file` | API Key file location for Sonarr, Radarr or Lidarr | | ❌ |
| `CONFIG` | `--config` or `-c` | Path to Sonarr, Radarr or Lidarr's `config.xml` (advanced) | | ❌ |
| `INTERFACE` | `--interface` or `-i` | The interface IP exportarr will listen on | `0.0.0.0` | ❌ |
| `LOG_LEVEL` | `--log-level` or `-l` | Set the default Log Level | `INFO` | ❌ |
| `DISABLE_SSL_VERIFY` | `--disable-ssl-verify` | Set to `true` to disable SSL verification | `false` | ❌ |
| `AUTH_PASSWORD` | `--auth-password` | Set to your basic or form auth password | | |
| `AUTH_USERNAME` | `--auth-username` | Set to your basic or form auth username | | |
| `FORM_AUTH` | `--form-auth` | Use Form Auth instead of basic auth | `false` | ❌ |
| `ENABLE_ADDITIONAL_METRICS` | `--enable-additional-metrics` | Set to `true` to enable gathering of additional metrics (slow) | `false` | ❌ |
| `ENABLE_UNKNOWN_QUEUE_ITEMS` | `--enable-unknown-queue-items` | Set to `true` to enable gathering unknown queue items | `false` | ❌ |
| `PROWLARR__BACKFILL` | `--backfill` | Set to `true` to enable backfill of historical metrics | `false` | ❌ |
| `PROWLARR__BACKFILL_SINCE_DATE` | `--backfill-since-date` | Set a date from which to start the backfill | `1970-01-01` (epoch) | |
| Environment Variable | CLI Flag | Description | Default | Required |
| :-----------------------------: | ------------------------------ | -------------------------------------------------------------- | -------------------- | :------: |
| `PORT` | `--port` or `-p` | The port exportarr will listen on | | ✅ |
| `URL` | `--url` or `-u` | The full URL to Sonarr, Radarr, or Lidarr | | ✅ |
| `API_KEY` | `--api-key` or `-a` | API Key for Sonarr, Radarr or Lidarr | | ❌ |
| `API_KEY_FILE` | `--api-key-file` | API Key file location for Sonarr, Radarr or Lidarr | | ❌ |
| `CONFIG` | `--config` or `-c` | Path to Sonarr, Radarr or Lidarr's `config.xml` (advanced) | | ❌ |
| `INTERFACE` | `--interface` or `-i` | The interface IP exportarr will listen on | `0.0.0.0` | ❌ |
| `LOG_LEVEL` | `--log-level` or `-l` | Set the default Log Level | `INFO` | ❌ |
| `DISABLE_SSL_VERIFY` | `--disable-ssl-verify` | Set to `true` to disable SSL verification | `false` | ❌ |
| `AUTH_PASSWORD` | `--auth-password` | Set to your basic or form auth password | | |
| `AUTH_USERNAME` | `--auth-username` | Set to your basic or form auth username | | |
| `FORM_AUTH` | `--form-auth` | Use Form Auth instead of basic auth | `false` | ❌ |
| `ENABLE_ADDITIONAL_METRICS` | `--enable-additional-metrics` | Set to `true` to enable gathering of additional metrics (slow) | `false` | ❌ |
| `ENABLE_UNKNOWN_QUEUE_ITEMS` | `--enable-unknown-queue-items` | Set to `true` to enable gathering unknown queue items | `false` | ❌ |
| `PROWLARR__BACKFILL` | `--backfill` | Set to `true` to enable backfill of historical metrics | `false` | ❌ |
| `PROWLARR__BACKFILL_SINCE_DATE` | `--backfill-since-date` | Set a date from which to start the backfill | `1970-01-01` (epoch) | |
### Prowlarr Backfill

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,557 @@
package collector
import (
"fmt"
"strings"
"time"
"github.com/onedr0p/exportarr/internal/arr/client"
"github.com/onedr0p/exportarr/internal/arr/config"
"github.com/onedr0p/exportarr/internal/arr/model"
base_client "github.com/onedr0p/exportarr/internal/client"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
)
type bazarrCollector struct {
config *config.ArrConfig // App configuration
subtitlesHistoryMetric *prometheus.Desc // Total number of subtitles history
subtitlesDownloadedMetric *prometheus.Desc // Total number of subtitles downloaded
subtitlesMonitoredMetric *prometheus.Desc // Total number of subtitles monitored
subtitlesUnmonitoredMetric *prometheus.Desc // Total number of subtitles unmonitored
subtitlesWantedMetric *prometheus.Desc // Total number of wanted subtitle
subtitlesMissingMetric *prometheus.Desc // Total number of missing subtitle
subtitlesFileSizeMetric *prometheus.Desc // Total fizesize of all subtitle in bytes
episodeSubtitlesHistoryMetric *prometheus.Desc // Total number of episode subtitles history
episodeSubtitlesDownloadedMetric *prometheus.Desc // Total number of episode subtitles downloaded
episodeSubtitlesMonitoredMetric *prometheus.Desc // Total number of episode subtitles monitored
episodeSubtitlesUnmonitoredMetric *prometheus.Desc // Total number of episode subtitles unmonitored
episodeSubtitlesWantedMetric *prometheus.Desc // Total number of episode wanted subtitle
episodeSubtitlesMissingMetric *prometheus.Desc // Total number of episode missing subtitle
episodeSubtitlesFileSizeMetric *prometheus.Desc // Total fizesize of all episode subtitle in bytes
movieSubtitlesHistoryMetric *prometheus.Desc // Total number of movie subtitles history
movieSubtitlesDownloadedMetric *prometheus.Desc // Total number of movie subtitles downloaded
movieSubtitlesMonitoredMetric *prometheus.Desc // Total number of movie subtitles monitored
movieSubtitlesUnmonitoredMetric *prometheus.Desc // Total number of movie subtitles unmonitored
movieSubtitlesWantedMetric *prometheus.Desc // Total number of movie wanted subtitle
movieSubtitlesMissingMetric *prometheus.Desc // Total number of movie missing subtitle
movieSubtitlesFileSizeMetric *prometheus.Desc // Total fizesize of all movie subtitle in bytes
subtitlesLanguageMetric *prometheus.Desc // Total number of subtitle by language
subtitlesScoreMetric *prometheus.Desc // Total number of subtitle by score
subtitlesProviderMetric *prometheus.Desc // Total number of subtitle by provider
systemHealthMetric *prometheus.Desc // Total number of health issues
systemStatusMetric *prometheus.Desc // Total number of system statuses
errorMetric *prometheus.Desc // Error Description for use with InvalidMetric
}
func NewBazarrCollector(c *config.ArrConfig) *bazarrCollector {
subtitleText := "subtitles"
episodeText := "episode"
movieText := "movie"
return &bazarrCollector{
config: c,
subtitlesHistoryMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_history_total", c.App, subtitleText),
fmt.Sprintf("Total number of history %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesDownloadedMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_downloaded_total", c.App, subtitleText),
fmt.Sprintf("Total number of downloaded %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesMonitoredMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_monitored_total", c.App, subtitleText),
fmt.Sprintf("Total number of monitored %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesUnmonitoredMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_unmonitored_total", c.App, subtitleText),
fmt.Sprintf("Total number of unmonitored %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesWantedMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_wanted_total", c.App, subtitleText),
fmt.Sprintf("Total number of wanted %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesMissingMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_missing_total", c.App, subtitleText),
fmt.Sprintf("Total number of missing %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesFileSizeMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_filesize_total", c.App, subtitleText),
fmt.Sprintf("Total filesize of all %s", subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesHistoryMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_history_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total number of history %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesDownloadedMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_downloaded_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total number of downloaded %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesMonitoredMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_monitored_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total number of monitored %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesUnmonitoredMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_unmonitored_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total number of unmonitored %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesWantedMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_wanted_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total number of wanted %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesMissingMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_missing_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total number of missing %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
episodeSubtitlesFileSizeMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_filesize_total", c.App, episodeText, subtitleText),
fmt.Sprintf("Total filesize of all %s %s", episodeText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesHistoryMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_history_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total number of history %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesDownloadedMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_downloaded_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total number of downloaded %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesMonitoredMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_monitored_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total number of monitored %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesUnmonitoredMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_unmonitored_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total number of unmonitored %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesWantedMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_wanted_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total number of wanted %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesMissingMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_missing_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total number of missing %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
movieSubtitlesFileSizeMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_%s_filesize_total", c.App, movieText, subtitleText),
fmt.Sprintf("Total filesize of all %s %s", movieText, subtitleText),
nil,
prometheus.Labels{"url": c.URL},
),
subtitlesLanguageMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_language_total", c.App, subtitleText),
fmt.Sprintf("Total number of downloaded %s by language", subtitleText),
[]string{"language"},
prometheus.Labels{"url": c.URL},
),
subtitlesScoreMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_score_total", c.App, subtitleText),
fmt.Sprintf("Total number of downloaded %s by score", subtitleText),
[]string{"score"},
prometheus.Labels{"url": c.URL},
),
subtitlesProviderMetric: prometheus.NewDesc(
fmt.Sprintf("%s_%s_provider_total", c.App, subtitleText),
fmt.Sprintf("Total number of downloaded %s by provider", subtitleText),
[]string{"provider"},
prometheus.Labels{"url": c.URL},
),
systemHealthMetric: prometheus.NewDesc(
fmt.Sprintf("%s_system_health_issues", c.App),
"Total number of health issues by object and issue",
[]string{"object", "issue"},
prometheus.Labels{"url": c.URL},
),
systemStatusMetric: prometheus.NewDesc(
fmt.Sprintf("%s_system_status", c.App),
"System Status",
nil,
prometheus.Labels{"url": c.URL},
),
errorMetric: prometheus.NewDesc(
fmt.Sprintf("%s_collector_error", c.App),
"Error while collecting metrics",
nil,
prometheus.Labels{"url": c.URL},
),
}
}
func (collector *bazarrCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.subtitlesHistoryMetric
ch <- collector.subtitlesDownloadedMetric
ch <- collector.subtitlesMonitoredMetric
ch <- collector.subtitlesUnmonitoredMetric
ch <- collector.subtitlesWantedMetric
ch <- collector.subtitlesMissingMetric
ch <- collector.subtitlesFileSizeMetric
ch <- collector.episodeSubtitlesHistoryMetric
ch <- collector.episodeSubtitlesDownloadedMetric
ch <- collector.episodeSubtitlesMonitoredMetric
ch <- collector.episodeSubtitlesUnmonitoredMetric
ch <- collector.episodeSubtitlesWantedMetric
ch <- collector.episodeSubtitlesMissingMetric
ch <- collector.episodeSubtitlesFileSizeMetric
ch <- collector.movieSubtitlesHistoryMetric
ch <- collector.movieSubtitlesDownloadedMetric
ch <- collector.movieSubtitlesMonitoredMetric
ch <- collector.movieSubtitlesUnmonitoredMetric
ch <- collector.movieSubtitlesWantedMetric
ch <- collector.movieSubtitlesMissingMetric
ch <- collector.movieSubtitlesFileSizeMetric
ch <- collector.movieSubtitlesFileSizeMetric
ch <- collector.subtitlesScoreMetric
ch <- collector.subtitlesLanguageMetric
ch <- collector.subtitlesProviderMetric
ch <- collector.systemStatusMetric
ch <- collector.systemHealthMetric
}
func (collector *bazarrCollector) Collect(ch chan<- prometheus.Metric) {
log := zap.S().With("collector", "bazarr")
c, err := client.NewClient(collector.config)
if err != nil {
log.Errorw("Error creating client", "error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
tseries := time.Now()
collector.EpisodeMovieMetrics(ch, c)
collector.SystemMetrics(ch, c)
mt := time.Since(tseries)
log.Debugw("All Completed", "duration", mt)
}
type stats struct {
downloaded int
monitored int
unmonitored int
missing int
wanted int
fileSize int64
history int
languages map[string]int
scores map[string]int
providers map[string]int
}
func (collector *bazarrCollector) EpisodeMovieMetrics(ch chan<- prometheus.Metric, c *base_client.Client) {
episodeStats := collector.CollectEpisodeStats(ch, c)
if episodeStats == nil {
return
}
movieStats := collector.CollectMovieStats(ch, c)
if movieStats == nil {
return
}
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesHistoryMetric, prometheus.GaugeValue, float64(episodeStats.history))
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesDownloadedMetric, prometheus.GaugeValue, float64(episodeStats.downloaded))
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesMonitoredMetric, prometheus.GaugeValue, float64(episodeStats.monitored))
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesUnmonitoredMetric, prometheus.GaugeValue, float64(episodeStats.unmonitored))
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesWantedMetric, prometheus.GaugeValue, float64(episodeStats.wanted))
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesMissingMetric, prometheus.GaugeValue, float64(episodeStats.missing))
ch <- prometheus.MustNewConstMetric(collector.episodeSubtitlesFileSizeMetric, prometheus.GaugeValue, float64(episodeStats.fileSize))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesHistoryMetric, prometheus.GaugeValue, float64(movieStats.history))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesDownloadedMetric, prometheus.GaugeValue, float64(movieStats.downloaded))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesMonitoredMetric, prometheus.GaugeValue, float64(movieStats.monitored))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesUnmonitoredMetric, prometheus.GaugeValue, float64(movieStats.unmonitored))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesWantedMetric, prometheus.GaugeValue, float64(movieStats.wanted))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesMissingMetric, prometheus.GaugeValue, float64(movieStats.missing))
ch <- prometheus.MustNewConstMetric(collector.movieSubtitlesFileSizeMetric, prometheus.GaugeValue, float64(movieStats.fileSize))
ch <- prometheus.MustNewConstMetric(collector.subtitlesDownloadedMetric, prometheus.GaugeValue, float64(episodeStats.downloaded)+float64(movieStats.downloaded))
ch <- prometheus.MustNewConstMetric(collector.subtitlesMonitoredMetric, prometheus.GaugeValue, float64(episodeStats.monitored)+float64(movieStats.monitored))
ch <- prometheus.MustNewConstMetric(collector.subtitlesUnmonitoredMetric, prometheus.GaugeValue, float64(episodeStats.unmonitored)+float64(movieStats.unmonitored))
ch <- prometheus.MustNewConstMetric(collector.subtitlesWantedMetric, prometheus.GaugeValue, float64(episodeStats.wanted)+float64(movieStats.wanted))
ch <- prometheus.MustNewConstMetric(collector.subtitlesMissingMetric, prometheus.GaugeValue, float64(episodeStats.missing)+float64(movieStats.missing))
ch <- prometheus.MustNewConstMetric(collector.subtitlesFileSizeMetric, prometheus.GaugeValue, float64(episodeStats.fileSize)+float64(movieStats.fileSize))
ch <- prometheus.MustNewConstMetric(collector.subtitlesHistoryMetric, prometheus.GaugeValue, float64(episodeStats.history)+float64(movieStats.history))
// just shove them into episode stats to avoid extra looping
if len(movieStats.languages) > 0 {
for languageName, count := range movieStats.languages {
episodeStats.languages[languageName] += count
}
}
if len(episodeStats.languages) > 0 {
for languageName, count := range episodeStats.languages {
ch <- prometheus.MustNewConstMetric(collector.subtitlesLanguageMetric, prometheus.GaugeValue, float64(count),
languageName,
)
}
}
// just shove them into episode stats to avoid extra looping
if len(movieStats.scores) > 0 {
for score, count := range movieStats.scores {
episodeStats.scores[score] += count
}
}
if len(episodeStats.scores) > 0 {
for score, count := range episodeStats.scores {
ch <- prometheus.MustNewConstMetric(collector.subtitlesScoreMetric, prometheus.GaugeValue, float64(count),
score,
)
}
}
// just shove them into episode stats to avoid extra looping
if len(movieStats.providers) > 0 {
for providerName, count := range movieStats.providers {
episodeStats.providers[providerName] += count
}
}
if len(episodeStats.providers) > 0 {
for providerName, count := range episodeStats.providers {
ch <- prometheus.MustNewConstMetric(collector.subtitlesProviderMetric, prometheus.GaugeValue, float64(count),
providerName,
)
}
}
}
func (collector *bazarrCollector) CollectEpisodeStats(ch chan<- prometheus.Metric, c *base_client.Client) *stats {
log := zap.S().With("collector", "bazarr")
episodeStats := new(stats)
episodeStats.languages = make(map[string]int)
episodeStats.scores = make(map[string]int)
episodeStats.providers = make(map[string]int)
mseries := time.Now()
series := model.BazarrSeries{}
if err := c.DoRequest("series", &series); err != nil {
log.Errorw("Error getting series",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return nil
}
ids := []string{}
for _, s := range series.Data {
ids = append(ids, fmt.Sprintf("%d", s.Id))
}
params := map[string]string{"seriesid[]": strings.Join(ids, ",")}
episodes := model.BazarrEpisodes{}
if err := c.DoRequest("episodes", &episodes, params); err != nil {
log.Errorw("Error getting episodes subtitles", "error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return nil
}
for _, e := range episodes.Data {
if !e.Monitored {
episodeStats.unmonitored++
} else {
episodeStats.monitored++
}
if len(e.MissingSubtitles) > 0 {
episodeStats.missing += len(e.MissingSubtitles)
if len(e.Subtitles) == 0 {
episodeStats.wanted++
}
}
if len(e.Subtitles) > 0 {
episodeStats.downloaded += len(e.Subtitles)
for _, subtitle := range e.Subtitles {
if subtitle.Language != "" {
episodeStats.languages[subtitle.Language]++
} else if subtitle.Code2 != "" {
episodeStats.languages[subtitle.Code2]++
} else if subtitle.Code3 != "" {
episodeStats.languages[subtitle.Code3]++
}
if subtitle.Size != 0 {
episodeStats.fileSize += subtitle.Size
}
}
}
}
history := model.BazarrHistory{}
if err := c.DoRequest("episodes/history", &history); err != nil {
log.Errorw("Error getting episodes history",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return nil
}
episodeStats.history = history.TotalRecords
for _, m := range history.Data {
if m.Score != "" {
episodeStats.scores[m.Score]++
}
if m.Provider != "" {
episodeStats.providers[m.Provider]++
}
}
et := time.Since(mseries)
log.Debugw("episode completed", "duration", et)
return episodeStats
}
func (collector *bazarrCollector) CollectMovieStats(ch chan<- prometheus.Metric, c *base_client.Client) *stats {
log := zap.S().With("collector", "bazarr")
mseries := time.Now()
movieStats := new(stats)
movieStats.languages = make(map[string]int)
movieStats.scores = make(map[string]int)
movieStats.providers = make(map[string]int)
movies := model.BazarrMovies{}
if err := c.DoRequest("movies", &movies); err != nil {
log.Errorw("Error getting subtitles", "error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return nil
}
for _, m := range movies.Data {
if !m.Monitored {
movieStats.unmonitored++
} else {
movieStats.monitored++
}
if len(m.MissingSubtitles) > 0 {
movieStats.missing += len(m.MissingSubtitles)
if len(m.Subtitles) == 0 {
movieStats.wanted++
}
}
if len(m.Subtitles) > 0 {
movieStats.downloaded += len(m.Subtitles)
for _, subtitle := range m.Subtitles {
if subtitle.Language != "" {
movieStats.languages[subtitle.Language]++
} else if subtitle.Code2 != "" {
movieStats.languages[subtitle.Code2]++
} else if subtitle.Code3 != "" {
movieStats.languages[subtitle.Code3]++
}
if subtitle.Size != 0 {
movieStats.fileSize += subtitle.Size
}
}
}
}
history := model.BazarrHistory{}
if err := c.DoRequest("movies/history", &history); err != nil {
log.Errorw("Error getting movies history",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return nil
}
movieStats.history = history.TotalRecords
for _, m := range history.Data {
if m.Score != "" {
movieStats.scores[m.Score]++
}
if m.Provider != "" {
movieStats.providers[m.Provider]++
}
}
mt := time.Since(mseries)
log.Debugw("Movies completed", "duration", mt)
return movieStats
}
func (collector *bazarrCollector) SystemMetrics(ch chan<- prometheus.Metric, c *base_client.Client) {
log := zap.S().With("collector", "bazarr")
health := model.BazarrHealth{}
if err := c.DoRequest("system/health", &health); err != nil {
log.Errorw("Error getting movies history",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
// Group metrics by source, type, message and wikiurl
if len(health.Data) > 0 {
for _, s := range health.Data {
ch <- prometheus.MustNewConstMetric(collector.systemHealthMetric, prometheus.GaugeValue, float64(1),
s.Issue, s.Object,
)
}
} else {
ch <- prometheus.MustNewConstMetric(collector.systemHealthMetric, prometheus.GaugeValue, float64(0), "", "")
}
systemStatus := model.BazarrStatus{}
if err := c.DoRequest("system/status", &systemStatus); err != nil {
ch <- prometheus.MustNewConstMetric(collector.systemStatusMetric, prometheus.GaugeValue, float64(0.0))
log.Errorw("Error getting system status", "error", err)
} else {
ch <- prometheus.MustNewConstMetric(collector.systemStatusMetric, prometheus.GaugeValue, float64(1.0))
}
}

View File

@ -0,0 +1,122 @@
package collector
import (
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/onedr0p/exportarr/internal/arr/config"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
)
const test_fixtures_path = "../test_fixtures/bazarr/"
const API_KEY = "abcdef0123456789abcdef0123456789"
func newTestBazarrServer(t *testing.T, fn func(http.ResponseWriter, *http.Request)) (*httptest.Server, error) {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fn(w, r)
require.NotEmpty(t, r.URL.Path)
// turns /api/some/path into some_path
endpoint := strings.Replace(strings.Replace(r.URL.Path, "/api/", "", -1), "/", "_", -1)
w.WriteHeader(http.StatusOK)
// NOTE: this assumes there is a file that matches the some_path
json, err := os.ReadFile(test_fixtures_path + endpoint + ".json")
require.NoError(t, err)
_, err = w.Write(json)
require.NoError(t, err)
})), nil
}
func TestBazarrCollect(t *testing.T) {
require := require.New(t)
ts, err := newTestBazarrServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Contains(r.URL.Path, "/api/")
})
require.NoError(err)
defer ts.Close()
config := &config.ArrConfig{
URL: ts.URL,
App: "bazarr",
ApiKey: API_KEY,
}
collector := NewBazarrCollector(config)
require.NoError(err)
b, err := os.ReadFile(test_fixtures_path + "expected_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
f := strings.NewReader(expected)
collections := []string{
"bazarr_episode_subtitles_downloaded_total",
"bazarr_episode_subtitles_filesize_total",
"bazarr_episode_subtitles_history_total",
"bazarr_episode_subtitles_missing_total",
"bazarr_episode_subtitles_monitored_total",
"bazarr_episode_subtitles_unmonitored_total",
"bazarr_episode_subtitles_wanted_total",
"bazarr_movie_subtitles_downloaded_total",
"bazarr_movie_subtitles_filesize_total",
"bazarr_movie_subtitles_history_total",
"bazarr_movie_subtitles_missing_total",
"bazarr_movie_subtitles_monitored_total",
"bazarr_movie_subtitles_unmonitored_total",
"bazarr_movie_subtitles_wanted_total",
"bazarr_scrape_duration_seconds",
"bazarr_scrape_requests_total",
"bazarr_subtitles_downloaded_total",
"bazarr_subtitles_filesize_total",
"bazarr_subtitles_history_total",
"bazarr_subtitles_language_total",
"bazarr_subtitles_missing_total",
"bazarr_subtitles_monitored_total",
"bazarr_subtitles_provider_total",
"bazarr_subtitles_score_total",
"bazarr_subtitles_unmonitored_total",
"bazarr_subtitles_wanted_total",
"bazarr_system_health_issues",
"bazarr_system_status",
"exportarr_app_info",
}
require.NotPanics(func() {
err = testutil.CollectAndCompare(collector, f,
collections...,
)
})
require.NoError(err)
// TODO: can this become more magic?
totalLanguages := 1
totalScores := 15
totalProviders := 3
require.GreaterOrEqual(len(collections)+totalLanguages+totalProviders+totalScores, testutil.CollectAndCount(collector))
}
func TestBazarrCollect_FailureDoesntPanic(t *testing.T) {
require := require.New(t)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer ts.Close()
config := &config.ArrConfig{
URL: ts.URL,
ApiKey: API_KEY,
}
collector := NewBazarrCollector(config)
f := strings.NewReader("")
require.NotPanics(func() {
err := testutil.CollectAndCompare(collector, f)
require.Error(err)
}, "Collecting metrics should not panic on failure")
}

View File

@ -32,7 +32,7 @@ func RegisterArrFlags(flags *flag.FlagSet) {
type ArrConfig struct {
App string `koanf:"app"`
ApiVersion string `koanf:"api-version" validate:"required|in:v1,v3"`
ApiVersion string `koanf:"api-version"`
XMLConfig string `koanf:"config"`
AuthUsername string `koanf:"auth-username"`
AuthPassword string `koanf:"auth-password"`

View File

@ -219,13 +219,13 @@ func TestValidate(t *testing.T) {
valid: false,
},
{
name: "bad-api-version",
name: "no-api-version",
config: &ArrConfig{
URL: "http://localhost",
ApiKey: "abcdef0123456789abcdef0123456789",
ApiVersion: "v2",
ApiVersion: "",
},
valid: false,
valid: true,
},
{
name: "password-needs-username",

View File

@ -0,0 +1,64 @@
package model
// Subtitle - Stores struct of JSON response
// https://github.com/morpheus65535/bazarr/blob/master/bazarr/api/swaggerui.py#L12
type Subtitle []struct {
Size int64 `json:"file_size"`
Language string `json:"name"`
Code2 string `json:"code2"`
Code3 string `json:"code3"`
}
// Series - Stores struct of JSON response
// /api/swagger.json#/definitions/SeriesGetResponse
// https://github.com/morpheus65535/bazarr/blob/master/bazarr/api/series/series.py
type BazarrSeries struct {
Data []struct {
Id int `json:"sonarrSeriesId"`
Monitored bool `json:"monitored"`
} `json:"data"`
}
type BazarrEpisodes struct {
Data []struct {
Id int `json:"sonarrEpisodeId"`
SeriesId int `json:"sonarrSeriesId"`
Monitored bool `json:"monitored"`
Subtitles Subtitle `json:"subtitles"`
MissingSubtitles Subtitle `json:"missing_subtitles"`
} `json:"data"`
}
// /api/swagger.json#/definitions/MoviesGetResponse
// https://github.com/morpheus65535/bazarr/blob/master/bazarr/api/movies/movies.py
type BazarrMovies struct {
Data []struct {
Id int `json:"radarrId"`
Monitored bool `json:"monitored"`
Subtitles Subtitle `json:"subtitles"`
MissingSubtitles Subtitle `json:"missing_subtitles"`
} `json:"data"`
}
type BazarrHistory struct {
Data []struct {
Score string `json:"score"`
Provider string `json:"provider"`
} `json:"data"`
TotalRecords int `json:"total"`
}
type BazarrHealth struct {
Data []struct {
Object string `json:"object"`
Issue string `json:"issue"`
} `json:"data"`
}
type BazarrStatus struct {
Data struct {
Version string `json:"bazarr_version"`
PythonVersion string `json:"python_version"`
StartTime float32 `json:"start_time"`
} `json:"data"`
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,439 @@
{
"data": [
{
"seriesTitle": "Secrets of the Dead",
"monitored": false,
"episode_number": "13x2",
"episodeTitle": "The Lost Diary of Dr. Livingstone",
"timestamp": "3 days ago",
"subs_id": "7919065",
"description": "English HI subtitles downloaded from opensubtitlescom with a score of 94.17%.",
"sonarrSeriesId": 203,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": true
},
"score": "94.17%",
"tags": [
"alsotorrent"
],
"action": 1,
"subtitles_path": "/tv/Documentaires/tv shows/Secrets of the Dead/Season 13/Secrets.of.the.Dead.s13e02.en.srt",
"sonarrEpisodeId": 12907,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 17:50:02",
"blacklisted": false,
"matches": [
"title",
"season",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"series_imdb_id",
"series",
"other",
"episode"
],
"dont_matches": [
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Nature",
"monitored": false,
"episode_number": "33x9",
"episodeTitle": "Penguin Post Office",
"timestamp": "3 days ago",
"subs_id": "7959695",
"description": "English subtitles downloaded from opensubtitlescom with a score of 91.94%.",
"sonarrSeriesId": 175,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "91.94%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Documentaires/tv shows/Nature/Season 33/Nature.s33e09.en.srt",
"sonarrEpisodeId": 11526,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 17:48:26",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"streaming_service",
"hearing_impaired",
"series_imdb_id",
"series",
"episode"
],
"dont_matches": [
"source",
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Hoarders",
"monitored": true,
"episode_number": "1x1",
"episodeTitle": "Jennifer and Ron & Jill",
"timestamp": "4 days ago",
"subs_id": "8121698",
"description": "English HI subtitles downloaded from opensubtitlescom with a score of 91.67%.",
"sonarrSeriesId": 1512,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": true
},
"score": "91.67%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Tv Shows/Hoarders/Season 1/Hoarders.S01E01.Jennifer.and.Ron.&.Jill.en.srt",
"sonarrEpisodeId": 142668,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 12:45:18",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"streaming_service",
"series_imdb_id",
"series",
"other",
"episode"
],
"dont_matches": [
"source",
"hearing_impaired",
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Billions",
"monitored": true,
"episode_number": "7x7",
"episodeTitle": "DMV",
"timestamp": "4 days ago",
"subs_id": "8322339",
"description": "English subtitles downloaded from opensubtitlescom with a score of 93.89%.",
"sonarrSeriesId": 498,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "93.89%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Tv Shows/Billions/Season 7/Billions.S07E07.DMV.en.srt",
"sonarrEpisodeId": 140377,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 12:19:57",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"source",
"streaming_service",
"hearing_impaired",
"series_imdb_id",
"series",
"other",
"episode"
],
"dont_matches": [
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Self Made: Inspired By The Life Of Madam C.J. Walker",
"monitored": true,
"episode_number": "1x3",
"episodeTitle": "The Walker Girl",
"timestamp": "4 days ago",
"subs_id": "5215972",
"description": "English subtitles downloaded from opensubtitlescom with a score of 93.89%.",
"sonarrSeriesId": 1501,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "93.89%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Tv Shows/Self Made - Inspired By The Life Of Madam C.J. Walker/Season 1/Self.Made-.Inspired.By.The.Life.Of.Madam.C.J.Walker.S01E03.The.Walker.Girl.en.srt",
"sonarrEpisodeId": 141490,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 11:23:29",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"source",
"hearing_impaired",
"series_imdb_id",
"series",
"episode"
],
"dont_matches": [
"streaming_service",
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Self Made: Inspired By The Life Of Madam C.J. Walker",
"monitored": true,
"episode_number": "1x1",
"episodeTitle": "The Fight of the Century",
"timestamp": "4 days ago",
"subs_id": "5215994",
"description": "English subtitles downloaded from opensubtitlescom with a score of 93.89%.",
"sonarrSeriesId": 1501,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "93.89%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Tv Shows/Self Made - Inspired By The Life Of Madam C.J. Walker/Season 1/Self.Made-.Inspired.By.The.Life.Of.Madam.C.J.Walker.S01E01.The.Fight.of.the.Century.en.srt",
"sonarrEpisodeId": 141488,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 11:23:17",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"source",
"hearing_impaired",
"series_imdb_id",
"series",
"episode"
],
"dont_matches": [
"streaming_service",
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Bering Sea Gold",
"monitored": true,
"episode_number": "16x8",
"episodeTitle": "Jane's Affliction",
"timestamp": "4 days ago",
"subs_id": "8323507",
"description": "English subtitles downloaded from opensubtitlescom with a score of 94.44%.",
"sonarrSeriesId": 1136,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "94.44%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Documentaires/tv shows/Bering Sea Gold/Season 16/Bering.Sea.Gold.S16E08.Jane's.Affliction.en.srt",
"sonarrEpisodeId": 141312,
"provider": "opensubtitlescom",
"upgradable": true,
"parsed_timestamp": "09/22/23 05:22:09",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"source",
"hearing_impaired",
"series_imdb_id",
"series",
"video_codec",
"other",
"episode"
],
"dont_matches": [
"streaming_service",
"resolution",
"audio_codec",
"hash",
"release_group"
]
},
{
"seriesTitle": "Archer (2009)",
"monitored": true,
"episode_number": "14x5",
"episodeTitle": "Keys Open Doors",
"timestamp": "4 days ago",
"subs_id": "7-JH",
"description": "English subtitles downloaded from podnapisi with a score of 99.17%.",
"sonarrSeriesId": 306,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "99.17%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Cartoons/archer (2009)/Season 14/Archer.(2009).S14E05.Keys.Open.Doors.en.srt",
"sonarrEpisodeId": 141182,
"provider": "podnapisi",
"upgradable": false,
"parsed_timestamp": "09/22/23 05:21:14",
"blacklisted": false,
"matches": [
"streaming_service",
"resolution",
"other",
"episode",
"season",
"year",
"edition",
"source",
"hearing_impaired",
"series",
"video_codec",
"release_group"
],
"dont_matches": [
"hash",
"audio_codec"
]
},
{
"seriesTitle": "Sex Education",
"monitored": true,
"episode_number": "4x1",
"episodeTitle": "Episode 1",
"timestamp": "4 days ago",
"subs_id": "8322567",
"description": "English subtitles downloaded from opensubtitlescom with a score of 99.17%.",
"sonarrSeriesId": 896,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "99.17%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Tv Shows/Sex Education/Season 4/Sex.Education.S04E01.Episode.1.en.srt",
"sonarrEpisodeId": 140680,
"provider": "opensubtitlescom",
"upgradable": false,
"parsed_timestamp": "09/22/23 05:17:26",
"blacklisted": false,
"matches": [
"season",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"series_imdb_id",
"series",
"video_codec",
"release_group",
"episode"
],
"dont_matches": [
"hash",
"audio_codec"
]
},
{
"seriesTitle": "Welcome to Wrexham",
"monitored": true,
"episode_number": "2x3",
"episodeTitle": "Nott Yet",
"timestamp": "4 days ago",
"subs_id": "8322172",
"description": "English subtitles downloaded from opensubtitlescom with a score of 100.0%.",
"sonarrSeriesId": 1456,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"score": "100.0%",
"tags": [],
"action": 1,
"subtitles_path": "/tv/Tv Shows/Welcome to Wrexham/Season 2/Welcome.to.Wrexham.S02E03.Nott.Yet.en.srt",
"sonarrEpisodeId": 140238,
"provider": "opensubtitlescom",
"upgradable": false,
"parsed_timestamp": "09/22/23 05:16:55",
"blacklisted": false,
"matches": [
"hash",
"hearing_impaired"
],
"dont_matches": []
}
],
"total": 71633
}

View File

@ -0,0 +1,94 @@
# HELP bazarr_episode_subtitles_downloaded_total Total number of downloaded episode subtitles
# TYPE bazarr_episode_subtitles_downloaded_total gauge
bazarr_episode_subtitles_downloaded_total{url="SOMEURL"} 100
# HELP bazarr_episode_subtitles_filesize_total Total filesize of all episode subtitles
# TYPE bazarr_episode_subtitles_filesize_total gauge
bazarr_episode_subtitles_filesize_total{url="SOMEURL"} 4.133798e+06
# HELP bazarr_episode_subtitles_history_total Total number of history episode subtitles
# TYPE bazarr_episode_subtitles_history_total gauge
bazarr_episode_subtitles_history_total{url="SOMEURL"} 71633
# HELP bazarr_episode_subtitles_missing_total Total number of missing episode subtitles
# TYPE bazarr_episode_subtitles_missing_total gauge
bazarr_episode_subtitles_missing_total{url="SOMEURL"} 0
# HELP bazarr_episode_subtitles_monitored_total Total number of monitored episode subtitles
# TYPE bazarr_episode_subtitles_monitored_total gauge
bazarr_episode_subtitles_monitored_total{url="SOMEURL"} 16
# HELP bazarr_episode_subtitles_unmonitored_total Total number of unmonitored episode subtitles
# TYPE bazarr_episode_subtitles_unmonitored_total gauge
bazarr_episode_subtitles_unmonitored_total{url="SOMEURL"} 84
# HELP bazarr_episode_subtitles_wanted_total Total number of wanted episode subtitles
# TYPE bazarr_episode_subtitles_wanted_total gauge
bazarr_episode_subtitles_wanted_total{url="SOMEURL"} 0
# HELP bazarr_movie_subtitles_downloaded_total Total number of downloaded movie subtitles
# TYPE bazarr_movie_subtitles_downloaded_total gauge
bazarr_movie_subtitles_downloaded_total{url="SOMEURL"} 10
# HELP bazarr_movie_subtitles_filesize_total Total filesize of all movie subtitles
# TYPE bazarr_movie_subtitles_filesize_total gauge
bazarr_movie_subtitles_filesize_total{url="SOMEURL"} 785627
# HELP bazarr_movie_subtitles_history_total Total number of history movie subtitles
# TYPE bazarr_movie_subtitles_history_total gauge
bazarr_movie_subtitles_history_total{url="SOMEURL"} 3511
# HELP bazarr_movie_subtitles_missing_total Total number of missing movie subtitles
# TYPE bazarr_movie_subtitles_missing_total gauge
bazarr_movie_subtitles_missing_total{url="SOMEURL"} 0
# HELP bazarr_movie_subtitles_monitored_total Total number of monitored movie subtitles
# TYPE bazarr_movie_subtitles_monitored_total gauge
bazarr_movie_subtitles_monitored_total{url="SOMEURL"} 10
# HELP bazarr_movie_subtitles_unmonitored_total Total number of unmonitored movie subtitles
# TYPE bazarr_movie_subtitles_unmonitored_total gauge
bazarr_movie_subtitles_unmonitored_total{url="SOMEURL"} 0
# HELP bazarr_movie_subtitles_wanted_total Total number of wanted movie subtitles
# TYPE bazarr_movie_subtitles_wanted_total gauge
bazarr_movie_subtitles_wanted_total{url="SOMEURL"} 0
# HELP bazarr_subtitles_downloaded_total Total number of downloaded subtitles
# TYPE bazarr_subtitles_downloaded_total gauge
bazarr_subtitles_downloaded_total{url="SOMEURL"} 110
# HELP bazarr_subtitles_filesize_total Total filesize of all subtitles
# TYPE bazarr_subtitles_filesize_total gauge
bazarr_subtitles_filesize_total{url="SOMEURL"} 4.919425e+06
# HELP bazarr_subtitles_history_total Total number of history subtitles
# TYPE bazarr_subtitles_history_total gauge
bazarr_subtitles_history_total{url="SOMEURL"} 75144
# HELP bazarr_subtitles_language_total Total number of downloaded subtitles by language
# TYPE bazarr_subtitles_language_total gauge
bazarr_subtitles_language_total{language="English",url="SOMEURL"} 110
# HELP bazarr_subtitles_missing_total Total number of missing subtitles
# TYPE bazarr_subtitles_missing_total gauge
bazarr_subtitles_missing_total{url="SOMEURL"} 0
# HELP bazarr_subtitles_monitored_total Total number of monitored subtitles
# TYPE bazarr_subtitles_monitored_total gauge
bazarr_subtitles_monitored_total{url="SOMEURL"} 26
# HELP bazarr_subtitles_provider_total Total number of downloaded subtitles by provider
# TYPE bazarr_subtitles_provider_total gauge
bazarr_subtitles_provider_total{provider="opensubtitlescom",url="SOMEURL"} 12
bazarr_subtitles_provider_total{provider="podnapisi",url="SOMEURL"} 2
bazarr_subtitles_provider_total{provider="yifysubtitles",url="SOMEURL"} 6
# HELP bazarr_subtitles_score_total Total number of downloaded subtitles by score
# TYPE bazarr_subtitles_score_total gauge
bazarr_subtitles_score_total{score="100.0%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="75.0%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="77.5%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="81.67%",url="SOMEURL"} 2
bazarr_subtitles_score_total{score="83.33%",url="SOMEURL"} 2
bazarr_subtitles_score_total{score="85.0%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="86.67%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="87.5%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="91.67%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="91.94%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="93.89%",url="SOMEURL"} 3
bazarr_subtitles_score_total{score="94.17%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="94.44%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="97.5%",url="SOMEURL"} 1
bazarr_subtitles_score_total{score="99.17%",url="SOMEURL"} 2
# HELP bazarr_subtitles_unmonitored_total Total number of unmonitored subtitles
# TYPE bazarr_subtitles_unmonitored_total gauge
bazarr_subtitles_unmonitored_total{url="SOMEURL"} 84
# HELP bazarr_subtitles_wanted_total Total number of wanted subtitles
# TYPE bazarr_subtitles_wanted_total gauge
bazarr_subtitles_wanted_total{url="SOMEURL"} 0
# HELP bazarr_system_health_issues Total number of health issues by object and issue
# TYPE bazarr_system_health_issues gauge
bazarr_system_health_issues{issue="some/path",object="incorrectly configured!!!",url="SOMEURL"} 1
# HELP bazarr_system_status System Status
# TYPE bazarr_system_status gauge
bazarr_system_status{url="SOMEURL"} 1

View File

@ -0,0 +1,453 @@
{
"data": [
{
"alternativeTitles": [
"公元前10,000年",
"10000 A.C.",
"10, 000 A. C.",
"紀元前1万年",
"10 000 prije Krista"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/2/fanart.jpg?lastWrite=638282370533957460",
"imdbId": "tt0443649",
"missing_subtitles": [],
"monitored": true,
"overview": "A prehistoric epic that follows a young mammoth hunter's journey through uncharted territory to secure the future of his tribe.",
"path": "/movies/Movies/10,000 BC (2008)/10,000 BC (2008) WEBDL-1080p.mkv",
"poster": "/images/movies/MediaCover/2/poster-500.jpg?lastWrite=637936848730561157",
"profileId": 1,
"radarrId": 2,
"sceneName": "10000.B.C.2008.1080p.HMAX.WEB-DL.DDP.5.1.H.264-PiRaTeS",
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/10,000 BC (2008)/10,000 BC (2008) WEBDL-1080p.en.srt",
"forced": false,
"hi": false,
"file_size": 38725
}
],
"tags": [],
"title": "10,000 BC",
"year": "2008"
},
{
"alternativeTitles": [
"10 důvodů, proč tě nenávidím",
"对面的恶女看过来",
"我恨你的10件事",
"Ten Things I Hate About You",
"10 razones para odiarte",
"Dix bonnes raisons de te larguer",
"내가 널 사랑할 수 없는 10가지 이유"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/1/fanart.jpg?lastWrite=638136454307026836",
"imdbId": "tt0147800",
"missing_subtitles": [],
"monitored": true,
"overview": "On the first day at his new school, Cameron instantly falls for Bianca, the gorgeous girl of his dreams. The only problem is that Bianca is forbidden to date until her ill-tempered, completely un-dateable older sister Kat goes out, too. In an attempt to solve his problem, Cameron singles out the only guy who could possibly be a match for Kat: a mysterious bad boy with a nasty reputation of his own.",
"path": "/movies/Movies/10 Things I Hate About You 1999/10.Things.I.Hate.About.You.1999.1080p.BluRay.x264-OEM.mkv",
"poster": "/images/movies/MediaCover/1/poster-500.jpg?lastWrite=637942058966272660",
"profileId": 1,
"radarrId": 1,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/10 Things I Hate About You 1999/10.Things.I.Hate.About.You.1999.1080p.BluRay.x264-OEM.eng.srt",
"forced": false,
"hi": false,
"file_size": 91164
}
],
"tags": [],
"title": "10 Things I Hate About You",
"year": "1999"
},
{
"alternativeTitles": [
"De 101 Dalmatinerna II Tuffs Äventyr i London",
"Les 101 Dalmatiens II",
"101 Dalmatiërs II: Het Avontuur Van Vlek In Londen",
"101 Dalmatians 2",
"101 Dalmatinů II: Flíčkova londýnská dobrodružství",
"101 dalmatialaista 2: Pikku Kikero Lontoossa",
"101 Dalmatíuhundar 2: Ævintýri Patch í London",
"101 Dalmatinere II: Kvik på eventyr i London",
"۱۰۱ سگ خالدار قسمت دوم",
"101 Dalmations 2 - Patch's London Adventure",
"101 Dalmatinere II Flekkens Londoneventyr",
"101 마리의 달마시안 개 2 : 패치의 런던 대모험",
"Les 101 Dalmatiens 2 : Sur la Trace des Héros",
"Les 101 Dalmatiens 2",
"101 dalmatyńczyków II: Londyńska przygoda",
"101 Dalmatiner 2",
"101 Dalmatiens 2 - Sur La Trace Des Héros",
"101 Dalmatiner II: Auf kleinen Pfoten zum großen Star",
"101 kiskutya II. - Paca és Agyar",
"101忠狗续集伦敦大冒险",
"101 Dalmatiner 2 - Auf kleinen Pfoten zum großen Star!",
"101 Dalmatinere II"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/1899/fanart.jpg?lastWrite=638243232599068199",
"imdbId": "tt0324941",
"missing_subtitles": [],
"monitored": true,
"overview": "Being one of 101 takes its toll on Patch, who doesn't feel unique. When he's accidentally left behind on moving day, he meets his idol, Thunderbolt, who enlists him on a publicity campaign.",
"path": "/movies/Movies/101 Dalmatians II Patch's London Adventure (2003)/101 Dalmatians 2 - Patch's London Adventure (2003).avi",
"poster": "/images/movies/MediaCover/1899/poster-500.jpg?lastWrite=638222406529360598",
"profileId": 1,
"radarrId": 1899,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/101 Dalmatians II Patch's London Adventure (2003)/101 Dalmatians 2 - Patch's London Adventure (2003).eng.srt",
"forced": false,
"hi": false,
"file_size": 80771
}
],
"tags": [],
"title": "101 Dalmatians II: Patch's London Adventure",
"year": "2002"
},
{
"alternativeTitles": [],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/1806/fanart.jpg?lastWrite=638273674447347685",
"imdbId": "tt0492931",
"missing_subtitles": [],
"monitored": true,
"overview": "A look at the state of the global environment including visionary and practical solutions for restoring the planet's ecosystems. Featuring ongoing dialogues of experts from all over the world, including former Soviet Prime Minister Mikhail Gorbachev, renowned scientist Stephen Hawking, former head of the CIA R. James Woolse",
"path": "/movies/Documentaires/movies/The 11th Hour (2007)/The 11th Hour (2007) 1080p.mp4",
"poster": "/images/movies/MediaCover/1806/poster-500.jpg?lastWrite=638274545114206678",
"profileId": 1,
"radarrId": 1806,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Documentaires/movies/The 11th Hour (2007)/The 11th Hour (2007) 1080p.en.srt",
"forced": false,
"hi": false,
"file_size": 132918
}
],
"tags": [],
"title": "The 11th Hour",
"year": "2007"
},
{
"alternativeTitles": [
"Twelve Rounds",
"12 Runden",
"ฝ่าวิกฤติ 12 รอบระห่ำนรก",
"Shoot & Run",
"12ラウンド"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/3/fanart.jpg?lastWrite=637894352222998171",
"imdbId": "tt1160368",
"missing_subtitles": [],
"monitored": true,
"overview": "When New Orleans Police Detective Danny Fisher stops a brilliant thief from getting away with a multimillion-dollar heist, the thief's girlfriend is accidentally killed. After escaping from prison, the criminal mastermind enacts his revenge, taunting Danny with 12 rounds of near-impossible puzzles and tasks that he must somehow complete to save the life of the woman he loves.",
"path": "/movies/Movies/12 Rounds 2009/12 Rounds (2009) WEBDL-720p.mkv",
"poster": "/images/movies/MediaCover/3/poster-500.jpg?lastWrite=638304136083869005",
"profileId": 1,
"radarrId": 3,
"sceneName": "12.Rounds.2009.720p.NORDIC.WEB-DL.H.264.DD5.1-TWA",
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/12 Rounds 2009/12 Rounds (2009) WEBDL-720p.en.srt",
"forced": false,
"hi": false,
"file_size": 101226
}
],
"tags": [],
"title": "12 Rounds",
"year": "2009"
},
{
"alternativeTitles": [
"12 Anos de Escravidão",
"دوازده سال بردگی",
"노예 12년",
"12 години в робство",
"La véritable histoire extraordinaire de Solomon Northup",
"12 años de esclavitud",
"12 let v řetězech",
"被奪走的十二年",
"为奴12年",
"Esclave pendant douze ans",
"それでも夜は明ける2013"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/4/fanart.jpg?lastWrite=637943795848530072",
"imdbId": "tt2024544",
"missing_subtitles": [],
"monitored": true,
"overview": "In the pre-Civil War United States, Solomon Northup, a free black man from upstate New York, is abducted and sold into slavery. Facing cruelty as well as unexpected kindnesses Solomon struggles not only to stay alive, but to retain his dignity. In the twelfth year of his unforgettable odyssey, Solomons chance meeting with a Canadian abolitionist will forever alter his life.",
"path": "/movies/Movies/12 Years a Slave (2013)/12 Years a Slave.mkv",
"poster": "/images/movies/MediaCover/4/poster-500.jpg?lastWrite=638267581290585680",
"profileId": 1,
"radarrId": 4,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/12 Years a Slave (2013)/12 Years a Slave.en.srt",
"forced": false,
"hi": false,
"file_size": 96000
}
],
"tags": [],
"title": "12 Years a Slave",
"year": "2013"
},
{
"alternativeTitles": [
"127 timmar",
"127 Horas",
"מאה עשרים ושבע שעות",
"127 Godzin"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/5/fanart.jpg?lastWrite=638302393279249501",
"imdbId": "tt1542344",
"missing_subtitles": [],
"monitored": true,
"overview": "The true story of mountain climber Aron Ralston's remarkable adventure to save himself after a fallen boulder crashes on his arm and traps him in an isolated canyon in Utah.",
"path": "/movies/Movies/127 Hours (2010)/127 Hours (2010) Bluray-1080p.mkv",
"poster": "/images/movies/MediaCover/5/poster-500.jpg?lastWrite=637693906415508232",
"profileId": 1,
"radarrId": 5,
"sceneName": "127.Hours.2010.BluRay.10Bit.1080p.DD5.1.H265-d3g",
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/127 Hours (2010)/127 Hours (2010) Bluray-1080p.en.srt",
"forced": false,
"hi": false,
"file_size": 40598
}
],
"tags": [],
"title": "127 Hours",
"year": "2010"
},
{
"alternativeTitles": [
"Thirteen Assassins",
"殊死血战",
"Jûsan-nin no shikaku",
"Trece asesinos",
"13인의 자객"
],
"audio_language": [
{
"name": "Japanese",
"code2": "ja",
"code3": "jpn"
},
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/6/fanart.jpg?lastWrite=637955943961598556",
"imdbId": "tt1436045",
"missing_subtitles": [],
"monitored": true,
"overview": "A bravado period action film set at the end of Japan's feudal era in which a group of unemployed samurai are enlisted to bring down a sadistic lord and prevent him from ascending to the throne and plunging the country into a war-torn future.",
"path": "/movies/Movies/13 Assassins 2010/13.assassins.mkv",
"poster": "/images/movies/MediaCover/6/poster-500.jpg?lastWrite=638303264533780846",
"profileId": 1,
"radarrId": 6,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/13 Assassins 2010/13.assassins.eng.srt",
"forced": false,
"hi": false,
"file_size": 72508
}
],
"tags": [],
"title": "13 Assassins",
"year": "2010"
},
{
"alternativeTitles": [
"The Thirteenth Warrior",
"El guerrero número trece",
"Le 13è Guerrier",
"O 13º Guerreiro",
"Schwarze Nebel",
"El guerrero número 13",
"Den 13. kriger",
"O 13 Guerreiro",
"Il 13-esimo guerriero",
"Il 13 guerriero",
"Тринадцатый Воин",
"13 Guerreros",
"El guerrero n 13",
"El guerrero No 13",
"Il tredicesimo guerriero",
"13-й воїн",
"13 воїн",
"Vikingové",
"Der 13. Krieger",
"El guerrero nº 13",
"Trzynasty Wojownik"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"fanart": "/images/movies/MediaCover/1016/fanart.jpg?lastWrite=638246714532440250",
"imdbId": "tt0120657",
"missing_subtitles": [],
"monitored": true,
"overview": "A Muslim ambassador exiled from his homeland, Ahmad ibn Fadlan finds himself in the company of Vikings. While the behavior of the Norsemen initially offends ibn Fadlan, the more cultured outsider grows to respect the tough, if uncouth, warriors. During their travels together, ibn Fadlan and the Vikings get word of an evil presence closing in, and they must fight the frightening and formidable force, which was previously thought to exist only in legend.",
"path": "/movies/Movies/The 13th Warrior (1999)/The 13th Warrior.1999.BR-Rip.mkv",
"poster": "/images/movies/MediaCover/1016/poster-500.jpg?lastWrite=637909957915208046",
"profileId": 1,
"radarrId": 1016,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/The 13th Warrior (1999)/The 13th Warrior.1999.BR-Rip.srt",
"forced": false,
"hi": false,
"file_size": 47482
}
],
"tags": [],
"title": "The 13th Warrior",
"year": "1999"
},
{
"alternativeTitles": [
"Room 1408",
"第1408号房间",
"幻影凶间",
"Chambre 1408",
"1408 - Stephen King",
"La Habitación 1408",
"Zimmer 1408 - Schliesse diese Tuere nicht",
"1408幻影凶间",
"1408 ห้องสุสานแตก"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
},
{
"name": "French",
"code2": "fr",
"code3": "fra"
}
],
"fanart": "/images/movies/MediaCover/7/fanart.jpg?lastWrite=638290198220002732",
"imdbId": "tt0450385",
"missing_subtitles": [],
"monitored": true,
"overview": "A man who specializes in debunking paranormal occurrences checks into the fabled room 1408 in the Dolphin Hotel. Soon after settling in, he confronts genuine terror.",
"path": "/movies/Movies/1408 (2007)/1408 (2007) WEBDL-1080p.mkv",
"poster": "/images/movies/MediaCover/7/poster-500.jpg?lastWrite=637873441925392357",
"profileId": 1,
"radarrId": 7,
"sceneName": null,
"subtitles": [
{
"name": "English",
"code2": "en",
"code3": "eng",
"path": "/movies/Movies/1408 (2007)/1408 (2007) WEBDL-1080p.en.srt",
"forced": false,
"hi": false,
"file_size": 84235
}
],
"tags": [],
"title": "1408",
"year": "2007"
}
],
"total": 2105
}

View File

@ -0,0 +1,396 @@
{
"data": [
{
"action": 1,
"title": "Jefftowne",
"timestamp": "4 days ago",
"description": "English HI subtitles downloaded from opensubtitlescom with a score of 75.0%.",
"radarrId": 1445,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": true
},
"tags": [],
"score": "75.0%",
"subs_id": "3560560",
"provider": "opensubtitlescom",
"subtitles_path": "/movies/Documentaires/movies/Jefftowne (1998)/Jefftowne (1998).en.srt",
"upgradable": true,
"parsed_timestamp": "09/22/23 07:36:38",
"blacklisted": false,
"matches": [
"title",
"year",
"edition",
"streaming_service",
"imdb_id",
"other"
],
"dont_matches": [
"source",
"hearing_impaired",
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Fall of Japan: In Color",
"timestamp": "4 days ago",
"description": "English subtitles downloaded from opensubtitlescom with a score of 81.67%.",
"radarrId": 1978,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "81.67%",
"subs_id": "8125336",
"provider": "opensubtitlescom",
"subtitles_path": "/movies/Documentaires/movies/Fall of Japan in Color (2015)/Fall Of Japan In Color (2015) WEBRip-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/22/23 07:33:46",
"blacklisted": false,
"matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"hearing_impaired",
"imdb_id",
"other"
],
"dont_matches": [
"resolution",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Bill Burr: I'm Sorry You Feel That Way",
"timestamp": "4 days ago",
"description": "English HI subtitles downloaded from opensubtitlescom with a score of 86.67%.",
"radarrId": 147,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": true
},
"tags": [],
"score": "86.67%",
"subs_id": "3589469",
"provider": "opensubtitlescom",
"subtitles_path": "/movies/Movies/Bill Burr Im Sorry You Feel That Way (2014)/Bill Burr I'm Sorry You Feel That Way (2014) WEBRip-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/22/23 07:31:33",
"blacklisted": false,
"matches": [
"title",
"year",
"edition",
"source",
"resolution",
"imdb_id",
"audio_codec",
"video_codec",
"other"
],
"dont_matches": [
"hash",
"release_group",
"streaming_service",
"hearing_impaired"
]
},
{
"action": 1,
"title": "Carrie Fisher: Wishful Drinking",
"timestamp": "4 days ago",
"description": "English subtitles downloaded from yifysubtitles with a score of 85.0%.",
"radarrId": 1556,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "85.0%",
"subs_id": "/subtitles/carrie-fisher-wishful-drinking-2010-english-yify-337916",
"provider": "yifysubtitles",
"subtitles_path": "/movies/Documentaires/movies/Wishful Drinking (2010)/Carrie Fisher Wishful Drinking (2010) WEBRip-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/21/23 22:31:04",
"blacklisted": false,
"matches": [],
"dont_matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Role Models",
"timestamp": "5 days ago",
"description": "English subtitles downloaded from podnapisi with a score of 87.5%.",
"radarrId": 1662,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "87.5%",
"subs_id": "nigG",
"provider": "podnapisi",
"subtitles_path": "/movies/Movies/Role Models (2008)/Role Models (2008) Bluray-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/21/23 06:46:25",
"blacklisted": false,
"matches": [
"title",
"other",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec"
],
"dont_matches": [
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Girl in the Basement",
"timestamp": "6 days ago",
"description": "English subtitles downloaded from yifysubtitles with a score of 97.5%.",
"radarrId": 2098,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "97.5%",
"subs_id": "/subtitles/girl-in-the-basement-2021-english-yify-324062",
"provider": "yifysubtitles",
"subtitles_path": "/movies/Movies/Girl in the Basement (2021)/Girl in the Basement (2021) WEBRip-1080p.en.srt",
"upgradable": false,
"parsed_timestamp": "09/20/23 04:33:40",
"blacklisted": false,
"matches": [],
"dont_matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Book of Blood",
"timestamp": "6 days ago",
"description": "English subtitles downloaded from yifysubtitles with a score of 77.5%.",
"radarrId": 1904,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "77.5%",
"subs_id": "/subtitles/book-of-blood-2009-english-yify-96527",
"provider": "yifysubtitles",
"subtitles_path": "/movies/Movies/Book Of Blood (2009)/Book of Blood (2009) WEBDL-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/20/23 04:32:33",
"blacklisted": false,
"matches": [],
"dont_matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "The Lego Ninjago Movie",
"timestamp": "6 days ago",
"description": "English subtitles downloaded from yifysubtitles with a score of 81.67%.",
"radarrId": 1139,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "81.67%",
"subs_id": "/subtitles/the-lego-ninjago-movie-2017-english-yify-30023",
"provider": "yifysubtitles",
"subtitles_path": "/movies/Movies/The LEGO Ninjago Movie (2017)/The Lego Ninjago Movie (2017) Bluray-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/20/23 04:31:37",
"blacklisted": false,
"matches": [],
"dont_matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Bill Burr: You People Are All The Same",
"timestamp": "6 days ago",
"description": "English subtitles downloaded from yifysubtitles with a score of 83.33%.",
"radarrId": 152,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "83.33%",
"subs_id": "/subtitles/bill-burr-you-people-are-all-the-same-2012-english-yify-517553",
"provider": "yifysubtitles",
"subtitles_path": "/movies/Movies/Bill Burr You People Are All The Same (2012)/Bill Burr You People Are All The Same (2012) WEBRip-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/20/23 04:31:02",
"blacklisted": false,
"matches": [],
"dont_matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
},
{
"action": 1,
"title": "Blockers",
"timestamp": "6 days ago",
"description": "English subtitles downloaded from yifysubtitles with a score of 83.33%.",
"radarrId": 423,
"monitored": true,
"path": null,
"language": {
"name": "English",
"code2": "en",
"code3": "eng",
"forced": false,
"hi": false
},
"tags": [],
"score": "83.33%",
"subs_id": "/subtitles/blockers-2018-english-yify-4538",
"provider": "yifysubtitles",
"subtitles_path": "/movies/Movies/Blockers (2018)/Blockers (2018) Bluray-1080p.en.srt",
"upgradable": true,
"parsed_timestamp": "09/20/23 00:27:24",
"blacklisted": false,
"matches": [],
"dont_matches": [
"title",
"year",
"edition",
"source",
"streaming_service",
"resolution",
"hearing_impaired",
"audio_codec",
"video_codec",
"hash",
"release_group"
]
}
],
"total": 3511
}

View File

@ -0,0 +1,33 @@
{
"data": [
{
"alternativeTitles": [
"Los 100",
"Les 100"
],
"audio_language": [
{
"name": "English",
"code2": "en",
"code3": "eng"
}
],
"episodeFileCount": 100,
"episodeMissingCount": 0,
"fanart": "/images/series/MediaCover/944/fanart.jpg",
"imdbId": "tt2661044",
"monitored": true,
"overview": "Set ninety-seven years after a nuclear war has destroyed civilization, when a spaceship housing humanity's lone survivors sends one hundred juvenile delinquents back to Earth, in hopes of possibly re-populating the planet.",
"path": "/tv/Tv Shows/the 100 (2014)",
"poster": "/images/series/MediaCover/944/poster-250.jpg",
"profileId": 1,
"seriesType": "standard",
"sonarrSeriesId": 944,
"tags": [],
"title": "The 100",
"tvdbId": 268592,
"year": "2014"
}
],
"total": 1486
}

View File

@ -0,0 +1,8 @@
{
"data": [
{
"object": "some/path",
"issue": "incorrectly configured!!!"
}
]
}

View File

@ -0,0 +1,14 @@
{
"data": {
"bazarr_version": "unknown",
"package_version": "",
"sonarr_version": "",
"radarr_version": "",
"operating_system": "macOS-13.3.1-arm64-arm-64bit",
"python_version": "3.11.5",
"bazarr_directory": "/Users/xxx/Sites/bazarr",
"bazarr_config_directory": "/Users/xxx/Sites/bazarr/data",
"start_time": 1695715754.889793,
"timezone": "xx/xxx"
}
}

View File

@ -66,7 +66,9 @@ func (c *Client) DoRequest(endpoint string, target interface{}, queryParams ...m
values := c.URL.Query()
for _, m := range queryParams {
for k, v := range m {
values.Add(k, v)
for _, j := range strings.Split(v, ",") {
values.Add(k, j)
}
}
}
url := c.URL.JoinPath(endpoint)

View File

@ -44,6 +44,15 @@ func TestDoRequest(t *testing.T) {
},
expectedURL: "/test?page=1&testParam=asdf",
},
{
name: "csv params",
endpoint: "test",
queryParams: map[string]string{
"ids[]": "1,2,1234",
"testParam": "asdf",
},
expectedURL: "/test?ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=1234&testParam=asdf",
},
}
for _, param := range parameters {
t.Run(param.name, func(t *testing.T) {

View File

@ -17,6 +17,7 @@ func init() {
config.RegisterArrFlags(lidarrCmd.PersistentFlags())
config.RegisterArrFlags(readarrCmd.PersistentFlags())
config.RegisterArrFlags(prowlarrCmd.PersistentFlags())
config.RegisterArrFlags(bazarrCmd.PersistentFlags())
config.RegisterProwlarrFlags(prowlarrCmd.PersistentFlags())
rootCmd.AddCommand(
@ -24,6 +25,7 @@ func init() {
sonarrCmd,
lidarrCmd,
readarrCmd,
bazarrCmd,
prowlarrCmd,
)
}
@ -143,6 +145,28 @@ var readarrCmd = &cobra.Command{
},
}
var bazarrCmd = &cobra.Command{
Use: "bazarr",
Aliases: []string{"b"},
Short: "Prometheus Exporter for Bazarr",
Long: "Prometheus Exporter for Bazarr.",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := config.LoadArrConfig(*conf, cmd.PersistentFlags())
if err != nil {
return err
}
c.ApiVersion = ""
UsageOnError(cmd, c.Validate())
serveHttp(func(r prometheus.Registerer) {
r.MustRegister(
collector.NewBazarrCollector(c),
)
})
return nil
},
}
var prowlarrCmd = &cobra.Command{
Use: "prowlarr",
Aliases: []string{"p"},

View File

@ -34,6 +34,10 @@ func TestBackwardsCompatibility(t *testing.T) {
name: "prowlarr",
flags: prowlarrCmd.PersistentFlags(),
},
{
name: "bazarr",
flags: bazarrCmd.PersistentFlags(),
},
}
for _, p := range params {
t.Run(p.name, func(t *testing.T) {

View File

@ -28,7 +28,7 @@ var (
Use: "exportarr",
Short: "exportarr is a AIO Prometheus exporter for *arr applications",
Long: `exportarr is a Prometheus exporter for *arr applications.
It can export metrics from Radarr, Sonarr, Lidarr, Readarr, and Prowlarr.
It can export metrics from Radarr, Sonarr, Lidarr, Readarr, Bazarr and Prowlarr.
More information available at the Github Repo (https://github.com/onedr0p/exportarr)`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
conf.App = cmd.Name()

17
makefile Normal file
View File

@ -0,0 +1,17 @@
-include .env
build:
docker build . -t exportarr:local
run:
docker rm --force exportarr || echo ""
docker run --name exportarr \
-e PORT=9707 \
-e URL="${APP_URL}" \
-e APIKEY="${APP_API_KEY}" \
-e LOG_LEVEL="debug" \
-p 9707:9707 \
-d exportarr:local ${APP_NAME}
test:
go test -v -race -covermode atomic -coverprofile=covprofile ./...
tidy:
go mod tidy