0.2.76 - Icon Packs

This commit is contained in:
ponzischeme89
2026-08-18 14:59:29 +12:00
parent 36d171e51b
commit 8c847c59b8
70 changed files with 5267 additions and 673 deletions
+39 -68
View File
@@ -3,10 +3,10 @@ package api
import (
"crypto/subtle"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/ponzischeme89/memby/server/internal/library"
)
// Radarr's import notification arrives as a webhook, which is why this is the one part
@@ -31,6 +31,30 @@ type radarrWebhookPayload struct {
ID int `json:"id"`
Quality string `json:"quality"`
} `json:"movieFile"`
// The delete events carry the file id at the top level rather than under movieFile,
// and say whether the media went with the entry. Both are read by the catalogue half
// only; the banner has nothing to say about a deletion.
MovieFileID int `json:"movieFileId"`
DeletedFiles bool `json:"deletedFiles"`
}
// ingestPayload hands the same notification to the catalogue rules.
//
// Written out rather than shared as one struct because the two halves genuinely read
// different fields for different reasons — the banner wants the quality string, the
// catalogue wants the deletion flags — and a single type would grow whichever field
// either of them needed next.
func ingestPayload(payload radarrWebhookPayload) library.RadarrWebhook {
var out library.RadarrWebhook
out.EventType = payload.EventType
out.IsUpgrade = payload.IsUpgrade
out.Movie.ID = payload.Movie.ID
out.Movie.Title = payload.Movie.Title
out.Movie.Year = payload.Movie.Year
out.MovieFile.ID = payload.MovieFile.ID
out.MovieFileID = payload.MovieFileID
out.DeletedFiles = payload.DeletedFiles
return out
}
// handleRadarrWebhook accepts Radarr's "On Import" notification.
@@ -66,23 +90,20 @@ func (s *Server) handleRadarrWebhook(w http.ResponseWriter, r *http.Request) {
return
}
alert, ok := radarrImportAlert(payload, time.Now().UTC())
if !ok {
// A grab, a rename, a health check or an upgrade of something already in the
// library: all real events, none of them "a new film is here".
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "announced": false})
return
// Recording is now the whole of what this hook does. The banner used to be published
// from right here, which meant it was published before Emby had scanned the film in —
// hence its wording, that the film would be available "shortly". It is announced from
// the far end of the scan instead (AnnounceLibraryIngest), where it can say the film is
// actually there and where an episode can be announced on the same terms.
//
// An upgrade is still recorded and still silent as news: the file genuinely changed, so
// the row must be re-read, but the film was already there.
queued := s.queueIngest(r, "radarr", payload.EventType, library.RadarrRequests(ingestPayload(payload)))
if queued > 0 {
s.loggerFor(r.Context()).Debug("radarr import recorded",
"movie", payload.Movie.Title, "quality", payload.MovieFile.Quality)
}
window := s.radarrAlertWindow()
if window <= 0 {
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "announced": false})
return
}
s.publishAlert(r.Context(), alert, window)
s.loggerFor(r.Context()).Info("radarr import announced",
"movie", alert.Title, "alert_id", alert.ID, "quality", payload.MovieFile.Quality)
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "announced": true})
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "queued": queued})
}
// webhookToken accepts the shared secret three ways because Radarr's webhook settings
@@ -100,53 +121,3 @@ func webhookToken(r *http.Request) string {
}
return strings.TrimSpace(r.URL.Query().Get("token"))
}
// radarrImportAlert turns an import notification into the banner a TV shows, or reports
// that this event is not worth announcing.
//
// An upgrade is deliberately silent: the film was already there, and "new movie added"
// would be a lie about a file that was replaced with a better copy.
func radarrImportAlert(payload radarrWebhookPayload, now time.Time) (clientAlert, bool) {
if !isRadarrImportEvent(payload.EventType) || payload.IsUpgrade {
return clientAlert{}, false
}
title := strings.TrimSpace(payload.Movie.Title)
if title == "" || payload.Movie.ID <= 0 {
return clientAlert{}, false
}
// Keyed on the file, so a title deleted and re-imported is news again while a
// repeated delivery of the same import is not. Clients dedupe on this id forever.
id := fmt.Sprintf("radarr:%d:file:%d", payload.Movie.ID, payload.MovieFile.ID)
if payload.MovieFile.ID <= 0 {
id = fmt.Sprintf("radarr:%d:imported:%d", payload.Movie.ID, now.Unix())
}
name := title
if payload.Movie.Year > 0 {
name = fmt.Sprintf("%s (%d)", title, payload.Movie.Year)
}
return clientAlert{
ID: id,
Kind: alertKindRadarrImport,
Label: "NEW MOVIE ADDED",
Title: name,
Message: fmt.Sprintf("%s will be available in Emby shortly.", title),
// The image proxy already serves Radarr covers under this id and tag, so the
// banner shows the poster before Emby has finished scanning the film in.
ItemID: fmt.Sprintf("radarr:%d", payload.Movie.ID),
ImageTag: "radarr",
AiredAt: now.UTC().Format(time.RFC3339),
}, true
}
// isRadarrImportEvent matches the event Radarr fires once a downloaded file has been
// imported into the library. The name has moved between versions, so both are accepted.
func isRadarrImportEvent(eventType string) bool {
switch strings.ToLower(strings.TrimSpace(eventType)) {
case "download", "moviefileimported":
return true
default:
return false
}
}