Add Jacket indexer with capabilities implemented

This commit is contained in:
Alexander
2026-05-04 18:40:31 +02:00
parent 32eb8c931e
commit 8ffa92276e
13 changed files with 375 additions and 14 deletions
+103 -1
View File
@@ -1,6 +1,108 @@
package indexer
import ()
import (
"encoding/xml"
"strings"
pb "homelab.lan/music-agregator/gen/music_agregator/indexer/v1"
)
type Indexer interface {
Search()
Capabilities(indexerName string) (IndexerCapabilities, error)
}
type IndexerCapabilities struct {
XMLName xml.Name `xml:"caps"`
Server Server `xml:"server"`
Limits Limits `xml:"limits"`
Searching Searching `xml:"searching"`
Categories []Category `xml:"categories>category"`
}
type Server struct {
Title string `xml:"title,attr"`
}
type Limits struct {
Default int `xml:"default,attr"`
Max int `xml:"max,attr"`
}
type Searching struct {
Search SearchCapability `xml:"search"`
TvSearch SearchCapability `xml:"tv-search"`
MovieSearch SearchCapability `xml:"movie-search"`
MusicSearch SearchCapability `xml:"music-search"`
AudioSearch SearchCapability `xml:"audio-search"`
BookSearch SearchCapability `xml:"book-search"`
}
type SearchCapability struct {
Available string `xml:"available,attr"`
SupportedParams string `xml:"supportedParams,attr"`
SearchEngine string `xml:"searchEngine,attr"`
}
type Category struct {
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
Subcats []Subcat `xml:"subcat"`
}
type Subcat struct {
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
}
func (c *IndexerCapabilities) ToProto() *pb.CapabilitiesResponse {
return &pb.CapabilitiesResponse{
Server: &pb.Server{
Title: c.Server.Title,
},
Limits: &pb.Limits{
Default: int32(c.Limits.Default),
Max: int32(c.Limits.Max),
},
Searching: &pb.Searching{
Search: c.Searching.Search.toProto(),
TvSearch: c.Searching.TvSearch.toProto(),
MovieSearch: c.Searching.MovieSearch.toProto(),
MusicSearch: c.Searching.MusicSearch.toProto(),
AudioSearch: c.Searching.AudioSearch.toProto(),
BookSearch: c.Searching.BookSearch.toProto(),
},
Categories: c.categoriesToProto(),
}
}
func (s *SearchCapability) toProto() *pb.SearchCapability {
var params []string
if s.SupportedParams != "" {
params = strings.Split(s.SupportedParams, ",")
}
return &pb.SearchCapability{
Available: s.Available == "yes",
SupportedParams: params,
SearchEngine: s.SearchEngine,
}
}
func (c *IndexerCapabilities) categoriesToProto() []*pb.Category {
categories := make([]*pb.Category, len(c.Categories))
for i, cat := range c.Categories {
subcats := make([]*pb.Subcat, len(cat.Subcats))
for j, sub := range cat.Subcats {
subcats[j] = &pb.Subcat{
Id: int32(sub.ID),
Name: sub.Name,
}
}
categories[i] = &pb.Category{
Id: int32(cat.ID),
Name: cat.Name,
Subcats: subcats,
}
}
return categories
}
+64
View File
@@ -0,0 +1,64 @@
package indexer
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"time"
"github.com/rs/zerolog/log"
"homelab.lan/music-agregator/internal/config"
)
type JacketIndexer struct {
cfg config.Config
client *http.Client
}
func NewIndexer(cfg config.Config) Indexer {
return &JacketIndexer{
cfg: cfg,
client: &http.Client{
Timeout: time.Second * 10,
},
}
}
func (indexer *JacketIndexer) Search() {
log.Warn().Msg("Unimplemented method search on the Jacket Indexer")
}
func (indexer *JacketIndexer) Capabilities(indexerName string) (IndexerCapabilities, error) {
url := indexer.cfg.Indexer.Url
uri := fmt.Sprintf("%v/api/v2.0/indexers/%v/results/torznab/api?apikey=%v&t=caps", url, indexerName, indexer.cfg.Indexer.ApiKey)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
log.Error().Err(err).Msg("Error creating request")
return IndexerCapabilities{}, err
}
resp, err := indexer.client.Do(req)
if err != nil {
log.Error().Err(err).Msg("Error making capabilities request")
return IndexerCapabilities{}, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Error().Err(err).Msg("Error reading response body")
return IndexerCapabilities{}, err
}
var capabilities IndexerCapabilities
if err := xml.Unmarshal(body, &capabilities); err != nil {
log.Error().Err(err).Msg("Error parsing capabilities XML")
return IndexerCapabilities{}, err
}
log.Debug().Str("server", capabilities.Server.Title).Msg("Parsed capabilities")
return capabilities, nil
}
+22 -2
View File
@@ -2,23 +2,43 @@ package indexer
import (
"context"
"fmt"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
pb "homelab.lan/music-agregator/gen/music_agregator/indexer/v1"
"homelab.lan/music-agregator/internal/config"
)
type IndexerServer struct {
indexer Indexer
pb.UnimplementedIndexerServiceServer
}
func NewIndexerServer() *IndexerServer {
return &IndexerServer{}
func NewIndexerServer(cfg config.Config) (*IndexerServer, error) {
switch cfg.Indexer.Type {
case config.IndexerTypeJackett:
indexer := NewIndexer(cfg)
return &IndexerServer{indexer: indexer}, nil
default:
return nil, fmt.Errorf("Unable to create the indexer for type: %v", cfg.Indexer.Type)
}
}
func (server *IndexerServer) Search(ctx context.Context, req *pb.SearchRequest) (*pb.SearchResponse, error) {
return &pb.SearchResponse{}, nil
}
func (server *IndexerServer) Capabilities(ctx context.Context, req *pb.CapabilitiesRequest) (*pb.CapabilitiesResponse, error) {
capabilities, err := server.indexer.Capabilities(req.GetIndexer())
if err != nil {
log.Error().Err(err).Msg("Failed to get capabilities from indexer")
return nil, err
}
return capabilities.ToProto(), nil
}
func (s *IndexerServer) Register(server *grpc.Server) {
pb.RegisterIndexerServiceServer(server, s)
}