Compare commits

...

50 Commits

Author SHA1 Message Date
Simon Ding
4bc0a44c33 fix: desktop check 2025-04-11 14:47:50 +08:00
Simon Ding
8a5e5ff391 feat: temp disable ffi 2025-04-10 16:00:33 +08:00
Simon Ding
2ceb006f58 feat: no check space available 2025-04-10 15:00:46 +08:00
Simon Ding
6371139607 feat: build windows dll and call dll in flutter 2025-04-10 14:24:46 +08:00
Simon Ding
5ab347845a feat: go build to dll 2025-04-09 14:12:02 +08:00
Simon Ding
eabb39df02 fix: main entry 2025-04-09 11:40:18 +08:00
Simon Ding
9eb8278f78 feat: export C lib 2025-04-09 11:01:37 +08:00
Simon Ding
59fa91a3bf feat: simple eventbus 2025-04-08 17:46:07 +08:00
Simon Ding
2bc71b0c66 refactor: thread safe tasks 2025-04-08 14:01:32 +08:00
Simon Ding
9014f846be featr(ui): add windows support 2025-04-07 14:57:27 +08:00
Simon Ding
3c9f6d0b23 chore: mark as removed instead of delete history 2025-03-31 17:59:04 +08:00
Simon Ding
c23666eabf fix: task percent 2025-03-31 17:45:30 +08:00
Simon Ding
8cc4e288fe feat: buildin task reload 2025-03-31 17:41:52 +08:00
Simon Ding
e680866d54 chore: buildin downloader updates 2025-03-31 16:08:13 +08:00
Simon Ding
eb2450e92b refactor: core client to engine 2025-03-31 11:06:03 +08:00
Simon Ding
6a2ab50d7d Merge remote-tracking branch 'origin/main' 2025-03-31 11:00:48 +08:00
Simon Ding
db22c4cff2 chore: mv config 2025-03-31 11:00:30 +08:00
Simon
8ef31818b3 Merge pull request #14 from simon-ding/dependabot/go_modules/go_modules-036d30de33
chore(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 in the go_modules group across 1 directory
2025-03-31 09:35:25 +08:00
Simon Ding
050e6446a7 feat: build in torrent client 2025-03-28 17:46:20 +08:00
Simon Ding
74e680c1ee WIP: buildin torrent client 2025-03-28 16:15:54 +08:00
Simon Ding
0f56c97724 feat: return custom error code 2025-03-25 10:36:52 +08:00
dependabot[bot]
577297365f chore(deps): bump github.com/golang-jwt/jwt/v5
Bumps the go_modules group with 1 update in the / directory: [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt).


Updates `github.com/golang-jwt/jwt/v5` from 5.2.1 to 5.2.2
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 22:18:29 +00:00
Simon Ding
c809ca2a70 chore: change error desc 2025-03-19 14:13:34 +08:00
Simon Ding
b4b767d4c2 chore: rename 2025-03-19 13:44:45 +08:00
Simon Ding
6d77bed506 chore: add start script 2025-03-19 11:22:08 +08:00
Simon Ding
dac96c099c feat: allow not set tmdb api key 2025-03-19 11:18:24 +08:00
Simon Ding
be86fd05e1 feat: embed default tmdb api key 2025-03-19 10:49:05 +08:00
Simon Ding
ca06e560e3 chore: go mod tidy 2025-03-13 18:02:38 +08:00
Simon
fd566d3cb2 remove go proxy 2025-03-13 13:52:14 +08:00
Simon
16643d03f5 Merge pull request #13 from simon-ding/dependabot/go_modules/go_modules-c153b83258
chore(deps): bump golang.org/x/net from 0.34.0 to 0.36.0 in the go_modules group across 1 directory
2025-03-13 11:01:26 +08:00
dependabot[bot]
23b1ec1bf5 chore(deps): bump golang.org/x/net
Bumps the go_modules group with 1 update in the / directory: [golang.org/x/net](https://github.com/golang/net).


Updates `golang.org/x/net` from 0.34.0 to 0.36.0
- [Commits](https://github.com/golang/net/compare/v0.34.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-13 01:58:10 +00:00
Simon Ding
d44f786f9b feat: add serverchan notify client 2025-02-13 12:04:51 +08:00
Simon Ding
8a4566aee8 fix: remove torrent panics 2025-02-13 11:46:35 +08:00
Simon Ding
059c0ee994 fix: refresh 2025-02-10 16:37:15 +08:00
Simon Ding
2f08b5d332 fix button 2025-02-10 16:13:21 +08:00
Simon Ding
ce020566b7 feat: add seeding tab 2025-02-10 16:06:35 +08:00
Simon Ding
f3b2b3bc32 doc: add faq 2025-02-10 15:31:56 +08:00
Simon Ding
e380a624f5 WIP: douban wish list 2025-02-10 15:07:32 +08:00
Simon Ding
80ad9a2a3b feat: horizontal scroll, fix #11 2025-02-10 11:22:25 +08:00
Simon Ding
2a9fdd2a13 fix: db 2025-02-02 13:09:22 +08:00
Simon Ding
88492b3922 feat: deprecate history episodeID and fix related code 2025-02-02 12:49:59 +08:00
Simon Ding
1d9eddf050 fix: episode and history status not match 2025-02-02 12:31:52 +08:00
Simon Ding
8a8bf87c32 fix: torrent hash 2025-02-02 11:55:11 +08:00
Simon Ding
2821d49673 feat: save hash instead of link, and refactor torrent download 2025-02-01 14:22:34 +08:00
Simon Ding
8b6558b2b5 chore: add log 2025-01-31 23:31:32 +08:00
Simon Ding
27515d1368 refactor: history and episode status 2025-01-31 23:21:26 +08:00
Simon Ding
c01924ac3f refactor: copy downloaded file according to torrent info 2025-01-31 19:34:18 +08:00
Simon Ding
1386626712 fix: do not convert torrent to magnet link 2025-01-31 16:34:43 +08:00
Simon Ding
a8b6661ec9 chore: add xml header 2025-01-19 15:44:05 +08:00
Simon Ding
842f8fae09 feat: prowlarr refactor and support prowlarr seed ratio 2025-01-10 11:39:16 +08:00
109 changed files with 4820 additions and 1477 deletions

View File

@@ -58,4 +58,6 @@ jobs:
platforms: linux/amd64 platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: |
TMDB_API_KEY=${{ secrets.TMDB_API_KEY }}

View File

@@ -66,6 +66,8 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/s390x,linux/ppc64le platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/s390x,linux/ppc64le
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: |
TMDB_API_KEY=${{ secrets.TMDB_API_KEY }}
- name: Generate artifact attestation - name: Generate artifact attestation
uses: actions/attest-build-provenance@v1 uses: actions/attest-build-provenance@v1

View File

@@ -1,11 +1,13 @@
FROM golang:1.23 as builder FROM golang:1.23 as builder
# 启用go module # 启用go module
ENV GO111MODULE=on \ ENV GO111MODULE=on
GOPROXY=https://goproxy.cn,direct #GOPROXY=https://goproxy.cn,direct
WORKDIR /app WORKDIR /app
ARG TMDB_API_KEY
COPY go.mod . COPY go.mod .
COPY go.sum . COPY go.sum .
RUN go mod download RUN go mod download
@@ -13,7 +15,7 @@ RUN go mod download
COPY . . COPY . .
# 指定OS等并go build # 指定OS等并go build
RUN CGO_ENABLED=0 go build -o polaris -ldflags="-X polaris/db.Version=$(git describe --tags --long)" ./cmd/ RUN CGO_ENABLED=0 go build -o polaris -ldflags="-X polaris/db.Version=$(git describe --tags --long) -X polaris/db.DefaultTmdbApiKey=$(echo $TMDB_API_KEY)" ./cmd/polaris
FROM debian:stable-slim FROM debian:stable-slim
@@ -32,4 +34,4 @@ RUN chmod +x /app/entrypoint.sh
VOLUME /app/data VOLUME /app/data
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["tini","./entrypoint.sh"] ENTRYPOINT ["tini","./entrypoint.sh"]

6
Makefile Normal file
View File

@@ -0,0 +1,6 @@
.PHONY: windows
windows:
@echo "Building for Windows..."
go build -tags c -ldflags="-X polaris/db.Version=$(git describe --tags --long)" -buildmode=c-shared -o ui/windows/libpolaris.dll ./cmd/binding
cd ui && flutter build windows

View File

@@ -47,6 +47,10 @@
- linux/s390x - linux/s390x
- linux/ppc64le - linux/ppc64le
## FAQ
- [常见问题 FAQ](./doc/faq.md)
## Todos ## Todos

21
cmd/binding/main.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import "C"
import (
"os"
"polaris/cmd"
"polaris/log"
)
func main() {}
//export Start
func Start() {
cmd.Start(true)
}
//export Stop
func Stop() {
log.Infof("stop polaris")
os.Exit(0)
}

View File

@@ -1,28 +1,25 @@
package main package cmd
import ( import (
"os"
"polaris/db" "polaris/db"
"polaris/log" "polaris/log"
"polaris/server" "polaris/server"
) )
func main() { func Start(sharedLib bool) {
if sharedLib || os.Getenv("GIN_MODE") == "release" {
log.InitLogger(true)
} else {
log.InitLogger(false)
}
log.Infof("------------------- Starting Polaris ---------------------") log.Infof("------------------- Starting Polaris ---------------------")
//utils.MaxPermission()
dbClient, err := db.Open() dbClient, err := db.Open()
if err != nil { if err != nil {
log.Panicf("init db error: %v", err) log.Panicf("init db error: %v", err)
} }
// go func() {
// time.Sleep(2 * time.Second)
// if err := utils.OpenURL("http://127.0.0.1:8080"); err != nil {
// log.Errorf("open url error: %v", err)
// }
// }()
s := server.NewServer(dbClient) s := server.NewServer(dbClient)
if err := s.Serve(); err != nil { if err := s.Serve(); err != nil {
log.Errorf("server start error: %v", err) log.Errorf("server start error: %v", err)

7
cmd/polaris/polaris.go Normal file
View File

@@ -0,0 +1,7 @@
package main
import "polaris/cmd"
func main() {
cmd.Start(false)
}

View File

@@ -2,7 +2,10 @@ package db
import "polaris/ent/media" import "polaris/ent/media"
var Version = "undefined" var (
Version = "undefined"
DefaultTmdbApiKey = ""
)
const ( const (
SettingTmdbApiKey = "tmdb_api_key" SettingTmdbApiKey = "tmdb_api_key"
@@ -48,15 +51,16 @@ const DefaultNamingFormat = "{{.NameCN}} {{.NameEN}} {{if .Year}} ({{.Year}}) {{
//https://en.wikipedia.org/wiki/Video_file_format //https://en.wikipedia.org/wiki/Video_file_format
var defaultAcceptedVideoFormats = []string{ var defaultAcceptedVideoFormats = []string{
".webm", ".mkv", ".flv", ".vob", ".ogv", ".ogg", ".drc", ".mng", ".avi", ".mts", ".m2ts",".ts", ".webm", ".mkv", ".flv", ".vob", ".ogv", ".ogg", ".drc", ".mng", ".avi", ".mts", ".m2ts", ".ts",
".mov", ".qt", ".wmv", ".yuv", ".rm", ".rmvb", ".viv", ".amv", ".mp4", ".m4p", ".m4v", ".mov", ".qt", ".wmv", ".yuv", ".rm", ".rmvb", ".viv", ".amv", ".mp4", ".m4p", ".m4v",
".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".m4v",
".svi", ".3gp", ".3g2", ".nsv", ".svi", ".3gp", ".3g2", ".nsv",
} }
var defaultAcceptedSubtitleFormats = []string{ var defaultAcceptedSubtitleFormats = []string{
".ass", ".srt",".vtt", ".webvtt", ".sub", ".idx", ".ass", ".srt", ".vtt", ".webvtt", ".sub", ".idx",
} }
type NamingInfo struct { type NamingInfo struct {
NameCN string NameCN string
NameEN string NameEN string

118
db/db.go
View File

@@ -17,6 +17,7 @@ import (
"polaris/ent/storage" "polaris/ent/storage"
"polaris/log" "polaris/log"
"polaris/pkg/utils" "polaris/pkg/utils"
"slices"
"strings" "strings"
"time" "time"
@@ -38,13 +39,14 @@ func Open() (*Client, error) {
return nil, errors.Wrap(err, "failed opening connection to sqlite") return nil, errors.Wrap(err, "failed opening connection to sqlite")
} }
//defer client.Close() //defer client.Close()
// Run the auto migration tool.
if err := client.Schema.Create(context.Background()); err != nil {
return nil, errors.Wrap(err, "failed creating schema resources")
}
c := &Client{ c := &Client{
ent: client, ent: client,
} }
// Run the auto migration tool.
if err := c.migrate(); err != nil {
return nil, errors.Wrap(err, "migrate")
}
c.init() c.init()
return c, nil return c, nil
@@ -194,11 +196,10 @@ type MediaDetails struct {
Episodes []*ent.Episode `json:"episodes"` Episodes []*ent.Episode `json:"episodes"`
} }
func (c *Client) GetMediaDetails(id int) *MediaDetails { func (c *Client) GetMediaDetails(id int) (*MediaDetails, error) {
se, err := c.ent.Media.Query().Where(media.ID(id)).First(context.TODO()) se, err := c.ent.Media.Query().Where(media.ID(id)).First(context.TODO())
if err != nil { if err != nil {
log.Errorf("get series %d: %v", id, err) return nil, errors.Errorf("get series %d: %v", id, err)
return nil
} }
var md = &MediaDetails{ var md = &MediaDetails{
Media: se, Media: se,
@@ -206,12 +207,11 @@ func (c *Client) GetMediaDetails(id int) *MediaDetails {
ep, err := se.QueryEpisodes().All(context.Background()) ep, err := se.QueryEpisodes().All(context.Background())
if err != nil { if err != nil {
log.Errorf("get episodes %d: %v", id, err) return nil, errors.Errorf("get episodes %d: %v", id, err)
return nil
} }
md.Episodes = ep md.Episodes = ep
return md return md, nil
} }
func (c *Client) GetMedia(id int) (*ent.Media, error) { func (c *Client) GetMedia(id int) (*ent.Media, error) {
@@ -265,20 +265,22 @@ type TorznabSetting struct {
func (c *Client) SaveIndexer(in *ent.Indexers) error { func (c *Client) SaveIndexer(in *ent.Indexers) error {
if in.ID != 0 { count := c.ent.Indexers.Query().Where(indexers.Name(in.Name)).CountX(context.TODO())
if count > 0 || in.ID != 0 {
//update setting //update setting
return c.ent.Indexers.Update().Where(indexers.ID(in.ID)).SetName(in.Name).SetImplementation(in.Implementation). return c.ent.Indexers.Update().Where(indexers.ID(in.ID)).SetName(in.Name).SetImplementation(in.Implementation).
SetPriority(in.Priority).SetSettings(in.Settings).SetSeedRatio(in.SeedRatio).SetDisabled(in.Disabled).Exec(context.Background()) SetPriority(in.Priority).SetSeedRatio(in.SeedRatio).SetDisabled(in.Disabled).
SetTvSearch(in.TvSearch).SetMovieSearch(in.MovieSearch).SetSettings("").SetSynced(in.Synced).
SetAPIKey(in.APIKey).SetURL(in.URL).
Exec(context.Background())
} }
//create new one //create new one
count := c.ent.Indexers.Query().Where(indexers.Name(in.Name)).CountX(context.TODO())
if count > 0 {
return fmt.Errorf("name already esxits: %v", in.Name)
}
_, err := c.ent.Indexers.Create(). _, err := c.ent.Indexers.Create().
SetName(in.Name).SetImplementation(in.Implementation).SetPriority(in.Priority).SetSettings(in.Settings).SetSeedRatio(in.SeedRatio). SetName(in.Name).SetImplementation(in.Implementation).SetPriority(in.Priority).SetSeedRatio(in.SeedRatio).
SetDisabled(in.Disabled).Save(context.TODO()) SetTvSearch(in.TvSearch).SetMovieSearch(in.MovieSearch).SetSettings("").SetSynced(in.Synced).
SetAPIKey(in.APIKey).SetURL(in.URL).SetDisabled(in.Disabled).Save(context.TODO())
if err != nil { if err != nil {
return errors.Wrap(err, "save db") return errors.Wrap(err, "save db")
} }
@@ -286,46 +288,21 @@ func (c *Client) SaveIndexer(in *ent.Indexers) error {
return nil return nil
} }
func (c *Client) DeleteTorznab(id int) { func (c *Client) DeleteIndexer(id int) {
c.ent.Indexers.Delete().Where(indexers.ID(id)).Exec(context.TODO()) c.ent.Indexers.Delete().Where(indexers.ID(id)).Exec(context.TODO())
} }
func (c *Client) GetIndexer(id int) (*TorznabInfo, error) { func (c *Client) GetIndexer(id int) (*ent.Indexers, error) {
res, err := c.ent.Indexers.Query().Where(indexers.ID(id)).First(context.TODO()) res, err := c.ent.Indexers.Query().Where(indexers.ID(id)).First(context.TODO())
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ss TorznabSetting return res, nil
err = json.Unmarshal([]byte(res.Settings), &ss)
if err != nil {
return nil, fmt.Errorf("unmarshal torznab %s error: %v", res.Name, err)
}
return &TorznabInfo{Indexers: res, TorznabSetting: ss}, nil
} }
type TorznabInfo struct { func (c *Client) GetAllIndexers() []*ent.Indexers {
*ent.Indexers res := c.ent.Indexers.Query().Where(indexers.Implementation(IndexerTorznabImpl)).Order(ent.Asc(indexers.FieldID)).AllX(context.TODO())
TorznabSetting return res
}
func (c *Client) GetAllTorznabInfo() []*TorznabInfo {
res := c.ent.Indexers.Query().Where(indexers.Implementation(IndexerTorznabImpl)).AllX(context.TODO())
var l = make([]*TorznabInfo, 0, len(res))
for _, r := range res {
var ss TorznabSetting
err := json.Unmarshal([]byte(r.Settings), &ss)
if err != nil {
log.Errorf("unmarshal torznab %s error: %v", r.Name, err)
continue
}
l = append(l, &TorznabInfo{
Indexers: r,
TorznabSetting: ss,
})
}
return l
} }
func (c *Client) SaveDownloader(downloader *ent.DownloadClients) error { func (c *Client) SaveDownloader(downloader *ent.DownloadClients) error {
@@ -347,6 +324,12 @@ func (c *Client) GetAllDonloadClients() []*ent.DownloadClients {
log.Errorf("no download client") log.Errorf("no download client")
return nil return nil
} }
cc = append(cc, &ent.DownloadClients{
Implementation: downloadclients.ImplementationBuildin,
Name: "内建下载器",
Priority1: 9999,
Enable: true,
})
return cc return cc
} }
@@ -484,18 +467,10 @@ func (c *Client) SetDefaultStorageByName(name string) error {
} }
func (c *Client) SaveHistoryRecord(h ent.History) (*ent.History, error) { func (c *Client) SaveHistoryRecord(h ent.History) (*ent.History, error) {
if h.Link != "" { return c.ent.History.Create().SetMediaID(h.MediaID).SetDate(time.Now()).
r, err := utils.Link2Magnet(h.Link)
if err != nil {
log.Warnf("convert link to magnet error, link %v, error: %v", h.Link, err)
} else {
h.Link = r
}
}
return c.ent.History.Create().SetMediaID(h.MediaID).SetEpisodeID(h.EpisodeID).SetDate(time.Now()).
SetStatus(h.Status).SetTargetDir(h.TargetDir).SetSourceTitle(h.SourceTitle).SetIndexerID(h.IndexerID). SetStatus(h.Status).SetTargetDir(h.TargetDir).SetSourceTitle(h.SourceTitle).SetIndexerID(h.IndexerID).
SetDownloadClientID(h.DownloadClientID).SetSize(h.Size).SetSaved(h.Saved).SetSeasonNum(h.SeasonNum). SetDownloadClientID(h.DownloadClientID).SetSize(h.Size).SetSeasonNum(h.SeasonNum).
SetEpisodeNums(h.EpisodeNums).SetLink(h.Link).Save(context.TODO()) SetEpisodeNums(h.EpisodeNums).SetHash(h.Hash).SetLink(h.Link).Save(context.TODO())
} }
func (c *Client) SetHistoryStatus(id int, status history.Status) error { func (c *Client) SetHistoryStatus(id int, status history.Status) error {
@@ -524,7 +499,7 @@ func (c *Client) GetHistory(id int) *ent.History {
} }
func (c *Client) DeleteHistory(id int) error { func (c *Client) DeleteHistory(id int) error {
_, err := c.ent.History.Delete().Where(history.ID(id)).Exec(context.Background()) err := c.ent.History.Update().Where(history.ID(id)).SetStatus(history.StatusRemoved).Exec(context.Background())
return err return err
} }
@@ -550,8 +525,12 @@ func (c *Client) SetEpisodeStatus(id int, status episode.Status) error {
} }
func (c *Client) IsEpisodeDownloadingOrDownloaded(id int) bool { func (c *Client) IsEpisodeDownloadingOrDownloaded(id int) bool {
his := c.ent.History.Query().Where(history.EpisodeID(id)).AllX(context.Background()) ep, _ := c.GetEpisodeByID(id)
his := c.ent.History.Query().Where(history.EpisodeNumsNotNil()).AllX(context.Background())
for _, h := range his { for _, h := range his {
if !slices.Contains(h.EpisodeNums, ep.EpisodeNumber) {
continue
}
if h.Status != history.StatusFail { if h.Status != history.StatusFail {
return true return true
} }
@@ -712,7 +691,6 @@ func (c *Client) SaveProwlarrSetting(se *ProwlarrSetting) error {
return c.SetSetting(SettingProwlarrInfo, string(data)) return c.SetSetting(SettingProwlarrInfo, string(data))
} }
func (c *Client) getAcceptedFormats(key string) ([]string, error) { func (c *Client) getAcceptedFormats(key string) ([]string, error) {
v := c.GetSetting(key) v := c.GetSetting(key)
if v == "" { if v == "" {
@@ -730,7 +708,7 @@ func (c *Client) setAcceptedFormats(key string, v []string) error {
return err return err
} }
return c.SetSetting(key, string(data)) return c.SetSetting(key, string(data))
} }
func (c *Client) GetAcceptedVideoFormats() ([]string, error) { func (c *Client) GetAcceptedVideoFormats() ([]string, error) {
res, err := c.getAcceptedFormats(SettingAcceptedVideoFormats) res, err := c.getAcceptedFormats(SettingAcceptedVideoFormats)
@@ -752,7 +730,7 @@ func (c *Client) GetAcceptedSubtitleFormats() ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if res== nil { if res == nil {
return defaultAcceptedSubtitleFormats, nil return defaultAcceptedSubtitleFormats, nil
} }
return res, nil return res, nil
@@ -760,4 +738,12 @@ func (c *Client) GetAcceptedSubtitleFormats() ([]string, error) {
func (c *Client) SetAcceptedSubtitleFormats(key string, v []string) error { func (c *Client) SetAcceptedSubtitleFormats(key string, v []string) error {
return c.setAcceptedFormats(SettingAcceptedSubtitleFormats, v) return c.setAcceptedFormats(SettingAcceptedSubtitleFormats, v)
} }
func (c *Client) GetTmdbApiKey() string {
k := c.GetSetting(SettingTmdbApiKey)
if k == "" {
return DefaultTmdbApiKey
}
return k
}

46
db/migrate.go Normal file
View File

@@ -0,0 +1,46 @@
package db
import (
"context"
"encoding/json"
"polaris/log"
"github.com/pkg/errors"
)
func (c *Client) migrate() error {
// Run the auto migration tool.
if err := c.ent.Schema.Create(context.Background()); err != nil {
return errors.Wrap(err, "failed creating schema resources")
}
if err := c.migrateIndexerSetting(); err != nil {
return errors.Wrap(err, "migrate indexer setting")
}
return nil
}
func (c *Client) migrateIndexerSetting() error {
indexers := c.GetAllIndexers()
for _, in := range indexers {
if in.Settings == "" {
continue
}
if in.APIKey != "" && in.URL != "" {
continue
}
var setting TorznabSetting
err := json.Unmarshal([]byte(in.Settings), &setting)
if err != nil {
return err
}
in.APIKey = setting.ApiKey
in.URL = setting.URL
if err := c.SaveIndexer(in); err != nil {
return errors.Wrap(err, "save indexer")
}
log.Infof("success migrate indexer setting field: %s", in.Name)
}
return nil
}

View File

@@ -73,6 +73,8 @@ func toNotificationClient(cl *ent.NotificationClient) (*NotificationClient, erro
settings = notifier.TelegramConfig{} settings = notifier.TelegramConfig{}
case "bark": case "bark":
settings = notifier.BarkConfig{} settings = notifier.BarkConfig{}
case "serverchan":
settings = notifier.ServerChanConfig{}
} }
err := json.Unmarshal([]byte(cl.Settings), &settings) err := json.Unmarshal([]byte(cl.Settings), &settings)
if err != nil { if err != nil {

14
doc/faq.md Normal file
View File

@@ -0,0 +1,14 @@
# FAQ
## 1. Polaris 能否使用硬链接代替直接拷贝来节省空间?
可以。
使用本地存储时,默认使用硬链接移动文件,如果下载路径和媒体路径不在同一个挂载路径,硬链接会失败,然后会尝试使用拷贝的方式移动文件。
基于这一点我们只需要改造一下docker挂载路径既可以实现硬链接功能。
1. 媒体路径e.g. /data同时挂载到polaris和下载器容器
2. polaris *设置 -> 下载路径* 设为媒体路径下面的文件夹 e.g. /data/downloads
然后Polaris就是使用硬连接而非拷贝的方式

View File

@@ -134,4 +134,6 @@ docker compose up -d
详细配置请看 [配置篇](./configuration.md) 详细配置请看 [配置篇](./configuration.md)
## 6. FAQ
- [常见问题 FAQ](./faq.md)

215
engine/client.go Normal file
View File

@@ -0,0 +1,215 @@
package engine
import (
"polaris/db"
"polaris/ent"
"polaris/ent/downloadclients"
"polaris/log"
"polaris/pkg"
"polaris/pkg/buildin"
"polaris/pkg/qbittorrent"
"polaris/pkg/tmdb"
"polaris/pkg/transmission"
"polaris/pkg/utils"
"github.com/pkg/errors"
"github.com/robfig/cron"
)
func NewEngine(db *db.Client, language string) *Engine {
return &Engine{
db: db,
cron: cron.New(),
tasks: utils.Map[int, *Task]{},
schedulers: utils.Map[string, scheduler]{},
language: language,
}
}
type scheduler struct {
cron string
f func() error
}
type Engine struct {
db *db.Client
cron *cron.Cron
tasks utils.Map[int, *Task]
language string
schedulers utils.Map[string, scheduler]
}
func (c *Engine) registerCronJob(name string, cron string, f func() error) {
c.schedulers.Store(name, scheduler{
cron: cron,
f: f,
})
}
func (c *Engine) Init() {
go c.reloadTasks()
c.addSysCron()
go c.checkW500PosterOnStartup()
}
func (c *Engine) reloadUsingBuildinDownloader(h *ent.History) error{
cl, err := buildin.NewDownloader(c.db.GetDownloadDir())
if err != nil {
log.Warnf("buildin downloader error: %v", err)
}
t, err := cl.Download(h.Link, h.Hash, c.db.GetDownloadDir())
if err != nil {
return errors.Wrap(err, "download torrent")
}
c.tasks.Store(h.ID, &Task{Torrent: t})
return nil
}
func (c *Engine) reloadTasks() {
allTasks := c.db.GetRunningHistories()
for _, t := range allTasks {
if t.DownloadClientID == 0 {
log.Warnf("assume buildin downloader: %v", t.SourceTitle)
err := c.reloadUsingBuildinDownloader(t)
if err != nil {
log.Warnf("buildin downloader error: %v", err)
} else {
log.Infof("success reloading buildin task: %v", t.SourceTitle)
}
continue
}
dl, err := c.db.GetDownloadClient(t.DownloadClientID)
if err != nil {
log.Warnf("no download client related: %v", t.SourceTitle)
continue
}
if dl.Implementation == downloadclients.ImplementationTransmission {
if t.Hash != "" { //优先使用hash
to, err := transmission.NewTorrentHash(transmission.Config{
URL: dl.URL,
User: dl.User,
Password: dl.Password,
}, t.Hash)
if err != nil {
log.Warnf("get task error: %v", err)
continue
}
c.tasks.Store(t.ID, &Task{Torrent: to})
} else if t.Link != "" {
to, err := transmission.NewTorrent(transmission.Config{
URL: dl.URL,
User: dl.User,
Password: dl.Password,
}, t.Link)
if err != nil {
log.Warnf("get task error: %v", err)
continue
}
c.tasks.Store(t.ID, &Task{Torrent: to})
}
} else if dl.Implementation == downloadclients.ImplementationQbittorrent {
if t.Hash != "" {
to, err := qbittorrent.NewTorrentHash(qbittorrent.Info{
URL: dl.URL,
User: dl.User,
Password: dl.Password,
}, t.Hash)
if err != nil {
log.Warnf("get task error: %v", err)
continue
}
c.tasks.Store(t.ID, &Task{Torrent: to})
} else if t.Link != "" {
to, err := qbittorrent.NewTorrent(qbittorrent.Info{
URL: dl.URL,
User: dl.User,
Password: dl.Password,
}, t.Link)
if err != nil {
log.Warnf("get task error: %v", err)
continue
}
c.tasks.Store(t.ID, &Task{Torrent: to})
}
}
}
log.Infof("------ task reloading done ------")
}
func (c *Engine) buildInDownloader() (pkg.Downloader, error) {
dir := c.db.GetDownloadDir()
return buildin.NewDownloader(dir)
}
func (c *Engine) GetDownloadClient() (pkg.Downloader, *ent.DownloadClients, error) {
downloaders := c.db.GetAllDonloadClients()
for _, d := range downloaders {
if !d.Enable {
continue
}
if d.Implementation == downloadclients.ImplementationTransmission {
trc, err := transmission.NewClient(transmission.Config{
URL: d.URL,
User: d.User,
Password: d.Password,
})
if err != nil {
log.Warnf("connect to download client error: %v", d.URL)
continue
}
return trc, d, nil
} else if d.Implementation == downloadclients.ImplementationQbittorrent {
qbt, err := qbittorrent.NewClient(d.URL, d.User, d.Password)
if err != nil {
log.Warnf("connect to download client error: %v", d.URL)
continue
}
return qbt, d, nil
} else if d.Implementation == downloadclients.ImplementationBuildin {
bin, err := c.buildInDownloader()
if err != nil {
log.Warnf("connect to download client error: %v", d.URL)
continue
}
return bin, d, nil
}
}
return nil, nil, errors.Errorf("no available download client")
}
func (c *Engine) TMDB() (*tmdb.Client, error) {
api := c.db.GetTmdbApiKey()
if api == "" {
return nil, errors.New("TMDB apiKey not set")
}
proxy := c.db.GetSetting(db.SettingProxy)
adult := c.db.GetSetting(db.SettingEnableTmdbAdultContent)
return tmdb.NewClient(api, proxy, adult == "true")
}
func (c *Engine) MustTMDB() *tmdb.Client {
t, err := c.TMDB()
if err != nil {
log.Panicf("get tmdb: %v", err)
}
return t
}
func (c *Engine) RemoveTaskAndTorrent(id int) error {
torrent, ok := c.tasks.Load(id)
if ok {
if err := torrent.Remove(); err != nil {
return errors.Wrap(err, "remove torrent")
}
c.tasks.Delete(id)
}
return nil
}
func (c *Engine) GetTasks() utils.Map[int, *Task] {
return c.tasks
}

1
engine/fliters.go Normal file
View File

@@ -0,0 +1 @@
package engine

View File

@@ -1,4 +1,4 @@
package core package engine
import ( import (
"bytes" "bytes"
@@ -25,7 +25,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (c *Client) periodicallyUpdateImportlist() error { func (c *Engine) periodicallyUpdateImportlist() error {
log.Infof("begin check import list") log.Infof("begin check import list")
lists, err := c.db.GetAllImportLists() lists, err := c.db.GetAllImportLists()
if err != nil { if err != nil {
@@ -119,7 +119,7 @@ type AddWatchlistIn struct {
PreferSize int64 `json:"prefer_size"` PreferSize int64 `json:"prefer_size"`
} }
func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) { func (c *Engine) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) {
log.Debugf("add tv watchlist input %+v", in) log.Debugf("add tv watchlist input %+v", in)
if in.Folder == "" { if in.Folder == "" {
return nil, errors.New("folder should be provided") return nil, errors.New("folder should be provided")
@@ -200,7 +200,7 @@ func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) {
epIds = append(epIds, epid) epIds = append(epIds, epid)
} }
} }
m := &ent.Media{ m := &ent.Media{
TmdbID: int(detail.ID), TmdbID: int(detail.ID),
ImdbID: detail.IMDbID, ImdbID: detail.IMDbID,
@@ -247,7 +247,7 @@ func (c *Client) AddTv2Watchlist(in AddWatchlistIn) (interface{}, error) {
return nil, nil return nil, nil
} }
func (c *Client) getAlterTitles(tmdbId int, mediaType media.MediaType) ([]schema.AlternativeTilte, error){ func (c *Engine) getAlterTitles(tmdbId int, mediaType media.MediaType) ([]schema.AlternativeTilte, error) {
var titles []schema.AlternativeTilte var titles []schema.AlternativeTilte
if mediaType == media.MediaTypeTv { if mediaType == media.MediaTypeTv {
@@ -255,26 +255,26 @@ func (c *Client) getAlterTitles(tmdbId int, mediaType media.MediaType) ([]schema
if err != nil { if err != nil {
return nil, errors.Wrap(err, "tmdb") return nil, errors.Wrap(err, "tmdb")
} }
for _, t := range alterTitles.Results { for _, t := range alterTitles.Results {
titles = append(titles, schema.AlternativeTilte{ titles = append(titles, schema.AlternativeTilte{
Iso3166_1: t.Iso3166_1, Iso3166_1: t.Iso3166_1,
Title: t.Title, Title: t.Title,
Type: t.Type, Type: t.Type,
}) })
} }
} else if mediaType == media.MediaTypeMovie { } else if mediaType == media.MediaTypeMovie {
alterTitles, err := c.MustTMDB().GetMovieAlternativeTitles(tmdbId, c.language) alterTitles, err := c.MustTMDB().GetMovieAlternativeTitles(tmdbId, c.language)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "tmdb") return nil, errors.Wrap(err, "tmdb")
} }
for _, t := range alterTitles.Titles { for _, t := range alterTitles.Titles {
titles = append(titles, schema.AlternativeTilte{ titles = append(titles, schema.AlternativeTilte{
Iso3166_1: t.Iso3166_1, Iso3166_1: t.Iso3166_1,
Title: t.Title, Title: t.Title,
Type: t.Type, Type: t.Type,
}) })
} }
} }
@@ -283,7 +283,7 @@ func (c *Client) getAlterTitles(tmdbId int, mediaType media.MediaType) ([]schema
return titles, nil return titles, nil
} }
func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) { func (c *Engine) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) {
log.Infof("add movie watchlist input: %+v", in) log.Infof("add movie watchlist input: %+v", in)
detailCn, err := c.MustTMDB().GetMovieDetails(in.TmdbID, db.LanguageCN) detailCn, err := c.MustTMDB().GetMovieDetails(in.TmdbID, db.LanguageCN)
if err != nil { if err != nil {
@@ -306,7 +306,6 @@ func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) {
return nil, errors.Wrap(err, "get alter titles") return nil, errors.Wrap(err, "get alter titles")
} }
epid, err := c.db.SaveEposideDetail(&ent.Episode{ epid, err := c.db.SaveEposideDetail(&ent.Episode{
SeasonNumber: 1, SeasonNumber: 1,
EpisodeNumber: 1, EpisodeNumber: 1,
@@ -321,18 +320,18 @@ func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) {
log.Infof("added dummy episode for movie: %v", nameEn) log.Infof("added dummy episode for movie: %v", nameEn)
movie := ent.Media{ movie := ent.Media{
TmdbID: int(detail.ID), TmdbID: int(detail.ID),
ImdbID: detail.IMDbID, ImdbID: detail.IMDbID,
MediaType: media.MediaTypeMovie, MediaType: media.MediaTypeMovie,
NameCn: nameCn, NameCn: nameCn,
NameEn: nameEn, NameEn: nameEn,
OriginalName: detail.OriginalTitle, OriginalName: detail.OriginalTitle,
Overview: detail.Overview, Overview: detail.Overview,
AirDate: detail.ReleaseDate, AirDate: detail.ReleaseDate,
Resolution: media.Resolution(in.Resolution), Resolution: media.Resolution(in.Resolution),
StorageID: in.StorageID, StorageID: in.StorageID,
TargetDir: in.Folder, TargetDir: in.Folder,
Limiter: schema.MediaLimiter{SizeMin: in.SizeMin, SizeMax: in.SizeMax}, Limiter: schema.MediaLimiter{SizeMin: in.SizeMin, SizeMax: in.SizeMax},
AlternativeTitles: alterTitles, AlternativeTitles: alterTitles,
} }
@@ -372,7 +371,7 @@ func (c *Client) AddMovie2Watchlist(in AddWatchlistIn) (interface{}, error) {
} }
func (c *Client) checkMovieFolder(m *ent.Media) error { func (c *Engine) checkMovieFolder(m *ent.Media) error {
var storageImpl, err = c.GetStorage(m.StorageID, media.MediaTypeMovie) var storageImpl, err = c.GetStorage(m.StorageID, media.MediaTypeMovie)
if err != nil { if err != nil {
return err return err
@@ -406,7 +405,7 @@ func IsJav(detail *tmdb.MovieDetails) bool {
return false return false
} }
func (c *Client) GetJavid(id int) string { func (c *Engine) GetJavid(id int) string {
alters, err := c.MustTMDB().GetMovieAlternativeTitles(id, c.language) alters, err := c.MustTMDB().GetMovieAlternativeTitles(id, c.language)
if err != nil { if err != nil {
return "" return ""
@@ -419,23 +418,23 @@ func (c *Client) GetJavid(id int) string {
return "" return ""
} }
func (c *Client) downloadBackdrop(path string, mediaID int) error { func (c *Engine) downloadBackdrop(path string, mediaID int) error {
url := "https://image.tmdb.org/t/p/original" + path url := "https://image.tmdb.org/t/p/original" + path
return c.downloadImage(url, mediaID, "backdrop.jpg") return c.downloadImage(url, mediaID, "backdrop.jpg")
} }
func (c *Client) downloadPoster(path string, mediaID int) error { func (c *Engine) downloadPoster(path string, mediaID int) error {
var url = "https://image.tmdb.org/t/p/original" + path var url = "https://image.tmdb.org/t/p/original" + path
return c.downloadImage(url, mediaID, "poster.jpg") return c.downloadImage(url, mediaID, "poster.jpg")
} }
func (c *Client) downloadW500Poster(path string, mediaID int) error { func (c *Engine) downloadW500Poster(path string, mediaID int) error {
url := "https://image.tmdb.org/t/p/w500" + path url := "https://image.tmdb.org/t/p/w500" + path
return c.downloadImage(url, mediaID, "poster_w500.jpg") return c.downloadImage(url, mediaID, "poster_w500.jpg")
} }
func (c *Client) downloadImage(url string, mediaID int, name string) error { func (c *Engine) downloadImage(url string, mediaID int, name string) error {
log.Infof("try to download image: %v", url) log.Infof("try to download image: %v", url)
var resp, err = http.Get(url) var resp, err = http.Get(url)
@@ -460,7 +459,7 @@ func (c *Client) downloadImage(url string, mediaID int, name string) error {
} }
func (c *Client) checkW500PosterOnStartup() { func (c *Engine) checkW500PosterOnStartup() {
log.Infof("check all w500 posters") log.Infof("check all w500 posters")
all := c.db.GetMediaWatchlist(media.MediaTypeTv) all := c.db.GetMediaWatchlist(media.MediaTypeTv)
movies := c.db.GetMediaWatchlist(media.MediaTypeMovie) movies := c.db.GetMediaWatchlist(media.MediaTypeMovie)
@@ -470,37 +469,37 @@ func (c *Client) checkW500PosterOnStartup() {
if _, err := os.Stat(targetFile); err != nil { if _, err := os.Stat(targetFile); err != nil {
log.Infof("poster_w500.jpg not exist for %s, will download it", e.NameEn) log.Infof("poster_w500.jpg not exist for %s, will download it", e.NameEn)
if e.MediaType ==media.MediaTypeTv { if e.MediaType == media.MediaTypeTv {
detail, err := c.MustTMDB().GetTvDetails(e.TmdbID, db.LanguageCN) detail, err := c.MustTMDB().GetTvDetails(e.TmdbID, db.LanguageCN)
if err != nil { if err != nil {
log.Warnf("get tmdb detail for %s error: %v", e.NameEn, err) log.Warnf("get tmdb detail for %s error: %v", e.NameEn, err)
continue continue
} }
if err := c.downloadW500Poster(detail.PosterPath, e.ID); err != nil { if err := c.downloadW500Poster(detail.PosterPath, e.ID); err != nil {
log.Warnf("download w500 poster error: %v", err) log.Warnf("download w500 poster error: %v", err)
continue continue
} }
} else { } else {
detail, err := c.MustTMDB().GetMovieDetails(e.TmdbID, db.LanguageCN) detail, err := c.MustTMDB().GetMovieDetails(e.TmdbID, db.LanguageCN)
if err != nil { if err != nil {
log.Warnf("get tmdb detail for %s error: %v", e.NameEn, err) log.Warnf("get tmdb detail for %s error: %v", e.NameEn, err)
continue continue
} }
if err := c.downloadW500Poster(detail.PosterPath, e.ID); err != nil { if err := c.downloadW500Poster(detail.PosterPath, e.ID); err != nil {
log.Warnf("download w500 poster error: %v", err) log.Warnf("download w500 poster error: %v", err)
continue continue
} }
} }
} }
} }
} }
func (c *Client) SuggestedMovieFolderName(tmdbId int) (string, error) { func (c *Engine) SuggestedMovieFolderName(tmdbId int) (string, error) {
d1, err := c.MustTMDB().GetMovieDetails(tmdbId, c.language) d1, err := c.MustTMDB().GetMovieDetails(tmdbId, c.language)
if err != nil { if err != nil {
@@ -545,7 +544,7 @@ func (c *Client) SuggestedMovieFolderName(tmdbId int) (string, error) {
return res, nil return res, nil
} }
func (c *Client) SuggestedSeriesFolderName(tmdbId int) (string, error) { func (c *Engine) SuggestedSeriesFolderName(tmdbId int) (string, error) {
d, err := c.MustTMDB().GetTvDetails(tmdbId, c.language) d, err := c.MustTMDB().GetTvDetails(tmdbId, c.language)
if err != nil { if err != nil {

79
engine/indexer.go Normal file
View File

@@ -0,0 +1,79 @@
package engine
import (
"polaris/ent"
"polaris/log"
"polaris/pkg/prowlarr"
"strings"
"github.com/pkg/errors"
)
const prowlarrPrefix = "Prowlarr_"
func (c *Engine) SyncProwlarrIndexers(apiKey, url string) error {
client := prowlarr.New(apiKey, url)
if ins, err := client.GetIndexers(); err != nil {
return errors.Wrap(err, "connect to prowlarr error")
} else {
var prowlarrNames = make(map[string]bool, len(ins))
for _, in := range ins {
prowlarrNames[in.Name] = true
}
all := c.db.GetAllIndexers()
for _, index := range all {
if index.Synced {
if !prowlarrNames[strings.TrimPrefix(index.Name, prowlarrPrefix)] {
c.db.DeleteIndexer(index.ID) //remove deleted indexers
}
}
}
for _, indexer := range ins {
if err := c.db.SaveIndexer(&ent.Indexers{
Disabled: indexer.Disabled,
Name: prowlarrPrefix + indexer.Name,
Priority: indexer.Priority,
SeedRatio: indexer.SeedRatio,
//Settings: indexer.Settings,
Implementation: "torznab",
APIKey: indexer.APIKey,
URL: indexer.URL,
TvSearch: indexer.TvSearch,
MovieSearch: indexer.MovieSearch,
Synced: true,
}); err != nil {
return errors.Wrap(err, "save prowlarr indexers")
}
log.Debugf("synced prowlarr indexer to db: %v", indexer.Name)
}
}
return nil
}
func (c *Engine) syncProwlarr() error {
p, err := c.db.GetProwlarrSetting()
if err != nil {
return errors.Wrap(err, "db")
}
if p.Disabled {
return nil
}
if err := c.SyncProwlarrIndexers(p.ApiKey, p.URL); err != nil {
return errors.Wrap(err, "sync prowlarr indexers")
}
return nil
}
func (c *Engine) DeleteAllProwlarrIndexers() error {
all := c.db.GetAllIndexers()
for _, index := range all {
if index.Synced {
c.db.DeleteIndexer(index.ID)
log.Debugf("success delete prowlarr indexer: %s", index.Name)
}
}
return nil
}

View File

@@ -1,10 +1,10 @@
package core package engine
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"os" "io/fs"
"path/filepath" "path/filepath"
"polaris/db" "polaris/db"
"polaris/ent/media" "polaris/ent/media"
@@ -14,7 +14,6 @@ import (
"polaris/pkg/metadata" "polaris/pkg/metadata"
"polaris/pkg/notifier" "polaris/pkg/notifier"
"polaris/pkg/storage" "polaris/pkg/storage"
"polaris/pkg/utils"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -22,7 +21,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (c *Client) writeNfoFile(historyId int) error { func (c *Engine) writeNfoFile(historyId int) error {
if !c.nfoSupportEnabled() { if !c.nfoSupportEnabled() {
return nil return nil
} }
@@ -66,7 +65,7 @@ func (c *Client) writeNfoFile(historyId int) error {
if err != nil { if err != nil {
return errors.Wrap(err, "xml marshal") return errors.Wrap(err, "xml marshal")
} }
return st.WriteFile(nfoPath, data) return st.WriteFile(nfoPath, []byte(xml.Header+string(data)))
} }
} else if md.MediaType == media.MediaTypeMovie { //movie.nfo } else if md.MediaType == media.MediaTypeMovie { //movie.nfo
@@ -101,13 +100,13 @@ func (c *Client) writeNfoFile(historyId int) error {
if err != nil { if err != nil {
return errors.Wrap(err, "xml marshal") return errors.Wrap(err, "xml marshal")
} }
return st.WriteFile(nfoPath, data) return st.WriteFile(nfoPath, []byte(xml.Header+string(data)))
} }
} }
return nil return nil
} }
func (c *Client) writePlexmatch(historyId int) error { func (c *Engine) writePlexmatch(historyId int) error {
if !c.plexmatchEnabled() { if !c.plexmatchEnabled() {
return nil return nil
@@ -150,54 +149,35 @@ func (c *Client) writePlexmatch(historyId int) error {
} else { } else {
buff.Write(data) buff.Write(data)
} }
episodesIds := c.GetEpisodeIds(his)
if his.EpisodeID > 0 { for _, id := range episodesIds {
//single episode download ep, err := c.db.GetEpisodeByID(id)
ep, err := c.db.GetEpisodeByID(his.EpisodeID)
if err != nil { if err != nil {
return errors.Wrap(err, "query episode") log.Warnf("query episode: %v", err)
continue
} }
if strings.Contains(buff.String(), ep.TargetFile) { if strings.Contains(buff.String(), ep.TargetFile) {
log.Debugf("already write plex episode line: %v", ep.TargetFile) log.Debugf("already write plex episode line: %v", ep.TargetFile)
return nil return nil
} }
buff.WriteString(fmt.Sprintf("\nep: %d: %s\n", ep.EpisodeNumber, ep.TargetFile)) buff.WriteString(fmt.Sprintf("\nep: %d: %s\n", ep.EpisodeNumber, ep.TargetFile))
} else {
seasonNum, err := utils.SeasonId(his.TargetDir)
if err != nil {
return errors.Wrap(err, "no season id")
}
allEpisodes, err := c.db.GetSeasonEpisodes(his.MediaID, seasonNum)
if err != nil {
return errors.Wrap(err, "query season episode")
}
for _, ep := range allEpisodes {
if ep.TargetFile == "" {
log.Errorf("no episode file of episode %d, season %d", ep.EpisodeNumber, ep.SeasonNumber)
//TODO update db
continue
}
if strings.Contains(buff.String(), ep.TargetFile) {
log.Debugf("already write plex episode line: %v", ep.TargetFile)
continue
}
buff.WriteString(fmt.Sprintf("\nep: %d: %s\n", ep.EpisodeNumber, ep.TargetFile))
}
} }
log.Infof("write season plexmatch file content: %s", buff.String()) log.Infof("write season plexmatch file content: %s", buff.String())
return st.WriteFile(seasonPlex, buff.Bytes()) return st.WriteFile(seasonPlex, buff.Bytes())
} }
func (c *Client) plexmatchEnabled() bool { func (c *Engine) plexmatchEnabled() bool {
return c.db.GetSetting(db.SettingPlexMatchEnabled) == "true" return c.db.GetSetting(db.SettingPlexMatchEnabled) == "true"
} }
func (c *Client) nfoSupportEnabled() bool { func (c *Engine) nfoSupportEnabled() bool {
return c.db.GetSetting(db.SettingNfoSupportEnabled) == "true" return c.db.GetSetting(db.SettingNfoSupportEnabled) == "true"
} }
func (c *Client) GetStorage(storageId int, mediaType media.MediaType) (storage.Storage, error) { func (c *Engine) GetStorage(storageId int, mediaType media.MediaType) (storage.Storage, error) {
st := c.db.GetStorage(storageId) st := c.db.GetStorage(storageId)
targetPath := st.TvPath targetPath := st.TvPath
if mediaType == media.MediaTypeMovie { if mediaType == media.MediaTypeMovie {
@@ -212,7 +192,6 @@ func (c *Client) GetStorage(storageId int, mediaType media.MediaType) (storage.S
log.Warnf("get accepted subtitle format error: %v", err) log.Warnf("get accepted subtitle format error: %v", err)
} }
switch st.Implementation { switch st.Implementation {
case storage1.ImplementationLocal: case storage1.ImplementationLocal:
@@ -240,7 +219,7 @@ func (c *Client) GetStorage(storageId int, mediaType media.MediaType) (storage.S
return nil, errors.New("no storage found") return nil, errors.New("no storage found")
} }
func (c *Client) sendMsg(msg string) { func (c *Engine) sendMsg(msg string) {
clients, err := c.db.GetAllNotificationClients2() clients, err := c.db.GetAllNotificationClients2()
if err != nil { if err != nil {
log.Errorf("query notification clients: %v", err) log.Errorf("query notification clients: %v", err)
@@ -269,66 +248,36 @@ func (c *Client) sendMsg(msg string) {
} }
} }
func (c *Client) findEpisodeFilesPreMoving(historyId int) error { func (c *Engine) findEpisodeFilesPreMoving(historyId int) error {
his := c.db.GetHistory(historyId) his := c.db.GetHistory(historyId)
isSingleEpisode := his.EpisodeID > 0 episodeIds := c.GetEpisodeIds(his)
downloadDir := c.db.GetDownloadDir()
task := c.tasks[historyId] task, _ := c.tasks.Load(historyId)
name, err := task.Name()
ff, err := c.db.GetAcceptedVideoFormats()
if err != nil { if err != nil {
return err return err
} }
target := filepath.Join(downloadDir, name) for _, id := range episodeIds {
fi, err := os.Stat(target) ep, _ := c.db.GetEpisode(his.MediaID, his.SeasonNum, id)
if err != nil { task.WalkFunc()(func(path string, info fs.FileInfo) error {
return errors.Wrapf(err, "read dir %v", target) if info.IsDir() {
} return nil
if isSingleEpisode {
if fi.IsDir() {
//download single episode in dir
//TODO
} else {
//is file
if err := c.db.UpdateEpisodeTargetFile(his.EpisodeID, fi.Name()); err != nil {
log.Errorf("writing downloaded file name to db error: %v", err)
} }
} ext := filepath.Ext(info.Name())
} else { if slices.Contains(ff, ext) {
if !fi.IsDir() { return nil
return fmt.Errorf("not season pack downloaded")
}
seasonNum, err := utils.SeasonId(his.TargetDir)
if err != nil {
return errors.Wrap(err, "no season id")
}
files, err := os.ReadDir(target)
if err != nil {
return err
}
for _, f := range files {
if f.IsDir() { //want media file
continue
} }
excludedExt := []string{".txt", ".srt", ".ass", ".sub"} meta := metadata.ParseTv(info.Name())
ext := filepath.Ext(f.Name()) if meta.StartEpisode == meta.EndEpisode && meta.StartEpisode == ep.EpisodeNumber {
if slices.Contains(excludedExt, strings.ToLower(ext)) { if err := c.db.UpdateEpisodeTargetFile(id, info.Name()); err != nil {
continue log.Errorf("writing downloaded file name to db error: %v", err)
}
meta := metadata.ParseTv(f.Name())
if meta.StartEpisode > 0 {
//episode exists
ep, err := c.db.GetEpisode(his.MediaID, seasonNum, meta.StartEpisode)
if err != nil {
return err
}
if err := c.db.UpdateEpisodeTargetFile(ep.ID, f.Name()); err != nil {
return errors.Wrap(err, "update episode file")
} }
} }
} return nil
})
} }
return nil return nil
} }

View File

@@ -1,4 +1,4 @@
package core package engine
import "encoding/xml" import "encoding/xml"

View File

@@ -1,4 +1,4 @@
package core package engine
import ( import (
"bytes" "bytes"
@@ -6,6 +6,7 @@ import (
"polaris/ent" "polaris/ent"
"polaris/ent/episode" "polaris/ent/episode"
"polaris/ent/history" "polaris/ent/history"
"polaris/ent/media"
"polaris/log" "polaris/log"
"polaris/pkg/metadata" "polaris/pkg/metadata"
"polaris/pkg/notifier/message" "polaris/pkg/notifier/message"
@@ -15,103 +16,21 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum int, episodeNums ...int) (*string, error) { func (c *Engine) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum int, episodeNums ...int) (*string, error) {
trc, dlc, err := c.GetDownloadClient()
series, err := c.db.GetMedia(seriesId)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "connect transmission")
}
series := c.db.GetMediaDetails(seriesId)
if series == nil {
return nil, fmt.Errorf("no tv series of id %v", seriesId) return nil, fmt.Errorf("no tv series of id %v", seriesId)
} }
//check space available return c.downloadTorrent(series, r1, seasonNum, episodeNums...)
downloadDir := c.db.GetDownloadDir()
size := utils.AvailableSpace(downloadDir)
if size < uint64(r1.Size) {
log.Errorf("space available %v, space needed %v", size, r1.Size)
return nil, errors.New("no enough space")
}
magnet, err := utils.Link2Magnet(r1.Link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", r1.Link, err)
}
dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum)
if len(episodeNums) > 0 {
for _, epNum := range episodeNums {
var ep *ent.Episode
for _, e := range series.Episodes {
if e.SeasonNumber == seasonNum && e.EpisodeNumber == epNum {
ep = e
}
}
if ep == nil {
return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, epNum)
}
if ep.Status == episode.StatusMissing {
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
}
}
} else { //season package download
c.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading)
}
history, err := c.db.SaveHistoryRecord(ent.History{
MediaID: seriesId,
EpisodeNums: episodeNums,
SeasonNum: seasonNum,
SourceTitle: r1.Name,
TargetDir: dir,
Status: history.StatusRunning,
Size: int(r1.Size),
//Saved: torrent.Save(),
Link: magnet,
DownloadClientID: dlc.ID,
IndexerID: r1.IndexerId,
})
if err != nil {
return nil, errors.Wrap(err, "save record")
}
torrent, err := trc.Download(magnet, downloadDir)
if err != nil {
return nil, errors.Wrap(err, "downloading")
}
torrent.Start()
c.tasks[history.ID] = &Task{Torrent: torrent}
name := r1.Name
if len(episodeNums) > 0 {
buff := &bytes.Buffer{}
for i, ep := range episodeNums {
if i != 0 {
buff.WriteString(",")
}
buff.WriteString(fmt.Sprint(ep))
}
name = fmt.Sprintf("第%s集 (%s)", buff.String(), name)
} else {
name = fmt.Sprintf("全集 (%s)", name)
}
c.sendMsg(fmt.Sprintf(message.BeginDownload, name))
log.Infof("success add %s to download task", r1.Name)
return &r1.Name, nil
} }
/* /*
tmdb 校验获取的资源名如果用资源名在tmdb搜索出来的结果能匹配上想要的资源则认为资源有效否则无效 tmdb 校验获取的资源名如果用资源名在tmdb搜索出来的结果能匹配上想要的资源则认为资源有效否则无效
解决名称过于简单的影视会匹配过多资源的问题 例如梦魇绝镇 FROM 解决名称过于简单的影视会匹配过多资源的问题 例如梦魇绝镇 FROM
*/ */
func (c *Client) checkBtReourceWithTmdb(r *torznab.Result, seriesId int) bool { func (c *Engine) checkBtReourceWithTmdb(r *torznab.Result, seriesId int) bool {
m := metadata.ParseTv(r.Name) m := metadata.ParseTv(r.Name)
se, err := c.MustTMDB().SearchMedia(m.NameEn, "", 1) se, err := c.MustTMDB().SearchMedia(m.NameEn, "", 1)
if err != nil { if err != nil {
@@ -122,7 +41,11 @@ func (c *Client) checkBtReourceWithTmdb(r *torznab.Result, seriesId int) bool {
log.Debugf("tmdb search no result, consider this torrent ok: %s", r.Name) //because tv name parse is not accurate log.Debugf("tmdb search no result, consider this torrent ok: %s", r.Name) //because tv name parse is not accurate
return true return true
} }
series := c.db.GetMediaDetails(seriesId) series, err := c.db.GetMediaDetails(seriesId)
if err != nil {
log.Warnf("get media details error: %v", err)
return false
}
se0 := se.Results[0] se0 := se.Results[0]
if se0.ID != int64(series.TmdbID) { if se0.ID != int64(series.TmdbID) {
@@ -134,7 +57,7 @@ func (c *Client) checkBtReourceWithTmdb(r *torznab.Result, seriesId int) bool {
} }
} }
func (c *Client) SearchAndDownload(seriesId, seasonNum int, episodeNums ...int) ([]string, error) { func (c *Engine) SearchAndDownload(seriesId, seasonNum int, episodeNums ...int) ([]string, error) {
res, err := SearchTvSeries(c.db, &SearchParam{ res, err := SearchTvSeries(c.db, &SearchParam{
MediaId: seriesId, MediaId: seriesId,
@@ -192,53 +115,96 @@ lo:
return torrentNames, nil return torrentNames, nil
} }
func (c *Client) DownloadMovie(m *ent.Media, link, name string, size int64, indexerID int) (*string, error) { func (c *Engine) DownloadMovie(m *ent.Media, r1 torznab.Result) (*string, error) {
return c.downloadTorrent(m, r1, 0)
}
func (c *Engine) downloadTorrent(m *ent.Media, r1 torznab.Result, seasonNum int, episodeNums ...int) (*string, error) {
trc, dlc, err := c.GetDownloadClient() trc, dlc, err := c.GetDownloadClient()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "connect transmission") return nil, errors.Wrap(err, "get download client")
}
magnet, err := utils.Link2Magnet(link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
} }
torrent, err := trc.Download(magnet, c.db.GetDownloadDir()) downloadDir := c.db.GetDownloadDir()
//due to reported bug by user, this will be temporarily disabled
// size := utils.AvailableSpace(downloadDir)
// if size < uint64(r1.Size) {
// log.Errorf("space available %v, space needed %v", size, r1.Size)
// return nil, errors.New("not enough space")
// }
var name = r1.Name
var targetDir = m.TargetDir
if m.MediaType == media.MediaTypeTv { //tv download
targetDir = fmt.Sprintf("%s/Season %02d/", m.TargetDir, seasonNum)
if len(episodeNums) > 0 {
for _, epNum := range episodeNums {
ep, err := c.db.GetEpisode(m.ID, seasonNum, epNum)
if err != nil {
return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, epNum)
}
if ep.Status == episode.StatusMissing {
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
}
}
buff := &bytes.Buffer{}
for i, ep := range episodeNums {
if i != 0 {
buff.WriteString(",")
}
buff.WriteString(fmt.Sprint(ep))
}
name = fmt.Sprintf("第%s集 (%s)", buff.String(), name)
} else { //season package download
name = fmt.Sprintf("全集 (%s)", name)
c.db.SetSeasonAllEpisodeStatus(m.ID, seasonNum, episode.StatusDownloading)
}
} else {
ep, _ := c.db.GetMovieDummyEpisode(m.ID)
if ep.Status == episode.StatusMissing {
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
}
}
hash, err := utils.Link2Hash(r1.Link)
if err != nil {
return nil, errors.Wrap(err, "get hash")
}
history, err := c.db.SaveHistoryRecord(ent.History{
MediaID: m.ID,
EpisodeNums: episodeNums,
SeasonNum: seasonNum,
SourceTitle: r1.Name,
TargetDir: targetDir,
Status: history.StatusRunning,
Size: int(r1.Size),
//Saved: torrent.Save(),
Link: r1.Link,
Hash: hash,
DownloadClientID: dlc.ID,
IndexerID: r1.IndexerId,
})
if err != nil {
return nil, errors.Wrap(err, "save record")
}
torrent, err := trc.Download(r1.Link, hash, downloadDir)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "downloading") return nil, errors.Wrap(err, "downloading")
} }
torrent.Start() torrent.Start()
if name == "" { c.tasks.Store(history.ID, &Task{Torrent: torrent})
name = m.OriginalName
}
go func() {
ep, _ := c.db.GetMovieDummyEpisode(m.ID)
history, err := c.db.SaveHistoryRecord(ent.History{
MediaID: m.ID,
EpisodeID: ep.ID,
SourceTitle: name,
TargetDir: m.TargetDir,
Status: history.StatusRunning,
Size: int(size),
//Saved: torrent.Save(),
Link: magnet,
DownloadClientID: dlc.ID,
IndexerID: indexerID,
})
if err != nil {
log.Errorf("save history error: %v", err)
}
c.tasks[history.ID] = &Task{Torrent: torrent}
if ep.Status == episode.StatusMissing {
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
}
}()
c.sendMsg(fmt.Sprintf(message.BeginDownload, name)) c.sendMsg(fmt.Sprintf(message.BeginDownload, name))
log.Infof("success add %s to download task", name)
return &name, nil
log.Infof("success add %s to download task", r1.Name)
return &r1.Name, nil
} }

View File

@@ -1,4 +1,4 @@
package core package engine
import ( import (
"fmt" "fmt"
@@ -18,13 +18,16 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (c *Client) addSysCron() { func (c *Engine) addSysCron() {
c.registerCronJob("check_running_tasks", "@every 1m", c.checkTasks) c.registerCronJob("check_running_tasks", "@every 1m", c.checkTasks)
c.registerCronJob("check_available_medias_to_download", "0 0 * * * *", func() error { c.registerCronJob("check_available_medias_to_download", "0 0 * * * *", func() error {
v := os.Getenv("POLARIS_NO_AUTO_DOWNLOAD") v := os.Getenv("POLARIS_NO_AUTO_DOWNLOAD")
if v == "true" { if v == "true" {
return nil return nil
} }
if err := c.syncProwlarr(); err != nil {
log.Warnf("sync prowlarr error: %v", err)
}
c.downloadAllTvSeries() c.downloadAllTvSeries()
c.downloadAllMovies() c.downloadAllMovies()
return nil return nil
@@ -45,14 +48,14 @@ func (c *Client) addSysCron() {
log.Infof("--------- add cron jobs done --------") log.Infof("--------- add cron jobs done --------")
} }
func (c *Client) mustAddCron(spec string, cmd func()) { func (c *Engine) mustAddCron(spec string, cmd func()) {
if err := c.cron.AddFunc(spec, cmd); err != nil { if err := c.cron.AddFunc(spec, cmd); err != nil {
log.Errorf("add func error: %v", err) log.Errorf("add func error: %v", err)
panic(err) panic(err)
} }
} }
func (c *Client) TriggerCronJob(name string) error { func (c *Engine) TriggerCronJob(name string) error {
job, ok := c.schedulers.Load(name) job, ok := c.schedulers.Load(name)
if !ok { if !ok {
return fmt.Errorf("job name not exists: %s", name) return fmt.Errorf("job name not exists: %s", name)
@@ -60,51 +63,119 @@ func (c *Client) TriggerCronJob(name string) error {
return job.f() return job.f()
} }
func (c *Client) checkTasks() error { func (c *Engine) checkTasks() error {
log.Debug("begin check tasks...") log.Debug("begin check tasks...")
for id, t := range c.tasks { c.tasks.Range(func(id int, t *Task) bool {
r := c.db.GetHistory(id) r := c.db.GetHistory(id)
if !t.Exists() { if !t.Exists() {
log.Infof("task no longer exists: %v", id) log.Infof("task no longer exists: %v", id)
delete(c.tasks, id) c.tasks.Delete(id)
continue return true
} }
name, err := t.Name() name, err := t.Name()
if err != nil { if err != nil {
return errors.Wrap(err, "get name") log.Warnf("get task name error: %v", err)
return true
} }
progress, err := t.Progress() progress, err := t.Progress()
if err != nil { if err != nil {
return errors.Wrap(err, "get progress") log.Warnf("get task progress error: %v", err)
return true
} }
log.Infof("task (%s) percentage done: %d%%", name, progress) log.Infof("task (%s) percentage done: %d%%", name, progress)
if progress == 100 { if progress == 100 {
if r.Status == history.StatusSeeding { if r.Status == history.StatusSeeding {
//task already success, check seed ratio //task already success, check seed ratio
torrent := c.tasks[id] torrent, _ := c.tasks.Load(id)
ratio, ok := c.isSeedRatioLimitReached(r.IndexerID, torrent) ratio, ok := c.isSeedRatioLimitReached(r.IndexerID, torrent)
if ok { if ok {
log.Infof("torrent file seed ratio reached, remove: %v, current seed ratio: %v", name, ratio) log.Infof("torrent file seed ratio reached, remove: %v, current seed ratio: %v", name, ratio)
torrent.Remove() torrent.Remove()
delete(c.tasks, id) c.tasks.Delete(id)
c.setHistoryStatus(id, history.StatusSuccess)
} else { } else {
log.Infof("torrent file still sedding: %v, current seed ratio: %v", name, ratio) log.Infof("torrent file still sedding: %v, current seed ratio: %v", name, ratio)
} }
continue return true
} else if r.Status == history.StatusRunning {
log.Infof("task is done: %v", name)
c.sendMsg(fmt.Sprintf(message.DownloadComplete, name))
go c.postTaskProcessing(id)
} }
log.Infof("task is done: %v", name)
c.sendMsg(fmt.Sprintf(message.DownloadComplete, name))
go c.postTaskProcessing(id)
} }
}
return true
})
return nil return nil
} }
func (c *Client) postTaskProcessing(id int) { /*
episode 状态有3种missingdownloadingdownloaded
history状态有5种running, success, fail, uploading, seeding
没有下载的剧集状态都是missing已下载完成的都是downloaded正在下载的是downloading
对应的history状态下载任务创建成功正常跑着是running出了问题失败了就是fail下载完成的任务会先进入uploading状态进一步处理
uploading状态下会传输到对应的存储里面uploading成功如果需要做种会进入seeding状态如果不做种进入success状态失败了会进入fail状态
seeding状态中会定时检查做种状态达到指定分享率会置为success
任务创建成功episode状态会由missing置为downloading如果任务失败重新置为missing如果任务成功进入success或seedingepisode状态应置为downloaded
*/
func (c *Engine) setHistoryStatus(id int, status history.Status) {
r := c.db.GetHistory(id)
episodeIds := c.GetEpisodeIds(r)
switch status {
case history.StatusRunning:
c.db.SetHistoryStatus(id, history.StatusRunning)
c.setEpsideoStatus(episodeIds, episode.StatusDownloading)
case history.StatusSuccess:
c.db.SetHistoryStatus(id, history.StatusSuccess)
c.setEpsideoStatus(episodeIds, episode.StatusDownloaded)
case history.StatusUploading:
c.db.SetHistoryStatus(id, history.StatusUploading)
case history.StatusSeeding:
c.db.SetHistoryStatus(id, history.StatusSeeding)
c.setEpsideoStatus(episodeIds, episode.StatusDownloaded)
case history.StatusFail:
c.db.SetHistoryStatus(id, history.StatusFail)
c.setEpsideoStatus(episodeIds, episode.StatusMissing)
default:
panic(fmt.Sprintf("unkown status %v", status))
}
}
func (c *Engine) setEpsideoStatus(episodeIds []int, status episode.Status) error {
for _, id := range episodeIds {
ep, err := c.db.GetEpisodeByID(id)
if err != nil {
return err
}
if ep.Status == episode.StatusDownloaded {
//已经下载完成的任务,不再重新设置状态
continue
}
if err := c.db.SetEpisodeStatus(id, status); err != nil {
return err
}
}
return nil
}
func (c *Engine) postTaskProcessing(id int) {
if err := c.findEpisodeFilesPreMoving(id); err != nil { if err := c.findEpisodeFilesPreMoving(id); err != nil {
log.Errorf("finding all episode file error: %v", err) log.Errorf("finding all episode file error: %v", err)
} else { } else {
@@ -132,15 +203,20 @@ func getSeasonNum(h *ent.History) int {
return seasonNum return seasonNum
} }
func (c *Client) GetEpisodeIds(r *ent.History) []int { func (c *Engine) GetEpisodeIds(r *ent.History) []int {
var episodeIds []int var episodeIds []int
seasonNum := getSeasonNum(r) seasonNum := getSeasonNum(r)
if r.EpisodeID > 0 { // if r.EpisodeID > 0 {
episodeIds = append(episodeIds, r.EpisodeID) // episodeIds = append(episodeIds, r.EpisodeID)
// }
series, err := c.db.GetMediaDetails(r.MediaID)
if err != nil {
log.Errorf("get media details error: %v", err)
return []int{}
} }
if len(r.EpisodeNums) > 0 { if len(r.EpisodeNums) > 0 {
series := c.db.GetMediaDetails(r.MediaID)
for _, epNum := range r.EpisodeNums { for _, epNum := range r.EpisodeNums {
for _, ep := range series.Episodes { for _, ep := range series.Episodes {
if ep.SeasonNumber == seasonNum && ep.EpisodeNumber == epNum { if ep.SeasonNumber == seasonNum && ep.EpisodeNumber == epNum {
@@ -148,18 +224,26 @@ func (c *Client) GetEpisodeIds(r *ent.History) []int {
} }
} }
} }
} else {
for _, ep := range series.Episodes {
if ep.SeasonNumber == seasonNum {
episodeIds = append(episodeIds, ep.ID)
}
}
} }
return episodeIds return episodeIds
} }
func (c *Client) moveCompletedTask(id int) (err1 error) { func (c *Engine) moveCompletedTask(id int) (err1 error) {
torrent := c.tasks[id] torrent, _ := c.tasks.Load(id)
r := c.db.GetHistory(id) r := c.db.GetHistory(id)
if r.Status == history.StatusUploading { // if r.Status == history.StatusUploading {
log.Infof("task %d is already uploading, skip", id) // log.Infof("task %d is already uploading, skip", id)
return nil // return nil
} // }
c.db.SetHistoryStatus(r.ID, history.StatusUploading)
c.setHistoryStatus(r.ID, history.StatusUploading)
downloadclient, err := c.db.GetDownloadClient(r.DownloadClientID) downloadclient, err := c.db.GetDownloadClient(r.DownloadClientID)
if err != nil { if err != nil {
@@ -171,36 +255,24 @@ func (c *Client) moveCompletedTask(id int) (err1 error) {
return err return err
} }
seasonNum := getSeasonNum(r)
episodeIds := c.GetEpisodeIds(r)
defer func() { defer func() {
if err1 != nil { if err1 != nil {
c.db.SetHistoryStatus(r.ID, history.StatusFail) c.setHistoryStatus(r.ID, history.StatusFail)
if len(episodeIds) > 0 {
for _, id := range episodeIds {
if !c.db.IsEpisodeDownloadingOrDownloaded(id) {
c.db.SetEpisodeStatus(id, episode.StatusMissing)
}
}
} else {
c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusMissing)
}
c.sendMsg(fmt.Sprintf(message.ProcessingFailed, err1)) c.sendMsg(fmt.Sprintf(message.ProcessingFailed, err1))
if downloadclient.RemoveFailedDownloads { if downloadclient.RemoveFailedDownloads {
log.Debugf("task failed, remove failed torrent and files related") log.Debugf("task failed, remove failed torrent and files related")
delete(c.tasks, r.ID) c.tasks.Delete(r.ID)
torrent.Remove() torrent.Remove()
} }
} }
}() }()
series := c.db.GetMediaDetails(r.MediaID) series, err := c.db.GetMediaDetails(r.MediaID)
if series == nil { if err != nil {
return nil return err
} }
st := c.db.GetStorage(series.StorageID) st := c.db.GetStorage(series.StorageID)
log.Infof("move task files to target dir: %v", r.TargetDir) log.Infof("move task files to target dir: %v", r.TargetDir)
stImpl, err := c.GetStorage(st.ID, series.MediaType) stImpl, err := c.GetStorage(st.ID, series.MediaType)
@@ -209,35 +281,30 @@ func (c *Client) moveCompletedTask(id int) (err1 error) {
} }
//如果种子是路径,则会把路径展开,只移动文件,类似 move dir/* dir2/, 如果种子是文件,则会直接移动文件,类似 move file dir/ //如果种子是路径,则会把路径展开,只移动文件,类似 move dir/* dir2/, 如果种子是文件,则会直接移动文件,类似 move file dir/
if err := stImpl.Copy(filepath.Join(c.db.GetDownloadDir(), torrentName), r.TargetDir); err != nil { if err := stImpl.Copy(filepath.Join(c.db.GetDownloadDir(), torrentName), r.TargetDir, torrent.WalkFunc()); err != nil {
return errors.Wrap(err, "move file") return errors.Wrap(err, "move file")
} }
torrent.UploadProgresser = stImpl.UploadProgress torrent.UploadProgresser = stImpl.UploadProgress
c.db.SetHistoryStatus(r.ID, history.StatusSeeding)
if len(episodeIds) > 0 {
for _, id := range episodeIds {
c.db.SetEpisodeStatus(id, episode.StatusDownloaded)
}
} else {
c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusDownloaded)
}
c.sendMsg(fmt.Sprintf(message.ProcessingComplete, torrentName)) c.sendMsg(fmt.Sprintf(message.ProcessingComplete, torrentName))
//判断是否需要删除本地文件 //判断是否需要删除本地文件, TODO prowlarr has no indexer id
r1, ok := c.isSeedRatioLimitReached(r.IndexerID, torrent) r1, ok := c.isSeedRatioLimitReached(r.IndexerID, torrent)
if downloadclient.RemoveCompletedDownloads && ok { if downloadclient.RemoveCompletedDownloads && ok {
log.Debugf("download complete,remove torrent and files related, torrent: %v, seed ratio: %v", torrentName, r1) log.Debugf("download complete,remove torrent and files related, torrent: %v, seed ratio: %v", torrentName, r1)
c.db.SetHistoryStatus(r.ID, history.StatusSuccess) c.setHistoryStatus(r.ID, history.StatusSuccess)
delete(c.tasks, r.ID) c.tasks.Delete(r.ID)
torrent.Remove() torrent.Remove()
} else {
log.Infof("task complete but still needs seeding: %v", torrentName)
c.setHistoryStatus(r.ID, history.StatusSeeding)
} }
log.Infof("move downloaded files to target dir success, file: %v, target dir: %v", torrentName, r.TargetDir) log.Infof("move downloaded files to target dir success, file: %v, target dir: %v", torrentName, r.TargetDir)
return nil return nil
} }
func (c *Client) CheckDownloadedSeriesFiles(m *ent.Media) error { func (c *Engine) CheckDownloadedSeriesFiles(m *ent.Media) error {
if m.MediaType != media.MediaTypeTv { if m.MediaType != media.MediaTypeTv {
return nil return nil
} }
@@ -294,8 +361,12 @@ type Task struct {
UploadProgresser func() float64 UploadProgresser func() float64
} }
func (c *Client) DownloadSeriesAllEpisodes(id int) []string { func (c *Engine) DownloadSeriesAllEpisodes(id int) []string {
tvDetail := c.db.GetMediaDetails(id) tvDetail, err := c.db.GetMediaDetails(id)
if err != nil {
log.Errorf("get media details error: %v", err)
return nil
}
m := make(map[int][]*ent.Episode) m := make(map[int][]*ent.Episode)
for _, ep := range tvDetail.Episodes { for _, ep := range tvDetail.Episodes {
m[ep.SeasonNumber] = append(m[ep.SeasonNumber], ep) m[ep.SeasonNumber] = append(m[ep.SeasonNumber], ep)
@@ -362,7 +433,7 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string {
return allNames return allNames
} }
func (c *Client) downloadAllTvSeries() { func (c *Engine) downloadAllTvSeries() {
log.Infof("begin check all tv series resources") log.Infof("begin check all tv series resources")
allSeries := c.db.GetMediaWatchlist(media.MediaTypeTv) allSeries := c.db.GetMediaWatchlist(media.MediaTypeTv)
for _, series := range allSeries { for _, series := range allSeries {
@@ -370,7 +441,7 @@ func (c *Client) downloadAllTvSeries() {
} }
} }
func (c *Client) downloadAllMovies() { func (c *Engine) downloadAllMovies() {
log.Infof("begin check all movie resources") log.Infof("begin check all movie resources")
allSeries := c.db.GetMediaWatchlist(media.MediaTypeMovie) allSeries := c.db.GetMediaWatchlist(media.MediaTypeMovie)
@@ -381,8 +452,11 @@ func (c *Client) downloadAllMovies() {
} }
} }
func (c *Client) DownloadMovieByID(id int) (string, error) { func (c *Engine) DownloadMovieByID(id int) (string, error) {
detail := c.db.GetMediaDetails(id) detail, err := c.db.GetMediaDetails(id)
if err != nil {
return "", errors.Wrap(err, "get media details")
}
if len(detail.Episodes) == 0 { if len(detail.Episodes) == 0 {
return "", fmt.Errorf("no related dummy episode: %v", detail.NameEn) return "", fmt.Errorf("no related dummy episode: %v", detail.NameEn)
} }
@@ -391,18 +465,15 @@ func (c *Client) DownloadMovieByID(id int) (string, error) {
return "", nil return "", nil
} }
if name, err := c.downloadMovieSingleEpisode(ep, detail.TargetDir); err != nil { if name, err := c.downloadMovieSingleEpisode(detail.Media, ep); err != nil {
return "", errors.Wrap(err, "download movie") return "", errors.Wrap(err, "download movie")
} else { } else {
return name, nil return name, nil
} }
} }
func (c *Client) downloadMovieSingleEpisode(ep *ent.Episode, targetDir string) (string, error) { func (c *Engine) downloadMovieSingleEpisode(m *ent.Media, ep *ent.Episode) (string, error) {
trc, dlc, err := c.GetDownloadClient()
if err != nil {
return "", errors.Wrap(err, "connect transmission")
}
qiangban := c.db.GetSetting(db.SettingAllowQiangban) qiangban := c.db.GetSetting(db.SettingAllowQiangban)
allowQiangban := false allowQiangban := false
if qiangban == "true" { if qiangban == "true" {
@@ -416,46 +487,19 @@ func (c *Client) downloadMovieSingleEpisode(ep *ent.Episode, targetDir string) (
FilterQiangban: !allowQiangban, FilterQiangban: !allowQiangban,
}) })
if err != nil { if err != nil {
return "", errors.Wrap(err, "search movie") return "", errors.Wrap(err, "search movie")
} }
r1 := res[0] r1 := res[0]
log.Infof("begin download torrent resource: %v", r1.Name) log.Infof("begin download torrent resource: %v", r1.Name)
magnet, err := utils.Link2Magnet(r1.Link) s, err := c.downloadTorrent(m, r1, 0)
if err != nil { if err != nil {
return "", errors.Errorf("converting link to magnet error, link: %v, error: %v", r1.Link, err) return "", err
} }
return *s, nil
torrent, err := trc.Download(magnet, c.db.GetDownloadDir())
if err != nil {
return "", errors.Wrap(err, "downloading")
}
torrent.Start()
history, err := c.db.SaveHistoryRecord(ent.History{
MediaID: ep.MediaID,
EpisodeID: ep.ID,
SourceTitle: r1.Name,
TargetDir: targetDir,
Status: history.StatusRunning,
Size: int(r1.Size),
//Saved: torrent.Save(),
Link: magnet,
DownloadClientID: dlc.ID,
IndexerID: r1.IndexerId,
})
if err != nil {
log.Errorf("save history error: %v", err)
}
c.tasks[history.ID] = &Task{Torrent: torrent}
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
return r1.Name, nil
} }
func (c *Client) checkAllSeriesNewSeason() error { func (c *Engine) checkAllSeriesNewSeason() error {
log.Infof("begin checking series all new season") log.Infof("begin checking series all new season")
allSeries := c.db.GetMediaWatchlist(media.MediaTypeTv) allSeries := c.db.GetMediaWatchlist(media.MediaTypeTv)
for _, series := range allSeries { for _, series := range allSeries {
@@ -467,7 +511,7 @@ func (c *Client) checkAllSeriesNewSeason() error {
return nil return nil
} }
func (c *Client) checkSeiesNewSeason(media *ent.Media) error { func (c *Engine) checkSeiesNewSeason(media *ent.Media) error {
d, err := c.MustTMDB().GetTvDetails(media.TmdbID, c.language) d, err := c.MustTMDB().GetTvDetails(media.TmdbID, c.language)
if err != nil { if err != nil {
return errors.Wrap(err, "tmdb") return errors.Wrap(err, "tmdb")
@@ -505,7 +549,7 @@ func (c *Client) checkSeiesNewSeason(media *ent.Media) error {
return nil return nil
} }
func (c *Client) isSeedRatioLimitReached(indexId int, t pkg.Torrent) (float64, bool) { func (c *Engine) isSeedRatioLimitReached(indexId int, t pkg.Torrent) (float64, bool) {
indexer, err := c.db.GetIndexer(indexId) indexer, err := c.db.GetIndexer(indexId)
if err != nil { if err != nil {
return 0, true return 0, true

View File

@@ -1,4 +1,4 @@
package core package engine
import ( import (
"fmt" "fmt"
@@ -7,7 +7,6 @@ import (
"polaris/ent/media" "polaris/ent/media"
"polaris/log" "polaris/log"
"polaris/pkg/metadata" "polaris/pkg/metadata"
"polaris/pkg/prowlarr"
"polaris/pkg/torznab" "polaris/pkg/torznab"
"slices" "slices"
"sort" "sort"
@@ -73,9 +72,9 @@ func names2Query(media *ent.Media) []string {
} }
func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) { func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) {
series := db1.GetMediaDetails(param.MediaId) series, err := db1.GetMediaDetails(param.MediaId)
if series == nil { if err != nil {
return nil, fmt.Errorf("no tv series of id %v", param.MediaId) return nil, fmt.Errorf("no tv series of id %v: %v", param.MediaId, err)
} }
limiter, err := db1.GetSizeLimiter("tv") limiter, err := db1.GetSizeLimiter("tv")
if err != nil { if err != nil {
@@ -86,7 +85,7 @@ func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error
names := names2Query(series.Media) names := names2Query(series.Media)
res := searchWithTorznab(db1, prowlarr.TV, names...) res := searchWithTorznab(db1, SearchTypeTv, names...)
var filtered []torznab.Result var filtered []torznab.Result
lo: lo:
@@ -236,9 +235,9 @@ func isNoSeasonSeries(detail *db.MediaDetails) bool {
} }
func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) { func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) {
movieDetail := db1.GetMediaDetails(param.MediaId) movieDetail, err := db1.GetMediaDetails(param.MediaId)
if movieDetail == nil { if err != nil {
return nil, errors.New("no media found of id") return nil, err
} }
limiter, err := db1.GetSizeLimiter("movie") limiter, err := db1.GetSizeLimiter("movie")
@@ -248,9 +247,9 @@ func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) {
} }
names := names2Query(movieDetail.Media) names := names2Query(movieDetail.Media)
res := searchWithTorznab(db1, prowlarr.Movie, names...) res := searchWithTorznab(db1, SearchTypeMovie, names...)
if movieDetail.Extras.IsJav() { if movieDetail.Extras.IsJav() {
res1 := searchWithTorznab(db1, prowlarr.Movie, movieDetail.Extras.JavId) res1 := searchWithTorznab(db1, SearchTypeMovie, movieDetail.Extras.JavId)
res = append(res, res1...) res = append(res, res1...)
} }
@@ -304,21 +303,18 @@ func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) {
} }
func searchWithTorznab(db *db.Client, t prowlarr.ProwlarrSupportType, queries ...string) []torznab.Result { type SearchType int
const (
SearchTypeTv SearchType = 1
SearchTypeMovie SearchType = 2
)
func searchWithTorznab(db *db.Client, t SearchType, queries ...string) []torznab.Result {
var res []torznab.Result var res []torznab.Result
allTorznab := db.GetAllTorznabInfo() allTorznab := db.GetAllIndexers()
p, err := db.GetProwlarrSetting()
if err == nil && !p.Disabled { //prowlarr exists
c := prowlarr.New(p.ApiKey, p.URL)
all, err := c.GetIndexers(t)
if err != nil {
log.Warnf("get prowlarr all indexer error: %v", err)
} else {
allTorznab = append(allTorznab, all...)
}
}
resChan := make(chan []torznab.Result) resChan := make(chan []torznab.Result)
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -326,6 +322,13 @@ func searchWithTorznab(db *db.Client, t prowlarr.ProwlarrSupportType, queries ..
if tor.Disabled { if tor.Disabled {
continue continue
} }
if t == SearchTypeTv && !tor.TvSearch {
continue
}
if t == SearchTypeMovie && !tor.MovieSearch {
continue
}
for _, q := range queries { for _, q := range queries {
wg.Add(1) wg.Add(1)

View File

@@ -91,6 +91,7 @@ type Implementation string
const ( const (
ImplementationTransmission Implementation = "transmission" ImplementationTransmission Implementation = "transmission"
ImplementationQbittorrent Implementation = "qbittorrent" ImplementationQbittorrent Implementation = "qbittorrent"
ImplementationBuildin Implementation = "buildin"
) )
func (i Implementation) String() string { func (i Implementation) String() string {
@@ -100,7 +101,7 @@ func (i Implementation) String() string {
// ImplementationValidator is a validator for the "implementation" field enum values. It is called by the builders before save. // ImplementationValidator is a validator for the "implementation" field enum values. It is called by the builders before save.
func ImplementationValidator(i Implementation) error { func ImplementationValidator(i Implementation) error {
switch i { switch i {
case ImplementationTransmission, ImplementationQbittorrent: case ImplementationTransmission, ImplementationQbittorrent, ImplementationBuildin:
return nil return nil
default: default:
return fmt.Errorf("downloadclients: invalid enum value for implementation field: %q", i) return fmt.Errorf("downloadclients: invalid enum value for implementation field: %q", i)

View File

@@ -20,8 +20,6 @@ type History struct {
ID int `json:"id,omitempty"` ID int `json:"id,omitempty"`
// MediaID holds the value of the "media_id" field. // MediaID holds the value of the "media_id" field.
MediaID int `json:"media_id,omitempty"` MediaID int `json:"media_id,omitempty"`
// deprecated
EpisodeID int `json:"episode_id,omitempty"`
// EpisodeNums holds the value of the "episode_nums" field. // EpisodeNums holds the value of the "episode_nums" field.
EpisodeNums []int `json:"episode_nums,omitempty"` EpisodeNums []int `json:"episode_nums,omitempty"`
// SeasonNum holds the value of the "season_num" field. // SeasonNum holds the value of the "season_num" field.
@@ -38,12 +36,12 @@ type History struct {
DownloadClientID int `json:"download_client_id,omitempty"` DownloadClientID int `json:"download_client_id,omitempty"`
// IndexerID holds the value of the "indexer_id" field. // IndexerID holds the value of the "indexer_id" field.
IndexerID int `json:"indexer_id,omitempty"` IndexerID int `json:"indexer_id,omitempty"`
// Link holds the value of the "link" field. // deprecated, use hash instead
Link string `json:"link,omitempty"` Link string `json:"link,omitempty"`
// torrent hash
Hash string `json:"hash,omitempty"`
// Status holds the value of the "status" field. // Status holds the value of the "status" field.
Status history.Status `json:"status,omitempty"` Status history.Status `json:"status,omitempty"`
// deprecated
Saved string `json:"saved,omitempty"`
selectValues sql.SelectValues selectValues sql.SelectValues
} }
@@ -54,9 +52,9 @@ func (*History) scanValues(columns []string) ([]any, error) {
switch columns[i] { switch columns[i] {
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
values[i] = new([]byte) values[i] = new([]byte)
case history.FieldID, history.FieldMediaID, history.FieldEpisodeID, history.FieldSeasonNum, history.FieldSize, history.FieldDownloadClientID, history.FieldIndexerID: case history.FieldID, history.FieldMediaID, history.FieldSeasonNum, history.FieldSize, history.FieldDownloadClientID, history.FieldIndexerID:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case history.FieldSourceTitle, history.FieldTargetDir, history.FieldLink, history.FieldStatus, history.FieldSaved: case history.FieldSourceTitle, history.FieldTargetDir, history.FieldLink, history.FieldHash, history.FieldStatus:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case history.FieldDate: case history.FieldDate:
values[i] = new(sql.NullTime) values[i] = new(sql.NullTime)
@@ -87,12 +85,6 @@ func (h *History) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
h.MediaID = int(value.Int64) h.MediaID = int(value.Int64)
} }
case history.FieldEpisodeID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field episode_id", values[i])
} else if value.Valid {
h.EpisodeID = int(value.Int64)
}
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
if value, ok := values[i].(*[]byte); !ok { if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field episode_nums", values[i]) return fmt.Errorf("unexpected type %T for field episode_nums", values[i])
@@ -149,18 +141,18 @@ func (h *History) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
h.Link = value.String h.Link = value.String
} }
case history.FieldHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field hash", values[i])
} else if value.Valid {
h.Hash = value.String
}
case history.FieldStatus: case history.FieldStatus:
if value, ok := values[i].(*sql.NullString); !ok { if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field status", values[i]) return fmt.Errorf("unexpected type %T for field status", values[i])
} else if value.Valid { } else if value.Valid {
h.Status = history.Status(value.String) h.Status = history.Status(value.String)
} }
case history.FieldSaved:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field saved", values[i])
} else if value.Valid {
h.Saved = value.String
}
default: default:
h.selectValues.Set(columns[i], values[i]) h.selectValues.Set(columns[i], values[i])
} }
@@ -200,9 +192,6 @@ func (h *History) String() string {
builder.WriteString("media_id=") builder.WriteString("media_id=")
builder.WriteString(fmt.Sprintf("%v", h.MediaID)) builder.WriteString(fmt.Sprintf("%v", h.MediaID))
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("episode_id=")
builder.WriteString(fmt.Sprintf("%v", h.EpisodeID))
builder.WriteString(", ")
builder.WriteString("episode_nums=") builder.WriteString("episode_nums=")
builder.WriteString(fmt.Sprintf("%v", h.EpisodeNums)) builder.WriteString(fmt.Sprintf("%v", h.EpisodeNums))
builder.WriteString(", ") builder.WriteString(", ")
@@ -230,11 +219,11 @@ func (h *History) String() string {
builder.WriteString("link=") builder.WriteString("link=")
builder.WriteString(h.Link) builder.WriteString(h.Link)
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("hash=")
builder.WriteString(h.Hash)
builder.WriteString(", ")
builder.WriteString("status=") builder.WriteString("status=")
builder.WriteString(fmt.Sprintf("%v", h.Status)) builder.WriteString(fmt.Sprintf("%v", h.Status))
builder.WriteString(", ")
builder.WriteString("saved=")
builder.WriteString(h.Saved)
builder.WriteByte(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }

View File

@@ -15,8 +15,6 @@ const (
FieldID = "id" FieldID = "id"
// FieldMediaID holds the string denoting the media_id field in the database. // FieldMediaID holds the string denoting the media_id field in the database.
FieldMediaID = "media_id" FieldMediaID = "media_id"
// FieldEpisodeID holds the string denoting the episode_id field in the database.
FieldEpisodeID = "episode_id"
// FieldEpisodeNums holds the string denoting the episode_nums field in the database. // FieldEpisodeNums holds the string denoting the episode_nums field in the database.
FieldEpisodeNums = "episode_nums" FieldEpisodeNums = "episode_nums"
// FieldSeasonNum holds the string denoting the season_num field in the database. // FieldSeasonNum holds the string denoting the season_num field in the database.
@@ -35,10 +33,10 @@ const (
FieldIndexerID = "indexer_id" FieldIndexerID = "indexer_id"
// FieldLink holds the string denoting the link field in the database. // FieldLink holds the string denoting the link field in the database.
FieldLink = "link" FieldLink = "link"
// FieldHash holds the string denoting the hash field in the database.
FieldHash = "hash"
// FieldStatus holds the string denoting the status field in the database. // FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status" FieldStatus = "status"
// FieldSaved holds the string denoting the saved field in the database.
FieldSaved = "saved"
// Table holds the table name of the history in the database. // Table holds the table name of the history in the database.
Table = "histories" Table = "histories"
) )
@@ -47,7 +45,6 @@ const (
var Columns = []string{ var Columns = []string{
FieldID, FieldID,
FieldMediaID, FieldMediaID,
FieldEpisodeID,
FieldEpisodeNums, FieldEpisodeNums,
FieldSeasonNum, FieldSeasonNum,
FieldSourceTitle, FieldSourceTitle,
@@ -57,8 +54,8 @@ var Columns = []string{
FieldDownloadClientID, FieldDownloadClientID,
FieldIndexerID, FieldIndexerID,
FieldLink, FieldLink,
FieldHash,
FieldStatus, FieldStatus,
FieldSaved,
} }
// ValidColumn reports if the column name is valid (part of the table columns). // ValidColumn reports if the column name is valid (part of the table columns).
@@ -86,6 +83,7 @@ const (
StatusFail Status = "fail" StatusFail Status = "fail"
StatusUploading Status = "uploading" StatusUploading Status = "uploading"
StatusSeeding Status = "seeding" StatusSeeding Status = "seeding"
StatusRemoved Status = "removed"
) )
func (s Status) String() string { func (s Status) String() string {
@@ -95,7 +93,7 @@ func (s Status) String() string {
// StatusValidator is a validator for the "status" field enum values. It is called by the builders before save. // StatusValidator is a validator for the "status" field enum values. It is called by the builders before save.
func StatusValidator(s Status) error { func StatusValidator(s Status) error {
switch s { switch s {
case StatusRunning, StatusSuccess, StatusFail, StatusUploading, StatusSeeding: case StatusRunning, StatusSuccess, StatusFail, StatusUploading, StatusSeeding, StatusRemoved:
return nil return nil
default: default:
return fmt.Errorf("history: invalid enum value for status field: %q", s) return fmt.Errorf("history: invalid enum value for status field: %q", s)
@@ -115,11 +113,6 @@ func ByMediaID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMediaID, opts...).ToFunc() return sql.OrderByField(FieldMediaID, opts...).ToFunc()
} }
// ByEpisodeID orders the results by the episode_id field.
func ByEpisodeID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEpisodeID, opts...).ToFunc()
}
// BySeasonNum orders the results by the season_num field. // BySeasonNum orders the results by the season_num field.
func BySeasonNum(opts ...sql.OrderTermOption) OrderOption { func BySeasonNum(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSeasonNum, opts...).ToFunc() return sql.OrderByField(FieldSeasonNum, opts...).ToFunc()
@@ -160,12 +153,12 @@ func ByLink(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLink, opts...).ToFunc() return sql.OrderByField(FieldLink, opts...).ToFunc()
} }
// ByHash orders the results by the hash field.
func ByHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldHash, opts...).ToFunc()
}
// ByStatus orders the results by the status field. // ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption { func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc() return sql.OrderByField(FieldStatus, opts...).ToFunc()
} }
// BySaved orders the results by the saved field.
func BySaved(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSaved, opts...).ToFunc()
}

View File

@@ -59,11 +59,6 @@ func MediaID(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldMediaID, v)) return predicate.History(sql.FieldEQ(FieldMediaID, v))
} }
// EpisodeID applies equality check predicate on the "episode_id" field. It's identical to EpisodeIDEQ.
func EpisodeID(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldEpisodeID, v))
}
// SeasonNum applies equality check predicate on the "season_num" field. It's identical to SeasonNumEQ. // SeasonNum applies equality check predicate on the "season_num" field. It's identical to SeasonNumEQ.
func SeasonNum(v int) predicate.History { func SeasonNum(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldSeasonNum, v)) return predicate.History(sql.FieldEQ(FieldSeasonNum, v))
@@ -104,9 +99,9 @@ func Link(v string) predicate.History {
return predicate.History(sql.FieldEQ(FieldLink, v)) return predicate.History(sql.FieldEQ(FieldLink, v))
} }
// Saved applies equality check predicate on the "saved" field. It's identical to SavedEQ. // Hash applies equality check predicate on the "hash" field. It's identical to HashEQ.
func Saved(v string) predicate.History { func Hash(v string) predicate.History {
return predicate.History(sql.FieldEQ(FieldSaved, v)) return predicate.History(sql.FieldEQ(FieldHash, v))
} }
// MediaIDEQ applies the EQ predicate on the "media_id" field. // MediaIDEQ applies the EQ predicate on the "media_id" field.
@@ -149,56 +144,6 @@ func MediaIDLTE(v int) predicate.History {
return predicate.History(sql.FieldLTE(FieldMediaID, v)) return predicate.History(sql.FieldLTE(FieldMediaID, v))
} }
// EpisodeIDEQ applies the EQ predicate on the "episode_id" field.
func EpisodeIDEQ(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldEpisodeID, v))
}
// EpisodeIDNEQ applies the NEQ predicate on the "episode_id" field.
func EpisodeIDNEQ(v int) predicate.History {
return predicate.History(sql.FieldNEQ(FieldEpisodeID, v))
}
// EpisodeIDIn applies the In predicate on the "episode_id" field.
func EpisodeIDIn(vs ...int) predicate.History {
return predicate.History(sql.FieldIn(FieldEpisodeID, vs...))
}
// EpisodeIDNotIn applies the NotIn predicate on the "episode_id" field.
func EpisodeIDNotIn(vs ...int) predicate.History {
return predicate.History(sql.FieldNotIn(FieldEpisodeID, vs...))
}
// EpisodeIDGT applies the GT predicate on the "episode_id" field.
func EpisodeIDGT(v int) predicate.History {
return predicate.History(sql.FieldGT(FieldEpisodeID, v))
}
// EpisodeIDGTE applies the GTE predicate on the "episode_id" field.
func EpisodeIDGTE(v int) predicate.History {
return predicate.History(sql.FieldGTE(FieldEpisodeID, v))
}
// EpisodeIDLT applies the LT predicate on the "episode_id" field.
func EpisodeIDLT(v int) predicate.History {
return predicate.History(sql.FieldLT(FieldEpisodeID, v))
}
// EpisodeIDLTE applies the LTE predicate on the "episode_id" field.
func EpisodeIDLTE(v int) predicate.History {
return predicate.History(sql.FieldLTE(FieldEpisodeID, v))
}
// EpisodeIDIsNil applies the IsNil predicate on the "episode_id" field.
func EpisodeIDIsNil() predicate.History {
return predicate.History(sql.FieldIsNull(FieldEpisodeID))
}
// EpisodeIDNotNil applies the NotNil predicate on the "episode_id" field.
func EpisodeIDNotNil() predicate.History {
return predicate.History(sql.FieldNotNull(FieldEpisodeID))
}
// EpisodeNumsIsNil applies the IsNil predicate on the "episode_nums" field. // EpisodeNumsIsNil applies the IsNil predicate on the "episode_nums" field.
func EpisodeNumsIsNil() predicate.History { func EpisodeNumsIsNil() predicate.History {
return predicate.History(sql.FieldIsNull(FieldEpisodeNums)) return predicate.History(sql.FieldIsNull(FieldEpisodeNums))
@@ -644,6 +589,81 @@ func LinkContainsFold(v string) predicate.History {
return predicate.History(sql.FieldContainsFold(FieldLink, v)) return predicate.History(sql.FieldContainsFold(FieldLink, v))
} }
// HashEQ applies the EQ predicate on the "hash" field.
func HashEQ(v string) predicate.History {
return predicate.History(sql.FieldEQ(FieldHash, v))
}
// HashNEQ applies the NEQ predicate on the "hash" field.
func HashNEQ(v string) predicate.History {
return predicate.History(sql.FieldNEQ(FieldHash, v))
}
// HashIn applies the In predicate on the "hash" field.
func HashIn(vs ...string) predicate.History {
return predicate.History(sql.FieldIn(FieldHash, vs...))
}
// HashNotIn applies the NotIn predicate on the "hash" field.
func HashNotIn(vs ...string) predicate.History {
return predicate.History(sql.FieldNotIn(FieldHash, vs...))
}
// HashGT applies the GT predicate on the "hash" field.
func HashGT(v string) predicate.History {
return predicate.History(sql.FieldGT(FieldHash, v))
}
// HashGTE applies the GTE predicate on the "hash" field.
func HashGTE(v string) predicate.History {
return predicate.History(sql.FieldGTE(FieldHash, v))
}
// HashLT applies the LT predicate on the "hash" field.
func HashLT(v string) predicate.History {
return predicate.History(sql.FieldLT(FieldHash, v))
}
// HashLTE applies the LTE predicate on the "hash" field.
func HashLTE(v string) predicate.History {
return predicate.History(sql.FieldLTE(FieldHash, v))
}
// HashContains applies the Contains predicate on the "hash" field.
func HashContains(v string) predicate.History {
return predicate.History(sql.FieldContains(FieldHash, v))
}
// HashHasPrefix applies the HasPrefix predicate on the "hash" field.
func HashHasPrefix(v string) predicate.History {
return predicate.History(sql.FieldHasPrefix(FieldHash, v))
}
// HashHasSuffix applies the HasSuffix predicate on the "hash" field.
func HashHasSuffix(v string) predicate.History {
return predicate.History(sql.FieldHasSuffix(FieldHash, v))
}
// HashIsNil applies the IsNil predicate on the "hash" field.
func HashIsNil() predicate.History {
return predicate.History(sql.FieldIsNull(FieldHash))
}
// HashNotNil applies the NotNil predicate on the "hash" field.
func HashNotNil() predicate.History {
return predicate.History(sql.FieldNotNull(FieldHash))
}
// HashEqualFold applies the EqualFold predicate on the "hash" field.
func HashEqualFold(v string) predicate.History {
return predicate.History(sql.FieldEqualFold(FieldHash, v))
}
// HashContainsFold applies the ContainsFold predicate on the "hash" field.
func HashContainsFold(v string) predicate.History {
return predicate.History(sql.FieldContainsFold(FieldHash, v))
}
// StatusEQ applies the EQ predicate on the "status" field. // StatusEQ applies the EQ predicate on the "status" field.
func StatusEQ(v Status) predicate.History { func StatusEQ(v Status) predicate.History {
return predicate.History(sql.FieldEQ(FieldStatus, v)) return predicate.History(sql.FieldEQ(FieldStatus, v))
@@ -664,81 +684,6 @@ func StatusNotIn(vs ...Status) predicate.History {
return predicate.History(sql.FieldNotIn(FieldStatus, vs...)) return predicate.History(sql.FieldNotIn(FieldStatus, vs...))
} }
// SavedEQ applies the EQ predicate on the "saved" field.
func SavedEQ(v string) predicate.History {
return predicate.History(sql.FieldEQ(FieldSaved, v))
}
// SavedNEQ applies the NEQ predicate on the "saved" field.
func SavedNEQ(v string) predicate.History {
return predicate.History(sql.FieldNEQ(FieldSaved, v))
}
// SavedIn applies the In predicate on the "saved" field.
func SavedIn(vs ...string) predicate.History {
return predicate.History(sql.FieldIn(FieldSaved, vs...))
}
// SavedNotIn applies the NotIn predicate on the "saved" field.
func SavedNotIn(vs ...string) predicate.History {
return predicate.History(sql.FieldNotIn(FieldSaved, vs...))
}
// SavedGT applies the GT predicate on the "saved" field.
func SavedGT(v string) predicate.History {
return predicate.History(sql.FieldGT(FieldSaved, v))
}
// SavedGTE applies the GTE predicate on the "saved" field.
func SavedGTE(v string) predicate.History {
return predicate.History(sql.FieldGTE(FieldSaved, v))
}
// SavedLT applies the LT predicate on the "saved" field.
func SavedLT(v string) predicate.History {
return predicate.History(sql.FieldLT(FieldSaved, v))
}
// SavedLTE applies the LTE predicate on the "saved" field.
func SavedLTE(v string) predicate.History {
return predicate.History(sql.FieldLTE(FieldSaved, v))
}
// SavedContains applies the Contains predicate on the "saved" field.
func SavedContains(v string) predicate.History {
return predicate.History(sql.FieldContains(FieldSaved, v))
}
// SavedHasPrefix applies the HasPrefix predicate on the "saved" field.
func SavedHasPrefix(v string) predicate.History {
return predicate.History(sql.FieldHasPrefix(FieldSaved, v))
}
// SavedHasSuffix applies the HasSuffix predicate on the "saved" field.
func SavedHasSuffix(v string) predicate.History {
return predicate.History(sql.FieldHasSuffix(FieldSaved, v))
}
// SavedIsNil applies the IsNil predicate on the "saved" field.
func SavedIsNil() predicate.History {
return predicate.History(sql.FieldIsNull(FieldSaved))
}
// SavedNotNil applies the NotNil predicate on the "saved" field.
func SavedNotNil() predicate.History {
return predicate.History(sql.FieldNotNull(FieldSaved))
}
// SavedEqualFold applies the EqualFold predicate on the "saved" field.
func SavedEqualFold(v string) predicate.History {
return predicate.History(sql.FieldEqualFold(FieldSaved, v))
}
// SavedContainsFold applies the ContainsFold predicate on the "saved" field.
func SavedContainsFold(v string) predicate.History {
return predicate.History(sql.FieldContainsFold(FieldSaved, v))
}
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.History) predicate.History { func And(predicates ...predicate.History) predicate.History {
return predicate.History(sql.AndPredicates(predicates...)) return predicate.History(sql.AndPredicates(predicates...))

View File

@@ -26,20 +26,6 @@ func (hc *HistoryCreate) SetMediaID(i int) *HistoryCreate {
return hc return hc
} }
// SetEpisodeID sets the "episode_id" field.
func (hc *HistoryCreate) SetEpisodeID(i int) *HistoryCreate {
hc.mutation.SetEpisodeID(i)
return hc
}
// SetNillableEpisodeID sets the "episode_id" field if the given value is not nil.
func (hc *HistoryCreate) SetNillableEpisodeID(i *int) *HistoryCreate {
if i != nil {
hc.SetEpisodeID(*i)
}
return hc
}
// SetEpisodeNums sets the "episode_nums" field. // SetEpisodeNums sets the "episode_nums" field.
func (hc *HistoryCreate) SetEpisodeNums(i []int) *HistoryCreate { func (hc *HistoryCreate) SetEpisodeNums(i []int) *HistoryCreate {
hc.mutation.SetEpisodeNums(i) hc.mutation.SetEpisodeNums(i)
@@ -134,26 +120,26 @@ func (hc *HistoryCreate) SetNillableLink(s *string) *HistoryCreate {
return hc return hc
} }
// SetHash sets the "hash" field.
func (hc *HistoryCreate) SetHash(s string) *HistoryCreate {
hc.mutation.SetHash(s)
return hc
}
// SetNillableHash sets the "hash" field if the given value is not nil.
func (hc *HistoryCreate) SetNillableHash(s *string) *HistoryCreate {
if s != nil {
hc.SetHash(*s)
}
return hc
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (hc *HistoryCreate) SetStatus(h history.Status) *HistoryCreate { func (hc *HistoryCreate) SetStatus(h history.Status) *HistoryCreate {
hc.mutation.SetStatus(h) hc.mutation.SetStatus(h)
return hc return hc
} }
// SetSaved sets the "saved" field.
func (hc *HistoryCreate) SetSaved(s string) *HistoryCreate {
hc.mutation.SetSaved(s)
return hc
}
// SetNillableSaved sets the "saved" field if the given value is not nil.
func (hc *HistoryCreate) SetNillableSaved(s *string) *HistoryCreate {
if s != nil {
hc.SetSaved(*s)
}
return hc
}
// Mutation returns the HistoryMutation object of the builder. // Mutation returns the HistoryMutation object of the builder.
func (hc *HistoryCreate) Mutation() *HistoryMutation { func (hc *HistoryCreate) Mutation() *HistoryMutation {
return hc.mutation return hc.mutation
@@ -250,10 +236,6 @@ func (hc *HistoryCreate) createSpec() (*History, *sqlgraph.CreateSpec) {
_spec.SetField(history.FieldMediaID, field.TypeInt, value) _spec.SetField(history.FieldMediaID, field.TypeInt, value)
_node.MediaID = value _node.MediaID = value
} }
if value, ok := hc.mutation.EpisodeID(); ok {
_spec.SetField(history.FieldEpisodeID, field.TypeInt, value)
_node.EpisodeID = value
}
if value, ok := hc.mutation.EpisodeNums(); ok { if value, ok := hc.mutation.EpisodeNums(); ok {
_spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value) _spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value)
_node.EpisodeNums = value _node.EpisodeNums = value
@@ -290,14 +272,14 @@ func (hc *HistoryCreate) createSpec() (*History, *sqlgraph.CreateSpec) {
_spec.SetField(history.FieldLink, field.TypeString, value) _spec.SetField(history.FieldLink, field.TypeString, value)
_node.Link = value _node.Link = value
} }
if value, ok := hc.mutation.Hash(); ok {
_spec.SetField(history.FieldHash, field.TypeString, value)
_node.Hash = value
}
if value, ok := hc.mutation.Status(); ok { if value, ok := hc.mutation.Status(); ok {
_spec.SetField(history.FieldStatus, field.TypeEnum, value) _spec.SetField(history.FieldStatus, field.TypeEnum, value)
_node.Status = value _node.Status = value
} }
if value, ok := hc.mutation.Saved(); ok {
_spec.SetField(history.FieldSaved, field.TypeString, value)
_node.Saved = value
}
return _node, _spec return _node, _spec
} }

View File

@@ -50,33 +50,6 @@ func (hu *HistoryUpdate) AddMediaID(i int) *HistoryUpdate {
return hu return hu
} }
// SetEpisodeID sets the "episode_id" field.
func (hu *HistoryUpdate) SetEpisodeID(i int) *HistoryUpdate {
hu.mutation.ResetEpisodeID()
hu.mutation.SetEpisodeID(i)
return hu
}
// SetNillableEpisodeID sets the "episode_id" field if the given value is not nil.
func (hu *HistoryUpdate) SetNillableEpisodeID(i *int) *HistoryUpdate {
if i != nil {
hu.SetEpisodeID(*i)
}
return hu
}
// AddEpisodeID adds i to the "episode_id" field.
func (hu *HistoryUpdate) AddEpisodeID(i int) *HistoryUpdate {
hu.mutation.AddEpisodeID(i)
return hu
}
// ClearEpisodeID clears the value of the "episode_id" field.
func (hu *HistoryUpdate) ClearEpisodeID() *HistoryUpdate {
hu.mutation.ClearEpisodeID()
return hu
}
// SetEpisodeNums sets the "episode_nums" field. // SetEpisodeNums sets the "episode_nums" field.
func (hu *HistoryUpdate) SetEpisodeNums(i []int) *HistoryUpdate { func (hu *HistoryUpdate) SetEpisodeNums(i []int) *HistoryUpdate {
hu.mutation.SetEpisodeNums(i) hu.mutation.SetEpisodeNums(i)
@@ -259,6 +232,26 @@ func (hu *HistoryUpdate) ClearLink() *HistoryUpdate {
return hu return hu
} }
// SetHash sets the "hash" field.
func (hu *HistoryUpdate) SetHash(s string) *HistoryUpdate {
hu.mutation.SetHash(s)
return hu
}
// SetNillableHash sets the "hash" field if the given value is not nil.
func (hu *HistoryUpdate) SetNillableHash(s *string) *HistoryUpdate {
if s != nil {
hu.SetHash(*s)
}
return hu
}
// ClearHash clears the value of the "hash" field.
func (hu *HistoryUpdate) ClearHash() *HistoryUpdate {
hu.mutation.ClearHash()
return hu
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (hu *HistoryUpdate) SetStatus(h history.Status) *HistoryUpdate { func (hu *HistoryUpdate) SetStatus(h history.Status) *HistoryUpdate {
hu.mutation.SetStatus(h) hu.mutation.SetStatus(h)
@@ -273,26 +266,6 @@ func (hu *HistoryUpdate) SetNillableStatus(h *history.Status) *HistoryUpdate {
return hu return hu
} }
// SetSaved sets the "saved" field.
func (hu *HistoryUpdate) SetSaved(s string) *HistoryUpdate {
hu.mutation.SetSaved(s)
return hu
}
// SetNillableSaved sets the "saved" field if the given value is not nil.
func (hu *HistoryUpdate) SetNillableSaved(s *string) *HistoryUpdate {
if s != nil {
hu.SetSaved(*s)
}
return hu
}
// ClearSaved clears the value of the "saved" field.
func (hu *HistoryUpdate) ClearSaved() *HistoryUpdate {
hu.mutation.ClearSaved()
return hu
}
// Mutation returns the HistoryMutation object of the builder. // Mutation returns the HistoryMutation object of the builder.
func (hu *HistoryUpdate) Mutation() *HistoryMutation { func (hu *HistoryUpdate) Mutation() *HistoryMutation {
return hu.mutation return hu.mutation
@@ -353,15 +326,6 @@ func (hu *HistoryUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := hu.mutation.AddedMediaID(); ok { if value, ok := hu.mutation.AddedMediaID(); ok {
_spec.AddField(history.FieldMediaID, field.TypeInt, value) _spec.AddField(history.FieldMediaID, field.TypeInt, value)
} }
if value, ok := hu.mutation.EpisodeID(); ok {
_spec.SetField(history.FieldEpisodeID, field.TypeInt, value)
}
if value, ok := hu.mutation.AddedEpisodeID(); ok {
_spec.AddField(history.FieldEpisodeID, field.TypeInt, value)
}
if hu.mutation.EpisodeIDCleared() {
_spec.ClearField(history.FieldEpisodeID, field.TypeInt)
}
if value, ok := hu.mutation.EpisodeNums(); ok { if value, ok := hu.mutation.EpisodeNums(); ok {
_spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value) _spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value)
} }
@@ -421,15 +385,15 @@ func (hu *HistoryUpdate) sqlSave(ctx context.Context) (n int, err error) {
if hu.mutation.LinkCleared() { if hu.mutation.LinkCleared() {
_spec.ClearField(history.FieldLink, field.TypeString) _spec.ClearField(history.FieldLink, field.TypeString)
} }
if value, ok := hu.mutation.Hash(); ok {
_spec.SetField(history.FieldHash, field.TypeString, value)
}
if hu.mutation.HashCleared() {
_spec.ClearField(history.FieldHash, field.TypeString)
}
if value, ok := hu.mutation.Status(); ok { if value, ok := hu.mutation.Status(); ok {
_spec.SetField(history.FieldStatus, field.TypeEnum, value) _spec.SetField(history.FieldStatus, field.TypeEnum, value)
} }
if value, ok := hu.mutation.Saved(); ok {
_spec.SetField(history.FieldSaved, field.TypeString, value)
}
if hu.mutation.SavedCleared() {
_spec.ClearField(history.FieldSaved, field.TypeString)
}
if n, err = sqlgraph.UpdateNodes(ctx, hu.driver, _spec); err != nil { if n, err = sqlgraph.UpdateNodes(ctx, hu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok { if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{history.Label} err = &NotFoundError{history.Label}
@@ -471,33 +435,6 @@ func (huo *HistoryUpdateOne) AddMediaID(i int) *HistoryUpdateOne {
return huo return huo
} }
// SetEpisodeID sets the "episode_id" field.
func (huo *HistoryUpdateOne) SetEpisodeID(i int) *HistoryUpdateOne {
huo.mutation.ResetEpisodeID()
huo.mutation.SetEpisodeID(i)
return huo
}
// SetNillableEpisodeID sets the "episode_id" field if the given value is not nil.
func (huo *HistoryUpdateOne) SetNillableEpisodeID(i *int) *HistoryUpdateOne {
if i != nil {
huo.SetEpisodeID(*i)
}
return huo
}
// AddEpisodeID adds i to the "episode_id" field.
func (huo *HistoryUpdateOne) AddEpisodeID(i int) *HistoryUpdateOne {
huo.mutation.AddEpisodeID(i)
return huo
}
// ClearEpisodeID clears the value of the "episode_id" field.
func (huo *HistoryUpdateOne) ClearEpisodeID() *HistoryUpdateOne {
huo.mutation.ClearEpisodeID()
return huo
}
// SetEpisodeNums sets the "episode_nums" field. // SetEpisodeNums sets the "episode_nums" field.
func (huo *HistoryUpdateOne) SetEpisodeNums(i []int) *HistoryUpdateOne { func (huo *HistoryUpdateOne) SetEpisodeNums(i []int) *HistoryUpdateOne {
huo.mutation.SetEpisodeNums(i) huo.mutation.SetEpisodeNums(i)
@@ -680,6 +617,26 @@ func (huo *HistoryUpdateOne) ClearLink() *HistoryUpdateOne {
return huo return huo
} }
// SetHash sets the "hash" field.
func (huo *HistoryUpdateOne) SetHash(s string) *HistoryUpdateOne {
huo.mutation.SetHash(s)
return huo
}
// SetNillableHash sets the "hash" field if the given value is not nil.
func (huo *HistoryUpdateOne) SetNillableHash(s *string) *HistoryUpdateOne {
if s != nil {
huo.SetHash(*s)
}
return huo
}
// ClearHash clears the value of the "hash" field.
func (huo *HistoryUpdateOne) ClearHash() *HistoryUpdateOne {
huo.mutation.ClearHash()
return huo
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (huo *HistoryUpdateOne) SetStatus(h history.Status) *HistoryUpdateOne { func (huo *HistoryUpdateOne) SetStatus(h history.Status) *HistoryUpdateOne {
huo.mutation.SetStatus(h) huo.mutation.SetStatus(h)
@@ -694,26 +651,6 @@ func (huo *HistoryUpdateOne) SetNillableStatus(h *history.Status) *HistoryUpdate
return huo return huo
} }
// SetSaved sets the "saved" field.
func (huo *HistoryUpdateOne) SetSaved(s string) *HistoryUpdateOne {
huo.mutation.SetSaved(s)
return huo
}
// SetNillableSaved sets the "saved" field if the given value is not nil.
func (huo *HistoryUpdateOne) SetNillableSaved(s *string) *HistoryUpdateOne {
if s != nil {
huo.SetSaved(*s)
}
return huo
}
// ClearSaved clears the value of the "saved" field.
func (huo *HistoryUpdateOne) ClearSaved() *HistoryUpdateOne {
huo.mutation.ClearSaved()
return huo
}
// Mutation returns the HistoryMutation object of the builder. // Mutation returns the HistoryMutation object of the builder.
func (huo *HistoryUpdateOne) Mutation() *HistoryMutation { func (huo *HistoryUpdateOne) Mutation() *HistoryMutation {
return huo.mutation return huo.mutation
@@ -804,15 +741,6 @@ func (huo *HistoryUpdateOne) sqlSave(ctx context.Context) (_node *History, err e
if value, ok := huo.mutation.AddedMediaID(); ok { if value, ok := huo.mutation.AddedMediaID(); ok {
_spec.AddField(history.FieldMediaID, field.TypeInt, value) _spec.AddField(history.FieldMediaID, field.TypeInt, value)
} }
if value, ok := huo.mutation.EpisodeID(); ok {
_spec.SetField(history.FieldEpisodeID, field.TypeInt, value)
}
if value, ok := huo.mutation.AddedEpisodeID(); ok {
_spec.AddField(history.FieldEpisodeID, field.TypeInt, value)
}
if huo.mutation.EpisodeIDCleared() {
_spec.ClearField(history.FieldEpisodeID, field.TypeInt)
}
if value, ok := huo.mutation.EpisodeNums(); ok { if value, ok := huo.mutation.EpisodeNums(); ok {
_spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value) _spec.SetField(history.FieldEpisodeNums, field.TypeJSON, value)
} }
@@ -872,15 +800,15 @@ func (huo *HistoryUpdateOne) sqlSave(ctx context.Context) (_node *History, err e
if huo.mutation.LinkCleared() { if huo.mutation.LinkCleared() {
_spec.ClearField(history.FieldLink, field.TypeString) _spec.ClearField(history.FieldLink, field.TypeString)
} }
if value, ok := huo.mutation.Hash(); ok {
_spec.SetField(history.FieldHash, field.TypeString, value)
}
if huo.mutation.HashCleared() {
_spec.ClearField(history.FieldHash, field.TypeString)
}
if value, ok := huo.mutation.Status(); ok { if value, ok := huo.mutation.Status(); ok {
_spec.SetField(history.FieldStatus, field.TypeEnum, value) _spec.SetField(history.FieldStatus, field.TypeEnum, value)
} }
if value, ok := huo.mutation.Saved(); ok {
_spec.SetField(history.FieldSaved, field.TypeString, value)
}
if huo.mutation.SavedCleared() {
_spec.ClearField(history.FieldSaved, field.TypeString)
}
_node = &History{config: huo.config} _node = &History{config: huo.config}
_spec.Assign = _node.assignValues _spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues _spec.ScanValues = _node.scanValues

View File

@@ -20,7 +20,7 @@ type Indexers struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// Implementation holds the value of the "implementation" field. // Implementation holds the value of the "implementation" field.
Implementation string `json:"implementation,omitempty"` Implementation string `json:"implementation,omitempty"`
// Settings holds the value of the "settings" field. // deprecated, use api_key and url
Settings string `json:"settings,omitempty"` Settings string `json:"settings,omitempty"`
// EnableRss holds the value of the "enable_rss" field. // EnableRss holds the value of the "enable_rss" field.
EnableRss bool `json:"enable_rss,omitempty"` EnableRss bool `json:"enable_rss,omitempty"`
@@ -29,7 +29,17 @@ type Indexers struct {
// minimal seed ratio requied, before removing torrent // minimal seed ratio requied, before removing torrent
SeedRatio float32 `json:"seed_ratio,omitempty"` SeedRatio float32 `json:"seed_ratio,omitempty"`
// Disabled holds the value of the "disabled" field. // Disabled holds the value of the "disabled" field.
Disabled bool `json:"disabled,omitempty"` Disabled bool `json:"disabled,omitempty"`
// TvSearch holds the value of the "tv_search" field.
TvSearch bool `json:"tv_search,omitempty"`
// MovieSearch holds the value of the "movie_search" field.
MovieSearch bool `json:"movie_search,omitempty"`
// APIKey holds the value of the "api_key" field.
APIKey string `json:"api_key,omitempty"`
// URL holds the value of the "url" field.
URL string `json:"url,omitempty"`
// synced from prowlarr
Synced bool `json:"synced,omitempty"`
selectValues sql.SelectValues selectValues sql.SelectValues
} }
@@ -38,13 +48,13 @@ func (*Indexers) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns)) values := make([]any, len(columns))
for i := range columns { for i := range columns {
switch columns[i] { switch columns[i] {
case indexers.FieldEnableRss, indexers.FieldDisabled: case indexers.FieldEnableRss, indexers.FieldDisabled, indexers.FieldTvSearch, indexers.FieldMovieSearch, indexers.FieldSynced:
values[i] = new(sql.NullBool) values[i] = new(sql.NullBool)
case indexers.FieldSeedRatio: case indexers.FieldSeedRatio:
values[i] = new(sql.NullFloat64) values[i] = new(sql.NullFloat64)
case indexers.FieldID, indexers.FieldPriority: case indexers.FieldID, indexers.FieldPriority:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case indexers.FieldName, indexers.FieldImplementation, indexers.FieldSettings: case indexers.FieldName, indexers.FieldImplementation, indexers.FieldSettings, indexers.FieldAPIKey, indexers.FieldURL:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
default: default:
values[i] = new(sql.UnknownType) values[i] = new(sql.UnknownType)
@@ -109,6 +119,36 @@ func (i *Indexers) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
i.Disabled = value.Bool i.Disabled = value.Bool
} }
case indexers.FieldTvSearch:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field tv_search", values[j])
} else if value.Valid {
i.TvSearch = value.Bool
}
case indexers.FieldMovieSearch:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field movie_search", values[j])
} else if value.Valid {
i.MovieSearch = value.Bool
}
case indexers.FieldAPIKey:
if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field api_key", values[j])
} else if value.Valid {
i.APIKey = value.String
}
case indexers.FieldURL:
if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field url", values[j])
} else if value.Valid {
i.URL = value.String
}
case indexers.FieldSynced:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field synced", values[j])
} else if value.Valid {
i.Synced = value.Bool
}
default: default:
i.selectValues.Set(columns[j], values[j]) i.selectValues.Set(columns[j], values[j])
} }
@@ -165,6 +205,21 @@ func (i *Indexers) String() string {
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("disabled=") builder.WriteString("disabled=")
builder.WriteString(fmt.Sprintf("%v", i.Disabled)) builder.WriteString(fmt.Sprintf("%v", i.Disabled))
builder.WriteString(", ")
builder.WriteString("tv_search=")
builder.WriteString(fmt.Sprintf("%v", i.TvSearch))
builder.WriteString(", ")
builder.WriteString("movie_search=")
builder.WriteString(fmt.Sprintf("%v", i.MovieSearch))
builder.WriteString(", ")
builder.WriteString("api_key=")
builder.WriteString(i.APIKey)
builder.WriteString(", ")
builder.WriteString("url=")
builder.WriteString(i.URL)
builder.WriteString(", ")
builder.WriteString("synced=")
builder.WriteString(fmt.Sprintf("%v", i.Synced))
builder.WriteByte(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }

View File

@@ -25,6 +25,16 @@ const (
FieldSeedRatio = "seed_ratio" FieldSeedRatio = "seed_ratio"
// FieldDisabled holds the string denoting the disabled field in the database. // FieldDisabled holds the string denoting the disabled field in the database.
FieldDisabled = "disabled" FieldDisabled = "disabled"
// FieldTvSearch holds the string denoting the tv_search field in the database.
FieldTvSearch = "tv_search"
// FieldMovieSearch holds the string denoting the movie_search field in the database.
FieldMovieSearch = "movie_search"
// FieldAPIKey holds the string denoting the api_key field in the database.
FieldAPIKey = "api_key"
// FieldURL holds the string denoting the url field in the database.
FieldURL = "url"
// FieldSynced holds the string denoting the synced field in the database.
FieldSynced = "synced"
// Table holds the table name of the indexers in the database. // Table holds the table name of the indexers in the database.
Table = "indexers" Table = "indexers"
) )
@@ -39,6 +49,11 @@ var Columns = []string{
FieldPriority, FieldPriority,
FieldSeedRatio, FieldSeedRatio,
FieldDisabled, FieldDisabled,
FieldTvSearch,
FieldMovieSearch,
FieldAPIKey,
FieldURL,
FieldSynced,
} }
// ValidColumn reports if the column name is valid (part of the table columns). // ValidColumn reports if the column name is valid (part of the table columns).
@@ -52,6 +67,8 @@ func ValidColumn(column string) bool {
} }
var ( var (
// DefaultSettings holds the default value on creation for the "settings" field.
DefaultSettings string
// DefaultEnableRss holds the default value on creation for the "enable_rss" field. // DefaultEnableRss holds the default value on creation for the "enable_rss" field.
DefaultEnableRss bool DefaultEnableRss bool
// DefaultPriority holds the default value on creation for the "priority" field. // DefaultPriority holds the default value on creation for the "priority" field.
@@ -60,6 +77,12 @@ var (
DefaultSeedRatio float32 DefaultSeedRatio float32
// DefaultDisabled holds the default value on creation for the "disabled" field. // DefaultDisabled holds the default value on creation for the "disabled" field.
DefaultDisabled bool DefaultDisabled bool
// DefaultTvSearch holds the default value on creation for the "tv_search" field.
DefaultTvSearch bool
// DefaultMovieSearch holds the default value on creation for the "movie_search" field.
DefaultMovieSearch bool
// DefaultSynced holds the default value on creation for the "synced" field.
DefaultSynced bool
) )
// OrderOption defines the ordering options for the Indexers queries. // OrderOption defines the ordering options for the Indexers queries.
@@ -104,3 +127,28 @@ func BySeedRatio(opts ...sql.OrderTermOption) OrderOption {
func ByDisabled(opts ...sql.OrderTermOption) OrderOption { func ByDisabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDisabled, opts...).ToFunc() return sql.OrderByField(FieldDisabled, opts...).ToFunc()
} }
// ByTvSearch orders the results by the tv_search field.
func ByTvSearch(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTvSearch, opts...).ToFunc()
}
// ByMovieSearch orders the results by the movie_search field.
func ByMovieSearch(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMovieSearch, opts...).ToFunc()
}
// ByAPIKey orders the results by the api_key field.
func ByAPIKey(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAPIKey, opts...).ToFunc()
}
// ByURL orders the results by the url field.
func ByURL(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldURL, opts...).ToFunc()
}
// BySynced orders the results by the synced field.
func BySynced(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSynced, opts...).ToFunc()
}

View File

@@ -88,6 +88,31 @@ func Disabled(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldDisabled, v)) return predicate.Indexers(sql.FieldEQ(FieldDisabled, v))
} }
// TvSearch applies equality check predicate on the "tv_search" field. It's identical to TvSearchEQ.
func TvSearch(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldTvSearch, v))
}
// MovieSearch applies equality check predicate on the "movie_search" field. It's identical to MovieSearchEQ.
func MovieSearch(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldMovieSearch, v))
}
// APIKey applies equality check predicate on the "api_key" field. It's identical to APIKeyEQ.
func APIKey(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldAPIKey, v))
}
// URL applies equality check predicate on the "url" field. It's identical to URLEQ.
func URL(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldURL, v))
}
// Synced applies equality check predicate on the "synced" field. It's identical to SyncedEQ.
func Synced(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldSynced, v))
}
// NameEQ applies the EQ predicate on the "name" field. // NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.Indexers { func NameEQ(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldName, v)) return predicate.Indexers(sql.FieldEQ(FieldName, v))
@@ -273,6 +298,16 @@ func SettingsHasSuffix(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldHasSuffix(FieldSettings, v)) return predicate.Indexers(sql.FieldHasSuffix(FieldSettings, v))
} }
// SettingsIsNil applies the IsNil predicate on the "settings" field.
func SettingsIsNil() predicate.Indexers {
return predicate.Indexers(sql.FieldIsNull(FieldSettings))
}
// SettingsNotNil applies the NotNil predicate on the "settings" field.
func SettingsNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldSettings))
}
// SettingsEqualFold applies the EqualFold predicate on the "settings" field. // SettingsEqualFold applies the EqualFold predicate on the "settings" field.
func SettingsEqualFold(v string) predicate.Indexers { func SettingsEqualFold(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEqualFold(FieldSettings, v)) return predicate.Indexers(sql.FieldEqualFold(FieldSettings, v))
@@ -403,6 +438,216 @@ func DisabledNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldDisabled)) return predicate.Indexers(sql.FieldNotNull(FieldDisabled))
} }
// TvSearchEQ applies the EQ predicate on the "tv_search" field.
func TvSearchEQ(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldTvSearch, v))
}
// TvSearchNEQ applies the NEQ predicate on the "tv_search" field.
func TvSearchNEQ(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldNEQ(FieldTvSearch, v))
}
// TvSearchIsNil applies the IsNil predicate on the "tv_search" field.
func TvSearchIsNil() predicate.Indexers {
return predicate.Indexers(sql.FieldIsNull(FieldTvSearch))
}
// TvSearchNotNil applies the NotNil predicate on the "tv_search" field.
func TvSearchNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldTvSearch))
}
// MovieSearchEQ applies the EQ predicate on the "movie_search" field.
func MovieSearchEQ(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldMovieSearch, v))
}
// MovieSearchNEQ applies the NEQ predicate on the "movie_search" field.
func MovieSearchNEQ(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldNEQ(FieldMovieSearch, v))
}
// MovieSearchIsNil applies the IsNil predicate on the "movie_search" field.
func MovieSearchIsNil() predicate.Indexers {
return predicate.Indexers(sql.FieldIsNull(FieldMovieSearch))
}
// MovieSearchNotNil applies the NotNil predicate on the "movie_search" field.
func MovieSearchNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldMovieSearch))
}
// APIKeyEQ applies the EQ predicate on the "api_key" field.
func APIKeyEQ(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldAPIKey, v))
}
// APIKeyNEQ applies the NEQ predicate on the "api_key" field.
func APIKeyNEQ(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldNEQ(FieldAPIKey, v))
}
// APIKeyIn applies the In predicate on the "api_key" field.
func APIKeyIn(vs ...string) predicate.Indexers {
return predicate.Indexers(sql.FieldIn(FieldAPIKey, vs...))
}
// APIKeyNotIn applies the NotIn predicate on the "api_key" field.
func APIKeyNotIn(vs ...string) predicate.Indexers {
return predicate.Indexers(sql.FieldNotIn(FieldAPIKey, vs...))
}
// APIKeyGT applies the GT predicate on the "api_key" field.
func APIKeyGT(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldGT(FieldAPIKey, v))
}
// APIKeyGTE applies the GTE predicate on the "api_key" field.
func APIKeyGTE(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldGTE(FieldAPIKey, v))
}
// APIKeyLT applies the LT predicate on the "api_key" field.
func APIKeyLT(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldLT(FieldAPIKey, v))
}
// APIKeyLTE applies the LTE predicate on the "api_key" field.
func APIKeyLTE(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldLTE(FieldAPIKey, v))
}
// APIKeyContains applies the Contains predicate on the "api_key" field.
func APIKeyContains(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldContains(FieldAPIKey, v))
}
// APIKeyHasPrefix applies the HasPrefix predicate on the "api_key" field.
func APIKeyHasPrefix(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldHasPrefix(FieldAPIKey, v))
}
// APIKeyHasSuffix applies the HasSuffix predicate on the "api_key" field.
func APIKeyHasSuffix(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldHasSuffix(FieldAPIKey, v))
}
// APIKeyIsNil applies the IsNil predicate on the "api_key" field.
func APIKeyIsNil() predicate.Indexers {
return predicate.Indexers(sql.FieldIsNull(FieldAPIKey))
}
// APIKeyNotNil applies the NotNil predicate on the "api_key" field.
func APIKeyNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldAPIKey))
}
// APIKeyEqualFold applies the EqualFold predicate on the "api_key" field.
func APIKeyEqualFold(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEqualFold(FieldAPIKey, v))
}
// APIKeyContainsFold applies the ContainsFold predicate on the "api_key" field.
func APIKeyContainsFold(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldContainsFold(FieldAPIKey, v))
}
// URLEQ applies the EQ predicate on the "url" field.
func URLEQ(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldURL, v))
}
// URLNEQ applies the NEQ predicate on the "url" field.
func URLNEQ(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldNEQ(FieldURL, v))
}
// URLIn applies the In predicate on the "url" field.
func URLIn(vs ...string) predicate.Indexers {
return predicate.Indexers(sql.FieldIn(FieldURL, vs...))
}
// URLNotIn applies the NotIn predicate on the "url" field.
func URLNotIn(vs ...string) predicate.Indexers {
return predicate.Indexers(sql.FieldNotIn(FieldURL, vs...))
}
// URLGT applies the GT predicate on the "url" field.
func URLGT(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldGT(FieldURL, v))
}
// URLGTE applies the GTE predicate on the "url" field.
func URLGTE(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldGTE(FieldURL, v))
}
// URLLT applies the LT predicate on the "url" field.
func URLLT(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldLT(FieldURL, v))
}
// URLLTE applies the LTE predicate on the "url" field.
func URLLTE(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldLTE(FieldURL, v))
}
// URLContains applies the Contains predicate on the "url" field.
func URLContains(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldContains(FieldURL, v))
}
// URLHasPrefix applies the HasPrefix predicate on the "url" field.
func URLHasPrefix(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldHasPrefix(FieldURL, v))
}
// URLHasSuffix applies the HasSuffix predicate on the "url" field.
func URLHasSuffix(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldHasSuffix(FieldURL, v))
}
// URLIsNil applies the IsNil predicate on the "url" field.
func URLIsNil() predicate.Indexers {
return predicate.Indexers(sql.FieldIsNull(FieldURL))
}
// URLNotNil applies the NotNil predicate on the "url" field.
func URLNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldURL))
}
// URLEqualFold applies the EqualFold predicate on the "url" field.
func URLEqualFold(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldEqualFold(FieldURL, v))
}
// URLContainsFold applies the ContainsFold predicate on the "url" field.
func URLContainsFold(v string) predicate.Indexers {
return predicate.Indexers(sql.FieldContainsFold(FieldURL, v))
}
// SyncedEQ applies the EQ predicate on the "synced" field.
func SyncedEQ(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldEQ(FieldSynced, v))
}
// SyncedNEQ applies the NEQ predicate on the "synced" field.
func SyncedNEQ(v bool) predicate.Indexers {
return predicate.Indexers(sql.FieldNEQ(FieldSynced, v))
}
// SyncedIsNil applies the IsNil predicate on the "synced" field.
func SyncedIsNil() predicate.Indexers {
return predicate.Indexers(sql.FieldIsNull(FieldSynced))
}
// SyncedNotNil applies the NotNil predicate on the "synced" field.
func SyncedNotNil() predicate.Indexers {
return predicate.Indexers(sql.FieldNotNull(FieldSynced))
}
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.Indexers) predicate.Indexers { func And(predicates ...predicate.Indexers) predicate.Indexers {
return predicate.Indexers(sql.AndPredicates(predicates...)) return predicate.Indexers(sql.AndPredicates(predicates...))

View File

@@ -37,6 +37,14 @@ func (ic *IndexersCreate) SetSettings(s string) *IndexersCreate {
return ic return ic
} }
// SetNillableSettings sets the "settings" field if the given value is not nil.
func (ic *IndexersCreate) SetNillableSettings(s *string) *IndexersCreate {
if s != nil {
ic.SetSettings(*s)
}
return ic
}
// SetEnableRss sets the "enable_rss" field. // SetEnableRss sets the "enable_rss" field.
func (ic *IndexersCreate) SetEnableRss(b bool) *IndexersCreate { func (ic *IndexersCreate) SetEnableRss(b bool) *IndexersCreate {
ic.mutation.SetEnableRss(b) ic.mutation.SetEnableRss(b)
@@ -93,6 +101,76 @@ func (ic *IndexersCreate) SetNillableDisabled(b *bool) *IndexersCreate {
return ic return ic
} }
// SetTvSearch sets the "tv_search" field.
func (ic *IndexersCreate) SetTvSearch(b bool) *IndexersCreate {
ic.mutation.SetTvSearch(b)
return ic
}
// SetNillableTvSearch sets the "tv_search" field if the given value is not nil.
func (ic *IndexersCreate) SetNillableTvSearch(b *bool) *IndexersCreate {
if b != nil {
ic.SetTvSearch(*b)
}
return ic
}
// SetMovieSearch sets the "movie_search" field.
func (ic *IndexersCreate) SetMovieSearch(b bool) *IndexersCreate {
ic.mutation.SetMovieSearch(b)
return ic
}
// SetNillableMovieSearch sets the "movie_search" field if the given value is not nil.
func (ic *IndexersCreate) SetNillableMovieSearch(b *bool) *IndexersCreate {
if b != nil {
ic.SetMovieSearch(*b)
}
return ic
}
// SetAPIKey sets the "api_key" field.
func (ic *IndexersCreate) SetAPIKey(s string) *IndexersCreate {
ic.mutation.SetAPIKey(s)
return ic
}
// SetNillableAPIKey sets the "api_key" field if the given value is not nil.
func (ic *IndexersCreate) SetNillableAPIKey(s *string) *IndexersCreate {
if s != nil {
ic.SetAPIKey(*s)
}
return ic
}
// SetURL sets the "url" field.
func (ic *IndexersCreate) SetURL(s string) *IndexersCreate {
ic.mutation.SetURL(s)
return ic
}
// SetNillableURL sets the "url" field if the given value is not nil.
func (ic *IndexersCreate) SetNillableURL(s *string) *IndexersCreate {
if s != nil {
ic.SetURL(*s)
}
return ic
}
// SetSynced sets the "synced" field.
func (ic *IndexersCreate) SetSynced(b bool) *IndexersCreate {
ic.mutation.SetSynced(b)
return ic
}
// SetNillableSynced sets the "synced" field if the given value is not nil.
func (ic *IndexersCreate) SetNillableSynced(b *bool) *IndexersCreate {
if b != nil {
ic.SetSynced(*b)
}
return ic
}
// Mutation returns the IndexersMutation object of the builder. // Mutation returns the IndexersMutation object of the builder.
func (ic *IndexersCreate) Mutation() *IndexersMutation { func (ic *IndexersCreate) Mutation() *IndexersMutation {
return ic.mutation return ic.mutation
@@ -128,6 +206,10 @@ func (ic *IndexersCreate) ExecX(ctx context.Context) {
// defaults sets the default values of the builder before save. // defaults sets the default values of the builder before save.
func (ic *IndexersCreate) defaults() { func (ic *IndexersCreate) defaults() {
if _, ok := ic.mutation.Settings(); !ok {
v := indexers.DefaultSettings
ic.mutation.SetSettings(v)
}
if _, ok := ic.mutation.EnableRss(); !ok { if _, ok := ic.mutation.EnableRss(); !ok {
v := indexers.DefaultEnableRss v := indexers.DefaultEnableRss
ic.mutation.SetEnableRss(v) ic.mutation.SetEnableRss(v)
@@ -144,6 +226,18 @@ func (ic *IndexersCreate) defaults() {
v := indexers.DefaultDisabled v := indexers.DefaultDisabled
ic.mutation.SetDisabled(v) ic.mutation.SetDisabled(v)
} }
if _, ok := ic.mutation.TvSearch(); !ok {
v := indexers.DefaultTvSearch
ic.mutation.SetTvSearch(v)
}
if _, ok := ic.mutation.MovieSearch(); !ok {
v := indexers.DefaultMovieSearch
ic.mutation.SetMovieSearch(v)
}
if _, ok := ic.mutation.Synced(); !ok {
v := indexers.DefaultSynced
ic.mutation.SetSynced(v)
}
} }
// check runs all checks and user-defined validators on the builder. // check runs all checks and user-defined validators on the builder.
@@ -154,9 +248,6 @@ func (ic *IndexersCreate) check() error {
if _, ok := ic.mutation.Implementation(); !ok { if _, ok := ic.mutation.Implementation(); !ok {
return &ValidationError{Name: "implementation", err: errors.New(`ent: missing required field "Indexers.implementation"`)} return &ValidationError{Name: "implementation", err: errors.New(`ent: missing required field "Indexers.implementation"`)}
} }
if _, ok := ic.mutation.Settings(); !ok {
return &ValidationError{Name: "settings", err: errors.New(`ent: missing required field "Indexers.settings"`)}
}
if _, ok := ic.mutation.EnableRss(); !ok { if _, ok := ic.mutation.EnableRss(); !ok {
return &ValidationError{Name: "enable_rss", err: errors.New(`ent: missing required field "Indexers.enable_rss"`)} return &ValidationError{Name: "enable_rss", err: errors.New(`ent: missing required field "Indexers.enable_rss"`)}
} }
@@ -217,6 +308,26 @@ func (ic *IndexersCreate) createSpec() (*Indexers, *sqlgraph.CreateSpec) {
_spec.SetField(indexers.FieldDisabled, field.TypeBool, value) _spec.SetField(indexers.FieldDisabled, field.TypeBool, value)
_node.Disabled = value _node.Disabled = value
} }
if value, ok := ic.mutation.TvSearch(); ok {
_spec.SetField(indexers.FieldTvSearch, field.TypeBool, value)
_node.TvSearch = value
}
if value, ok := ic.mutation.MovieSearch(); ok {
_spec.SetField(indexers.FieldMovieSearch, field.TypeBool, value)
_node.MovieSearch = value
}
if value, ok := ic.mutation.APIKey(); ok {
_spec.SetField(indexers.FieldAPIKey, field.TypeString, value)
_node.APIKey = value
}
if value, ok := ic.mutation.URL(); ok {
_spec.SetField(indexers.FieldURL, field.TypeString, value)
_node.URL = value
}
if value, ok := ic.mutation.Synced(); ok {
_spec.SetField(indexers.FieldSynced, field.TypeBool, value)
_node.Synced = value
}
return _node, _spec return _node, _spec
} }

View File

@@ -69,6 +69,12 @@ func (iu *IndexersUpdate) SetNillableSettings(s *string) *IndexersUpdate {
return iu return iu
} }
// ClearSettings clears the value of the "settings" field.
func (iu *IndexersUpdate) ClearSettings() *IndexersUpdate {
iu.mutation.ClearSettings()
return iu
}
// SetEnableRss sets the "enable_rss" field. // SetEnableRss sets the "enable_rss" field.
func (iu *IndexersUpdate) SetEnableRss(b bool) *IndexersUpdate { func (iu *IndexersUpdate) SetEnableRss(b bool) *IndexersUpdate {
iu.mutation.SetEnableRss(b) iu.mutation.SetEnableRss(b)
@@ -151,6 +157,106 @@ func (iu *IndexersUpdate) ClearDisabled() *IndexersUpdate {
return iu return iu
} }
// SetTvSearch sets the "tv_search" field.
func (iu *IndexersUpdate) SetTvSearch(b bool) *IndexersUpdate {
iu.mutation.SetTvSearch(b)
return iu
}
// SetNillableTvSearch sets the "tv_search" field if the given value is not nil.
func (iu *IndexersUpdate) SetNillableTvSearch(b *bool) *IndexersUpdate {
if b != nil {
iu.SetTvSearch(*b)
}
return iu
}
// ClearTvSearch clears the value of the "tv_search" field.
func (iu *IndexersUpdate) ClearTvSearch() *IndexersUpdate {
iu.mutation.ClearTvSearch()
return iu
}
// SetMovieSearch sets the "movie_search" field.
func (iu *IndexersUpdate) SetMovieSearch(b bool) *IndexersUpdate {
iu.mutation.SetMovieSearch(b)
return iu
}
// SetNillableMovieSearch sets the "movie_search" field if the given value is not nil.
func (iu *IndexersUpdate) SetNillableMovieSearch(b *bool) *IndexersUpdate {
if b != nil {
iu.SetMovieSearch(*b)
}
return iu
}
// ClearMovieSearch clears the value of the "movie_search" field.
func (iu *IndexersUpdate) ClearMovieSearch() *IndexersUpdate {
iu.mutation.ClearMovieSearch()
return iu
}
// SetAPIKey sets the "api_key" field.
func (iu *IndexersUpdate) SetAPIKey(s string) *IndexersUpdate {
iu.mutation.SetAPIKey(s)
return iu
}
// SetNillableAPIKey sets the "api_key" field if the given value is not nil.
func (iu *IndexersUpdate) SetNillableAPIKey(s *string) *IndexersUpdate {
if s != nil {
iu.SetAPIKey(*s)
}
return iu
}
// ClearAPIKey clears the value of the "api_key" field.
func (iu *IndexersUpdate) ClearAPIKey() *IndexersUpdate {
iu.mutation.ClearAPIKey()
return iu
}
// SetURL sets the "url" field.
func (iu *IndexersUpdate) SetURL(s string) *IndexersUpdate {
iu.mutation.SetURL(s)
return iu
}
// SetNillableURL sets the "url" field if the given value is not nil.
func (iu *IndexersUpdate) SetNillableURL(s *string) *IndexersUpdate {
if s != nil {
iu.SetURL(*s)
}
return iu
}
// ClearURL clears the value of the "url" field.
func (iu *IndexersUpdate) ClearURL() *IndexersUpdate {
iu.mutation.ClearURL()
return iu
}
// SetSynced sets the "synced" field.
func (iu *IndexersUpdate) SetSynced(b bool) *IndexersUpdate {
iu.mutation.SetSynced(b)
return iu
}
// SetNillableSynced sets the "synced" field if the given value is not nil.
func (iu *IndexersUpdate) SetNillableSynced(b *bool) *IndexersUpdate {
if b != nil {
iu.SetSynced(*b)
}
return iu
}
// ClearSynced clears the value of the "synced" field.
func (iu *IndexersUpdate) ClearSynced() *IndexersUpdate {
iu.mutation.ClearSynced()
return iu
}
// Mutation returns the IndexersMutation object of the builder. // Mutation returns the IndexersMutation object of the builder.
func (iu *IndexersUpdate) Mutation() *IndexersMutation { func (iu *IndexersUpdate) Mutation() *IndexersMutation {
return iu.mutation return iu.mutation
@@ -201,6 +307,9 @@ func (iu *IndexersUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := iu.mutation.Settings(); ok { if value, ok := iu.mutation.Settings(); ok {
_spec.SetField(indexers.FieldSettings, field.TypeString, value) _spec.SetField(indexers.FieldSettings, field.TypeString, value)
} }
if iu.mutation.SettingsCleared() {
_spec.ClearField(indexers.FieldSettings, field.TypeString)
}
if value, ok := iu.mutation.EnableRss(); ok { if value, ok := iu.mutation.EnableRss(); ok {
_spec.SetField(indexers.FieldEnableRss, field.TypeBool, value) _spec.SetField(indexers.FieldEnableRss, field.TypeBool, value)
} }
@@ -225,6 +334,36 @@ func (iu *IndexersUpdate) sqlSave(ctx context.Context) (n int, err error) {
if iu.mutation.DisabledCleared() { if iu.mutation.DisabledCleared() {
_spec.ClearField(indexers.FieldDisabled, field.TypeBool) _spec.ClearField(indexers.FieldDisabled, field.TypeBool)
} }
if value, ok := iu.mutation.TvSearch(); ok {
_spec.SetField(indexers.FieldTvSearch, field.TypeBool, value)
}
if iu.mutation.TvSearchCleared() {
_spec.ClearField(indexers.FieldTvSearch, field.TypeBool)
}
if value, ok := iu.mutation.MovieSearch(); ok {
_spec.SetField(indexers.FieldMovieSearch, field.TypeBool, value)
}
if iu.mutation.MovieSearchCleared() {
_spec.ClearField(indexers.FieldMovieSearch, field.TypeBool)
}
if value, ok := iu.mutation.APIKey(); ok {
_spec.SetField(indexers.FieldAPIKey, field.TypeString, value)
}
if iu.mutation.APIKeyCleared() {
_spec.ClearField(indexers.FieldAPIKey, field.TypeString)
}
if value, ok := iu.mutation.URL(); ok {
_spec.SetField(indexers.FieldURL, field.TypeString, value)
}
if iu.mutation.URLCleared() {
_spec.ClearField(indexers.FieldURL, field.TypeString)
}
if value, ok := iu.mutation.Synced(); ok {
_spec.SetField(indexers.FieldSynced, field.TypeBool, value)
}
if iu.mutation.SyncedCleared() {
_spec.ClearField(indexers.FieldSynced, field.TypeBool)
}
if n, err = sqlgraph.UpdateNodes(ctx, iu.driver, _spec); err != nil { if n, err = sqlgraph.UpdateNodes(ctx, iu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok { if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{indexers.Label} err = &NotFoundError{indexers.Label}
@@ -287,6 +426,12 @@ func (iuo *IndexersUpdateOne) SetNillableSettings(s *string) *IndexersUpdateOne
return iuo return iuo
} }
// ClearSettings clears the value of the "settings" field.
func (iuo *IndexersUpdateOne) ClearSettings() *IndexersUpdateOne {
iuo.mutation.ClearSettings()
return iuo
}
// SetEnableRss sets the "enable_rss" field. // SetEnableRss sets the "enable_rss" field.
func (iuo *IndexersUpdateOne) SetEnableRss(b bool) *IndexersUpdateOne { func (iuo *IndexersUpdateOne) SetEnableRss(b bool) *IndexersUpdateOne {
iuo.mutation.SetEnableRss(b) iuo.mutation.SetEnableRss(b)
@@ -369,6 +514,106 @@ func (iuo *IndexersUpdateOne) ClearDisabled() *IndexersUpdateOne {
return iuo return iuo
} }
// SetTvSearch sets the "tv_search" field.
func (iuo *IndexersUpdateOne) SetTvSearch(b bool) *IndexersUpdateOne {
iuo.mutation.SetTvSearch(b)
return iuo
}
// SetNillableTvSearch sets the "tv_search" field if the given value is not nil.
func (iuo *IndexersUpdateOne) SetNillableTvSearch(b *bool) *IndexersUpdateOne {
if b != nil {
iuo.SetTvSearch(*b)
}
return iuo
}
// ClearTvSearch clears the value of the "tv_search" field.
func (iuo *IndexersUpdateOne) ClearTvSearch() *IndexersUpdateOne {
iuo.mutation.ClearTvSearch()
return iuo
}
// SetMovieSearch sets the "movie_search" field.
func (iuo *IndexersUpdateOne) SetMovieSearch(b bool) *IndexersUpdateOne {
iuo.mutation.SetMovieSearch(b)
return iuo
}
// SetNillableMovieSearch sets the "movie_search" field if the given value is not nil.
func (iuo *IndexersUpdateOne) SetNillableMovieSearch(b *bool) *IndexersUpdateOne {
if b != nil {
iuo.SetMovieSearch(*b)
}
return iuo
}
// ClearMovieSearch clears the value of the "movie_search" field.
func (iuo *IndexersUpdateOne) ClearMovieSearch() *IndexersUpdateOne {
iuo.mutation.ClearMovieSearch()
return iuo
}
// SetAPIKey sets the "api_key" field.
func (iuo *IndexersUpdateOne) SetAPIKey(s string) *IndexersUpdateOne {
iuo.mutation.SetAPIKey(s)
return iuo
}
// SetNillableAPIKey sets the "api_key" field if the given value is not nil.
func (iuo *IndexersUpdateOne) SetNillableAPIKey(s *string) *IndexersUpdateOne {
if s != nil {
iuo.SetAPIKey(*s)
}
return iuo
}
// ClearAPIKey clears the value of the "api_key" field.
func (iuo *IndexersUpdateOne) ClearAPIKey() *IndexersUpdateOne {
iuo.mutation.ClearAPIKey()
return iuo
}
// SetURL sets the "url" field.
func (iuo *IndexersUpdateOne) SetURL(s string) *IndexersUpdateOne {
iuo.mutation.SetURL(s)
return iuo
}
// SetNillableURL sets the "url" field if the given value is not nil.
func (iuo *IndexersUpdateOne) SetNillableURL(s *string) *IndexersUpdateOne {
if s != nil {
iuo.SetURL(*s)
}
return iuo
}
// ClearURL clears the value of the "url" field.
func (iuo *IndexersUpdateOne) ClearURL() *IndexersUpdateOne {
iuo.mutation.ClearURL()
return iuo
}
// SetSynced sets the "synced" field.
func (iuo *IndexersUpdateOne) SetSynced(b bool) *IndexersUpdateOne {
iuo.mutation.SetSynced(b)
return iuo
}
// SetNillableSynced sets the "synced" field if the given value is not nil.
func (iuo *IndexersUpdateOne) SetNillableSynced(b *bool) *IndexersUpdateOne {
if b != nil {
iuo.SetSynced(*b)
}
return iuo
}
// ClearSynced clears the value of the "synced" field.
func (iuo *IndexersUpdateOne) ClearSynced() *IndexersUpdateOne {
iuo.mutation.ClearSynced()
return iuo
}
// Mutation returns the IndexersMutation object of the builder. // Mutation returns the IndexersMutation object of the builder.
func (iuo *IndexersUpdateOne) Mutation() *IndexersMutation { func (iuo *IndexersUpdateOne) Mutation() *IndexersMutation {
return iuo.mutation return iuo.mutation
@@ -449,6 +694,9 @@ func (iuo *IndexersUpdateOne) sqlSave(ctx context.Context) (_node *Indexers, err
if value, ok := iuo.mutation.Settings(); ok { if value, ok := iuo.mutation.Settings(); ok {
_spec.SetField(indexers.FieldSettings, field.TypeString, value) _spec.SetField(indexers.FieldSettings, field.TypeString, value)
} }
if iuo.mutation.SettingsCleared() {
_spec.ClearField(indexers.FieldSettings, field.TypeString)
}
if value, ok := iuo.mutation.EnableRss(); ok { if value, ok := iuo.mutation.EnableRss(); ok {
_spec.SetField(indexers.FieldEnableRss, field.TypeBool, value) _spec.SetField(indexers.FieldEnableRss, field.TypeBool, value)
} }
@@ -473,6 +721,36 @@ func (iuo *IndexersUpdateOne) sqlSave(ctx context.Context) (_node *Indexers, err
if iuo.mutation.DisabledCleared() { if iuo.mutation.DisabledCleared() {
_spec.ClearField(indexers.FieldDisabled, field.TypeBool) _spec.ClearField(indexers.FieldDisabled, field.TypeBool)
} }
if value, ok := iuo.mutation.TvSearch(); ok {
_spec.SetField(indexers.FieldTvSearch, field.TypeBool, value)
}
if iuo.mutation.TvSearchCleared() {
_spec.ClearField(indexers.FieldTvSearch, field.TypeBool)
}
if value, ok := iuo.mutation.MovieSearch(); ok {
_spec.SetField(indexers.FieldMovieSearch, field.TypeBool, value)
}
if iuo.mutation.MovieSearchCleared() {
_spec.ClearField(indexers.FieldMovieSearch, field.TypeBool)
}
if value, ok := iuo.mutation.APIKey(); ok {
_spec.SetField(indexers.FieldAPIKey, field.TypeString, value)
}
if iuo.mutation.APIKeyCleared() {
_spec.ClearField(indexers.FieldAPIKey, field.TypeString)
}
if value, ok := iuo.mutation.URL(); ok {
_spec.SetField(indexers.FieldURL, field.TypeString, value)
}
if iuo.mutation.URLCleared() {
_spec.ClearField(indexers.FieldURL, field.TypeString)
}
if value, ok := iuo.mutation.Synced(); ok {
_spec.SetField(indexers.FieldSynced, field.TypeBool, value)
}
if iuo.mutation.SyncedCleared() {
_spec.ClearField(indexers.FieldSynced, field.TypeBool)
}
_node = &Indexers{config: iuo.config} _node = &Indexers{config: iuo.config}
_spec.Assign = _node.assignValues _spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues _spec.ScanValues = _node.scanValues

View File

@@ -26,7 +26,7 @@ var (
{Name: "id", Type: field.TypeInt, Increment: true}, {Name: "id", Type: field.TypeInt, Increment: true},
{Name: "enable", Type: field.TypeBool}, {Name: "enable", Type: field.TypeBool},
{Name: "name", Type: field.TypeString}, {Name: "name", Type: field.TypeString},
{Name: "implementation", Type: field.TypeEnum, Enums: []string{"transmission", "qbittorrent"}}, {Name: "implementation", Type: field.TypeEnum, Enums: []string{"transmission", "qbittorrent", "buildin"}},
{Name: "url", Type: field.TypeString}, {Name: "url", Type: field.TypeString},
{Name: "user", Type: field.TypeString, Default: ""}, {Name: "user", Type: field.TypeString, Default: ""},
{Name: "password", Type: field.TypeString, Default: ""}, {Name: "password", Type: field.TypeString, Default: ""},
@@ -73,7 +73,6 @@ var (
HistoriesColumns = []*schema.Column{ HistoriesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true}, {Name: "id", Type: field.TypeInt, Increment: true},
{Name: "media_id", Type: field.TypeInt}, {Name: "media_id", Type: field.TypeInt},
{Name: "episode_id", Type: field.TypeInt, Nullable: true},
{Name: "episode_nums", Type: field.TypeJSON, Nullable: true}, {Name: "episode_nums", Type: field.TypeJSON, Nullable: true},
{Name: "season_num", Type: field.TypeInt, Nullable: true}, {Name: "season_num", Type: field.TypeInt, Nullable: true},
{Name: "source_title", Type: field.TypeString}, {Name: "source_title", Type: field.TypeString},
@@ -83,8 +82,8 @@ var (
{Name: "download_client_id", Type: field.TypeInt, Nullable: true}, {Name: "download_client_id", Type: field.TypeInt, Nullable: true},
{Name: "indexer_id", Type: field.TypeInt, Nullable: true}, {Name: "indexer_id", Type: field.TypeInt, Nullable: true},
{Name: "link", Type: field.TypeString, Nullable: true}, {Name: "link", Type: field.TypeString, Nullable: true},
{Name: "status", Type: field.TypeEnum, Enums: []string{"running", "success", "fail", "uploading", "seeding"}}, {Name: "hash", Type: field.TypeString, Nullable: true},
{Name: "saved", Type: field.TypeString, Nullable: true}, {Name: "status", Type: field.TypeEnum, Enums: []string{"running", "success", "fail", "uploading", "seeding", "removed"}},
} }
// HistoriesTable holds the schema information for the "histories" table. // HistoriesTable holds the schema information for the "histories" table.
HistoriesTable = &schema.Table{ HistoriesTable = &schema.Table{
@@ -113,11 +112,16 @@ var (
{Name: "id", Type: field.TypeInt, Increment: true}, {Name: "id", Type: field.TypeInt, Increment: true},
{Name: "name", Type: field.TypeString}, {Name: "name", Type: field.TypeString},
{Name: "implementation", Type: field.TypeString}, {Name: "implementation", Type: field.TypeString},
{Name: "settings", Type: field.TypeString}, {Name: "settings", Type: field.TypeString, Nullable: true, Default: ""},
{Name: "enable_rss", Type: field.TypeBool, Default: true}, {Name: "enable_rss", Type: field.TypeBool, Default: true},
{Name: "priority", Type: field.TypeInt, Default: 50}, {Name: "priority", Type: field.TypeInt, Default: 50},
{Name: "seed_ratio", Type: field.TypeFloat32, Nullable: true, Default: 0}, {Name: "seed_ratio", Type: field.TypeFloat32, Nullable: true, Default: 0},
{Name: "disabled", Type: field.TypeBool, Nullable: true, Default: false}, {Name: "disabled", Type: field.TypeBool, Nullable: true, Default: false},
{Name: "tv_search", Type: field.TypeBool, Nullable: true, Default: true},
{Name: "movie_search", Type: field.TypeBool, Nullable: true, Default: true},
{Name: "api_key", Type: field.TypeString, Nullable: true},
{Name: "url", Type: field.TypeString, Nullable: true},
{Name: "synced", Type: field.TypeBool, Nullable: true, Default: false},
} }
// IndexersTable holds the schema information for the "indexers" table. // IndexersTable holds the schema information for the "indexers" table.
IndexersTable = &schema.Table{ IndexersTable = &schema.Table{

View File

@@ -2334,8 +2334,6 @@ type HistoryMutation struct {
id *int id *int
media_id *int media_id *int
addmedia_id *int addmedia_id *int
episode_id *int
addepisode_id *int
episode_nums *[]int episode_nums *[]int
appendepisode_nums []int appendepisode_nums []int
season_num *int season_num *int
@@ -2350,8 +2348,8 @@ type HistoryMutation struct {
indexer_id *int indexer_id *int
addindexer_id *int addindexer_id *int
link *string link *string
hash *string
status *history.Status status *history.Status
saved *string
clearedFields map[string]struct{} clearedFields map[string]struct{}
done bool done bool
oldValue func(context.Context) (*History, error) oldValue func(context.Context) (*History, error)
@@ -2512,76 +2510,6 @@ func (m *HistoryMutation) ResetMediaID() {
m.addmedia_id = nil m.addmedia_id = nil
} }
// SetEpisodeID sets the "episode_id" field.
func (m *HistoryMutation) SetEpisodeID(i int) {
m.episode_id = &i
m.addepisode_id = nil
}
// EpisodeID returns the value of the "episode_id" field in the mutation.
func (m *HistoryMutation) EpisodeID() (r int, exists bool) {
v := m.episode_id
if v == nil {
return
}
return *v, true
}
// OldEpisodeID returns the old "episode_id" field's value of the History entity.
// If the History object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *HistoryMutation) OldEpisodeID(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldEpisodeID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldEpisodeID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldEpisodeID: %w", err)
}
return oldValue.EpisodeID, nil
}
// AddEpisodeID adds i to the "episode_id" field.
func (m *HistoryMutation) AddEpisodeID(i int) {
if m.addepisode_id != nil {
*m.addepisode_id += i
} else {
m.addepisode_id = &i
}
}
// AddedEpisodeID returns the value that was added to the "episode_id" field in this mutation.
func (m *HistoryMutation) AddedEpisodeID() (r int, exists bool) {
v := m.addepisode_id
if v == nil {
return
}
return *v, true
}
// ClearEpisodeID clears the value of the "episode_id" field.
func (m *HistoryMutation) ClearEpisodeID() {
m.episode_id = nil
m.addepisode_id = nil
m.clearedFields[history.FieldEpisodeID] = struct{}{}
}
// EpisodeIDCleared returns if the "episode_id" field was cleared in this mutation.
func (m *HistoryMutation) EpisodeIDCleared() bool {
_, ok := m.clearedFields[history.FieldEpisodeID]
return ok
}
// ResetEpisodeID resets all changes to the "episode_id" field.
func (m *HistoryMutation) ResetEpisodeID() {
m.episode_id = nil
m.addepisode_id = nil
delete(m.clearedFields, history.FieldEpisodeID)
}
// SetEpisodeNums sets the "episode_nums" field. // SetEpisodeNums sets the "episode_nums" field.
func (m *HistoryMutation) SetEpisodeNums(i []int) { func (m *HistoryMutation) SetEpisodeNums(i []int) {
m.episode_nums = &i m.episode_nums = &i
@@ -3070,6 +2998,55 @@ func (m *HistoryMutation) ResetLink() {
delete(m.clearedFields, history.FieldLink) delete(m.clearedFields, history.FieldLink)
} }
// SetHash sets the "hash" field.
func (m *HistoryMutation) SetHash(s string) {
m.hash = &s
}
// Hash returns the value of the "hash" field in the mutation.
func (m *HistoryMutation) Hash() (r string, exists bool) {
v := m.hash
if v == nil {
return
}
return *v, true
}
// OldHash returns the old "hash" field's value of the History entity.
// If the History object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *HistoryMutation) OldHash(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldHash is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldHash requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldHash: %w", err)
}
return oldValue.Hash, nil
}
// ClearHash clears the value of the "hash" field.
func (m *HistoryMutation) ClearHash() {
m.hash = nil
m.clearedFields[history.FieldHash] = struct{}{}
}
// HashCleared returns if the "hash" field was cleared in this mutation.
func (m *HistoryMutation) HashCleared() bool {
_, ok := m.clearedFields[history.FieldHash]
return ok
}
// ResetHash resets all changes to the "hash" field.
func (m *HistoryMutation) ResetHash() {
m.hash = nil
delete(m.clearedFields, history.FieldHash)
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (m *HistoryMutation) SetStatus(h history.Status) { func (m *HistoryMutation) SetStatus(h history.Status) {
m.status = &h m.status = &h
@@ -3106,55 +3083,6 @@ func (m *HistoryMutation) ResetStatus() {
m.status = nil m.status = nil
} }
// SetSaved sets the "saved" field.
func (m *HistoryMutation) SetSaved(s string) {
m.saved = &s
}
// Saved returns the value of the "saved" field in the mutation.
func (m *HistoryMutation) Saved() (r string, exists bool) {
v := m.saved
if v == nil {
return
}
return *v, true
}
// OldSaved returns the old "saved" field's value of the History entity.
// If the History object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *HistoryMutation) OldSaved(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSaved is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSaved requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSaved: %w", err)
}
return oldValue.Saved, nil
}
// ClearSaved clears the value of the "saved" field.
func (m *HistoryMutation) ClearSaved() {
m.saved = nil
m.clearedFields[history.FieldSaved] = struct{}{}
}
// SavedCleared returns if the "saved" field was cleared in this mutation.
func (m *HistoryMutation) SavedCleared() bool {
_, ok := m.clearedFields[history.FieldSaved]
return ok
}
// ResetSaved resets all changes to the "saved" field.
func (m *HistoryMutation) ResetSaved() {
m.saved = nil
delete(m.clearedFields, history.FieldSaved)
}
// Where appends a list predicates to the HistoryMutation builder. // Where appends a list predicates to the HistoryMutation builder.
func (m *HistoryMutation) Where(ps ...predicate.History) { func (m *HistoryMutation) Where(ps ...predicate.History) {
m.predicates = append(m.predicates, ps...) m.predicates = append(m.predicates, ps...)
@@ -3189,13 +3117,10 @@ func (m *HistoryMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call // order to get all numeric fields that were incremented/decremented, call
// AddedFields(). // AddedFields().
func (m *HistoryMutation) Fields() []string { func (m *HistoryMutation) Fields() []string {
fields := make([]string, 0, 13) fields := make([]string, 0, 12)
if m.media_id != nil { if m.media_id != nil {
fields = append(fields, history.FieldMediaID) fields = append(fields, history.FieldMediaID)
} }
if m.episode_id != nil {
fields = append(fields, history.FieldEpisodeID)
}
if m.episode_nums != nil { if m.episode_nums != nil {
fields = append(fields, history.FieldEpisodeNums) fields = append(fields, history.FieldEpisodeNums)
} }
@@ -3223,12 +3148,12 @@ func (m *HistoryMutation) Fields() []string {
if m.link != nil { if m.link != nil {
fields = append(fields, history.FieldLink) fields = append(fields, history.FieldLink)
} }
if m.hash != nil {
fields = append(fields, history.FieldHash)
}
if m.status != nil { if m.status != nil {
fields = append(fields, history.FieldStatus) fields = append(fields, history.FieldStatus)
} }
if m.saved != nil {
fields = append(fields, history.FieldSaved)
}
return fields return fields
} }
@@ -3239,8 +3164,6 @@ func (m *HistoryMutation) Field(name string) (ent.Value, bool) {
switch name { switch name {
case history.FieldMediaID: case history.FieldMediaID:
return m.MediaID() return m.MediaID()
case history.FieldEpisodeID:
return m.EpisodeID()
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
return m.EpisodeNums() return m.EpisodeNums()
case history.FieldSeasonNum: case history.FieldSeasonNum:
@@ -3259,10 +3182,10 @@ func (m *HistoryMutation) Field(name string) (ent.Value, bool) {
return m.IndexerID() return m.IndexerID()
case history.FieldLink: case history.FieldLink:
return m.Link() return m.Link()
case history.FieldHash:
return m.Hash()
case history.FieldStatus: case history.FieldStatus:
return m.Status() return m.Status()
case history.FieldSaved:
return m.Saved()
} }
return nil, false return nil, false
} }
@@ -3274,8 +3197,6 @@ func (m *HistoryMutation) OldField(ctx context.Context, name string) (ent.Value,
switch name { switch name {
case history.FieldMediaID: case history.FieldMediaID:
return m.OldMediaID(ctx) return m.OldMediaID(ctx)
case history.FieldEpisodeID:
return m.OldEpisodeID(ctx)
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
return m.OldEpisodeNums(ctx) return m.OldEpisodeNums(ctx)
case history.FieldSeasonNum: case history.FieldSeasonNum:
@@ -3294,10 +3215,10 @@ func (m *HistoryMutation) OldField(ctx context.Context, name string) (ent.Value,
return m.OldIndexerID(ctx) return m.OldIndexerID(ctx)
case history.FieldLink: case history.FieldLink:
return m.OldLink(ctx) return m.OldLink(ctx)
case history.FieldHash:
return m.OldHash(ctx)
case history.FieldStatus: case history.FieldStatus:
return m.OldStatus(ctx) return m.OldStatus(ctx)
case history.FieldSaved:
return m.OldSaved(ctx)
} }
return nil, fmt.Errorf("unknown History field %s", name) return nil, fmt.Errorf("unknown History field %s", name)
} }
@@ -3314,13 +3235,6 @@ func (m *HistoryMutation) SetField(name string, value ent.Value) error {
} }
m.SetMediaID(v) m.SetMediaID(v)
return nil return nil
case history.FieldEpisodeID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetEpisodeID(v)
return nil
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
v, ok := value.([]int) v, ok := value.([]int)
if !ok { if !ok {
@@ -3384,6 +3298,13 @@ func (m *HistoryMutation) SetField(name string, value ent.Value) error {
} }
m.SetLink(v) m.SetLink(v)
return nil return nil
case history.FieldHash:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetHash(v)
return nil
case history.FieldStatus: case history.FieldStatus:
v, ok := value.(history.Status) v, ok := value.(history.Status)
if !ok { if !ok {
@@ -3391,13 +3312,6 @@ func (m *HistoryMutation) SetField(name string, value ent.Value) error {
} }
m.SetStatus(v) m.SetStatus(v)
return nil return nil
case history.FieldSaved:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSaved(v)
return nil
} }
return fmt.Errorf("unknown History field %s", name) return fmt.Errorf("unknown History field %s", name)
} }
@@ -3409,9 +3323,6 @@ func (m *HistoryMutation) AddedFields() []string {
if m.addmedia_id != nil { if m.addmedia_id != nil {
fields = append(fields, history.FieldMediaID) fields = append(fields, history.FieldMediaID)
} }
if m.addepisode_id != nil {
fields = append(fields, history.FieldEpisodeID)
}
if m.addseason_num != nil { if m.addseason_num != nil {
fields = append(fields, history.FieldSeasonNum) fields = append(fields, history.FieldSeasonNum)
} }
@@ -3434,8 +3345,6 @@ func (m *HistoryMutation) AddedField(name string) (ent.Value, bool) {
switch name { switch name {
case history.FieldMediaID: case history.FieldMediaID:
return m.AddedMediaID() return m.AddedMediaID()
case history.FieldEpisodeID:
return m.AddedEpisodeID()
case history.FieldSeasonNum: case history.FieldSeasonNum:
return m.AddedSeasonNum() return m.AddedSeasonNum()
case history.FieldSize: case history.FieldSize:
@@ -3460,13 +3369,6 @@ func (m *HistoryMutation) AddField(name string, value ent.Value) error {
} }
m.AddMediaID(v) m.AddMediaID(v)
return nil return nil
case history.FieldEpisodeID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddEpisodeID(v)
return nil
case history.FieldSeasonNum: case history.FieldSeasonNum:
v, ok := value.(int) v, ok := value.(int)
if !ok { if !ok {
@@ -3503,9 +3405,6 @@ func (m *HistoryMutation) AddField(name string, value ent.Value) error {
// mutation. // mutation.
func (m *HistoryMutation) ClearedFields() []string { func (m *HistoryMutation) ClearedFields() []string {
var fields []string var fields []string
if m.FieldCleared(history.FieldEpisodeID) {
fields = append(fields, history.FieldEpisodeID)
}
if m.FieldCleared(history.FieldEpisodeNums) { if m.FieldCleared(history.FieldEpisodeNums) {
fields = append(fields, history.FieldEpisodeNums) fields = append(fields, history.FieldEpisodeNums)
} }
@@ -3521,8 +3420,8 @@ func (m *HistoryMutation) ClearedFields() []string {
if m.FieldCleared(history.FieldLink) { if m.FieldCleared(history.FieldLink) {
fields = append(fields, history.FieldLink) fields = append(fields, history.FieldLink)
} }
if m.FieldCleared(history.FieldSaved) { if m.FieldCleared(history.FieldHash) {
fields = append(fields, history.FieldSaved) fields = append(fields, history.FieldHash)
} }
return fields return fields
} }
@@ -3538,9 +3437,6 @@ func (m *HistoryMutation) FieldCleared(name string) bool {
// error if the field is not defined in the schema. // error if the field is not defined in the schema.
func (m *HistoryMutation) ClearField(name string) error { func (m *HistoryMutation) ClearField(name string) error {
switch name { switch name {
case history.FieldEpisodeID:
m.ClearEpisodeID()
return nil
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
m.ClearEpisodeNums() m.ClearEpisodeNums()
return nil return nil
@@ -3556,8 +3452,8 @@ func (m *HistoryMutation) ClearField(name string) error {
case history.FieldLink: case history.FieldLink:
m.ClearLink() m.ClearLink()
return nil return nil
case history.FieldSaved: case history.FieldHash:
m.ClearSaved() m.ClearHash()
return nil return nil
} }
return fmt.Errorf("unknown History nullable field %s", name) return fmt.Errorf("unknown History nullable field %s", name)
@@ -3570,9 +3466,6 @@ func (m *HistoryMutation) ResetField(name string) error {
case history.FieldMediaID: case history.FieldMediaID:
m.ResetMediaID() m.ResetMediaID()
return nil return nil
case history.FieldEpisodeID:
m.ResetEpisodeID()
return nil
case history.FieldEpisodeNums: case history.FieldEpisodeNums:
m.ResetEpisodeNums() m.ResetEpisodeNums()
return nil return nil
@@ -3600,12 +3493,12 @@ func (m *HistoryMutation) ResetField(name string) error {
case history.FieldLink: case history.FieldLink:
m.ResetLink() m.ResetLink()
return nil return nil
case history.FieldHash:
m.ResetHash()
return nil
case history.FieldStatus: case history.FieldStatus:
m.ResetStatus() m.ResetStatus()
return nil return nil
case history.FieldSaved:
m.ResetSaved()
return nil
} }
return fmt.Errorf("unknown History field %s", name) return fmt.Errorf("unknown History field %s", name)
} }
@@ -4346,6 +4239,11 @@ type IndexersMutation struct {
seed_ratio *float32 seed_ratio *float32
addseed_ratio *float32 addseed_ratio *float32
disabled *bool disabled *bool
tv_search *bool
movie_search *bool
api_key *string
url *string
synced *bool
clearedFields map[string]struct{} clearedFields map[string]struct{}
done bool done bool
oldValue func(context.Context) (*Indexers, error) oldValue func(context.Context) (*Indexers, error)
@@ -4553,9 +4451,22 @@ func (m *IndexersMutation) OldSettings(ctx context.Context) (v string, err error
return oldValue.Settings, nil return oldValue.Settings, nil
} }
// ClearSettings clears the value of the "settings" field.
func (m *IndexersMutation) ClearSettings() {
m.settings = nil
m.clearedFields[indexers.FieldSettings] = struct{}{}
}
// SettingsCleared returns if the "settings" field was cleared in this mutation.
func (m *IndexersMutation) SettingsCleared() bool {
_, ok := m.clearedFields[indexers.FieldSettings]
return ok
}
// ResetSettings resets all changes to the "settings" field. // ResetSettings resets all changes to the "settings" field.
func (m *IndexersMutation) ResetSettings() { func (m *IndexersMutation) ResetSettings() {
m.settings = nil m.settings = nil
delete(m.clearedFields, indexers.FieldSettings)
} }
// SetEnableRss sets the "enable_rss" field. // SetEnableRss sets the "enable_rss" field.
@@ -4769,6 +4680,251 @@ func (m *IndexersMutation) ResetDisabled() {
delete(m.clearedFields, indexers.FieldDisabled) delete(m.clearedFields, indexers.FieldDisabled)
} }
// SetTvSearch sets the "tv_search" field.
func (m *IndexersMutation) SetTvSearch(b bool) {
m.tv_search = &b
}
// TvSearch returns the value of the "tv_search" field in the mutation.
func (m *IndexersMutation) TvSearch() (r bool, exists bool) {
v := m.tv_search
if v == nil {
return
}
return *v, true
}
// OldTvSearch returns the old "tv_search" field's value of the Indexers entity.
// If the Indexers object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *IndexersMutation) OldTvSearch(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldTvSearch is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldTvSearch requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldTvSearch: %w", err)
}
return oldValue.TvSearch, nil
}
// ClearTvSearch clears the value of the "tv_search" field.
func (m *IndexersMutation) ClearTvSearch() {
m.tv_search = nil
m.clearedFields[indexers.FieldTvSearch] = struct{}{}
}
// TvSearchCleared returns if the "tv_search" field was cleared in this mutation.
func (m *IndexersMutation) TvSearchCleared() bool {
_, ok := m.clearedFields[indexers.FieldTvSearch]
return ok
}
// ResetTvSearch resets all changes to the "tv_search" field.
func (m *IndexersMutation) ResetTvSearch() {
m.tv_search = nil
delete(m.clearedFields, indexers.FieldTvSearch)
}
// SetMovieSearch sets the "movie_search" field.
func (m *IndexersMutation) SetMovieSearch(b bool) {
m.movie_search = &b
}
// MovieSearch returns the value of the "movie_search" field in the mutation.
func (m *IndexersMutation) MovieSearch() (r bool, exists bool) {
v := m.movie_search
if v == nil {
return
}
return *v, true
}
// OldMovieSearch returns the old "movie_search" field's value of the Indexers entity.
// If the Indexers object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *IndexersMutation) OldMovieSearch(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldMovieSearch is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldMovieSearch requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldMovieSearch: %w", err)
}
return oldValue.MovieSearch, nil
}
// ClearMovieSearch clears the value of the "movie_search" field.
func (m *IndexersMutation) ClearMovieSearch() {
m.movie_search = nil
m.clearedFields[indexers.FieldMovieSearch] = struct{}{}
}
// MovieSearchCleared returns if the "movie_search" field was cleared in this mutation.
func (m *IndexersMutation) MovieSearchCleared() bool {
_, ok := m.clearedFields[indexers.FieldMovieSearch]
return ok
}
// ResetMovieSearch resets all changes to the "movie_search" field.
func (m *IndexersMutation) ResetMovieSearch() {
m.movie_search = nil
delete(m.clearedFields, indexers.FieldMovieSearch)
}
// SetAPIKey sets the "api_key" field.
func (m *IndexersMutation) SetAPIKey(s string) {
m.api_key = &s
}
// APIKey returns the value of the "api_key" field in the mutation.
func (m *IndexersMutation) APIKey() (r string, exists bool) {
v := m.api_key
if v == nil {
return
}
return *v, true
}
// OldAPIKey returns the old "api_key" field's value of the Indexers entity.
// If the Indexers object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *IndexersMutation) OldAPIKey(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldAPIKey is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldAPIKey requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldAPIKey: %w", err)
}
return oldValue.APIKey, nil
}
// ClearAPIKey clears the value of the "api_key" field.
func (m *IndexersMutation) ClearAPIKey() {
m.api_key = nil
m.clearedFields[indexers.FieldAPIKey] = struct{}{}
}
// APIKeyCleared returns if the "api_key" field was cleared in this mutation.
func (m *IndexersMutation) APIKeyCleared() bool {
_, ok := m.clearedFields[indexers.FieldAPIKey]
return ok
}
// ResetAPIKey resets all changes to the "api_key" field.
func (m *IndexersMutation) ResetAPIKey() {
m.api_key = nil
delete(m.clearedFields, indexers.FieldAPIKey)
}
// SetURL sets the "url" field.
func (m *IndexersMutation) SetURL(s string) {
m.url = &s
}
// URL returns the value of the "url" field in the mutation.
func (m *IndexersMutation) URL() (r string, exists bool) {
v := m.url
if v == nil {
return
}
return *v, true
}
// OldURL returns the old "url" field's value of the Indexers entity.
// If the Indexers object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *IndexersMutation) OldURL(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldURL is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldURL requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldURL: %w", err)
}
return oldValue.URL, nil
}
// ClearURL clears the value of the "url" field.
func (m *IndexersMutation) ClearURL() {
m.url = nil
m.clearedFields[indexers.FieldURL] = struct{}{}
}
// URLCleared returns if the "url" field was cleared in this mutation.
func (m *IndexersMutation) URLCleared() bool {
_, ok := m.clearedFields[indexers.FieldURL]
return ok
}
// ResetURL resets all changes to the "url" field.
func (m *IndexersMutation) ResetURL() {
m.url = nil
delete(m.clearedFields, indexers.FieldURL)
}
// SetSynced sets the "synced" field.
func (m *IndexersMutation) SetSynced(b bool) {
m.synced = &b
}
// Synced returns the value of the "synced" field in the mutation.
func (m *IndexersMutation) Synced() (r bool, exists bool) {
v := m.synced
if v == nil {
return
}
return *v, true
}
// OldSynced returns the old "synced" field's value of the Indexers entity.
// If the Indexers object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *IndexersMutation) OldSynced(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSynced is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSynced requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSynced: %w", err)
}
return oldValue.Synced, nil
}
// ClearSynced clears the value of the "synced" field.
func (m *IndexersMutation) ClearSynced() {
m.synced = nil
m.clearedFields[indexers.FieldSynced] = struct{}{}
}
// SyncedCleared returns if the "synced" field was cleared in this mutation.
func (m *IndexersMutation) SyncedCleared() bool {
_, ok := m.clearedFields[indexers.FieldSynced]
return ok
}
// ResetSynced resets all changes to the "synced" field.
func (m *IndexersMutation) ResetSynced() {
m.synced = nil
delete(m.clearedFields, indexers.FieldSynced)
}
// Where appends a list predicates to the IndexersMutation builder. // Where appends a list predicates to the IndexersMutation builder.
func (m *IndexersMutation) Where(ps ...predicate.Indexers) { func (m *IndexersMutation) Where(ps ...predicate.Indexers) {
m.predicates = append(m.predicates, ps...) m.predicates = append(m.predicates, ps...)
@@ -4803,7 +4959,7 @@ func (m *IndexersMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call // order to get all numeric fields that were incremented/decremented, call
// AddedFields(). // AddedFields().
func (m *IndexersMutation) Fields() []string { func (m *IndexersMutation) Fields() []string {
fields := make([]string, 0, 7) fields := make([]string, 0, 12)
if m.name != nil { if m.name != nil {
fields = append(fields, indexers.FieldName) fields = append(fields, indexers.FieldName)
} }
@@ -4825,6 +4981,21 @@ func (m *IndexersMutation) Fields() []string {
if m.disabled != nil { if m.disabled != nil {
fields = append(fields, indexers.FieldDisabled) fields = append(fields, indexers.FieldDisabled)
} }
if m.tv_search != nil {
fields = append(fields, indexers.FieldTvSearch)
}
if m.movie_search != nil {
fields = append(fields, indexers.FieldMovieSearch)
}
if m.api_key != nil {
fields = append(fields, indexers.FieldAPIKey)
}
if m.url != nil {
fields = append(fields, indexers.FieldURL)
}
if m.synced != nil {
fields = append(fields, indexers.FieldSynced)
}
return fields return fields
} }
@@ -4847,6 +5018,16 @@ func (m *IndexersMutation) Field(name string) (ent.Value, bool) {
return m.SeedRatio() return m.SeedRatio()
case indexers.FieldDisabled: case indexers.FieldDisabled:
return m.Disabled() return m.Disabled()
case indexers.FieldTvSearch:
return m.TvSearch()
case indexers.FieldMovieSearch:
return m.MovieSearch()
case indexers.FieldAPIKey:
return m.APIKey()
case indexers.FieldURL:
return m.URL()
case indexers.FieldSynced:
return m.Synced()
} }
return nil, false return nil, false
} }
@@ -4870,6 +5051,16 @@ func (m *IndexersMutation) OldField(ctx context.Context, name string) (ent.Value
return m.OldSeedRatio(ctx) return m.OldSeedRatio(ctx)
case indexers.FieldDisabled: case indexers.FieldDisabled:
return m.OldDisabled(ctx) return m.OldDisabled(ctx)
case indexers.FieldTvSearch:
return m.OldTvSearch(ctx)
case indexers.FieldMovieSearch:
return m.OldMovieSearch(ctx)
case indexers.FieldAPIKey:
return m.OldAPIKey(ctx)
case indexers.FieldURL:
return m.OldURL(ctx)
case indexers.FieldSynced:
return m.OldSynced(ctx)
} }
return nil, fmt.Errorf("unknown Indexers field %s", name) return nil, fmt.Errorf("unknown Indexers field %s", name)
} }
@@ -4928,6 +5119,41 @@ func (m *IndexersMutation) SetField(name string, value ent.Value) error {
} }
m.SetDisabled(v) m.SetDisabled(v)
return nil return nil
case indexers.FieldTvSearch:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetTvSearch(v)
return nil
case indexers.FieldMovieSearch:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetMovieSearch(v)
return nil
case indexers.FieldAPIKey:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetAPIKey(v)
return nil
case indexers.FieldURL:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetURL(v)
return nil
case indexers.FieldSynced:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSynced(v)
return nil
} }
return fmt.Errorf("unknown Indexers field %s", name) return fmt.Errorf("unknown Indexers field %s", name)
} }
@@ -4985,12 +5211,30 @@ func (m *IndexersMutation) AddField(name string, value ent.Value) error {
// mutation. // mutation.
func (m *IndexersMutation) ClearedFields() []string { func (m *IndexersMutation) ClearedFields() []string {
var fields []string var fields []string
if m.FieldCleared(indexers.FieldSettings) {
fields = append(fields, indexers.FieldSettings)
}
if m.FieldCleared(indexers.FieldSeedRatio) { if m.FieldCleared(indexers.FieldSeedRatio) {
fields = append(fields, indexers.FieldSeedRatio) fields = append(fields, indexers.FieldSeedRatio)
} }
if m.FieldCleared(indexers.FieldDisabled) { if m.FieldCleared(indexers.FieldDisabled) {
fields = append(fields, indexers.FieldDisabled) fields = append(fields, indexers.FieldDisabled)
} }
if m.FieldCleared(indexers.FieldTvSearch) {
fields = append(fields, indexers.FieldTvSearch)
}
if m.FieldCleared(indexers.FieldMovieSearch) {
fields = append(fields, indexers.FieldMovieSearch)
}
if m.FieldCleared(indexers.FieldAPIKey) {
fields = append(fields, indexers.FieldAPIKey)
}
if m.FieldCleared(indexers.FieldURL) {
fields = append(fields, indexers.FieldURL)
}
if m.FieldCleared(indexers.FieldSynced) {
fields = append(fields, indexers.FieldSynced)
}
return fields return fields
} }
@@ -5005,12 +5249,30 @@ func (m *IndexersMutation) FieldCleared(name string) bool {
// error if the field is not defined in the schema. // error if the field is not defined in the schema.
func (m *IndexersMutation) ClearField(name string) error { func (m *IndexersMutation) ClearField(name string) error {
switch name { switch name {
case indexers.FieldSettings:
m.ClearSettings()
return nil
case indexers.FieldSeedRatio: case indexers.FieldSeedRatio:
m.ClearSeedRatio() m.ClearSeedRatio()
return nil return nil
case indexers.FieldDisabled: case indexers.FieldDisabled:
m.ClearDisabled() m.ClearDisabled()
return nil return nil
case indexers.FieldTvSearch:
m.ClearTvSearch()
return nil
case indexers.FieldMovieSearch:
m.ClearMovieSearch()
return nil
case indexers.FieldAPIKey:
m.ClearAPIKey()
return nil
case indexers.FieldURL:
m.ClearURL()
return nil
case indexers.FieldSynced:
m.ClearSynced()
return nil
} }
return fmt.Errorf("unknown Indexers nullable field %s", name) return fmt.Errorf("unknown Indexers nullable field %s", name)
} }
@@ -5040,6 +5302,21 @@ func (m *IndexersMutation) ResetField(name string) error {
case indexers.FieldDisabled: case indexers.FieldDisabled:
m.ResetDisabled() m.ResetDisabled()
return nil return nil
case indexers.FieldTvSearch:
m.ResetTvSearch()
return nil
case indexers.FieldMovieSearch:
m.ResetMovieSearch()
return nil
case indexers.FieldAPIKey:
m.ResetAPIKey()
return nil
case indexers.FieldURL:
m.ResetURL()
return nil
case indexers.FieldSynced:
m.ResetSynced()
return nil
} }
return fmt.Errorf("unknown Indexers field %s", name) return fmt.Errorf("unknown Indexers field %s", name)
} }

View File

@@ -66,11 +66,15 @@ func init() {
historyFields := schema.History{}.Fields() historyFields := schema.History{}.Fields()
_ = historyFields _ = historyFields
// historyDescSize is the schema descriptor for size field. // historyDescSize is the schema descriptor for size field.
historyDescSize := historyFields[7].Descriptor() historyDescSize := historyFields[6].Descriptor()
// history.DefaultSize holds the default value on creation for the size field. // history.DefaultSize holds the default value on creation for the size field.
history.DefaultSize = historyDescSize.Default.(int) history.DefaultSize = historyDescSize.Default.(int)
indexersFields := schema.Indexers{}.Fields() indexersFields := schema.Indexers{}.Fields()
_ = indexersFields _ = indexersFields
// indexersDescSettings is the schema descriptor for settings field.
indexersDescSettings := indexersFields[2].Descriptor()
// indexers.DefaultSettings holds the default value on creation for the settings field.
indexers.DefaultSettings = indexersDescSettings.Default.(string)
// indexersDescEnableRss is the schema descriptor for enable_rss field. // indexersDescEnableRss is the schema descriptor for enable_rss field.
indexersDescEnableRss := indexersFields[3].Descriptor() indexersDescEnableRss := indexersFields[3].Descriptor()
// indexers.DefaultEnableRss holds the default value on creation for the enable_rss field. // indexers.DefaultEnableRss holds the default value on creation for the enable_rss field.
@@ -87,6 +91,18 @@ func init() {
indexersDescDisabled := indexersFields[6].Descriptor() indexersDescDisabled := indexersFields[6].Descriptor()
// indexers.DefaultDisabled holds the default value on creation for the disabled field. // indexers.DefaultDisabled holds the default value on creation for the disabled field.
indexers.DefaultDisabled = indexersDescDisabled.Default.(bool) indexers.DefaultDisabled = indexersDescDisabled.Default.(bool)
// indexersDescTvSearch is the schema descriptor for tv_search field.
indexersDescTvSearch := indexersFields[7].Descriptor()
// indexers.DefaultTvSearch holds the default value on creation for the tv_search field.
indexers.DefaultTvSearch = indexersDescTvSearch.Default.(bool)
// indexersDescMovieSearch is the schema descriptor for movie_search field.
indexersDescMovieSearch := indexersFields[8].Descriptor()
// indexers.DefaultMovieSearch holds the default value on creation for the movie_search field.
indexers.DefaultMovieSearch = indexersDescMovieSearch.Default.(bool)
// indexersDescSynced is the schema descriptor for synced field.
indexersDescSynced := indexersFields[11].Descriptor()
// indexers.DefaultSynced holds the default value on creation for the synced field.
indexers.DefaultSynced = indexersDescSynced.Default.(bool)
mediaFields := schema.Media{}.Fields() mediaFields := schema.Media{}.Fields()
_ = mediaFields _ = mediaFields
// mediaDescCreatedAt is the schema descriptor for created_at field. // mediaDescCreatedAt is the schema descriptor for created_at field.

View File

@@ -17,7 +17,7 @@ func (DownloadClients) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.Bool("enable"), field.Bool("enable"),
field.String("name"), field.String("name"),
field.Enum("implementation").Values("transmission", "qbittorrent"), field.Enum("implementation").Values("transmission", "qbittorrent", "buildin"),
field.String("url"), field.String("url"),
field.String("user").Default(""), field.String("user").Default(""),
field.String("password").Default(""), field.String("password").Default(""),

View File

@@ -14,7 +14,7 @@ type History struct {
func (History) Fields() []ent.Field { func (History) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.Int("media_id"), field.Int("media_id"),
field.Int("episode_id").Optional().Comment("deprecated"), //field.Int("episode_id").Optional().Comment("deprecated"),
field.Ints("episode_nums").Optional(), field.Ints("episode_nums").Optional(),
field.Int("season_num").Optional(), field.Int("season_num").Optional(),
field.String("source_title"), field.String("source_title"),
@@ -23,9 +23,10 @@ func (History) Fields() []ent.Field {
field.Int("size").Default(0), field.Int("size").Default(0),
field.Int("download_client_id").Optional(), field.Int("download_client_id").Optional(),
field.Int("indexer_id").Optional(), field.Int("indexer_id").Optional(),
field.String("link").Optional(), //should be magnet link field.String("link").Optional().Comment("deprecated, use hash instead"), //should be magnet link
field.Enum("status").Values("running", "success", "fail", "uploading", "seeding"), field.String("hash").Optional().Comment("torrent hash"),
field.String("saved").Optional().Comment("deprecated"), //deprecated field.Enum("status").Values("running", "success", "fail", "uploading", "seeding", "removed"),
//field.String("saved").Optional().Comment("deprecated"), //deprecated
} }
} }

View File

@@ -15,11 +15,16 @@ func (Indexers) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.String("name"), field.String("name"),
field.String("implementation"), field.String("implementation"),
field.String("settings"), field.String("settings").Optional().Default("").Comment("deprecated, use api_key and url"),
field.Bool("enable_rss").Default(true), field.Bool("enable_rss").Default(true),
field.Int("priority").Default(50), field.Int("priority").Default(50),
field.Float32("seed_ratio").Optional().Default(0).Comment("minimal seed ratio requied, before removing torrent"), field.Float32("seed_ratio").Optional().Default(0).Comment("minimal seed ratio requied, before removing torrent"),
field.Bool("disabled").Optional().Default(false), field.Bool("disabled").Optional().Default(false),
field.Bool("tv_search").Optional().Default(true),
field.Bool("movie_search").Optional().Default(true),
field.String("api_key").Optional(),
field.String("url").Optional(),
field.Bool("synced").Optional().Default(false).Comment("synced from prowlarr"),
} }
} }

97
go.mod
View File

@@ -1,23 +1,24 @@
module polaris module polaris
go 1.23 go 1.23.0
toolchain go1.23.1 toolchain go1.24.1
require ( require (
entgo.io/ent v0.13.1 entgo.io/ent v0.13.1
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.2
github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/robfig/cron v1.2.0 github.com/robfig/cron v1.2.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/net v0.33.0 golang.org/x/net v0.37.0
) )
require ( require (
github.com/PuerkitoBio/goquery v1.9.2 github.com/PuerkitoBio/goquery v1.10.1
github.com/anacrolix/torrent v1.57.1 github.com/anacrolix/torrent v1.58.1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/gin-contrib/zap v1.1.3 github.com/gin-contrib/zap v1.1.3
github.com/gocolly/colly v1.2.0
github.com/ncruces/go-sqlite3 v0.18.4 github.com/ncruces/go-sqlite3 v0.18.4
github.com/nikoksr/notify v1.0.0 github.com/nikoksr/notify v1.0.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
@@ -27,30 +28,100 @@ require (
require ( require (
github.com/BurntSushi/toml v1.4.0 // indirect github.com/BurntSushi/toml v1.4.0 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8 // indirect
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 // indirect
github.com/anacrolix/envpprof v1.3.0 // indirect
github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca // indirect github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca // indirect
github.com/anacrolix/go-libutp v1.3.2 // indirect
github.com/anacrolix/log v0.15.3-0.20240627045001-cd912c641d83 // indirect
github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
github.com/anacrolix/missinggo/v2 v2.7.4 // indirect github.com/anacrolix/missinggo/v2 v2.7.4 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect github.com/anacrolix/mmsg v1.0.1 // indirect
github.com/anacrolix/multiless v0.4.0 // indirect
github.com/anacrolix/stm v0.4.0 // indirect
github.com/anacrolix/sync v0.5.1 // indirect
github.com/anacrolix/upnp v0.1.4 // indirect
github.com/anacrolix/utp v0.1.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/antchfx/htmlquery v1.3.4 // indirect
github.com/antchfx/xmlquery v1.4.4 // indirect
github.com/antchfx/xpath v1.3.3 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/benbjohnson/immutable v0.3.0 // indirect
github.com/bits-and-blooms/bitset v1.2.2 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 // indirect
github.com/go-llsqlite/crawshaw v0.5.2-0.20240425034140-f30eb7704568 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-test/deep v1.0.4 // indirect github.com/go-test/deep v1.0.4 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gregdel/pushover v1.3.1 // indirect github.com/gregdel/pushover v1.3.1 // indirect
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect github.com/multiformats/go-varint v0.0.6 // indirect
github.com/ncruces/julianday v1.0.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect
github.com/pion/datachannel v1.5.9 // indirect
github.com/pion/dtls/v3 v3.0.3 // indirect
github.com/pion/ice/v4 v4.0.2 // indirect
github.com/pion/interceptor v0.1.37 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.14 // indirect
github.com/pion/rtp v1.8.9 // indirect
github.com/pion/sctp v1.8.33 // indirect
github.com/pion/sdp/v3 v3.0.9 // indirect
github.com/pion/srtp/v3 v3.0.4 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.0 // indirect
github.com/pion/webrtc/v4 v4.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/protolambda/ctxlock v0.1.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect github.com/tetratelabs/wazero v1.8.0 // indirect
golang.org/x/sync v0.10.0 // indirect github.com/tidwall/btree v1.6.0 // indirect
github.com/wlynxg/anet v0.0.3 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
lukechampine.com/blake3 v1.1.6 // indirect lukechampine.com/blake3 v1.1.6 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.21.1 // indirect
zombiezen.com/go/sqlite v0.13.1 // indirect
) )
require ( require (
@@ -95,12 +166,12 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect github.com/zclconf/go-cty v1.8.0 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
golang.org/x/mod v0.20.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/sys v0.28.0 golang.org/x/sys v0.31.0
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

283
go.sum
View File

@@ -6,58 +6,114 @@ crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oX
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o=
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8 h1:eyb0bBaQKMOh5Se/Qg54shijc8K4zpQiOjEhKFADkQM=
github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE=
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew= github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew=
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk=
github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca h1:aiiGqSQWjtVNdi8zUMfA//IrM8fPkv2bWwZVPbDe0wg= github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca h1:aiiGqSQWjtVNdi8zUMfA//IrM8fPkv2bWwZVPbDe0wg=
github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca/go.mod h1:MN3ve08Z3zSV/rTuX/ouI4lNdlfTxgdafQJiLzyNRB8= github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca/go.mod h1:MN3ve08Z3zSV/rTuX/ouI4lNdlfTxgdafQJiLzyNRB8=
github.com/anacrolix/go-libutp v1.3.2 h1:WswiaxTIogchbkzNgGHuHRfbrYLpv4o290mlvcx+++M=
github.com/anacrolix/go-libutp v1.3.2/go.mod h1:fCUiEnXJSe3jsPG554A200Qv+45ZzIIyGEvE56SHmyA=
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
github.com/anacrolix/log v0.15.3-0.20240627045001-cd912c641d83 h1:9o/yVzzLzYaBDFx8B27yhkvBLhNnRAuSTK7Y+yZKVtU=
github.com/anacrolix/log v0.15.3-0.20240627045001-cd912c641d83/go.mod h1:xvHjsYWWP7yO8PZwtuIp/k0DBlu07pSJqH4SEC78Vwc=
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y= github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y=
github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw= github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw=
github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc= github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc=
github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw=
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
github.com/anacrolix/missinggo/v2 v2.7.4 h1:47h5OXoPV8JbA/ACA+FLwKdYbAinuDO8osc2Cu9xkxg= github.com/anacrolix/missinggo/v2 v2.7.4 h1:47h5OXoPV8JbA/ACA+FLwKdYbAinuDO8osc2Cu9xkxg=
github.com/anacrolix/missinggo/v2 v2.7.4/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0= github.com/anacrolix/missinggo/v2 v2.7.4/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0=
github.com/anacrolix/mmsg v1.0.1 h1:TxfpV7kX70m3f/O7ielL/2I3OFkMPjrRCPo7+4X5AWw=
github.com/anacrolix/mmsg v1.0.1/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
github.com/anacrolix/multiless v0.4.0 h1:lqSszHkliMsZd2hsyrDvHOw4AbYWa+ijQ66LzbjqWjM=
github.com/anacrolix/multiless v0.4.0/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM=
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY=
github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8=
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
github.com/anacrolix/sync v0.5.1 h1:FbGju6GqSjzVoTgcXTUKkF041lnZkG5P0C3T5RL3SGc=
github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
github.com/anacrolix/torrent v1.57.1 h1:CS8rYfC2Oe15NPBhwCNs/3WBY6HiBCPDFpY+s9aFHbA= github.com/anacrolix/torrent v1.58.1 h1:6FP+KH57b1gyT2CpVL9fEqf9MGJEgh3xw1VA8rI0pW8=
github.com/anacrolix/torrent v1.57.1/go.mod h1:NNBg4lP2/us9Hp5+cLNcZRILM69cNoKIkqMGqr9AuR0= github.com/anacrolix/torrent v1.58.1/go.mod h1:/7ZdLuHNKgtCE1gjYJCfbtG9JodBcDaF5ip5EUWRtk8=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM=
github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=
github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc=
github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
github.com/benbjohnson/immutable v0.3.0 h1:TVRhuZx2wG9SZ0LRdqlbs9S5BZ6Y24hJEHTCgWHZEIw=
github.com/benbjohnson/immutable v0.3.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk=
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4= github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto= github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
@@ -68,12 +124,15 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyruzin/golang-tmdb v1.6.3 h1:TKK9h+uuwiDOaFlsVispG1KxqhsSM5Y4ZELnUF3GlqU= github.com/cyruzin/golang-tmdb v1.6.3 h1:TKK9h+uuwiDOaFlsVispG1KxqhsSM5Y4ZELnUF3GlqU=
github.com/cyruzin/golang-tmdb v1.6.3/go.mod h1:ZSryJLCcY+9TiKU+LbouXKns++YBrM8Tizannr05c+I= github.com/cyruzin/golang-tmdb v1.6.3/go.mod h1:ZSryJLCcY+9TiKU+LbouXKns++YBrM8Tizannr05c+I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -82,10 +141,15 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -109,8 +173,17 @@ github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1T
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 h1:OyQmpAN302wAopDgwVjgs2HkFawP9ahIEqkUYz7V7CA=
github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916/go.mod h1:DADrR88ONKPPeSGjFp5iEN55Arx3fi2qXZeKCYDpbmU=
github.com/go-llsqlite/crawshaw v0.5.2-0.20240425034140-f30eb7704568 h1:3EpZo8LxIzF4q3BT+vttQQlRfA6uTtTb/cxVisWa5HM=
github.com/go-llsqlite/crawshaw v0.5.2-0.20240425034140-f30eb7704568/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -121,20 +194,29 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -145,14 +227,22 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -164,6 +254,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORR
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregdel/pushover v1.3.1 h1:4bMLITOZ15+Zpi6qqoGqOPuVHCwSUvMCgVnN5Xhilfo= github.com/gregdel/pushover v1.3.1 h1:4bMLITOZ15+Zpi6qqoGqOPuVHCwSUvMCgVnN5Xhilfo=
github.com/gregdel/pushover v1.3.1/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to= github.com/gregdel/pushover v1.3.1/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@@ -177,12 +269,17 @@ github.com/hekmon/cunits/v2 v2.1.0 h1:k6wIjc4PlacNOHwKEMBgWV2/c8jyD4eRMs5mR1BBhI
github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M= github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M=
github.com/hekmon/transmissionrpc/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ= github.com/hekmon/transmissionrpc/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ=
github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg= github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -192,6 +289,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -202,6 +301,8 @@ github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgSh
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -216,6 +317,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -235,6 +338,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
@@ -249,6 +353,8 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/nikoksr/notify v1.0.0 h1:qe9/6FRsWdxBgQgWcpvQ0sv8LRGJZDpRB4TkL2uNdO8= github.com/nikoksr/notify v1.0.0 h1:qe9/6FRsWdxBgQgWcpvQ0sv8LRGJZDpRB4TkL2uNdO8=
github.com/nikoksr/notify v1.0.0/go.mod h1:hPaaDt30d6LAA7/5nb0e48Bp/MctDfycCSs8VEgN29I= github.com/nikoksr/notify v1.0.0/go.mod h1:hPaaDt30d6LAA7/5nb0e48Bp/MctDfycCSs8VEgN29I=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -257,6 +363,39 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA=
github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE=
github.com/pion/dtls/v3 v3.0.3 h1:j5ajZbQwff7Z8k3pE3S+rQ4STvKvXUdKsi/07ka+OWM=
github.com/pion/dtls/v3 v3.0.3/go.mod h1:weOTUyIV4z0bQaVzKe8kpaP17+us3yAuiQsEAG1STMU=
github.com/pion/ice/v4 v4.0.2 h1:1JhBRX8iQLi0+TfcavTjPjI6GO41MFn4CeTBX+Y9h5s=
github.com/pion/ice/v4 v4.0.2/go.mod h1:DCdqyzgtsDNYN6/3U8044j3U7qsJ9KFJC92VnOWHvXg=
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw=
github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM=
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/webrtc/v4 v4.0.0 h1:x8ec7uJQPP3D1iI8ojPAiTOylPI7Fa7QgqZrhpLyqZ8=
github.com/pion/webrtc/v4 v4.0.0/go.mod h1:SfNn8CcFxR6OUVjLXVslAQ3a3994JhyE3Hw1jAuqEto=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -280,16 +419,29 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/protolambda/ctxlock v0.1.0 h1:rCUY3+vRdcdZXqT07iXgyr744J2DU2LCBIXowYAjBCE=
github.com/protolambda/ctxlock v0.1.0/go.mod h1:vefhX6rIZH8rsg5ZpOJfEDYQOppZi19SfPiGOFrNnwM=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -300,12 +452,15 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:X
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
@@ -331,8 +486,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -344,12 +503,23 @@ github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg=
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -361,20 +531,32 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -383,16 +565,23 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -401,43 +590,71 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -445,16 +662,27 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golift.io/starr v1.0.0 h1:IDSaSL+ZYxdLT/Lg//dg/iwZ39LHO3D5CmbLCOgSXbI= golift.io/starr v1.0.0 h1:IDSaSL+ZYxdLT/Lg//dg/iwZ39LHO3D5CmbLCOgSXbI=
golift.io/starr v1.0.0/go.mod h1:xnUwp4vK62bDvozW9QHUYc08m6kjwaZnGw3Db65fQHw= golift.io/starr v1.0.0/go.mod h1:xnUwp4vK62bDvozW9QHUYc08m6kjwaZnGw3Db65fQHw=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -466,14 +694,17 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -493,5 +724,15 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c=
lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU=
modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
zombiezen.com/go/sqlite v0.13.1 h1:qDzxyWWmMtSSEH5qxamqBFmqA2BLSSbtODi3ojaE02o=
zombiezen.com/go/sqlite v0.13.1/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4=

View File

@@ -15,12 +15,12 @@ var atom zap.AtomicLevel
const dataPath = "./data" const dataPath = "./data"
func init() { func InitLogger(toFile bool) {
atom = zap.NewAtomicLevel() atom = zap.NewAtomicLevel()
atom.SetLevel(zap.DebugLevel) atom.SetLevel(zap.DebugLevel)
w := zapcore.Lock(os.Stdout) w := zapcore.Lock(os.Stdout)
if os.Getenv("GIN_MODE") == "release" { if toFile {
w = zapcore.AddSync(&lumberjack.Logger{ w = zapcore.AddSync(&lumberjack.Logger{
Filename: filepath.Join(dataPath, "logs", "polaris.log"), Filename: filepath.Join(dataPath, "logs", "polaris.log"),
MaxSize: 50, // megabytes MaxSize: 50, // megabytes

203
pkg/buildin/torrent.go Normal file
View File

@@ -0,0 +1,203 @@
package buildin
import (
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"polaris/log"
"polaris/pkg"
"strings"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
"github.com/pkg/errors"
)
func NewDownloader(downloadDir string) (*Downloader, error) {
cfg := torrent.NewDefaultClientConfig()
cfg.DataDir = downloadDir
cfg.ListenPort = 51243
t, err := torrent.NewClient(cfg)
if err != nil {
return nil, errors.Wrapf(err, "create torrent client")
}
return &Downloader{cl: t, dir: downloadDir}, nil
}
type Downloader struct {
cl *torrent.Client
dir string
}
func (d *Downloader) GetAll() ([]pkg.Torrent, error) {
ts := d.cl.Torrents()
var res []pkg.Torrent
for _, t := range ts {
res = append(res, &Torrent{
t: t,
cl: d.cl,
hash: t.InfoHash().HexString(),
})
}
return res, nil
}
func (d *Downloader) Download(link, hash, dir string) (pkg.Torrent, error) {
if strings.HasPrefix(strings.ToLower(link), "magnet:") {
t, err := d.cl.AddMagnet(link)
if err != nil {
return nil, fmt.Errorf("failed to add magnet: %v", err)
}
<-t.GotInfo()
return &Torrent{
t: t,
cl: d.cl,
hash: hash,
dir: d.dir,
}, nil
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse //do not follow redirects
},
}
resp, err := client.Get(link)
if err != nil {
return nil, errors.Wrap(err, "get link")
}
defer resp.Body.Close()
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
//redirects
tourl := resp.Header.Get("Location")
return d.Download(tourl, hash, dir)
}
info, err := metainfo.Load(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to load metainfo: %v", err)
}
t, err := d.cl.AddTorrent(info)
if err != nil {
return nil, fmt.Errorf("failed to add torrent: %v", err)
}
<-t.GotInfo()
return &Torrent{
t: t,
cl: d.cl,
hash: hash,
dir: d.dir,
}, nil
}
func NewTorrentFromHash(hash string, downloadDir string) (*Torrent, error) {
cl, err := NewDownloader(downloadDir)
if err != nil {
return nil, errors.Wrap(err, "create downloader")
}
ttt := cl.cl.Torrents()
log.Infof("all torrents: %+v", ttt)
t, _ := cl.cl.AddTorrentInfoHash(metainfo.NewHashFromHex(hash))
// if new {
// return nil, fmt.Errorf("torrent not found")
// }
<-t.GotInfo()
return &Torrent{
t: t,
cl: cl.cl,
hash: hash,
dir: downloadDir,
}, nil
}
type Torrent struct {
t *torrent.Torrent
cl *torrent.Client
hash string
dir string
}
func (t *Torrent) Name() (string, error) {
return t.t.Name(), nil
}
func (t *Torrent) TotalSize() int64 {
var c int64
for _, f := range t.t.Files() {
c += f.FileInfo().Length
}
return c
}
func (t *Torrent) Progress() (int, error) {
if t.t.Complete().Bool() {
return 100, nil
}
p := int(t.t.BytesCompleted() * 100 / t.TotalSize())
if p >= 100 {
p = 99
}
return 99, nil
}
func (t *Torrent) Stop() error {
return nil
}
func (t *Torrent) Start() error {
<-t.t.GotInfo()
t.t.DownloadAll()
return nil
}
func (t *Torrent) Remove() error {
files := t.t.Files()
for _, file := range files {
name := file.Path()
if err := os.RemoveAll(filepath.Join(t.dir, name)); err != nil {
return errors.Errorf("remove file (%s) error: %v", file.Path(), err)
}
}
t.t.Drop()
return nil
}
func (t *Torrent) Exists() bool {
tors := t.cl.Torrents()
for _, to := range tors {
if to.InfoHash().HexString() == t.hash {
return true
}
}
return false
}
func (t *Torrent) SeedRatio() (float64, error) {
return 0, nil
}
func (t *Torrent) GetHash() string {
return t.hash
}
func (t *Torrent) WalkFunc() func(fn func(path string, info fs.FileInfo) error) error {
files := t.t.Files()
return func(fn func(path string, info fs.FileInfo) error) error {
for _, file := range files {
name := filepath.Join(t.dir, file.Path())
info, err := os.Stat(name)
if err != nil {
return err
}
if err := fn(name, info); err != nil {
return errors.Errorf("proccess file (%s) error: %v", file.Path(), err)
}
}
return nil
}
}

View File

@@ -17,7 +17,7 @@ type TMDB struct {
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
viper.SetConfigName("config") // name of config file (without extension) viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yml") // REQUIRED if the config file does not have the extension in the name viper.SetConfigType("yml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath(".") viper.AddConfigPath(".")
viper.AddConfigPath("/app/data") viper.AddConfigPath("/app/data")

View File

@@ -1,5 +1,7 @@
package pkg package pkg
import "io/fs"
type Torrent interface { type Torrent interface {
Name() (string, error) Name() (string, error)
Progress() (int, error) Progress() (int, error)
@@ -11,10 +13,10 @@ type Torrent interface {
SeedRatio() (float64, error) SeedRatio() (float64, error)
GetHash() string GetHash() string
//Reload() error //Reload() error
WalkFunc() func(fn func(path string, info fs.FileInfo) error) error
} }
type Downloader interface { type Downloader interface {
GetAll() ([]Torrent, error) GetAll() ([]Torrent, error)
Download(link, dir string) (Torrent, error) Download(link, hash, dir string) (Torrent, error)
} }

83
pkg/eventbus/eventbus.go Normal file
View File

@@ -0,0 +1,83 @@
package eventbus
import (
"fmt"
"polaris/pkg/utils"
"reflect"
)
type EventBus struct {
handlers utils.Map[string, []EventHandler]
}
type EventHandler struct {
callback reflect.Value
async bool
}
func New() *EventBus {
return &EventBus{
handlers: utils.Map[string, []EventHandler]{},
}
}
func (e *EventBus) Subscribe(event string, fn any) error{
if reflect.TypeOf(fn).Kind() != reflect.Func {
return fmt.Errorf("handler must be a function: %v", reflect.TypeOf(fn).Kind())
}
if handlers, ok := e.handlers.Load(event); ok {
handlers = append(handlers, EventHandler{
callback: reflect.ValueOf(fn),})
e.handlers.Store(event, handlers)
} else {
e.handlers.Store(event, []EventHandler{
{callback: reflect.ValueOf(fn)},
})
}
return nil
}
func (e *EventBus) SubscribeAsync(event string, fn any) error{
if reflect.TypeOf(fn).Kind() != reflect.Func {
return fmt.Errorf("handler must be a function: %v", reflect.TypeOf(fn).Kind())
}
if handlers, ok := e.handlers.Load(event); ok {
handlers = append(handlers, EventHandler{
callback: reflect.ValueOf(fn), async: true,
})
e.handlers.Store(event, handlers)
} else {
e.handlers.Store(event, []EventHandler{
{callback: reflect.ValueOf(fn), async: true},
})
}
return nil
}
func (e *EventBus) Publish(event string, args... any) {
if handlers, ok := e.handlers.Load(event); ok {
for _, handler := range handlers {
args1 := reflectArgs(handler,args...)
if handler.async {
go handler.callback.Call(args1)
} else {
handler.callback.Call(args1)
}
}
}
}
func reflectArgs(handler EventHandler,args... any) []reflect.Value {
funcType := handler.callback.Type()
passedArguments := make([]reflect.Value, len(args))
for i, v := range args {
if v == nil {
passedArguments[i] = reflect.New(funcType.In(i)).Elem()
} else {
passedArguments[i] = reflect.ValueOf(v)
}
}
return passedArguments
}

View File

@@ -2,29 +2,25 @@ package douban
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"polaris/log" "polaris/log"
"polaris/pkg/importlist" "polaris/pkg/importlist"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/gocolly/colly"
) )
const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
func ParseDoulist(doulistUrl string) (*importlist.Response, error) { func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
if !strings.Contains(doulistUrl, "doulist") { if !strings.Contains(doulistUrl, "doulist") {
return nil, fmt.Errorf("not doulist") return nil, fmt.Errorf("not doulist")
} }
res, err := doHttpReq("GET", doulistUrl, nil)
req, err := http.NewRequest("GET", doulistUrl, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", ua)
res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -85,13 +81,8 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
func parseDetailPage(url string) (string, error) { func parseDetailPage(url string) (string, error) {
println(url) println(url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", ua)
res, err := http.DefaultClient.Do(req) res, err := doHttpReq("GET", url, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -115,3 +106,80 @@ func parseDetailPage(url string) (string, error) {
_ = doc _ = doc
return "", nil return "", nil
} }
func NewDoubanWishlist(personId string) *DoubanWishlist {
return &DoubanWishlist{PersonId: personId}
}
type DoubanWishlist struct {
PersonId string
}
const wishlistUrl = "https://movie.douban.com/people/%s/wish?sort=time&start=%d&mode=grid&tags_sort=count"
func (d *DoubanWishlist) GetWishlist(page int) (*importlist.Response, error) {
c := colly.NewCollector(colly.UserAgent(ua))
c.Limit(&colly.LimitRule{
DomainRegexp: "*",
Delay: 10 * time.Second,
RandomDelay: 2 * time.Second,
})
url := fmt.Sprintf(wishlistUrl, d.PersonId, (page-1)*15)
c.OnHTML("div[class='item comment-item']", func(e *colly.HTMLElement) {
if !strings.HasPrefix(e.Request.URL.String(), "https://movie.douban.com/people") {
return
}
e.DOM.Find("div[class='pic'] a[title]").Each(func(i int, selection *goquery.Selection) {
println(selection.Attr("href"))
url, ok := selection.Attr("href")
if ok {
c.Visit(url)
}
})
})
c.OnHTML("#content", func(h *colly.HTMLElement) {
var item importlist.Item
h.DOM.Find("h1").Each(func(i int, selection *goquery.Selection) {
selection.Find("span[property]").Each(func(i int, selection *goquery.Selection) {
println(selection.Text())
item.Title = selection.Text()
})
selection.Find("span[class='year']").Each(func(i int, selection *goquery.Selection) {
n, _ := strconv.Atoi(selection.Text())
item.Year = n
})
})
h.DOM.Find("#info").Each(func(i int, s *goquery.Selection) {
info := strings.TrimSpace(s.Text())
lines := strings.Split(info, "\n")
if len(lines) == 0 {
return
}
last := lines[len(lines)-1]
if !strings.HasPrefix(strings.ToLower(last), "imdb") {
return
}
ss := strings.Split(last, ":")
for _, p := range ss {
p := strings.TrimSpace(strings.ToLower(p))
if strings.HasPrefix(p, "tt") {
item.ImdbID = p
}
}
})
log.Info(item)
})
return nil, c.Visit(url)
}
func doHttpReq(method, url string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", ua)
return http.DefaultClient.Do(req)
}

View File

@@ -9,3 +9,10 @@ func TestParseDoulist(t *testing.T) {
r, err := ParseDoulist("https://www.douban.com/doulist/81580/") r, err := ParseDoulist("https://www.douban.com/doulist/81580/")
log.Info(r, err) log.Info(r, err)
} }
func Test111(t *testing.T) {
d := NewDoubanWishlist("69894889")
_, err := d.GetWishlist(1)
log.Infof("err: %v", err)
}

View File

@@ -0,0 +1,15 @@
package notifier
import "testing"
func TestServerChan(t *testing.T) {
s, err := NewServerChanClient(``)
if err != nil {
t.Error(err)
return
}
err = s.SendMsg("test")
if err != nil {
t.Error(err)
}
}

View File

@@ -30,6 +30,7 @@ func init() {
handler.Store("dingtalk", NewDingTalkClient) handler.Store("dingtalk", NewDingTalkClient)
handler.Store("telegram", NewTelegramClient) handler.Store("telegram", NewTelegramClient)
handler.Store("bark", NewbarkClient) handler.Store("bark", NewbarkClient)
handler.Store("serverchan", NewServerChanClient)
} }
func Gethandler(name string) (HandlerFunc, bool) { func Gethandler(name string) (HandlerFunc, bool) {

View File

@@ -0,0 +1,89 @@
package notifier
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/pkg/errors"
)
type ServerChanConfig struct {
Key string `json:"key"`
}
func NewServerChanClient(s string) (NotificationClient, error) {
var cfg ServerChanConfig
if err := json.Unmarshal([]byte(s), &cfg); err != nil {
return nil, errors.Wrap(err, "json")
}
return &ServerChan{Key: cfg.Key}, nil
}
type ServerChan struct {
Key string
}
func (s *ServerChan) SendMsg(msg string) error {
return scSend("Polaris", msg, s.Key)
}
func scSend(text string, desp string, key string) error {
data := url.Values{}
data.Set("text", text)
data.Set("desp", desp)
// 根据 sendkey 是否以 "sctp" 开头决定 API 的 URL
var apiUrl string
if strings.HasPrefix(key, "sctp") {
// 使用正则表达式提取数字部分
re := regexp.MustCompile(`sctp(\d+)t`)
matches := re.FindStringSubmatch(key)
if len(matches) > 1 {
num := matches[1]
apiUrl = fmt.Sprintf("https://%s.push.ft07.com/send/%s.send", num, key)
} else {
return errors.New("invalid sendkey format for sctp")
}
} else {
apiUrl = fmt.Sprintf("https://sctapi.ftqq.com/%s.send", key)
}
client := &http.Client{}
req, err := http.NewRequest("POST", apiUrl, strings.NewReader(data.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
d, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var r response
if err := json.Unmarshal(d, &r); err != nil {
return err
}
if r.Code != 0 {
return errors.New(r.Message)
}
return nil
}
type response struct {
Code int `json:"code"`
Message string `json:"message"`
}

View File

@@ -31,26 +31,28 @@ func New(apiKey, url string) *Client {
return &Client{p: p, apiKey: apiKey, url: url} return &Client{p: p, apiKey: apiKey, url: url}
} }
func (c *Client) GetIndexers(t ProwlarrSupportType) ([]*db.TorznabInfo, error) { func (c *Client) GetIndexers() ([]*ent.Indexers, error) {
ins, err := c.p.GetIndexers() ins, err := c.p.GetIndexers()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var indexers []*db.TorznabInfo var indexers []*ent.Indexers
for _, in := range ins { for _, in := range ins {
if !in.Enable {
continue tvSearch := true
movieSearch := true
if len(in.Capabilities.TvSearchParams) == 0 { //no tv resource in this indexer
tvSearch = false
} }
if t == "tv" && len(in.Capabilities.TvSearchParams) == 0 { //no tv resource in this indexer if len(in.Capabilities.MovieSearchParams) == 0 { //no movie resource in this indexer
continue movieSearch = false
} else if t == "movie" && len(in.Capabilities.MovieSearchParams) == 0 { //no movie resource in this indexer
continue
} }
seedRatio := 0.0 seedRatio := 0.0
for _, f := range in.Fields { for _, f := range in.Fields {
if f.Name == "torrentBaseSettings.seedRatio" && f.Value != nil { if f.Name == "torrentBaseSettings.seedRatio" && f.Value != nil {
if r, ok := f.Value.(float64); ok { if r, ok := f.Value.(float64); ok {
seedRatio = r seedRatio = r
break
} }
} }
} }
@@ -61,17 +63,18 @@ func (c *Client) GetIndexers(t ProwlarrSupportType) ([]*db.TorznabInfo, error) {
data, _ := json.Marshal(&setting) data, _ := json.Marshal(&setting)
entIndexer := ent.Indexers{ entIndexer := ent.Indexers{
Disabled: !in.Enable,
Name: in.Name, Name: in.Name,
Implementation: "torznab", Implementation: "torznab",
Priority: 128 - int(in.Priority), Priority: 128 - int(in.Priority),
SeedRatio: float32(seedRatio), SeedRatio: float32(seedRatio),
Settings: string(data), Settings: string(data),
TvSearch: tvSearch,
MovieSearch: movieSearch,
APIKey: c.apiKey,
URL: fmt.Sprintf("%s/%d/api", strings.TrimSuffix(c.url, "/"), in.ID),
} }
indexers = append(indexers, &entIndexer)
indexers = append(indexers, &db.TorznabInfo{
Indexers: &entIndexer,
TorznabSetting: setting,
})
} }
return indexers, nil return indexers, nil
} }

View File

@@ -7,7 +7,7 @@ import (
func Test111(t *testing.T) { func Test111(t *testing.T) {
c := New("", "http://10.0.0.8:9696/") c := New("", "http://10.0.0.8:9696/")
apis , err := c.GetIndexers("tv") apis , err := c.GetIndexers()
log.Infof("errors: %v", err) log.Infof("errors: %v", err)
log.Infof("indexers: %+v", apis[0]) log.Infof("indexers: %+v", apis[0])
} }

View File

@@ -2,7 +2,9 @@ package qbittorrent
import ( import (
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath"
"polaris/pkg" "polaris/pkg"
"polaris/pkg/go-qbittorrent/qbt" "polaris/pkg/go-qbittorrent/qbt"
"polaris/pkg/utils" "polaris/pkg/utils"
@@ -59,17 +61,8 @@ func (c *Client) GetAll() ([]pkg.Torrent, error) {
return res, nil return res, nil
} }
func (c *Client) Download(link, dir string) (pkg.Torrent, error) { func (c *Client) Download(link, hash, dir string) (pkg.Torrent, error) {
magnet, err := utils.Link2Magnet(link) err := c.c.DownloadLinks([]string{link}, qbt.DownloadOptions{Savepath: &dir, Category: &c.category})
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
}
hash, err := utils.MagnetHash(magnet)
if err != nil {
return nil, errors.Wrap(err, "get hash")
}
err = c.c.DownloadLinks([]string{magnet}, qbt.DownloadOptions{Savepath: &dir, Category: &c.category})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "qbt download") return nil, errors.Wrap(err, "qbt download")
} }
@@ -77,11 +70,23 @@ func (c *Client) Download(link, dir string) (pkg.Torrent, error) {
} }
func NewTorrent(info Info, link string) (*Torrent, error) { func NewTorrentHash(info Info, hash string) (*Torrent, error) {
c, err := NewClient(info.URL, info.User, info.Password) c, err := NewClient(info.URL, info.User, info.Password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t := &Torrent{
c: c.c,
hash: hash,
}
if !t.Exists() {
return nil, errors.Errorf("torrent not exist: %v", hash)
}
return t, nil
}
func NewTorrent(info Info, link string) (*Torrent, error) {
magnet, err := utils.Link2Magnet(link) magnet, err := utils.Link2Magnet(link)
if err != nil { if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err) return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
@@ -91,14 +96,8 @@ func NewTorrent(info Info, link string) (*Torrent, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
t := &Torrent{
c: c.c, return NewTorrentHash(info, hash)
hash: hash,
}
if !t.Exists() {
return nil, errors.Errorf("torrent not exist: %v", magnet)
}
return t, nil
} }
type Torrent struct { type Torrent struct {
@@ -202,3 +201,49 @@ func (t *Torrent) SeedRatio() (float64, error) {
} }
return qb.Ratio, nil return qb.Ratio, nil
} }
func (t *Torrent) Walk(f func(string) error) error {
files, err := t.c.TorrentFiles(t.hash)
if err != nil {
return err
}
for _, file := range files {
if err := f(file.Name); err != nil {
return errors.Errorf("proccess file (%s) error: %v", file.Name, err)
}
}
return nil
}
func (t *Torrent) WalkFunc() func(fn func(path string, info fs.FileInfo) error) error {
files, err := t.c.TorrentFiles(t.hash)
if err != nil {
return func(fn func(path string, info fs.FileInfo) error) error {
return err
}
}
path, err := t.c.DefaultSavePath()
if err != nil {
return func(fn func(path string, info fs.FileInfo) error) error {
return err
}
}
return func(fn func(path string, info fs.FileInfo) error) error {
for _, file := range files {
name := filepath.Join(path, file.Name)
info, err := os.Stat(name)
if err != nil {
return err
}
if err := fn(name, info); err != nil {
return errors.Errorf("proccess file (%s) error: %v", file.Name, err)
}
}
return nil
}
}

View File

@@ -3,7 +3,6 @@ package storage
import ( import (
"io" "io"
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
"polaris/pkg/alist" "polaris/pkg/alist"
@@ -28,14 +27,7 @@ type Alist struct {
subtitleFormats []string subtitleFormats []string
} }
func (a *Alist) Move(src, dest string) error { func (a *Alist) Copy(src, dest string, walkFn WalkFn) error {
if err := a.Copy(src, dest); err != nil {
return err
}
return os.RemoveAll(src)
}
func (a *Alist) Copy(src, dest string) error {
b, err := NewBase(src, a.videoFormats, a.subtitleFormats) b, err := NewBase(src, a.videoFormats, a.subtitleFormats)
if err != nil { if err != nil {
return err return err
@@ -51,7 +43,7 @@ func (a *Alist) Copy(src, dest string) error {
} }
baseDest := filepath.Join(a.baseDir, dest) baseDest := filepath.Join(a.baseDir, dest)
return b.Upload(baseDest, false, false, false, uploadFunc, mkdirFunc) return b.Upload(baseDest, false, false, false, uploadFunc, mkdirFunc, walkFn)
} }
func (a *Alist) ReadDir(dir string) ([]fs.FileInfo, error) { func (a *Alist) ReadDir(dir string) ([]fs.FileInfo, error) {
@@ -75,4 +67,4 @@ func (a *Alist) UploadProgress() float64 {
func (a *Alist) RemoveAll(path string) error { func (a *Alist) RemoveAll(path string) error {
return nil return nil
} }

View File

@@ -13,9 +13,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type WalkFn func(fn func(path string, info fs.FileInfo) error) error
type Storage interface { type Storage interface {
Move(src, dest string) error //Move(src, dest string) error
Copy(src, dest string) error Copy(src, dest string, walkFn WalkFn) error
ReadDir(dir string) ([]fs.FileInfo, error) ReadDir(dir string) ([]fs.FileInfo, error)
ReadFile(string) ([]byte, error) ReadFile(string) ([]byte, error)
WriteFile(string, []byte) error WriteFile(string, []byte) error
@@ -79,7 +80,7 @@ func (b *Base) isFileNeeded(name string) bool {
} }
func (b *Base) Upload(destDir string, tryLink, detectMime, changeMediaHash bool, upload uploadFunc, mkdir func(string) error) error { func (b *Base) Upload(destDir string, tryLink, detectMime, changeMediaHash bool, upload uploadFunc, mkdir func(string) error, walkFn WalkFn) error {
if !b.checkVideoFilesExist() { if !b.checkVideoFilesExist() {
return errors.Errorf("torrent has no video file(s)") return errors.Errorf("torrent has no video file(s)")
} }
@@ -95,7 +96,7 @@ func (b *Base) Upload(destDir string, tryLink, detectMime, changeMediaHash bool,
} }
log.Debugf("local storage target base dir is: %v", targetBase) log.Debugf("local storage target base dir is: %v", targetBase)
err = filepath.Walk(b.src, func(path string, info fs.FileInfo, err error) error { err = walkFn(func(path string, info fs.FileInfo) (err error) {
if err != nil { if err != nil {
return err return err
} }
@@ -112,6 +113,13 @@ func (b *Base) Upload(destDir string, tryLink, detectMime, changeMediaHash bool,
log.Debugf("file is not needed, skip: %s", info.Name()) log.Debugf("file is not needed, skip: %s", info.Name())
return nil return nil
} }
defer func () {
if err == nil {
log.Infof("copy file success, filename %s, destination %s", rel, destName)
}
}()
if tryLink { if tryLink {
if err := os.Link(path, destName); err == nil { if err := os.Link(path, destName); err == nil {
return nil //link success return nil //link success

View File

@@ -22,7 +22,7 @@ type LocalStorage struct {
subtitleFormats []string subtitleFormats []string
} }
func (l *LocalStorage) Copy(src, destDir string) error { func (l *LocalStorage) Copy(src, destDir string,walkFn WalkFn) error {
b, err := NewBase(src, l.videoFormats, l.subtitleFormats) b, err := NewBase(src, l.videoFormats, l.subtitleFormats)
if err != nil { if err != nil {
return err return err
@@ -44,14 +44,7 @@ func (l *LocalStorage) Copy(src, destDir string) error {
} }
return b.Upload(baseDest, true, false, false, uploadFunc, func(s string) error { return b.Upload(baseDest, true, false, false, uploadFunc, func(s string) error {
return os.Mkdir(s, os.ModePerm) return os.Mkdir(s, os.ModePerm)
}) }, walkFn)
}
func (l *LocalStorage) Move(src, destDir string) error {
if err := l.Copy(src, destDir); err != nil {
return err
}
return os.RemoveAll(src)
} }
func (l *LocalStorage) ReadDir(dir string) ([]fs.FileInfo, error) { func (l *LocalStorage) ReadDir(dir string) ([]fs.FileInfo, error) {

View File

@@ -34,7 +34,7 @@ func NewWebdavStorage(url, user, password, path string, changeMediaHash bool, vi
}, nil }, nil
} }
func (w *WebdavStorage) Copy(local, remoteDir string) error { func (w *WebdavStorage) Copy(local, remoteDir string, walkFn WalkFn) error {
b, err := NewBase(local, w.videoFormats, w.subtitleFormats) b, err := NewBase(local, w.videoFormats, w.subtitleFormats)
if err != nil { if err != nil {
return err return err
@@ -57,14 +57,7 @@ func (w *WebdavStorage) Copy(local, remoteDir string) error {
return b.Upload(filepath.Join(w.dir, remoteDir), false, true, w.changeMediaHash, uploadFunc, func(s string) error { return b.Upload(filepath.Join(w.dir, remoteDir), false, true, w.changeMediaHash, uploadFunc, func(s string) error {
return nil return nil
}) }, walkFn)
}
func (w *WebdavStorage) Move(local, remoteDir string) error {
if err := w.Copy(local, remoteDir); err != nil {
return err
}
return os.RemoveAll(local)
} }
func (w *WebdavStorage) ReadDir(dir string) ([]fs.FileInfo, error) { func (w *WebdavStorage) ReadDir(dir string) ([]fs.FileInfo, error) {

View File

@@ -7,7 +7,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"polaris/db" "polaris/ent"
"polaris/log" "polaris/log"
"slices" "slices"
"strconv" "strconv"
@@ -74,7 +74,7 @@ func (i *Item) GetAttr(key string) string {
} }
return "" return ""
} }
func (r *Response) ToResults(indexer *db.TorznabInfo) []Result { func (r *Response) ToResults(indexer *ent.Indexers) []Result {
var res []Result var res []Result
for _, item := range r.Channel.Item { for _, item := range r.Channel.Item {
if slices.Contains(item.Category, "3000") { //exclude audio files if slices.Contains(item.Category, "3000") { //exclude audio files
@@ -130,7 +130,7 @@ func tryParseFloat(s string) float32 {
return float32(r) return float32(r)
} }
func Search(indexer *db.TorznabInfo, keyWord string) ([]Result, error) { func Search(indexer *ent.Indexers, keyWord string) ([]Result, error) {
ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
defer cancel() defer cancel()
@@ -139,7 +139,7 @@ func Search(indexer *db.TorznabInfo, keyWord string) ([]Result, error) {
return nil, errors.Wrap(err, "new request") return nil, errors.Wrap(err, "new request")
} }
var q = url.Values{} var q = url.Values{}
q.Add("apikey", indexer.ApiKey) q.Add("apikey", indexer.APIKey)
q.Add("t", "search") q.Add("t", "search")
q.Add("q", keyWord) q.Add("q", keyWord)
req.URL.RawQuery = q.Encode() req.URL.RawQuery = q.Encode()
@@ -183,7 +183,7 @@ type Result struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Link string `json:"link"` Link string `json:"link"`
Size int64 `json:"size"` Size int64 `json:"size"`
Seeders int `json:"seeders"` Seeders int `json:"seeders"`
Peers int `json:"peers"` Peers int `json:"peers"`
Category int `json:"category"` Category int `json:"category"`

View File

@@ -3,7 +3,10 @@ package transmission
import ( import (
"context" "context"
"fmt" "fmt"
"io/fs"
"net/url" "net/url"
"os"
"path/filepath"
"polaris/log" "polaris/log"
"polaris/pkg" "polaris/pkg"
"polaris/pkg/utils" "polaris/pkg/utils"
@@ -60,21 +63,15 @@ func (c *Client) GetAll() ([]pkg.Torrent, error) {
return torrents, nil return torrents, nil
} }
func (c *Client) Download(link, dir string) (pkg.Torrent, error) { func (c *Client) Download(link, hash, dir string) (pkg.Torrent, error) {
magnet, err := utils.Link2Magnet(link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
}
hash, err := utils.MagnetHash(magnet)
if err != nil {
return nil, errors.Wrap(err, "get hash")
}
t, err := c.c.TorrentAdd(context.TODO(), transmissionrpc.TorrentAddPayload{ t, err := c.c.TorrentAdd(context.TODO(), transmissionrpc.TorrentAddPayload{
Filename: &magnet, Filename: &link,
DownloadDir: &dir, DownloadDir: &dir,
}) })
if err != nil {
return nil, err
}
log.Debugf("get torrent info: %+v", t) log.Debugf("get torrent info: %+v", t)
return &Torrent{ return &Torrent{
@@ -84,33 +81,38 @@ func (c *Client) Download(link, dir string) (pkg.Torrent, error) {
}, err }, err
} }
func NewTorrent(cfg Config, link string) (*Torrent, error) { func NewTorrentHash(cfg Config, hash string) (*Torrent, error) {
c, err := NewClient(cfg) c, err := NewClient(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
magnet, err := utils.Link2Magnet(link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
}
hash, err := utils.MagnetHash(magnet)
if err != nil {
return nil, err
}
t := &Torrent{ t := &Torrent{
c: c.c, c: c.c,
hash: hash, hash: hash,
//cfg: cfg, //cfg: cfg,
} }
if !t.Exists() { if !t.Exists() {
return nil, errors.Errorf("torrent not exist: %v", magnet) return nil, errors.Errorf("torrent not exist: %v", hash)
} }
return t, nil return t, nil
} }
func NewTorrent(cfg Config, link string) (*Torrent, error) {
magnet, err := utils.Link2Magnet(link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
}
hash, err := utils.MagnetHash(magnet)
if err != nil {
return nil, err
}
return NewTorrentHash(cfg, hash)
}
type Torrent struct { type Torrent struct {
//t *transmissionrpc.Torrent //t *transmissionrpc.Torrent
c *transmissionrpc.Client c *transmissionrpc.Client
@@ -208,3 +210,28 @@ func (t *Torrent) Size() (int, error) {
func (t *Torrent) GetHash() string { func (t *Torrent) GetHash() string {
return t.hash return t.hash
} }
func (t *Torrent) WalkFunc() func(fn func(path string, info fs.FileInfo) error) error {
tt, err := t.getTorrent()
if err != nil {
return func(fn func(path string, info fs.FileInfo) error) error {
return errors.Wrap(err, "get torrent info")
}
}
return func(fn func(path string, info fs.FileInfo) error) error {
for _, file := range tt.Files {
name := filepath.Join(*tt.DownloadDir, file.Name)
info, err := os.Stat(name)
if err != nil {
return err
}
if err := fn(name, info); err != nil {
return err
}
}
return nil
}
}

View File

@@ -222,6 +222,33 @@ func isWSL() bool {
return strings.Contains(strings.ToLower(string(releaseData)), "microsoft") return strings.Contains(strings.ToLower(string(releaseData)), "microsoft")
} }
func Link2Hash(link string) (string, error) {
if strings.HasPrefix(strings.ToLower(link), "magnet:") {
return MagnetHash(link)
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse //do not follow redirects
},
}
resp, err := client.Get(link)
if err != nil {
return "", errors.Wrap(err, "get link")
}
defer resp.Body.Close()
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
//redirects
tourl := resp.Header.Get("Location")
return Link2Hash(tourl)
}
info, err := metainfo.Load(resp.Body)
if err != nil {
return "", errors.Wrap(err, "parse response")
}
return info.HashInfoBytes().HexString(), nil
}
func Link2Magnet(link string) (string, error) { func Link2Magnet(link string) (string, error) {
if strings.HasPrefix(strings.ToLower(link), "magnet:") { if strings.HasPrefix(strings.ToLower(link), "magnet:") {
return link, nil return link, nil

12
pkg/utils/utils_test.go Normal file
View File

@@ -0,0 +1,12 @@
package utils
import (
"polaris/log"
"testing"
)
func TestLink2Magnet(t *testing.T) {
s, err := Link2Hash("https://api.m-team.io/api/rss/dlv2?useHttps=true&type=ipv6&sign=1a5174668feea2630acfd6a665f41e5c&t=1738468436&tid=901313&uid=346577")
log.Errorf("%v", err)
log.Infof("%v", s)
}

View File

@@ -2,6 +2,7 @@ package server
import ( import (
"fmt" "fmt"
"polaris/engine"
"polaris/ent" "polaris/ent"
"polaris/ent/blacklist" "polaris/ent/blacklist"
"polaris/ent/episode" "polaris/ent/episode"
@@ -31,7 +32,8 @@ func (s *Server) GetAllActivities(c *gin.Context) (interface{}, error) {
a := Activity{ a := Activity{
History: h, History: h,
} }
for id, task := range s.core.GetTasks() { tasks := s.core.GetTasks()
tasks.Range(func(id int, task *engine.Task) bool {
if h.ID == id && task.Exists() { if h.ID == id && task.Exists() {
p, err := task.Progress() p, err := task.Progress()
if err != nil { if err != nil {
@@ -49,7 +51,9 @@ func (s *Server) GetAllActivities(c *gin.Context) (interface{}, error) {
a.UploadProgress = task.UploadProgresser() a.UploadProgress = task.UploadProgresser()
} }
} }
} return true
})
activities = append(activities, a) activities = append(activities, a)
} }
} else { } else {
@@ -102,19 +106,13 @@ func (s *Server) RemoveActivity(c *gin.Context) (interface{}, error) {
return nil, errors.Wrap(err, "db") return nil, errors.Wrap(err, "db")
} }
if his.EpisodeID != 0 { episodeIds := s.core.GetEpisodeIds(his)
if !s.db.IsEpisodeDownloadingOrDownloaded(his.EpisodeID) {
s.db.SetEpisodeStatus(his.EpisodeID, episode.StatusMissing)
}
} else { for _, id := range episodeIds {
seasonNum, err := utils.SeasonId(his.TargetDir) ep, _ := s.db.GetEpisode(his.MediaID, his.SeasonNum, id)
if err != nil { if !s.db.IsEpisodeDownloadingOrDownloaded(id) && ep.Status != episode.StatusDownloaded {
log.Errorf("no season id: %v", his.TargetDir) //没有正在下载中或者下载完成的任务并且episode状态不是已经下载完成
seasonNum = -1 s.db.SetEpisodeStatus(id, episode.StatusMissing)
}
if his.Status == history.StatusRunning || his.Status == history.StatusUploading {
s.db.SetSeasonAllEpisodeStatus(his.MediaID, seasonNum, episode.StatusMissing)
} }
} }

View File

@@ -7,12 +7,26 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type Coder interface {
Code() int
}
func HttpHandler(f func(*gin.Context) (interface{}, error)) gin.HandlerFunc { func HttpHandler(f func(*gin.Context) (interface{}, error)) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
r, err := f(ctx) r, err := f(ctx)
if err != nil { if err != nil {
log.Errorf("url %v return error: %v", ctx.Request.URL, err) log.Errorf("url %v return error: %v", ctx.Request.URL, err)
cc, ok := err.(Coder)
if ok {
ctx.JSON(200, Response{
Code: cc.Code(),
Message: fmt.Sprintf("%v", err),
})
return
}
ctx.JSON(200, Response{ ctx.JSON(200, Response{
Code: 1, Code: 1,
Message: fmt.Sprintf("%v", err), Message: fmt.Sprintf("%v", err),

View File

@@ -1,150 +0,0 @@
package core
import (
"polaris/db"
"polaris/ent"
"polaris/ent/downloadclients"
"polaris/log"
"polaris/pkg"
"polaris/pkg/qbittorrent"
"polaris/pkg/tmdb"
"polaris/pkg/transmission"
"polaris/pkg/utils"
"github.com/pkg/errors"
"github.com/robfig/cron"
)
func NewClient(db *db.Client, language string) *Client {
return &Client{
db: db,
cron: cron.New(),
tasks: make(map[int]*Task, 0),
language: language,
}
}
type scheduler struct {
cron string
f func() error
}
type Client struct {
db *db.Client
cron *cron.Cron
tasks map[int]*Task
language string
schedulers utils.Map[string, scheduler]
}
func (c *Client) registerCronJob(name string, cron string, f func() error) {
c.schedulers.Store(name, scheduler{
cron: cron,
f: f,
})
}
func (c *Client) Init() {
go c.reloadTasks()
c.addSysCron()
go c.checkW500PosterOnStartup()
}
func (c *Client) reloadTasks() {
allTasks := c.db.GetRunningHistories()
for _, t := range allTasks {
dl, err := c.db.GetDownloadClient(t.DownloadClientID)
if err != nil {
log.Warnf("no download client related: %v", t.SourceTitle)
continue
}
if dl.Implementation == downloadclients.ImplementationTransmission {
to, err := transmission.NewTorrent(transmission.Config{
URL: dl.URL,
User: dl.User,
Password: dl.Password,
}, t.Link)
if err != nil {
log.Warnf("get task error: %v", err)
continue
}
c.tasks[t.ID] = &Task{Torrent: to}
} else if dl.Implementation == downloadclients.ImplementationQbittorrent {
to, err := qbittorrent.NewTorrent(qbittorrent.Info{
URL: dl.URL,
User: dl.User,
Password: dl.Password,
}, t.Link)
if err != nil {
log.Warnf("get task error: %v", err)
continue
}
c.tasks[t.ID] = &Task{Torrent: to}
}
}
log.Infof("------ task reloading done ------")
}
func (c *Client) GetDownloadClient() (pkg.Downloader, *ent.DownloadClients, error) {
downloaders := c.db.GetAllDonloadClients()
for _, d := range downloaders {
if !d.Enable {
continue
}
if d.Implementation == downloadclients.ImplementationTransmission {
trc, err := transmission.NewClient(transmission.Config{
URL: d.URL,
User: d.User,
Password: d.Password,
})
if err != nil {
log.Warnf("connect to download client error: %v", d.URL)
continue
}
return trc, d, nil
} else if d.Implementation == downloadclients.ImplementationQbittorrent {
qbt, err := qbittorrent.NewClient(d.URL, d.User, d.Password)
if err != nil {
log.Warnf("connect to download client error: %v", d.URL)
continue
}
return qbt, d, nil
}
}
return nil, nil, errors.Errorf("no available download client")
}
func (c *Client) TMDB() (*tmdb.Client, error) {
api := c.db.GetSetting(db.SettingTmdbApiKey)
if api == "" {
return nil, errors.New("TMDB apiKey not set")
}
proxy := c.db.GetSetting(db.SettingProxy)
adult := c.db.GetSetting(db.SettingEnableTmdbAdultContent)
return tmdb.NewClient(api, proxy, adult == "true")
}
func (c *Client) MustTMDB() *tmdb.Client {
t, err := c.TMDB()
if err != nil {
log.Panicf("get tmdb: %v", err)
}
return t
}
func (c *Client) RemoveTaskAndTorrent(id int) error {
torrent := c.tasks[id]
if torrent != nil {
if err := torrent.Remove(); err != nil {
return errors.Wrap(err, "remove torrent")
}
delete(c.tasks, id)
}
return nil
}
func (c *Client) GetTasks() map[int]*Task {
return c.tasks
}

View File

@@ -1,2 +0,0 @@
package core

View File

@@ -3,10 +3,10 @@ package server
import ( import (
"fmt" "fmt"
"polaris/db" "polaris/db"
"polaris/engine"
"polaris/ent/media" "polaris/ent/media"
"polaris/log" "polaris/log"
"polaris/pkg/torznab" "polaris/pkg/torznab"
"polaris/server/core"
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -15,7 +15,7 @@ import (
func (s *Server) searchAndDownloadSeasonPackage(seriesId, seasonNum int) (*string, error) { func (s *Server) searchAndDownloadSeasonPackage(seriesId, seasonNum int) (*string, error) {
res, err := core.SearchTvSeries(s.db, &core.SearchParam{ res, err := engine.SearchTvSeries(s.db, &engine.SearchParam{
MediaId: seriesId, MediaId: seriesId,
SeasonNum: seasonNum, SeasonNum: seasonNum,
Episodes: nil, Episodes: nil,
@@ -54,7 +54,7 @@ func (s *Server) SearchAvailableTorrents(c *gin.Context) (interface{}, error) {
if in.Episode == 0 { if in.Episode == 0 {
//search season package //search season package
log.Infof("search series season package S%02d", in.Season) log.Infof("search series season package S%02d", in.Season)
res, err = core.SearchTvSeries(s.db, &core.SearchParam{ res, err = engine.SearchTvSeries(s.db, &engine.SearchParam{
MediaId: in.ID, MediaId: in.ID,
SeasonNum: in.Season, SeasonNum: in.Season,
Episodes: nil, Episodes: nil,
@@ -64,7 +64,7 @@ func (s *Server) SearchAvailableTorrents(c *gin.Context) (interface{}, error) {
} }
} else { } else {
log.Infof("search series episode S%02dE%02d", in.Season, in.Episode) log.Infof("search series episode S%02dE%02d", in.Season, in.Episode)
res, err = core.SearchTvSeries(s.db, &core.SearchParam{ res, err = engine.SearchTvSeries(s.db, &engine.SearchParam{
MediaId: in.ID, MediaId: in.ID,
SeasonNum: in.Season, SeasonNum: in.Season,
Episodes: []int{in.Episode}, Episodes: []int{in.Episode},
@@ -85,7 +85,7 @@ func (s *Server) SearchAvailableTorrents(c *gin.Context) (interface{}, error) {
allowQiangban = true allowQiangban = true
} }
res, err = core.SearchMovie(s.db, &core.SearchParam{ res, err = engine.SearchMovie(s.db, &engine.SearchParam{
MediaId: in.ID, MediaId: in.ID,
FilterQiangban: !allowQiangban, FilterQiangban: !allowQiangban,
}) })
@@ -164,7 +164,13 @@ func (s *Server) DownloadTorrent(c *gin.Context) (interface{}, error) {
return s.core.DownloadEpisodeTorrent(res, in.MediaID, in.Season, in.Episode) return s.core.DownloadEpisodeTorrent(res, in.MediaID, in.Season, in.Episode)
} else { } else {
//movie //movie
return s.core.DownloadMovie(m, in.Link, in.Name, in.Size, in.IndexerId) name := in.Name
if name == "" {
name = m.OriginalName
}
res := torznab.Result{Name: name, Link: in.Link, Size: in.Size, IndexerId: in.IndexerId}
return s.core.DownloadMovie(m, res)
} }
} }

View File

@@ -6,10 +6,10 @@ import (
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"polaris/db" "polaris/db"
"polaris/engine"
"polaris/log" "polaris/log"
"polaris/pkg/cache" "polaris/pkg/cache"
"polaris/pkg/tmdb" "polaris/pkg/tmdb"
"polaris/server/core"
"polaris/ui" "polaris/ui"
"time" "time"
@@ -30,14 +30,14 @@ func NewServer(db *db.Client) *Server {
monitorNumCache: cache.NewCache[int, int](10 * time.Minute), monitorNumCache: cache.NewCache[int, int](10 * time.Minute),
downloadNumCache: cache.NewCache[int, int](10 * time.Minute), downloadNumCache: cache.NewCache[int, int](10 * time.Minute),
} }
s.core = core.NewClient(db, s.language) s.core = engine.NewEngine(db, s.language)
return s return s
} }
type Server struct { type Server struct {
r *gin.Engine r *gin.Engine
db *db.Client db *db.Client
core *core.Client core *engine.Engine
language string language string
jwtSerect string jwtSerect string
monitorNumCache *cache.Cache[int, int] monitorNumCache *cache.Cache[int, int]
@@ -146,7 +146,7 @@ func (s *Server) Serve() error {
} }
func (s *Server) TMDB() (*tmdb.Client, error) { func (s *Server) TMDB() (*tmdb.Client, error) {
api := s.db.GetSetting(db.SettingTmdbApiKey) api := s.db.GetTmdbApiKey()
if api == "" { if api == "" {
return nil, errors.New("TMDB apiKey not set") return nil, errors.New("TMDB apiKey not set")
} }

View File

@@ -1,14 +1,12 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"polaris/db" "polaris/db"
"polaris/ent" "polaris/ent"
"polaris/ent/downloadclients" "polaris/ent/downloadclients"
"polaris/log" "polaris/log"
"polaris/pkg/prowlarr"
"polaris/pkg/qbittorrent" "polaris/pkg/qbittorrent"
"polaris/pkg/torznab" "polaris/pkg/torznab"
"polaris/pkg/transmission" "polaris/pkg/transmission"
@@ -146,29 +144,25 @@ func (s *Server) AddTorznabInfo(c *gin.Context) (interface{}, error) {
utils.TrimFields(&in) utils.TrimFields(&in)
log.Infof("add indexer settings: %+v", in) log.Infof("add indexer settings: %+v", in)
setting := db.TorznabSetting{
URL: in.URL,
ApiKey: in.ApiKey,
}
data, err := json.Marshal(setting)
if err != nil {
return nil, errors.Wrap(err, "marshal json")
}
if in.Priority > 128 { if in.Priority > 128 {
in.Priority = 128 in.Priority = 128
} }
if in.Priority < 1 {
in.Priority = 1
}
indexer := ent.Indexers{ indexer := ent.Indexers{
ID: in.ID, ID: in.ID,
Name: in.Name, Name: in.Name,
Implementation: "torznab", Implementation: "torznab",
Settings: string(data),
Priority: in.Priority, Priority: in.Priority,
Disabled: in.Disabled, Disabled: in.Disabled,
SeedRatio: in.SeedRatio, SeedRatio: in.SeedRatio,
APIKey: in.ApiKey,
URL: in.URL,
} }
err = s.db.SaveIndexer(&indexer) err := s.db.SaveIndexer(&indexer)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "add ") return nil, errors.Wrap(err, "add ")
} }
@@ -183,12 +177,12 @@ func (s *Server) DeleteTorznabInfo(c *gin.Context) (interface{}, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("id is not correct: %v", ids) return nil, fmt.Errorf("id is not correct: %v", ids)
} }
s.db.DeleteTorznab(id) s.db.DeleteIndexer(id)
return "success", nil return "success", nil
} }
func (s *Server) GetAllIndexers(c *gin.Context) (interface{}, error) { func (s *Server) GetAllIndexers(c *gin.Context) (interface{}, error) {
indexers := s.db.GetAllTorznabInfo() indexers := s.db.GetAllIndexers()
if len(indexers) == 0 { if len(indexers) == 0 {
return nil, nil return nil, nil
} }
@@ -316,11 +310,16 @@ func (s *Server) SaveProwlarrSetting(c *gin.Context) (interface{}, error) {
if err := c.ShouldBindJSON(&in); err != nil { if err := c.ShouldBindJSON(&in); err != nil {
return nil, err return nil, err
} }
if !in.Disabled {
client := prowlarr.New(in.ApiKey, in.URL) if in.Disabled {
if _, err := client.GetIndexers(prowlarr.TV); err != nil { if err := s.core.DeleteAllProwlarrIndexers(); err != nil {
return nil, errors.Wrap(err, "connect to prowlarr error") return nil, errors.Wrap(err, "delete prowlarr indexers")
} }
} else {
if err := s.core.SyncProwlarrIndexers(in.ApiKey, in.URL); err != nil {
return nil, errors.Wrap(err, "verify prowlarr")
}
} }
err := s.db.SaveProwlarrSetting(&in) err := s.db.SaveProwlarrSetting(&in)
if err != nil { if err != nil {

View File

@@ -20,7 +20,7 @@ type LogFile struct {
func (s *Server) GetAllLogs(c *gin.Context) (interface{}, error) { func (s *Server) GetAllLogs(c *gin.Context) (interface{}, error) {
fs, err := os.ReadDir(db.LogPath) fs, err := os.ReadDir(db.LogPath)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "read log dir") return []LogFile{}, nil
} }
var logs []LogFile var logs []LogFile
for _, f := range fs { for _, f := range fs {

View File

@@ -4,11 +4,11 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"polaris/db" "polaris/db"
"polaris/engine"
"polaris/ent" "polaris/ent"
"polaris/ent/episode" "polaris/ent/episode"
"polaris/ent/media" "polaris/ent/media"
"polaris/log" "polaris/log"
"polaris/server/core"
"strconv" "strconv"
"strings" "strings"
@@ -68,7 +68,7 @@ type addWatchlistIn struct {
} }
func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) { func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
var in core.AddWatchlistIn var in engine.AddWatchlistIn
if err := c.ShouldBindJSON(&in); err != nil { if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind query") return nil, errors.Wrap(err, "bind query")
} }
@@ -76,7 +76,7 @@ func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
} }
func (s *Server) AddMovie2Watchlist(c *gin.Context) (interface{}, error) { func (s *Server) AddMovie2Watchlist(c *gin.Context) (interface{}, error) {
var in core.AddWatchlistIn var in engine.AddWatchlistIn
if err := c.ShouldBindJSON(&in); err != nil { if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind query") return nil, errors.Wrap(err, "bind query")
} }
@@ -109,7 +109,10 @@ func (s *Server) GetTvWatchlist(c *gin.Context) (interface{}, error) {
ms.MonitoredNum = mon ms.MonitoredNum = mon
ms.DownloadedNum = dow ms.DownloadedNum = dow
} else { } else {
details := s.db.GetMediaDetails(item.ID) details, err := s.db.GetMediaDetails(item.ID)
if err != nil {
return nil, errors.Wrap(err, "get details")
}
for _, ep := range details.Episodes { for _, ep := range details.Episodes {
if ep.Monitored { if ep.Monitored {
ms.MonitoredNum++ ms.MonitoredNum++
@@ -160,7 +163,10 @@ func (s *Server) GetMediaDetails(c *gin.Context) (interface{}, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "convert") return nil, errors.Wrap(err, "convert")
} }
detail := s.db.GetMediaDetails(id) detail, err := s.db.GetMediaDetails(id)
if err != nil {
return nil, errors.Wrap(err, "get details")
}
st := s.db.GetStorage(detail.StorageID) st := s.db.GetStorage(detail.StorageID)
return MediaDetails{MediaDetails: detail, Storage: &st.Storage}, nil return MediaDetails{MediaDetails: detail, Storage: &st.Storage}, nil
} }

1
ui/.gitignore vendored
View File

@@ -41,3 +41,4 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
windows/libpolaris.h

View File

@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable" channel: "stable"
project_type: app project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: macos - platform: windows
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section # User provided section

View File

@@ -1,3 +1,4 @@
//go:build !c
package ui package ui
import "embed" import "embed"

6
ui/embed_c.go Normal file
View File

@@ -0,0 +1,6 @@
//go:build c
package ui
import "embed"
var Web embed.FS

View File

@@ -20,7 +20,7 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_nestedTabController = new TabController(length: 2, vsync: this); _nestedTabController = new TabController(length: 3, vsync: this);
} }
@override @override
@@ -48,6 +48,9 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
Tab( Tab(
text: "下载中", text: "下载中",
), ),
Tab(
text: "做种中",
),
Tab( Tab(
text: "历史记录", text: "历史记录",
), ),
@@ -56,10 +59,12 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
Builder(builder: (context) { Builder(builder: (context) {
AsyncValue<List<Activity>>? activitiesWatcher; AsyncValue<List<Activity>>? activitiesWatcher;
if (selectedTab == 1) { if (selectedTab == 2) {
activitiesWatcher = ref.watch(activitiesDataProvider("archive")); activitiesWatcher = ref.watch(activitiesDataProvider(ActivityStatus.archive));
} else if (selectedTab == 1) {
activitiesWatcher = ref.watch(activitiesDataProvider(ActivityStatus.seeding));
} else if (selectedTab == 0) { } else if (selectedTab == 0) {
activitiesWatcher = ref.watch(activitiesDataProvider("active")); activitiesWatcher = ref.watch(activitiesDataProvider(ActivityStatus.active));
} }
return activitiesWatcher!.when( return activitiesWatcher!.when(
@@ -135,7 +140,7 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
], ],
), ),
), ),
trailing: selectedTab == 0 trailing: selectedTab != 2
? IconButton( ? IconButton(
tooltip: "删除任务", tooltip: "删除任务",
onPressed: () => onDelete()(ac.id!), onPressed: () => onDelete()(ac.id!),
@@ -158,7 +163,7 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
Function(int) onDelete() { Function(int) onDelete() {
return (id) { return (id) {
final f = ref final f = ref
.read(activitiesDataProvider("active").notifier) .read(activitiesDataProvider(ActivityStatus.active).notifier)
.deleteActivity(id); .deleteActivity(id);
showLoadingWithFuture(f); showLoadingWithFuture(f);
}; };

41
ui/lib/ffi/backend.dart Normal file
View File

@@ -0,0 +1,41 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:ui/widgets/utils.dart' as Utils;
class FFIBackend {
final lib = DynamicLibrary.open(libname());
static String libname() {
if (Utils.isDesktop()) {
if (Platform.isWindows) {
return 'libpolaris.dll';
} else if (Platform.isLinux) {
return 'libpolaris.so';
} else if (Platform.isMacOS) {
return 'libpolaris.dylib';
} else {
throw UnsupportedError(
'Unsupported platform: ${Platform.operatingSystem}');
}
} else {
return "";
}
}
Future<void> start() async {
var s = lib
.lookup<NativeFunction<Void Function()>>('Start')
.asFunction<void Function()>();
return Isolate.run(s);
}
Future<void> stop() async {
var s = lib
.lookup<NativeFunction<Void Function()>>('Stop')
.asFunction<void Function()>();
return s();
}
}

View File

@@ -15,7 +15,11 @@ import 'package:ui/tv_details.dart';
import 'package:ui/welcome_page.dart'; import 'package:ui/welcome_page.dart';
import 'package:ui/widgets/utils.dart'; import 'package:ui/widgets/utils.dart';
void main() { void main() async {
// if (isDesktop()) {
// FFIBackend().start();
// }
initializeDateFormatting() initializeDateFormatting()
.then((_) => runApp(const ProviderScope(child: MyApp()))); .then((_) => runApp(const ProviderScope(child: MyApp())));
} }

View File

@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:ui/providers/server_response.dart'; import 'package:ui/providers/server_response.dart';
import 'package:ui/widgets/utils.dart';
class APIs { class APIs {
static final _baseUrl = baseUrl(); static final _baseUrl = baseUrl();
@@ -68,6 +69,9 @@ class APIs {
static String baseUrl() { static String baseUrl() {
if (kReleaseMode) { if (kReleaseMode) {
if (!kIsWeb) {
return "http://127.0.0.1:8080";
}
return ""; return "";
} }
return "http://127.0.0.1:8080"; return "http://127.0.0.1:8080";

View File

@@ -5,7 +5,7 @@ import 'package:ui/providers/APIs.dart';
import 'package:ui/providers/server_response.dart'; import 'package:ui/providers/server_response.dart';
var activitiesDataProvider = AsyncNotifierProvider.autoDispose var activitiesDataProvider = AsyncNotifierProvider.autoDispose
.family<ActivityData, List<Activity>, String>(ActivityData.new); .family<ActivityData, List<Activity>, ActivityStatus>(ActivityData.new);
var mediaHistoryDataProvider = FutureProvider.autoDispose.family( var mediaHistoryDataProvider = FutureProvider.autoDispose.family(
(ref, arg) async { (ref, arg) async {
@@ -23,32 +23,47 @@ var mediaHistoryDataProvider = FutureProvider.autoDispose.family(
}, },
); );
enum ActivityStatus {
active,
seeding,
archive,
}
class ActivityData class ActivityData
extends AutoDisposeFamilyAsyncNotifier<List<Activity>, String> { extends AutoDisposeFamilyAsyncNotifier<List<Activity>, ActivityStatus> {
Timer? _timer;
@override @override
FutureOr<List<Activity>> build(String arg) async { FutureOr<List<Activity>> build(ActivityStatus arg) async {
if (_timer != null) {
_timer!.cancel();
}
final dio = APIs.getDio(); final dio = APIs.getDio();
var status = arg == ActivityStatus.archive
? "archive"
: "active"; //archive or active
var resp = var resp =
await dio.get(APIs.activityUrl, queryParameters: {"status": arg}); await dio.get(APIs.activityUrl, queryParameters: {"status": status});
final sp = ServerResponse.fromJson(resp.data); final sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) { if (sp.code != 0) {
throw sp.message; throw sp.message;
} }
List<Activity> activities = List.empty(growable: true); List<Activity> activities = List.empty(growable: true);
for (final a in sp.data as List) { for (final a in sp.data as List) {
activities.add(Activity.fromJson(a)); var activity = Activity.fromJson(a);
if (arg == ActivityStatus.archive) {
activities.add(activity);
} else {
if (arg == ActivityStatus.active && activity.status != "seeding") {
activities.add(activity);
} else if (arg == ActivityStatus.seeding &&
activity.status == "seeding") {
activities.add(activity);
}
}
} }
if (arg == "active") { if (status == "active") {
//refresh active downloads //refresh active downloads
_timer = Timer(const Duration(seconds: 5), final _timer = Timer(const Duration(seconds: 5),
() => ref.invalidateSelf()); //Periodically Refresh ref.invalidateSelf); //Periodically Refresh
ref.onDispose(_timer.cancel);
} }
return activities; return activities;
} }

View File

@@ -161,6 +161,7 @@ class Indexer {
int? priority; int? priority;
double? seedRatio; double? seedRatio;
bool? disabled; bool? disabled;
bool? synced;
Indexer( Indexer(
{this.name, {this.name,
@@ -169,7 +170,8 @@ class Indexer {
this.id, this.id,
this.priority = 50, this.priority = 50,
this.seedRatio = 0, this.seedRatio = 0,
this.disabled}); this.disabled,
this.synced});
Indexer.fromJson(Map<String, dynamic> json) { Indexer.fromJson(Map<String, dynamic> json) {
name = json['name']; name = json['name'];
@@ -179,6 +181,7 @@ class Indexer {
priority = json["priority"]; priority = json["priority"];
seedRatio = json["seed_ratio"] ?? 0; seedRatio = json["seed_ratio"] ?? 0;
disabled = json["disabled"] ?? false; disabled = json["disabled"] ?? false;
synced = json["synced"] ?? false;
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};

View File

@@ -111,7 +111,7 @@ class _SearchPageState extends ConsumerState<SearchPage> {
} }
return cards; return cards;
}, },
error: (err, trace) => [PoError(msg: "网络错误,请确认TMDB Key正确配置并且能够正常连接到TMDB网站", err: err)], error: (err, trace) => [PoError(msg: "网络错误,请确保本地网络能够正常访问TMDB网站", err: err)],
loading: () => [const MyProgressIndicator()]); loading: () => [const MyProgressIndicator()]);
var f = NotificationListener( var f = NotificationListener(

View File

@@ -46,10 +46,9 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
children: [ children: [
FormBuilderTextField( FormBuilderTextField(
name: "tmdb_api", name: "tmdb_api",
decoration: Commons.requiredTextFieldStyle( decoration: const InputDecoration(
text: "TMDB Api Key", icon: const Icon(Icons.key)), labelText: "TMDB Api Key", icon: Icon(Icons.key), helperText: "未防止被限流,可以提供自定义的 TMDB Api Key"),
// //
validator: FormBuilderValidators.required(),
), ),
FormBuilderTextField( FormBuilderTextField(
name: "download_dir", name: "download_dir",

View File

@@ -5,6 +5,7 @@ import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:ui/providers/settings.dart'; import 'package:ui/providers/settings.dart';
import 'package:ui/settings/dialog.dart'; import 'package:ui/settings/dialog.dart';
import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/widgets.dart'; import 'package:ui/widgets/widgets.dart';
class IndexerSettings extends ConsumerStatefulWidget { class IndexerSettings extends ConsumerStatefulWidget {
@@ -16,22 +17,112 @@ class IndexerSettings extends ConsumerStatefulWidget {
} }
class _IndexerState extends ConsumerState<IndexerSettings> { class _IndexerState extends ConsumerState<IndexerSettings> {
final _formKey = GlobalKey<FormBuilderState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var indexers = ref.watch(indexersProvider); var indexers = ref.watch(indexersProvider);
return indexers.when( return indexers.when(
data: (value) => Wrap( data: (value) {
children: List.generate(value.length + 1, (i) { return Column(
if (i < value.length) { crossAxisAlignment: CrossAxisAlignment.start,
var indexer = value[i]; children: [
return SettingsCard( Padding(
onTap: () => showIndexerDetails(indexer), padding: EdgeInsets.only(top: 10, bottom: 10, left: 10),
child: Text(indexer.name ?? "")); child: Text("Prowlarr 设置", style: TextStyle(fontSize: 18)),
} ),
return SettingsCard( Padding(
onTap: () => showIndexerDetails(Indexer()), padding: EdgeInsets.only(left: 20), child: prowlarrSetting()),
child: const Icon(Icons.add)); Divider(),
}), Padding(
padding: EdgeInsets.only(top: 10, bottom: 10, left: 10),
child: Text("索引器列表", style: TextStyle(fontSize: 18)),
),
Padding(
padding: EdgeInsets.only(left: 20),
child: Wrap(
children: List.generate(value.length + 1, (i) {
if (i < value.length) {
var indexer = value[i];
return SettingsCard(
onTap: () => showIndexerDetails(indexer),
child: Text(indexer.name ?? ""));
}
return SettingsCard(
onTap: () => showIndexerDetails(Indexer()),
child: const Icon(Icons.add));
}),
)),
],
);
},
error: (err, trace) => PoNetworkError(err: err),
loading: () => const MyProgressIndicator());
}
Widget prowlarrSetting() {
var ps = ref.watch(prowlarrSettingDataProvider);
return ps.when(
data: (v) => FormBuilder(
key: _formKey, //设置globalKey用于后面获取FormState
autovalidateMode: AutovalidateMode.onUserInteraction,
initialValue: {
"api_key": v.apiKey,
"url": v.url,
"disabled": v.disabled
},
child: Column(
children: [
FormBuilderTextField(
name: "url",
decoration: const InputDecoration(
labelText: "Prowlarr URL",
icon: Icon(Icons.web),
hintText: "http://10.0.0.8:9696"),
validator: FormBuilderValidators.required(),
),
FormBuilderTextField(
name: "api_key",
decoration: const InputDecoration(
labelText: "API Key",
icon: Icon(Icons.key),
helperText: "Prowlarr 设置 -> 通用 -> API 密钥"),
validator: FormBuilderValidators.required(),
),
FormBuilderSwitch(
name: "disabled",
title: const Text("禁用 Prowlarr"),
decoration:
InputDecoration(icon: Icon(Icons.do_not_disturb)),
),
Center(
child: Padding(
padding: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.saveAndValidate()) {
var values = _formKey.currentState!.value;
var f = ref
.read(prowlarrSettingDataProvider.notifier)
.save(ProwlarrSetting(
apiKey: values["api_key"],
url: values["url"],
disabled: values["disabled"]))
.then((v) {
showSnakeBar("更新成功");
ref.invalidate(indexersProvider);
});
showLoadingWithFuture(f);
}
},
child: const Padding(
padding: EdgeInsets.all(10),
child: Text("保存"),
)),
),
)
],
),
), ),
error: (err, trace) => PoNetworkError(err: err), error: (err, trace) => PoNetworkError(err: err),
loading: () => const MyProgressIndicator()); loading: () => const MyProgressIndicator());
@@ -54,6 +145,7 @@ class _IndexerState extends ConsumerState<IndexerSettings> {
child: Column( child: Column(
children: [ children: [
FormBuilderDropdown( FormBuilderDropdown(
enabled: indexer.synced != true,
name: "impl", name: "impl",
decoration: const InputDecoration(labelText: "类型"), decoration: const InputDecoration(labelText: "类型"),
items: const [ items: const [
@@ -61,24 +153,28 @@ class _IndexerState extends ConsumerState<IndexerSettings> {
], ],
), ),
FormBuilderTextField( FormBuilderTextField(
enabled: indexer.synced != true,
name: "name", name: "name",
decoration: Commons.requiredTextFieldStyle(text: "名称"), decoration: Commons.requiredTextFieldStyle(text: "名称"),
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(), validator: FormBuilderValidators.required(),
), ),
FormBuilderTextField( FormBuilderTextField(
enabled: indexer.synced != true,
name: "url", name: "url",
decoration: Commons.requiredTextFieldStyle(text: "地址"), decoration: Commons.requiredTextFieldStyle(text: "地址"),
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(), validator: FormBuilderValidators.required(),
), ),
FormBuilderTextField( FormBuilderTextField(
enabled: indexer.synced != true,
name: "api_key", name: "api_key",
decoration: Commons.requiredTextFieldStyle(text: "API Key"), decoration: Commons.requiredTextFieldStyle(text: "API Key"),
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(), validator: FormBuilderValidators.required(),
), ),
FormBuilderTextField( FormBuilderTextField(
enabled: indexer.synced != true,
name: "priority", name: "priority",
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "索引优先级", labelText: "索引优先级",
@@ -88,6 +184,7 @@ class _IndexerState extends ConsumerState<IndexerSettings> {
validator: FormBuilderValidators.positiveNumber(), validator: FormBuilderValidators.positiveNumber(),
), ),
FormBuilderTextField( FormBuilderTextField(
enabled: indexer.synced != true,
name: "seed_ratio", name: "seed_ratio",
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "做种率", labelText: "做种率",
@@ -97,7 +194,10 @@ class _IndexerState extends ConsumerState<IndexerSettings> {
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.numeric(), validator: FormBuilderValidators.numeric(),
), ),
FormBuilderSwitch(name: "disabled", title: const Text("禁用此索引器")) FormBuilderSwitch(
enabled: indexer.synced != true,
name: "disabled",
title: const Text("禁用此索引器"))
], ],
), ),
); );
@@ -122,6 +222,11 @@ class _IndexerState extends ConsumerState<IndexerSettings> {
} }
return showSettingDialog( return showSettingDialog(
context, "索引器", indexer.id != null, body, onSubmit, onDelete); context,
indexer.synced != true?"索引器":"Prowlarr 索引器",
(indexer.id != null) && (indexer.synced != true),
body,
onSubmit,
onDelete);
} }
} }

View File

@@ -84,6 +84,17 @@ class _NotifierState extends ConsumerState<NotifierSettings> {
showBarkNotifierDetails(NotifierData()); showBarkNotifierDetails(NotifierData());
}, },
), ),
),
SettingsCard(
child: InkWell(
child: const Center(
child: Text("Server酱"),
),
onTap: () {
Navigator.of(context).pop();
showServerChanNotifierDetails(NotifierData());
},
),
) )
], ],
), ),
@@ -98,10 +109,64 @@ class _NotifierState extends ConsumerState<NotifierSettings> {
return showBarkNotifierDetails(notifier); return showBarkNotifierDetails(notifier);
case "pushover": case "pushover":
return showPushoverNotifierDetails(notifier); return showPushoverNotifierDetails(notifier);
case "serverchan":
return showServerChanNotifierDetails(notifier);
} }
return Future<void>.value(); return Future<void>.value();
} }
Future<void> showServerChanNotifierDetails(NotifierData notifier) {
final _formKey = GlobalKey<FormBuilderState>();
var body = FormBuilder(
key: _formKey,
initialValue: {
"name": notifier.name,
"enabled": notifier.enabled ?? true,
"key": notifier.settings != null ? notifier.settings!["key"] : "",
},
child: Column(
children: [
const Text("https://sct.ftqq.com/"),
FormBuilderTextField(
name: "name",
decoration: Commons.requiredTextFieldStyle(text: "名称"),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(),
),
FormBuilderTextField(
name: "key",
decoration: Commons.requiredTextFieldStyle(text: "Key"),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: FormBuilderValidators.required(),
),
FormBuilderSwitch(name: "enabled", title: const Text("启用"))
],
),
);
onDelete() async {
return ref.read(notifiersDataProvider.notifier).delete(notifier.id!);
}
onSubmit() async {
if (_formKey.currentState!.saveAndValidate()) {
var values = _formKey.currentState!.value;
return ref.read(notifiersDataProvider.notifier).add(NotifierData(
name: values["name"],
service: "serverchan",
enabled: values["enabled"],
settings: {
"key": values["key"],
}));
} else {
throw "validation_error";
}
}
return showSettingDialog(
context, "Server酱", notifier.id != null, body, onSubmit, onDelete);
}
Future<void> showBarkNotifierDetails(NotifierData notifier) { Future<void> showBarkNotifierDetails(NotifierData notifier) {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();

View File

@@ -6,7 +6,6 @@ import 'package:ui/settings/general.dart';
import 'package:ui/settings/importlist.dart'; import 'package:ui/settings/importlist.dart';
import 'package:ui/settings/indexer.dart'; import 'package:ui/settings/indexer.dart';
import 'package:ui/settings/notifier.dart'; import 'package:ui/settings/notifier.dart';
import 'package:ui/settings/prowlarr.dart';
import 'package:ui/settings/storage.dart'; import 'package:ui/settings/storage.dart';
class SystemSettingsPage extends ConsumerStatefulWidget { class SystemSettingsPage extends ConsumerStatefulWidget {
@@ -27,7 +26,6 @@ class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
children: [ children: [
getExpansionTile("常规", const GeneralSettings()), getExpansionTile("常规", const GeneralSettings()),
getExpansionTile("索引器", const IndexerSettings()), getExpansionTile("索引器", const IndexerSettings()),
getExpansionTile("Prowlarr 设置", const ProwlarrSettingPage()),
getExpansionTile("下载器", const DownloaderSettings()), getExpansionTile("下载器", const DownloaderSettings()),
getExpansionTile("存储", const StorageSettings()), getExpansionTile("存储", const StorageSettings()),
getExpansionTile("通知客户端", const NotifierSettings()), getExpansionTile("通知客户端", const NotifierSettings()),

View File

@@ -23,7 +23,7 @@ class ResourceList extends ConsumerWidget {
seasonNumber: seasonNum, seasonNumber: seasonNum,
episodeNumber: episodeNum episodeNumber: episodeNum
))); )));
return torrents.when( var widgets = torrents.when(
data: (v) { data: (v) {
bool hasPrivate = false; bool hasPrivate = false;
for (final item in v) { for (final item in v) {
@@ -83,5 +83,9 @@ class ResourceList extends ConsumerWidget {
: Text("$err"); : Text("$err");
}, },
loading: () => const MyProgressIndicator()); loading: () => const MyProgressIndicator());
return isSmallScreen(context)
? SingleChildScrollView(
scrollDirection: Axis.horizontal, child: widgets)
: widgets;
} }
} }

View File

@@ -21,42 +21,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.11.0" version: "2.12.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.19.0" version: "1.19.1"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -69,34 +69,42 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.7.0" version: "5.8.0+1"
dio_web_adapter: dio_web_adapter:
dependency: transitive dependency: transitive
description: description:
name: dio_web_adapter name: dio_web_adapter
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.0" version: "2.1.1"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
name: equatable name: equatable
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.5" version: "2.0.7"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
ffi:
dependency: "direct main"
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -106,18 +114,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_adaptive_scaffold name: flutter_adaptive_scaffold
sha256: "8c515a2cb8abb3a567f8e77f10b33f47bb6fcadfe31f62364e0aca36280cdf93" sha256: "7279d74da2f2531a16d21c2ec327308778c3aedd672dfe4eaf3bf416463501f8"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.3.1" version: "0.3.2"
flutter_form_builder: flutter_form_builder:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_form_builder name: flutter_form_builder
sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545 sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "9.5.0" version: "9.7.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -169,26 +177,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: form_builder_validators name: form_builder_validators
sha256: c61ed7b1deecf0e1ebe49e2fa79e3283937c5a21c7e48e3ed9856a4a14e1191a sha256: cd617fa346250293ff3e2709961d0faf7b80e6e4f0ff7b500126b28d7422dd67
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "11.0.0" version: "11.1.2"
go_router: go_router:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "8ae664a70174163b9f65ea68dd8673e29db8f9095de7b5cd00e167c621f4fef5" sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "14.6.0" version: "14.8.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.0.2" version: "4.1.2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -217,18 +225,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "10.0.7" version: "10.0.8"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.0.8" version: "3.0.9"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
@@ -265,10 +273,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.0" version: "5.1.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -281,10 +289,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.17"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -297,10 +305,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -313,10 +321,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.9.0" version: "1.9.1"
phone_numbers_parser: phone_numbers_parser:
dependency: transitive dependency: transitive
description: description:
@@ -337,10 +345,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.1.2" version: "6.1.4"
quiver: quiver:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -382,18 +390,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.10.0" version: "1.10.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.12.0" version: "1.12.1"
state_notifier: state_notifier:
dependency: transitive dependency: transitive
description: description:
@@ -406,42 +414,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.0" version: "1.4.1"
table_calendar: table_calendar:
dependency: "direct main" dependency: "direct main"
description: description:
name: table_calendar name: table_calendar
sha256: "4ca32b2fc919452c9974abd4c6ea611a63e33b9e4f0b8c38dba3ac1f4a6549d1" sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.1.2" version: "3.1.3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.2.1" version: "1.2.2"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.3" version: "0.7.4"
timeago: timeago:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -470,18 +478,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.3.14" version: "6.3.15"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.3.1" version: "6.3.3"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@@ -494,10 +502,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.2.1" version: "3.2.2"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -510,18 +518,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.3.3" version: "2.4.0"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.1.3" version: "3.1.4"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -534,18 +542,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "14.3.0" version: "14.3.1"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.27.0"

View File

@@ -31,6 +31,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
dio: ^5.7.0 dio: ^5.7.0
ffi: ^2.0.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.

1
ui/run.bat Normal file
View File

@@ -0,0 +1 @@
flutter run -d edge --no-web-resources-cdn --web-browser-flag "--disable-web-security"

17
ui/windows/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

111
ui/windows/CMakeLists.txt Normal file
View File

@@ -0,0 +1,111 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(ui LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "ui")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(VERSION 3.14...3.25)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/libpolaris.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

View File

@@ -0,0 +1,109 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Set fallback configurations for older versions of the flutter tool.
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
set(FLUTTER_TARGET_PLATFORM "windows-x64")
endif()
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@@ -0,0 +1,14 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,24 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

121
ui/windows/runner/Runner.rc Normal file
View File

@@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0,0
#endif
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "ui" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "ui" "\0"
VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "ui.exe" "\0"
VALUE "ProductName", "ui" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -0,0 +1,71 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
// Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw();
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

Some files were not shown because too many files have changed in this diff Show More