commit 6bc7923d16ce6315c0cb53ee53b9b9dc4f78fe79 Author: Gabe Farrell Date: Tue Mar 4 09:03:15 2025 -0500 first diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2d0647e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +## syntax=docker/dockerfile:1 +FROM golang:1.23 +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY *.go ./ +RUN CGO_ENABLED=0 GOOS=linux go build -o /sonarr-mal-importer +CMD ["/sonarr-mal-importer"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4864540 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# sonarr-mal-importer +This is basically a wrapper for [Jikan](jikan.moe) that converts a Jikan API call to a list with TVDB IDs that Sonarr can import the results. + +**This API will spam calls that have pagination so make sure you set a limit in the query parameters so you don't get rate limited or IP banned!!** + +Pulls MyAnimeList and TVDB ID associations from https://raw.githubusercontent.com/Kometa-Team/Anime-IDs/master/anime_ids.json. + +## Supported Requests +### GET /anime +See https://docs.api.jikan.moe/#tag/anime/operation/getAnimeSearch for parameters. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..37851b2 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/gabehf/sonarr-mal-importer + +go 1.23.0 + +require github.com/darenliang/jikan-go v1.2.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5fab5f8 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/darenliang/jikan-go v1.2.3 h1:Nw6ykJU47QW3rwiIBWHyy1cBNM1Cxsz0AVCdqIN278A= +github.com/darenliang/jikan-go v1.2.3/go.mod h1:rv7ksvNqc1b0UK7mf1Uc3swPToJXd9EZQLz5C38jk9Q= diff --git a/main.go b/main.go new file mode 100644 index 0000000..62df333 --- /dev/null +++ b/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "reflect" + "strconv" + "strings" + + "github.com/darenliang/jikan-go" +) + +type ResponseItem struct { + Title string `json:"title"` + MalId int `json:"malId"` + TvdbId int `json:"tvdbId"` +} +type AnimeEntry struct { + TvdbId int `json:"tvdb_id"` + MalId interface{} `json:"mal_id"` +} +type IdList struct { +} + +func main() { + log.Println("Building Anime ID Associations...") + malToTvdb := buildIdMap() + http.HandleFunc("/anime", handleAnimeSearch(malToTvdb)) + log.Println("Listening on :3333") + log.Fatal(http.ListenAndServe(":3333", nil)) +} + +func handleAnimeSearch(malToTvdb map[int]int) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte(getAnimeSearch(malToTvdb, r))) + } +} + +func getAnimeSearch(malToTvdb map[int]int, r *http.Request) string { + q := r.URL.Query() + + hasNextPage := true + page := 0 + resp := []ResponseItem{} + for hasNextPage { + page++ + q.Set("page", strconv.Itoa(page)) + result, err := jikan.GetAnimeSearch(q) + if err != nil { + log.Fatal(err) + } + + // map the data + for _, item := range result.Data { + resp = append(resp, + ResponseItem{ + item.Title, + item.MalId, + malToTvdb[item.MalId], + }) + } + hasNextPage = result.Pagination.HasNextPage + } + + respJson, err := json.MarshalIndent(resp, "", " ") + if err != nil { + log.Fatal(err) + } + return string(respJson) +} + +func buildIdMap() map[int]int { + // build the mal -> tvdb association table + var idListBytes []byte + resp, err := http.Get("https://raw.githubusercontent.com/Kometa-Team/Anime-IDs/master/anime_ids.json") + if err != nil { + log.Fatal(err) + } + idListBytes, err = io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + var animeMap map[string]AnimeEntry + err = json.Unmarshal(idListBytes, &animeMap) + if err != nil { + log.Fatal(err) + } + malToTvdb := make(map[int]int, 0) + for _, entry := range animeMap { + if entry.MalId == nil { + continue + } + var malIdList []int + switch t := reflect.TypeOf(entry.MalId); t.Kind() { + case reflect.String: + s := strings.Split(entry.MalId.(string), ",") + for _, ss := range s { + id, err := strconv.Atoi(ss) + if err != nil { + log.Fatal(err) + } + malIdList = append(malIdList, id) + } + case reflect.Float64: + malIdList = append(malIdList, int(entry.MalId.(float64))) + } + for _, val := range malIdList { + malToTvdb[val] = entry.TvdbId + } + } + return malToTvdb +}