Initial commit
This commit is contained in:
commit
f1191196d5
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.log
|
||||
*.txt
|
||||
anitoru
|
||||
14
anitoru.service
Normal file
14
anitoru.service
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=AniToru System Daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=anitoru
|
||||
Group=anitoru
|
||||
ExecStart=/usr/bin/anitoru -daemon -config=/path/to/config.yml
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
199
cli.go
Normal file
199
cli.go
Normal file
@ -0,0 +1,199 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
e "git.kjao.me/kjao/anitoru/err"
|
||||
)
|
||||
|
||||
type SubscriptionManager struct {
|
||||
Subscriptions []string
|
||||
}
|
||||
|
||||
func NewSubscriptionManager() *SubscriptionManager {
|
||||
var m SubscriptionManager
|
||||
m.Get()
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) UpdateSubs(conn net.Conn) {
|
||||
rawData, err := ReadData(conn)
|
||||
e.ExitIf(err, "Unable to read from socket.")
|
||||
m.Subscriptions = strings.Split(string(rawData), "\n")
|
||||
if m.Subscriptions[0] == "" {
|
||||
m.Subscriptions = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) Get() {
|
||||
conn, err := net.Dial("unix", SocketPath)
|
||||
e.ExitIf(err, "Unable to open socket. Is the daemon running?")
|
||||
|
||||
err = SendData(conn, []byte{'V'})
|
||||
e.ExitIf(err, "Unable to write to socket.")
|
||||
m.UpdateSubs(conn)
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) View() {
|
||||
fmt.Println()
|
||||
fmt.Println("Current subscriptions:")
|
||||
for i, sub := range m.Subscriptions {
|
||||
fmt.Println(fmt.Sprintf("\t[%02d] %v", i+1, sub))
|
||||
}
|
||||
if len(m.Subscriptions) == 0 {
|
||||
fmt.Println("\t...Empty\n")
|
||||
return
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) Add() {
|
||||
fmt.Println()
|
||||
|
||||
// Get available subscriptions.
|
||||
fmt.Println("Available subscriptions:")
|
||||
titles, err := GetReleaseSchedule()
|
||||
if err != nil {
|
||||
fmt.Println("Unable to get release schedule.")
|
||||
return
|
||||
}
|
||||
|
||||
subMap := GetSubMap(&m.Subscriptions)
|
||||
|
||||
var available []string
|
||||
i := 0
|
||||
for _, title := range titles {
|
||||
_, exists := subMap[title]
|
||||
if !exists {
|
||||
i++
|
||||
available = append(available, title)
|
||||
fmt.Println(fmt.Sprintf("\t[%02d] %v", i, title))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Get comma-separated list of additions.
|
||||
newSubs := []string{}
|
||||
var add string
|
||||
var name string
|
||||
for {
|
||||
fmt.Print("Add subscriptions. (M for manual): ")
|
||||
fmt.Scan(&add)
|
||||
add = strings.ToLower(add)
|
||||
if add == "m" {
|
||||
fmt.Print("Name: ")
|
||||
fmt.Scan(&name)
|
||||
newSubs = append(newSubs, name)
|
||||
} else {
|
||||
addSplit := strings.Split(add, ",")
|
||||
retry := false
|
||||
for _, indexString := range addSplit {
|
||||
index, err := strconv.Atoi(indexString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in indices. Try again.")
|
||||
retry = true
|
||||
break
|
||||
}
|
||||
if index <= 0 || index > len(available) {
|
||||
fmt.Fprintln(os.Stdout, "Error in indices. %v is invalid. Try again", index)
|
||||
retry = true
|
||||
break
|
||||
}
|
||||
|
||||
newSubs = append(newSubs, available[index-1])
|
||||
}
|
||||
if retry {
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Send addition update to socket
|
||||
data := []byte{'A'}
|
||||
data = append(data, []byte(strings.Join(newSubs, "\n"))...)
|
||||
conn, err := net.Dial("unix", SocketPath)
|
||||
e.ExitIf(err, "Unable to open socket. Is the daemon running?")
|
||||
|
||||
err = SendData(conn, data)
|
||||
e.ExitIf(err, "Unable to write to socket. Is the daemon running?")
|
||||
m.UpdateSubs(conn)
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) Remove() {
|
||||
fmt.Println()
|
||||
m.View()
|
||||
fmt.Println()
|
||||
|
||||
subMap := GetSubMap(&m.Subscriptions)
|
||||
|
||||
// Get comma-separated list of removals.
|
||||
var remove string
|
||||
removeSubs := []string{}
|
||||
for {
|
||||
fmt.Print("Remove subscriptions: ")
|
||||
fmt.Scan(&remove)
|
||||
addSplit := strings.Split(remove, ",")
|
||||
skip := false
|
||||
for _, indexString := range addSplit {
|
||||
index, err := strconv.Atoi(indexString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in indices. Try again.")
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
if index <= 0 || index > len(m.Subscriptions) {
|
||||
fmt.Fprintln(os.Stdout, "Error in indices. %v is invalid. Try again", index)
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
removeSubs = append(removeSubs, (m.Subscriptions)[index-1])
|
||||
delete(subMap, (m.Subscriptions)[index-1])
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Send removal update to socket
|
||||
data := []byte{'R'}
|
||||
data = append(data, []byte(strings.Join(removeSubs, "\n"))...)
|
||||
conn, err := net.Dial("unix", SocketPath)
|
||||
e.ExitIf(err, "Unable to open socket. Is the daemon running?")
|
||||
|
||||
err = SendData(conn, data)
|
||||
e.ExitIf(err, "Unable to write to socket. Is the daemon running?")
|
||||
m.UpdateSubs(conn)
|
||||
m.View()
|
||||
}
|
||||
|
||||
func userCLI() {
|
||||
manager := NewSubscriptionManager()
|
||||
|
||||
var action string
|
||||
for {
|
||||
fmt.Print("View, Add, Remove subscriptions or Quit: ")
|
||||
fmt.Scan(&action)
|
||||
action = strings.ToLower(action)
|
||||
switch action {
|
||||
case "v":
|
||||
manager.View()
|
||||
break
|
||||
case "a":
|
||||
manager.Add()
|
||||
break
|
||||
case "r":
|
||||
manager.Remove()
|
||||
break
|
||||
case "q":
|
||||
os.Exit(1)
|
||||
default:
|
||||
fmt.Println("Invalid command, try again.")
|
||||
}
|
||||
}
|
||||
}
|
||||
68
err/err.go
Normal file
68
err/err.go
Normal file
@ -0,0 +1,68 @@
|
||||
package err
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type LogOption func(string)
|
||||
|
||||
type ErrorCallback struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
func If(err error) *ErrorCallback {
|
||||
return &ErrorCallback{err}
|
||||
}
|
||||
|
||||
func (err *ErrorCallback) ThenIf(f func() error) *ErrorCallback {
|
||||
if err.Error == nil {
|
||||
err.Error = f()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *ErrorCallback) Then(f func()) *ErrorCallback {
|
||||
if err.Error == nil {
|
||||
f()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *ErrorCallback) End() error {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
func Panic(err error) bool {
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func LogIf(err error, opts ...LogOption) bool {
|
||||
if err != nil {
|
||||
for _, opt := range opts {
|
||||
opt(err.Error())
|
||||
}
|
||||
log.Println(err.Error())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Log(msg string, opts ...LogOption) {
|
||||
for _, opt := range opts {
|
||||
opt(msg)
|
||||
}
|
||||
log.Println(msg)
|
||||
}
|
||||
|
||||
func ExitIf(err error, msg string) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, msg, "Error:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
34
go.mod
Normal file
34
go.mod
Normal file
@ -0,0 +1,34 @@
|
||||
module git.kjao.me/kjao/anitoru
|
||||
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.9.2
|
||||
github.com/gookit/config/v2 v2.2.5
|
||||
github.com/mmcdole/gofeed v1.3.0
|
||||
github.com/mrobinsn/go-rtorrent v1.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/fatih/color v1.14.1 // indirect
|
||||
github.com/goccy/go-yaml v1.11.2 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gookit/goutil v0.6.15 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
)
|
||||
103
go.sum
Normal file
103
go.sum
Normal file
@ -0,0 +1,103 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ=
|
||||
github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/gookit/config/v2 v2.2.5 h1:RECbYYbtherywmzn3LNeu9NA5ZqhD7MSKEMsJ7l+MpU=
|
||||
github.com/gookit/config/v2 v2.2.5/go.mod h1:NeX+yiNYn6Ei10eJvCQFXuHEPIE/IPS8bqaFIsszzaM=
|
||||
github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo=
|
||||
github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
|
||||
github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk=
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mrobinsn/go-rtorrent v1.8.0 h1:+61aDIP0asy57lRD/uZtmxfE0/gjkHnt3uddOhMKUJ8=
|
||||
github.com/mrobinsn/go-rtorrent v1.8.0/go.mod h1:CdVq2IwM+JU9D6TnWiQSg9lqZWu6zUfK67YXET2LqIM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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/net v0.0.0-20190620200207-3b0461eec859/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-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.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.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.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
546
main.go
Normal file
546
main.go
Normal file
@ -0,0 +1,546 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
e "git.kjao.me/kjao/anitoru/err"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/gookit/config/v2"
|
||||
"github.com/gookit/config/v2/yamlv3"
|
||||
"github.com/mmcdole/gofeed"
|
||||
"github.com/mrobinsn/go-rtorrent/rtorrent"
|
||||
)
|
||||
|
||||
const ReleaseURL string = "https://www.erai-raws.info/release-schedule/"
|
||||
const RSSURL string = "https://www.erai-raws.info/feed/?res=1080p&type=magnet&subs%5B0%5D=us&v0=no&d157edc6b50f28b2776442c03d067d56"
|
||||
const NyaaURL string = "https://nyaa.si/"
|
||||
|
||||
const SocketPath string = "/tmp/anitoru.sock"
|
||||
const SubscriptionsFile string = "subscriptions.txt"
|
||||
|
||||
var InvalidDotfile error = errors.New("Dotfile invalid.")
|
||||
|
||||
type Configuration struct {
|
||||
BaseDir string `config:"baseDir"`
|
||||
RTorrentHost string `config:"rTorrentHost"`
|
||||
PollingRate int `config:"pollingRate"`
|
||||
DownloadDir string `config:"downloadDir"`
|
||||
LogPath string `config:"logPath"`
|
||||
NotifyError bool `config:"notifyError"`
|
||||
}
|
||||
|
||||
type Anime struct {
|
||||
Name string
|
||||
Episode int
|
||||
Magnet string
|
||||
Version int
|
||||
}
|
||||
|
||||
func NewAnime(link string) Anime {
|
||||
return Anime{Magnet: link}
|
||||
}
|
||||
|
||||
func NTFYMessage(msg string) {
|
||||
http.Post("https://ntfy.kjao.me/anitoru", "text/plain", strings.NewReader(msg))
|
||||
}
|
||||
|
||||
func LogNTFY() e.LogOption {
|
||||
return func(msg string) {
|
||||
NTFYMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func LogStd() e.LogOption {
|
||||
return func(msg string) {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func GetSubMap(subscriptions *[]string) map[string]struct{} {
|
||||
subMap := make(map[string]struct{})
|
||||
for _, sub := range *subscriptions {
|
||||
var d struct{}
|
||||
subMap[sub] = d
|
||||
}
|
||||
return subMap
|
||||
}
|
||||
|
||||
func GetSubList(subMap *map[string]struct{}) []string {
|
||||
subscriptions := make([]string, len(*subMap), len(*subMap))
|
||||
i := 0
|
||||
for sub, _ := range *subMap {
|
||||
subscriptions[i] = sub
|
||||
i++
|
||||
}
|
||||
return subscriptions
|
||||
}
|
||||
|
||||
func (ani *Anime) InfoFromTitle(title string) error {
|
||||
titleSplit := strings.Split(title, " ")
|
||||
ani.Name = strings.Join(titleSplit[:len(titleSplit)-2], " ")
|
||||
|
||||
epInfo := strings.Split(titleSplit[len(titleSplit)-1], "v")
|
||||
ep, err := strconv.Atoi(epInfo[0])
|
||||
return e.If(err).ThenIf(func() error {
|
||||
ani.Episode = ep
|
||||
if len(epInfo) > 1 {
|
||||
ani.Version, err = strconv.Atoi(epInfo[1])
|
||||
return e.If(err).End()
|
||||
} else {
|
||||
ani.Version = 1
|
||||
return nil
|
||||
}
|
||||
}).End()
|
||||
}
|
||||
|
||||
// func Scheduler(clock string) <-chan int {
|
||||
// c := make(chan int)
|
||||
// clockTime, err := time.Parse("05:15", clock)
|
||||
// FatalErr(err)
|
||||
|
||||
// year, month, day := time.Now().Date()
|
||||
// scheduleDate := time.Date(year, month, day, clockTime.Hour(), clockTime.Minute(),
|
||||
// 0, 0, time.UTC).AddDate(0, 0, 1)
|
||||
|
||||
// go func(date time.Time) {
|
||||
// for {
|
||||
// if time.Now().After(date) {
|
||||
// c <- 1
|
||||
// date = date.AddDate(0, 0, 1)
|
||||
// } else {
|
||||
// time.Sleep(time.Minute)
|
||||
// }
|
||||
// }
|
||||
// }(scheduleDate)
|
||||
// return c
|
||||
// }
|
||||
|
||||
func GetReleaseSchedule() ([]string, error) {
|
||||
var doc *goquery.Document
|
||||
var titles []string
|
||||
|
||||
res, err := http.Get(ReleaseURL)
|
||||
defer res.Body.Close()
|
||||
|
||||
return titles, e.If(err).ThenIf(func() error { // The function will run first before return.
|
||||
doc, err = goquery.NewDocumentFromReader(res.Body)
|
||||
return err
|
||||
}).Then(func() {
|
||||
doc.Find("table .aa_ss_ops_new").Each(func(i int, s *goquery.Selection) {
|
||||
titles = append(titles, strings.TrimSpace(s.Text()))
|
||||
})
|
||||
}).End()
|
||||
}
|
||||
|
||||
func GetRSS() ([]Anime, error) {
|
||||
var animes []Anime
|
||||
|
||||
fp := gofeed.NewParser()
|
||||
feed, err := fp.ParseURL(RSSURL)
|
||||
|
||||
return animes, e.If(err).Then(func() {
|
||||
for _, item := range feed.Items {
|
||||
title := strings.Split(item.Title, "[Magnet] ")[1]
|
||||
title = strings.Split(title, " [1080p]")[0]
|
||||
if title[len(title)-6:] != "(HEVC)" {
|
||||
continue
|
||||
}
|
||||
title = title[:len(title)-7]
|
||||
anime := NewAnime(item.Link)
|
||||
e.LogIf(anime.InfoFromTitle(title))
|
||||
animes = append(animes, anime)
|
||||
}
|
||||
}).End()
|
||||
}
|
||||
|
||||
func GetAllAvailable(name string) ([]Anime, error) {
|
||||
var animes []Anime
|
||||
var res *http.Response
|
||||
var doc *goquery.Document
|
||||
|
||||
req, err := http.NewRequest("GET", NyaaURL, nil)
|
||||
|
||||
return animes, e.If(err).ThenIf(func() error {
|
||||
q := req.URL.Query()
|
||||
query := "[Erai-raws] " + name + " [HEVC]"
|
||||
query = strings.Replace(query, ":", " ", -1)
|
||||
query = strings.Replace(query, ".", " ", -1)
|
||||
q.Add("q", query)
|
||||
q.Add("s", "id") // Sort by date and descending
|
||||
q.Add("o", "desc")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
res, err = (&http.Client{}).Do(req)
|
||||
return err
|
||||
}).ThenIf(func() error {
|
||||
defer res.Body.Close()
|
||||
doc, err = goquery.NewDocumentFromReader(res.Body)
|
||||
return err
|
||||
}).Then(func() {
|
||||
var epMap = make(map[int]struct{})
|
||||
doc.Find(".success").Each(func(i int, s *goquery.Selection) {
|
||||
cols := s.Children()
|
||||
cols = cols.Next() // Filename
|
||||
title := cols.Text()
|
||||
|
||||
if !strings.Contains(title, "[HEVC]") || !strings.Contains(title, "[ENG]") {
|
||||
return
|
||||
}
|
||||
title = strings.Split(title, "[Erai-raws] ")[1]
|
||||
if strings.Contains(title, "(AAC 2.0)") {
|
||||
title = strings.Split(title, " (AAC 2.0)")[0]
|
||||
} else {
|
||||
title = strings.Split(title, " [1080p]")[0]
|
||||
}
|
||||
|
||||
cols = cols.Next() // Download link
|
||||
link := cols.Children().Next()
|
||||
anime := NewAnime(link.AttrOr("href", "none"))
|
||||
e.LogIf(anime.InfoFromTitle(title))
|
||||
anime.Name = name // Actual name, since filenames remove special characters.
|
||||
|
||||
_, exists := epMap[anime.Episode]
|
||||
if !exists {
|
||||
animes = append(animes, anime)
|
||||
var s struct{}
|
||||
epMap[anime.Episode] = s
|
||||
}
|
||||
})
|
||||
}).End()
|
||||
}
|
||||
|
||||
func SocketListener(sock net.Listener) chan net.Conn {
|
||||
c := make(chan net.Conn)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := sock.Accept()
|
||||
if e.LogIf(err) {
|
||||
continue
|
||||
}
|
||||
c <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type Daemon struct {
|
||||
Tor *rtorrent.RTorrent
|
||||
PollingRate time.Duration
|
||||
DownloadDir string
|
||||
Listener chan net.Conn
|
||||
Subscriptions map[string]struct{}
|
||||
LogOptions []e.LogOption
|
||||
SubscriptionsPath string
|
||||
}
|
||||
|
||||
func NewDaemon(server string, polling int, download string, socket net.Listener,
|
||||
notifyError bool, subPath string) Daemon {
|
||||
// Connect to rTorrent
|
||||
tor := rtorrent.New(server, false)
|
||||
name, err := tor.Name()
|
||||
|
||||
e.ExitIf(err, "Unable to connect to rTorrent.")
|
||||
fmt.Fprintln(os.Stdout, "Connected to", name, "at", server)
|
||||
|
||||
// Create simple socket for subscriptions
|
||||
listener := SocketListener(socket)
|
||||
|
||||
// Make subscriptions if doesn't exist.
|
||||
subFile, err := os.OpenFile(subPath, os.O_RDONLY|os.O_CREATE, 0644)
|
||||
e.ExitIf(err, "Unable to open subscriptions file.")
|
||||
subFile.Close()
|
||||
|
||||
data, err := os.ReadFile(subPath)
|
||||
e.ExitIf(err, "Unable to read subscriptions file.")
|
||||
var subscriptions []string
|
||||
if len(data) == 0 {
|
||||
subscriptions = []string{}
|
||||
} else {
|
||||
subscriptions = strings.Split(string(data), "\n")
|
||||
}
|
||||
subMap := GetSubMap(&subscriptions)
|
||||
|
||||
var opt []e.LogOption
|
||||
if notifyError {
|
||||
opt = []e.LogOption{LogNTFY(), LogStd()}
|
||||
} else {
|
||||
opt = []e.LogOption{LogStd()}
|
||||
}
|
||||
|
||||
return Daemon{
|
||||
Tor: tor,
|
||||
PollingRate: time.Duration(polling),
|
||||
DownloadDir: download,
|
||||
Listener: listener,
|
||||
Subscriptions: subMap,
|
||||
LogOptions: opt,
|
||||
SubscriptionsPath: subPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) Serve() {
|
||||
//d.CheckRSS()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(d.PollingRate * time.Minute):
|
||||
d.CheckRSS()
|
||||
case <-time.After(10 * time.Second):
|
||||
d.CheckTorrents()
|
||||
case conn := <-d.Listener:
|
||||
d.ReadSocket(conn)
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) CheckRSS() {
|
||||
animes, err := GetRSS()
|
||||
if e.LogIf(err, d.LogOptions...) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, anime := range animes {
|
||||
_, exists := d.Subscriptions[anime.Name]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
e.LogIf(d.Download(&anime), d.LogOptions...)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) CheckTorrents() {
|
||||
torrents, err := d.Tor.GetTorrents(rtorrent.ViewMain)
|
||||
e.LogIf(err, d.LogOptions...)
|
||||
for _, torrent := range torrents {
|
||||
if torrent.Completed {
|
||||
d.Tor.Delete(torrent)
|
||||
arr := strings.Split(torrent.Label, "\n")
|
||||
name := arr[0]
|
||||
dir := arr[1]
|
||||
ext := filepath.Ext(torrent.Name)
|
||||
err := os.Rename(torrent.Path+"/"+torrent.Name, dir+"/"+name+ext)
|
||||
e.LogIf(err, d.LogOptions...)
|
||||
e.Log(fmt.Sprintf("Finished downloading %v.", name), LogNTFY())
|
||||
} else {
|
||||
active, err := d.Tor.IsActive(torrent)
|
||||
e.LogIf(err, d.LogOptions...)
|
||||
fmt.Println(torrent.Name, "Active:", active)
|
||||
d.Tor.ResumeTorrent(torrent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) SendSubs(conn net.Conn) {
|
||||
subs := strings.Join(GetSubList(&d.Subscriptions), "\n")
|
||||
err := SendData(conn, []byte(subs))
|
||||
e.LogIf(err, d.LogOptions...)
|
||||
}
|
||||
|
||||
func (d *Daemon) ReadSocket(conn net.Conn) {
|
||||
rawData, err := ReadData(conn)
|
||||
if e.LogIf(err, d.LogOptions...) {
|
||||
return
|
||||
}
|
||||
|
||||
command := rawData[0]
|
||||
data := string(rawData[1:len(rawData)])
|
||||
e.Log("Socket Command: " + string(command))
|
||||
newSubs := strings.Split(data, "\n")
|
||||
|
||||
var write bool
|
||||
switch command {
|
||||
case 'A':
|
||||
// Add each subscription
|
||||
for _, sub := range newSubs {
|
||||
var s struct{}
|
||||
d.Subscriptions[sub] = s
|
||||
}
|
||||
d.SendSubs(conn)
|
||||
|
||||
for _, sub := range newSubs {
|
||||
// For each new subscription, get all available animes.
|
||||
animes, err := GetAllAvailable(sub)
|
||||
if e.LogIf(err, d.LogOptions...) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, anime := range animes {
|
||||
e.LogIf(d.Download(&anime), d.LogOptions...)
|
||||
}
|
||||
}
|
||||
write = true
|
||||
case 'R':
|
||||
for _, sub := range strings.Split(data, "\n") {
|
||||
delete(d.Subscriptions, sub)
|
||||
}
|
||||
write = true
|
||||
d.SendSubs(conn)
|
||||
case 'V':
|
||||
write = false
|
||||
d.SendSubs(conn)
|
||||
default:
|
||||
e.Log("Invalid Command")
|
||||
}
|
||||
|
||||
if write { // Write subscriptions to file.
|
||||
f, err := os.Create(d.SubscriptionsPath)
|
||||
if e.LogIf(err, d.LogOptions...) {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
f.Write([]byte(strings.Join(GetSubList(&d.Subscriptions), "\n")))
|
||||
f.Sync()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) Download(ani *Anime) error {
|
||||
aniDir := filepath.Join(d.DownloadDir, ani.Name)
|
||||
dotPath := filepath.Join(aniDir, ".anitoru")
|
||||
var dot map[int]int
|
||||
|
||||
err := os.MkdirAll(aniDir, os.ModePerm)
|
||||
return e.If(err).ThenIf(func() error {
|
||||
dot, err = LoadDotfile(dotPath)
|
||||
return err
|
||||
}).Then(func() {
|
||||
vers, exists := dot[ani.Episode]
|
||||
if exists || vers >= ani.Version { // No need to download.
|
||||
return
|
||||
}
|
||||
name := fmt.Sprintf("%v - %02d", ani.Name, ani.Episode)
|
||||
d.Tor.Add(ani.Magnet,
|
||||
rtorrent.DLabel.SetValue(name+"\n"+aniDir),
|
||||
)
|
||||
e.Log(fmt.Sprintf("Queueing download of %v...", name), LogNTFY())
|
||||
dot[ani.Episode] = ani.Version
|
||||
SaveDotfile(dotPath, dot)
|
||||
}).End()
|
||||
}
|
||||
|
||||
func SendData(conn net.Conn, data []byte) error {
|
||||
length := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(length, uint32(len(data)))
|
||||
_, err := conn.Write(append(length, data...))
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadData(conn net.Conn) ([]byte, error) {
|
||||
var data []byte
|
||||
lengthBuf := make([]byte, 4)
|
||||
_, err := conn.Read(lengthBuf)
|
||||
return data, e.If(err).Then(func() {
|
||||
length := binary.LittleEndian.Uint32(lengthBuf)
|
||||
data = make([]byte, length)
|
||||
}).ThenIf(func() error {
|
||||
_, err = conn.Read(data)
|
||||
return err
|
||||
}).End()
|
||||
}
|
||||
|
||||
func LoadDotfile(path string) (map[int]int, error) {
|
||||
dot := make(map[int]int)
|
||||
|
||||
// Check if exists.
|
||||
_, err := os.Stat(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return dot, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
return dot, e.If(err).ThenIf(func() error {
|
||||
eps := strings.Split(string(data), "\n")
|
||||
for _, epInfo := range eps {
|
||||
arr := strings.Split(epInfo, " ")
|
||||
if len(arr) != 2 {
|
||||
return InvalidDotfile
|
||||
}
|
||||
ep, err1 := strconv.Atoi(arr[0])
|
||||
v, err2 := strconv.Atoi(arr[1])
|
||||
if err1 != nil || err2 != nil {
|
||||
return InvalidDotfile
|
||||
}
|
||||
dot[ep] = v
|
||||
}
|
||||
return nil
|
||||
}).End()
|
||||
}
|
||||
|
||||
func SaveDotfile(path string, dot map[int]int) error {
|
||||
var epInfo = make([]string, len(dot), len(dot))
|
||||
i := 0
|
||||
for ep, v := range dot {
|
||||
epInfo[i] = fmt.Sprintf("%v %v", ep, v)
|
||||
i++
|
||||
}
|
||||
return os.WriteFile(path, []byte(strings.Join(epInfo, "\n")), 0666)
|
||||
}
|
||||
|
||||
func loadConfig(configPath string) Configuration {
|
||||
config.WithOptions(func(options *config.Options) {
|
||||
options.DecoderConfig.TagName = "config"
|
||||
options.ParseEnv = true
|
||||
options.Readonly = true
|
||||
})
|
||||
config.AddDriver(yamlv3.Driver)
|
||||
var conf Configuration
|
||||
e.ExitIf(config.LoadFiles(configPath), "Unable to load config.")
|
||||
config.Decode(&conf)
|
||||
return conf
|
||||
}
|
||||
|
||||
func main() {
|
||||
daemonize := flag.Bool("daemon", false, "Run the daemon")
|
||||
config := flag.String("config", "server.yml", "Config path")
|
||||
flag.Parse()
|
||||
|
||||
if *daemonize {
|
||||
conf := loadConfig(*config)
|
||||
|
||||
f, err := os.OpenFile(conf.LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0744)
|
||||
e.ExitIf(err, "Unable to open log file.")
|
||||
defer f.Close()
|
||||
log.SetOutput(f)
|
||||
|
||||
os.Remove(SocketPath)
|
||||
sock, err := net.Listen("unix", SocketPath)
|
||||
e.ExitIf(err, "Unable to open socket.")
|
||||
defer sock.Close()
|
||||
err = os.Chmod(SocketPath, 0777)
|
||||
|
||||
// For cleanup, in case of signal.
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
go func() {
|
||||
for _ = range c {
|
||||
f.Close()
|
||||
sock.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Run daemon
|
||||
daemon := NewDaemon(
|
||||
conf.RTorrentHost, conf.PollingRate, conf.DownloadDir, sock,
|
||||
conf.NotifyError, filepath.Join(conf.BaseDir, SubscriptionsFile),
|
||||
)
|
||||
daemon.Serve()
|
||||
} else {
|
||||
userCLI()
|
||||
}
|
||||
}
|
||||
6
server.yml
Normal file
6
server.yml
Normal file
@ -0,0 +1,6 @@
|
||||
baseDir: ./
|
||||
rTorrentHost: http://localhost/rTorrent
|
||||
pollingRate: 15
|
||||
downloadDir: /data/Videos/Anime/
|
||||
logPath: log.log
|
||||
notifyError: true
|
||||
Loading…
x
Reference in New Issue
Block a user