Compare commits

..

11 Commits

Author SHA1 Message Date
Simon Ding
5e9e85206e doc: update 2024-10-27 20:12:46 +08:00
Simon Ding
3babb9f5c7 doc: update prowlarr setting 2024-10-27 20:11:04 +08:00
Simon Ding
c9928f10ce revert: to html renderer 2024-10-21 09:24:35 +08:00
Simon Ding
a2da1e7479 feat: use canvas render 2024-10-21 08:59:51 +08:00
Simon Ding
038f643ce3 ui: upgrade dependences 2024-10-21 08:46:10 +08:00
Simon Ding
cf2fa90d5d ui: change prowlarr setting text 2024-10-20 22:28:12 +08:00
Simon Ding
677923ea8a fix: match priority 2024-10-20 16:36:34 +08:00
Simon Ding
1192d00fe0 feat: change icon 2024-10-20 16:24:28 +08:00
Simon Ding
b317636a8a feat: support prowlarr connection 2024-10-20 16:21:58 +08:00
Simon Ding
0e6465593b update 2024-10-20 13:37:33 +08:00
Simon
2cb8a5b6fb Update README.md 2024-10-20 13:20:13 +08:00
21 changed files with 384 additions and 95 deletions

View File

@@ -1,4 +1,10 @@
# Polaris
![main_page](./doc/assets/main_page.png)
<h1 align="center">Polaris</h1>
<div align="center">
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/simon-ding/polaris/go.yml)
![GitHub Release](https://img.shields.io/github/v/release/simon-ding/polaris)
@@ -6,11 +12,10 @@
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/simon-ding/polaris)
Polaris 是一个电视剧和电影的追踪下载软件。对动漫日剧美剧都有良好的匹配支持webdav或者本地存储。
**Polaris 是一个电视剧和电影的追踪下载软件。对动漫日剧美剧都有良好的匹配支持webdav或者本地存储。**
</div>
![main_page](./doc/assets/main_page.png)
![detail_page](./doc/assets/detail_page.png)
![anime](./doc/assets/anime_match.png)
交流群: https://t.me/+8R2nzrlSs2JhMDgx
@@ -40,6 +45,13 @@ Polaris 是一个电视剧和电影的追踪下载软件。对动漫日剧美剧
- [ ] 手机客户端
## 截图
![main_page](./doc/assets/main_page.png)
![detail_page](./doc/assets/detail_page.png)
![anime](./doc/assets/anime_match.png)
## 运行原理
Polaris本身不提供任何资源要使其能正常工作需要同时安装一个下载客户端transmission和一个索引客户端jackett
@@ -48,13 +60,17 @@ Polaris通过索引客户端查询相关的BT/PT站点然后把查到的资
![](./doc/assets/yuanli.png)
## 对比 sonarr/radarr
<!-- ## 对比 sonarr/radarr
* 更好的中文支持
* 对于动漫、日剧的良好支持,配合国内站点基本能匹配上对应资源
* 支持 webdav 后端存储,可以配合 alist 或者阿里云来实现下载后实时传到云上的功能。这样外出就可以不依靠家里的宽带来看电影了,或者实现个轻 NAS 功能,下载功能放在本地,数据放在云盘
* golang 实现后端,相比于 .NET 更节省资源
* 一个程序同时实现了电影、电视剧功能,不需要装两个程序
* 当然 sonarr/radarr 也是非常优秀的开源项目,目前 Polaris 功能还没有 sonarr/radarr 丰富
* 当然 sonarr/radarr 也是非常优秀的开源项目,目前 Polaris 功能还没有 sonarr/radarr 丰富 -->
## Stargazers over time
[![Stargazers over time](https://starchart.cc/simon-ding/polaris.svg?variant=adaptive)](https://starchart.cc/simon-ding/polaris)
-------------

View File

@@ -17,6 +17,7 @@ const (
SetttingSizeLimiter = "size_limiter"
SettingTvNamingFormat = "tv_naming_format"
SettingMovieNamingFormat = "movie_naming_format"
SettingProwlarrInfo = "prowlarr_info"
)
const (
@@ -60,3 +61,8 @@ type Limiter struct {
Max int `json:"max"`
Min int `json:"min"`
}
type ProwlarrSetting struct {
ApiKey string `json:"api_key"`
URL string `json:"url"`
}

View File

@@ -660,3 +660,24 @@ func (c *Client) CleanAllDanglingEpisodes() error {
func (c *Client) AddBlacklistItem(item *ent.Blacklist) error {
return c.ent.Blacklist.Create().SetType(item.Type).SetValue(item.Value).SetNotes(item.Notes).Exec(context.Background())
}
func (c *Client) GetProwlarrSetting() (*ProwlarrSetting, error) {
s := c.GetSetting(SettingProwlarrInfo)
if s == "" {
return nil, errors.New("prowlarr setting not set")
}
var se ProwlarrSetting
if err := json.Unmarshal([]byte(s), &se); err != nil {
return nil, err
}
return &se, nil
}
func (c *Client) SaveProwlarrSetting(se *ProwlarrSetting) error {
data, err := json.Marshal(se)
if err != nil {
return err
}
return c.SetSetting(SettingProwlarrInfo, string(data))
}

View File

@@ -2,7 +2,7 @@
要正确使用此程序,需要配置好以下设置:
### TMDB设置
### 1. TMDB设置
1. 因为此程序需要使用到 TMDB 的数据,使用此程序首先要申请一个 TMDB 的 Api Key. 申请教程请 google [tmdb api key申请](https://www.google.com/search?q=tmdb+api+key%E7%94%B3%E8%AF%B7)
@@ -10,35 +10,16 @@
**注意:** TMDB可能需要翻墙才能使用参考 [TMDB 访问问题](./tmdb.md)
### 索引器
### 2. 索引器
索引器是资源提供者,目前支持 torznab 协议,意味着 polarr 或者 jackett 都可以支持。请自行部署相关程序,或者使用的 docker compose 配置一起拉起
使用配置页面索引器配置或者prowlarr设置其中一个即可。
推荐使用 linuxserver 的镜像https://docs.linuxserver.io/images/docker-jackett/
#### jackett配置参考 [jackett](./jackett.md)
#### 索引器配
#### prowlarr设
索引器配置这里以 jackett 为例。使用默认 docker compose 配置拉起后以 http://< ip >:9117 可访问 jackett 的主页。
1. 打开 jackett 主页后,点击页面上面的 Add indexer会出现 BT/PT 站点列表,选择你需要的站点点击+号添加。如果是PT请自行配置好相关配置
![add indexer](./assets/add_indexer.png)
![search add](./assets/search_add.png)
2. 添加后主页即会显示相应的BT/PT站点点击 *Copy Torznab Feed* 即得到了我们需要的地址
![copy feed](./assets/copy_feed.png)
3. 回到我们的主程序 Polaris 当中,点击 *设置 -> 索引器设置* -> 点击+号增加新的索引器输入一个名称拷贝我们第2步得到的地址到地址栏
![polaris add indexer](./assets/polaris_add_indexer.png)
4. 选相框中我们可以看到,还需要一个 API Key我们回到 Jackett 中,在页面右上角,复制我们需要的 API Key
![api key](./assets/jackett_api_key.png)
5. 恭喜!你已经成功完成了索引器配置。如需要更多的站点,请重复相同的操作完成配置
1) 取得prowlarr的url和api key api key在 *Prowlarr 设置 -> 通用 -> API 密钥* 处取得
2) 对应参数填到 polaris程序*设置->prowlarr设置*当中
### 下载器

21
doc/jackett.md Normal file
View File

@@ -0,0 +1,21 @@
## jackett 索引器配置
1. 打开 jackett 主页后,点击页面上面的 Add indexer会出现 BT/PT 站点列表,选择你需要的站点点击+号添加。如果是PT请自行配置好相关配置
![add indexer](./assets/add_indexer.png)
![search add](./assets/search_add.png)
2. 添加后主页即会显示相应的BT/PT站点点击 *Copy Torznab Feed* 即得到了我们需要的地址
![copy feed](./assets/copy_feed.png)
3. 回到我们的主程序 Polaris 当中,点击 *设置 -> 索引器设置* -> 点击+号增加新的索引器输入一个名称拷贝我们第2步得到的地址到地址栏
![polaris add indexer](./assets/polaris_add_indexer.png)
4. 选相框中我们可以看到,还需要一个 API Key我们回到 Jackett 中,在页面右上角,复制我们需要的 API Key
![api key](./assets/jackett_api_key.png)
5. 恭喜!你已经成功完成了索引器配置。如需要更多的站点,请重复相同的操作完成配置

View File

@@ -1,8 +1,8 @@
# 快速开始
## 安装 Polaris
## 1. 安装 Polaris
### Docker Compose 方式安装
### 1.1 Docker Compose 方式安装
最简单使用本程序的方式是通过docker compose下面内容保存成 docker-compose.yml然后执行 docker compose up -d, 即可拉起程序。
@@ -23,7 +23,7 @@ services:
- 8080:8080
```
### Docker 方式安装
### 1.2 Docker 方式安装
也可以通过原始 docker 命令的方式安装 Polaris
@@ -40,19 +40,19 @@ docker run -d \
ghcr.io/simon-ding/polaris:latest
```
### Unraid 安装
### 1.3 Unraid 安装
参考下图进行配置
![](./assets/unraid.png)
### 访问
### 1.4 访问
拉起之后访问 http://< ip >:8080 即可访问 Polaris 的主页:
![](./assets/main_page.png)
## 安装下载客户端
## 2. 安装下载客户端
Polaris 需要下载客户端的配合使用,目前支持 Transmission 和 Qbittorrent。推荐使用linuxserver镜像进行安装
@@ -62,16 +62,18 @@ Polaris 需要下载客户端的配合使用,目前支持 Transmission 和 Qbi
需要注意的是下载客户端内 /downloads 路径的映射地址要和 Polaris的/downloads路径映射保持一致。也就是说他俩都要映射到同一路径。
## 安装 Jackett
Polaris 如果要正常工作还需要一个索引客户端的支持目前支持jackett索引客户端。
## 3. 安装 Jackett/Prowlarr
Polaris 如果要正常工作还需要一个索引客户端的支持目前支持jackett/prowlarr索引客户端。推荐使用prowlarr设置更简单
安装方式见:
* [linuxserver/jackett](https://docs.linuxserver.io/images/docker-jackett/)
* [linuxserver/prowlarr](https://docs.linuxserver.io/images/docker-prowlarr/)
## 联合安装
## 4. 联合安装
如果觉得一个个安装麻烦也可以使用下面docker compose文件一键拉起所有组件
@@ -125,7 +127,7 @@ docker compose up -d
```
## 配置
## 5. 配置
详细配置请看 [配置篇](./configuration.md)

1
go.mod
View File

@@ -21,6 +21,7 @@ require (
github.com/ncruces/go-sqlite3 v0.18.4
github.com/nikoksr/notify v1.0.0
github.com/stretchr/testify v1.9.0
golift.io/starr v1.0.0
)
require (

10
go.sum
View File

@@ -216,8 +216,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
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/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/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -251,8 +249,6 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
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/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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -310,8 +306,6 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
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/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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
@@ -453,10 +447,10 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golift.io/starr v1.0.0 h1:IDSaSL+ZYxdLT/Lg//dg/iwZ39LHO3D5CmbLCOgSXbI=
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

View File

@@ -37,6 +37,7 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
if err != nil {
return nil, err
}
var items []importlist.Item
doc.Find("div[class=doulist-item]").Each(func(i int, selection *goquery.Selection) {
titleDiv := selection.Find("div[class=title]")
link := titleDiv.Find("div>a")
@@ -64,18 +65,26 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
}
}
}
_, err := parseDetailPage(strings.TrimSpace(href))
if err != nil {
log.Errorf("get detail page: %v", err)
return
}
item := importlist.Item{
Title: strings.TrimSpace(link.Text()),
Year: year,
}
items = append(items, item)
_ = item
println(link.Text(), href)
//println(link.Text(), href)
})
return nil, nil
return &importlist.Response{Items: items}, nil
}
func parseDetailPage(url string) (string, error) {
println(url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
@@ -95,6 +104,14 @@ func parseDetailPage(url string) (string, error) {
if err != nil {
return "", err
}
doc.Find("div[class='subject clearfix']").Each(func(i int, se *goquery.Selection) {
println(se.Text())
se.Children().Get(1)
imdb := se.Find("div[class='info']").First().Children().Last()
println(imdb.Text())
})
_ = doc
return "", nil
}

View File

@@ -6,6 +6,6 @@ import (
)
func TestParseDoulist(t *testing.T) {
r, err := ParseDoulist("https://www.douban.com/doulist/166422/")
r, err := ParseDoulist("https://www.douban.com/doulist/81580/")
log.Info(r, err)
}

65
pkg/prowlarr/prowlarr.go Normal file
View File

@@ -0,0 +1,65 @@
package prowlarr
import (
"encoding/json"
"fmt"
"polaris/db"
"polaris/ent"
"strings"
"time"
"golift.io/starr"
"golift.io/starr/prowlarr"
)
type Client struct {
p *prowlarr.Prowlarr
apiKey string
url string
}
func New(apiKey, url string) *Client {
c := starr.New(apiKey, url, 10*time.Second)
p := prowlarr.New(c)
return &Client{p: p, apiKey: apiKey, url: url}
}
func (c *Client) GetIndexers() ([]*db.TorznabInfo, error) {
ins, err := c.p.GetIndexers()
if err != nil {
return nil, err
}
var indexers []*db.TorznabInfo
for _, in := range ins {
if !in.Enable {
continue
}
seedRatio := 0.0
for _, f := range in.Fields {
if f.Name == "torrentBaseSettings.seedRatio" && f.Value != nil {
if r, ok := f.Value.(float64); ok {
seedRatio = r
}
}
}
setting := db.TorznabSetting{
URL: fmt.Sprintf("%s/%d/api", strings.TrimSuffix(c.url, "/"), in.ID),
ApiKey: c.apiKey,
}
data, _ := json.Marshal(&setting)
entIndexer := ent.Indexers{
Name: in.Name,
Implementation: "torznab",
Priority: 128 - int(in.Priority),
SeedRatio: float32(seedRatio),
Settings: string(data),
}
indexers = append(indexers, &db.TorznabInfo{
Indexers: &entIndexer,
TorznabSetting: setting,
})
}
return indexers, nil
}

View File

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

View File

@@ -6,6 +6,7 @@ import (
"polaris/ent/media"
"polaris/log"
"polaris/pkg/metadata"
"polaris/pkg/prowlarr"
"polaris/pkg/torznab"
"slices"
"sort"
@@ -136,7 +137,7 @@ func torrentSizeOk(detail *db.MediaDetails, torrentSize int, param *SearchParam)
}
}
}
return torrentSize > defaultMinSize * multiplier
return torrentSize > defaultMinSize*multiplier
}
func seasonEpisodeCount(detail *db.MediaDetails, seasonNum int) int {
@@ -230,6 +231,17 @@ func searchWithTorznab(db *db.Client, queries ...string) []torznab.Result {
var res []torznab.Result
allTorznab := db.GetAllTorznabInfo()
p, err := db.GetProwlarrSetting()
if err == nil { //prowlarr exists
c := prowlarr.New(p.ApiKey, p.URL)
all, err := c.GetIndexers()
if err != nil {
log.Warnf("get prowlarr all indexer error: %v", err)
} else {
allTorznab = append(allTorznab, all...)
}
}
resChan := make(chan []torznab.Result)
var wg sync.WaitGroup

View File

@@ -70,6 +70,8 @@ func (s *Server) Serve() error {
setting.POST("/parse/movie", HttpHandler(s.ParseMovie))
setting.POST("/monitoring", HttpHandler(s.ChangeEpisodeMonitoring))
setting.POST("/cron/trigger", HttpHandler(s.TriggerCronJob))
setting.GET("/prowlarr", HttpHandler(s.GetProwlarrSetting))
setting.POST("/prowlarr", HttpHandler(s.SaveProwlarrSetting))
}
activity := api.Group("/activity")
{

View File

@@ -8,6 +8,7 @@ import (
"polaris/ent"
"polaris/ent/downloadclients"
"polaris/log"
"polaris/pkg/prowlarr"
"polaris/pkg/qbittorrent"
"polaris/pkg/torznab"
"polaris/pkg/transmission"
@@ -303,3 +304,26 @@ func (s *Server) TriggerCronJob(c *gin.Context) (interface{}, error) {
}
return "success", nil
}
func (s *Server) GetProwlarrSetting(c *gin.Context) (interface{}, error) {
se, err :=s.db.GetProwlarrSetting()
if err != nil {
return &db.ProwlarrSetting{}, nil
}
return se, nil
}
func (s *Server) SaveProwlarrSetting(c *gin.Context) (interface{}, error) {
var in db.ProwlarrSetting
if err := c.ShouldBindJSON(&in); err != nil {
return nil, err
}
client := prowlarr.New(in.ApiKey, in.URL)
if _, err := client.GetIndexers(); err != nil {
return nil, errors.Wrap(err, "connect to prowlarr error")
}
err := s.db.SaveProwlarrSetting(&in)
if err != nil {
return nil, err
}
return "success", nil
}

View File

@@ -40,6 +40,7 @@ class APIs {
static final addImportlistUrl = "$_baseUrl/api/v1/importlist/add";
static final deleteImportlistUrl = "$_baseUrl/api/v1/importlist/delete";
static final getAllImportlists = "$_baseUrl/api/v1/importlist/";
static final prowlarrUrl = "$_baseUrl/api/v1/setting/prowlarr";
static final notifierAllUrl = "$_baseUrl/api/v1/notifier/all";
static final notifierDeleteUrl = "$_baseUrl/api/v1/notifier/id/";

View File

@@ -25,6 +25,10 @@ var importlistProvider =
AsyncNotifierProvider.autoDispose<ImportListData, List<ImportList>>(
ImportListData.new);
var prowlarrSettingDataProvider =
AsyncNotifierProvider.autoDispose<ProwlarrSettingData, ProwlarrSetting>(
ProwlarrSettingData.new);
class EditSettingData extends AutoDisposeAsyncNotifier<GeneralSetting> {
@override
FutureOr<GeneralSetting> build() async {
@@ -503,3 +507,38 @@ class ImportListData extends AutoDisposeAsyncNotifier<List<ImportList>> {
ref.invalidateSelf();
}
}
class ProwlarrSetting {
final String apiKey;
final String url;
ProwlarrSetting({required this.apiKey, required this.url});
factory ProwlarrSetting.fromJson(Map<String, dynamic> json) {
return ProwlarrSetting(apiKey: json["api_key"], url: json["url"]);
}
Map<String, dynamic> tojson() => {"api_key": apiKey, "url": url};
}
class ProwlarrSettingData extends AutoDisposeAsyncNotifier<ProwlarrSetting> {
@override
FutureOr<ProwlarrSetting> build() async {
final dio = APIs.getDio();
var resp = await dio.get(APIs.prowlarrUrl);
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
var se = ProwlarrSetting.fromJson(sp.data);
return se;
}
Future<void> save(ProwlarrSetting ps) async {
final dio = APIs.getDio();
var resp = await dio.post(APIs.prowlarrUrl, data: ps.tojson());
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
ref.invalidateSelf();
}
}

View File

@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:ui/providers/settings.dart';
import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/widgets.dart';
class ProwlarrSettingPage extends ConsumerStatefulWidget {
const ProwlarrSettingPage({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() {
return ProwlarrSettingState();
}
}
class ProwlarrSettingState extends ConsumerState<ProwlarrSettingPage> {
final _formKey = GlobalKey<FormBuilderState>();
@override
Widget build(BuildContext context) {
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},
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(),
),
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"]))
.then((v) => showSnakeBar("更新成功"));
showLoadingWithFuture(f);
}
},
child: const Padding(padding: EdgeInsets.all(10), child: Text("保存"),)),
),
)
],
),
),
error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator());
}
}

View File

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

View File

@@ -69,18 +69,18 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.5.0+1"
version: "5.7.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
version: "2.0.0"
equatable:
dependency: transitive
description:
@@ -106,26 +106,26 @@ packages:
dependency: "direct main"
description:
name: flutter_adaptive_scaffold
sha256: "56d4d81fe88ecffe8ae96b8d89a1ae793c0a85035bb9b74ff28f20eea0cdbdc2"
sha256: "8c515a2cb8abb3a567f8e77f10b33f47bb6fcadfe31f62364e0aca36280cdf93"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.11+1"
version: "0.3.1"
flutter_form_builder:
dependency: "direct main"
description:
name: flutter_form_builder
sha256: "447f8808f68070f7df968e8063aada3c9d2e90e789b5b70f3b44e4b315212656"
sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.3.0"
version: "9.5.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
version: "5.0.0"
flutter_localizations:
dependency: transitive
description: flutter
@@ -143,10 +143,10 @@ packages:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
sha256: "6eda4e247774474c715a0805a2fb8e3cd55fbae4ead641e063c95b4bd5f3b317"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.1"
version: "2.6.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -177,10 +177,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc"
url: "https://pub.flutter-io.cn"
source: hosted
version: "14.2.3"
version: "14.3.0"
http_parser:
dependency: transitive
description:
@@ -265,18 +265,18 @@ packages:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
version: "5.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
version: "1.3.0"
matcher:
dependency: transitive
description:
@@ -345,18 +345,18 @@ packages:
dependency: "direct main"
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
version: "3.2.2"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
sha256: bd6e656a764e3d27f211975626e0c4f9b8d06ab16acf3c7ba7a8061e09744c75
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.1"
version: "2.6.0"
sign_in_button:
dependency: transitive
description:
@@ -454,26 +454,26 @@ packages:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.2"
version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.3.0"
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.3.8"
version: "6.3.12"
url_launcher_ios:
dependency: transitive
description:
@@ -486,18 +486,18 @@ packages:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.1"
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.0"
version: "3.2.1"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -518,10 +518,10 @@ packages:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.2"
version: "3.1.3"
vector_math:
dependency: transitive
description:
@@ -542,10 +542,10 @@ packages:
dependency: transitive
description:
name: web
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
version: "1.1.0"
sdks:
dart: ">=3.4.3 <4.0.0"
flutter: ">=3.22.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"

View File

@@ -41,7 +41,7 @@ dependencies:
quiver: ^3.2.1
flutter_login: ^5.0.0
intl: ^0.19.0
flutter_adaptive_scaffold: ^0.1.11+1
flutter_adaptive_scaffold: ^0.3.1
flutter_form_builder: ^9.3.0
form_builder_validators: ^11.0.0
url_launcher: ^6.3.0
@@ -57,7 +57,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec