Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
be963c7c54 | |||
661e676fd2 | |||
08b56d1c77 | |||
2c42204f90 | |||
dc31122c05 | |||
08d73d72e1 | |||
b559f15eb1 | |||
d864a8c486 | |||
0655700d15 | |||
4cff36e54f | |||
54e5dbab60 | |||
c45e261396 | |||
192479819d | |||
92c4cc6284 | |||
dd211f6077 |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.22
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ release
|
||||
dist
|
||||
.env
|
||||
dist/
|
||||
.DS_Store
|
||||
|
@ -1,6 +1,7 @@
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
- go test ./...
|
||||
|
||||
@ -13,11 +14,11 @@ builds:
|
||||
- darwin
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: [tar.gz]
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- .Os }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
@ -25,16 +26,28 @@ archives:
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
|
||||
changelog:
|
||||
skip: true
|
||||
disable: true
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- "tardisx/gropple:{{ .Tag }}"
|
||||
- "tardisx/gropple:v{{ .Major }}"
|
||||
- "tardisx/gropple:v{{ .Major }}.{{ .Minor }}"
|
||||
- "tardisx/gropple:latest"
|
||||
|
||||
- "tardisx/gropple:{{ .Tag }}-amd64"
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--platform=linux/amd64"
|
||||
- image_templates:
|
||||
- "tardisx/gropple:{{ .Tag }}-arm64"
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--platform=linux/arm64"
|
||||
goarch: arm64
|
||||
|
||||
docker_manifests:
|
||||
- name_template: "tardisx/gropple:{{ .Tag }}"
|
||||
image_templates:
|
||||
- "tardisx/gropple:{{ .Tag }}-amd64"
|
||||
- "tardisx/gropple:{{ .Tag }}-arm64"
|
||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,6 +2,16 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.1.4] - 2025-04-25
|
||||
|
||||
- Minor refactorings, upgrade dependencies
|
||||
- Migrate to goreleaser v2 configuration
|
||||
- Generate arm64 docker builds
|
||||
|
||||
## [v1.1.3] - 2024-03-17
|
||||
|
||||
- Code cleanups, better error checking
|
||||
|
||||
## [v1.1.2] - 2024-03-16
|
||||
|
||||
- Fix a crash for a certain pattern of log line
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:mantic
|
||||
FROM ubuntu:noble
|
||||
COPY gropple /
|
||||
|
||||
RUN apt update && apt install -y curl python3 ffmpeg
|
||||
|
@ -266,14 +266,14 @@ func (cs *ConfigService) LoadConfig() error {
|
||||
path := cs.ConfigPath
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not read config '%s': %v", path, err)
|
||||
return fmt.Errorf("could not read config '%s': %v", path, err)
|
||||
}
|
||||
c := Config{}
|
||||
cs.Config = &c
|
||||
|
||||
err = yaml.Unmarshal(b, &c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse YAML config '%s': %v", path, err)
|
||||
return fmt.Errorf("could not parse YAML config '%s': %v", path, err)
|
||||
}
|
||||
|
||||
// do migrations
|
||||
|
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -166,8 +167,13 @@ profiles:
|
||||
|
||||
func configServiceFromString(configString string) *ConfigService {
|
||||
tmpFile, _ := os.CreateTemp("", "gropple_test_*.yml")
|
||||
tmpFile.Write([]byte(configString))
|
||||
tmpFile.Close()
|
||||
_, err1 := tmpFile.Write([]byte(configString))
|
||||
err2 := tmpFile.Close()
|
||||
|
||||
if errors.Join(err1, err2) != nil {
|
||||
panic("got unexpected error")
|
||||
}
|
||||
|
||||
cs := ConfigService{
|
||||
Config: &Config{},
|
||||
ConfigPath: tmpFile.Name(),
|
||||
@ -204,7 +210,7 @@ func TestLookForExecutable(t *testing.T) {
|
||||
_, err = AbsPathToExecutable(cmd)
|
||||
assert.Error(t, err)
|
||||
|
||||
os.Chdir(cmdDir)
|
||||
os.Chdir(cmdDir) //nolint
|
||||
cmd = "./sleep"
|
||||
path, err = AbsPathToExecutable(cmd)
|
||||
if assert.NoError(t, err) {
|
||||
|
@ -2,7 +2,7 @@ version: "3.9"
|
||||
|
||||
services:
|
||||
gropple:
|
||||
image: tardisx/gropple:latest
|
||||
image: tardisx/gropple:v1.1.4
|
||||
volumes:
|
||||
- /tmp/gropple-config-dir/:/config
|
||||
- /tmp/downloads/:/downloads/
|
||||
|
@ -277,7 +277,7 @@ func (dl *Download) Begin() {
|
||||
}
|
||||
|
||||
// only add the url if it's not empty or an example URL. This helps us with testing
|
||||
if !(dl.Url == "" || strings.Contains(dl.domain(), "example.org")) {
|
||||
if dl.Url != "" && !strings.Contains(dl.domain(), "example.org") {
|
||||
cmdSlice = append(cmdSlice, dl.Url)
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,11 @@ func TestUpdateMetadata(t *testing.T) {
|
||||
// eta's might be xx:xx:xx or xx:xx
|
||||
newD.updateMetadata("[download] 0.0% of 504.09MiB at 135.71KiB/s ETA 01:03:36")
|
||||
if newD.Eta != "01:03:36" {
|
||||
t.Fatalf("bad long eta in dl\n%#v", newD)
|
||||
t.Fatalf("bad long eta in dl\n%#v", newD) //nolint
|
||||
}
|
||||
newD.updateMetadata("[download] 0.0% of 504.09MiB at 397.98KiB/s ETA 21:38")
|
||||
if newD.Eta != "21:38" {
|
||||
t.Fatalf("bad short eta in dl\n%#v", newD)
|
||||
t.Fatalf("bad short eta in dl\n%#v", newD) //nolint
|
||||
}
|
||||
|
||||
// added a new file, now we are tracking two
|
||||
@ -44,7 +44,7 @@ func TestUpdateMetadata(t *testing.T) {
|
||||
// different download
|
||||
newD.updateMetadata("[download] 99.3% of ~1.42GiB at 320.87KiB/s ETA 00:07 (frag 212/214)")
|
||||
if newD.Eta != "00:07" {
|
||||
t.Fatalf("bad short eta in dl with frag\n%v", newD)
|
||||
t.Fatalf("bad short eta in dl with frag\n%v", newD) //nolint
|
||||
}
|
||||
|
||||
// [FixupM3u8] Fixing MPEG-TS in MP4 container of "file [-168849776_456239489].mp4"
|
||||
|
8
go.mod
8
go.mod
@ -1,11 +1,13 @@
|
||||
module github.com/tardisx/gropple
|
||||
|
||||
go 1.20
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/mod v0.14.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/mod v0.24.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
|
8
go.sum
8
go.sum
@ -4,10 +4,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
|
11
main.go
11
main.go
@ -9,13 +9,18 @@ import (
|
||||
|
||||
"github.com/tardisx/gropple/config"
|
||||
"github.com/tardisx/gropple/download"
|
||||
"github.com/tardisx/gropple/version"
|
||||
v "github.com/tardisx/gropple/version"
|
||||
"github.com/tardisx/gropple/web"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
)
|
||||
|
||||
func main() {
|
||||
versionInfo := &version.Manager{
|
||||
VersionInfo: version.Info{CurrentVersion: "v1.1.2"},
|
||||
versionInfo := &v.Manager{
|
||||
// version from goreleaser has no leading 'v', even if the tag does
|
||||
VersionInfo: v.Info{CurrentVersion: "v" + version},
|
||||
}
|
||||
log.Printf("Starting gropple %s - https://github.com/tardisx/gropple", versionInfo.GetInfo().CurrentVersion)
|
||||
|
||||
|
70
web/web.go
70
web/web.go
@ -94,7 +94,7 @@ func homeHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ func homeHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -157,7 +157,7 @@ func configHandler() func(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ func configHandler() func(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -181,7 +181,7 @@ func configRESTHandler(cs *config.ConfigService) func(w http.ResponseWriter, r *
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
err = cs.Config.UpdateFromJSON(b)
|
||||
@ -238,7 +238,7 @@ func fetchInfoOneRESTHandler(cs *config.ConfigService, dm *download.Manager) fun
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -291,7 +291,7 @@ func fetchInfoRESTHandler(dm *download.Manager) func(w http.ResponseWriter, r *h
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
@ -330,7 +330,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -340,7 +340,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
return
|
||||
@ -353,23 +353,29 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
}
|
||||
|
||||
req := reqType{}
|
||||
json.NewDecoder(r.Body).Decode(&req)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
log.Printf("error decoding body of request: %s", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("popup POST request: %#v", req)
|
||||
|
||||
if req.URL == "" {
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_ = json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: "No URL supplied",
|
||||
})
|
||||
|
||||
return
|
||||
} else {
|
||||
|
||||
if req.ProfileChosen == "" {
|
||||
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
_ = json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: "you must choose a profile",
|
||||
})
|
||||
@ -379,7 +385,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
profile := cs.Config.ProfileCalled(req.ProfileChosen)
|
||||
if profile == nil {
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
_ = json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("no such profile: '%s'", req.ProfileChosen),
|
||||
})
|
||||
@ -397,7 +403,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
dm.Queue(newDL)
|
||||
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(queuedResponse{
|
||||
_ = json.NewEncoder(w).Encode(queuedResponse{
|
||||
Success: true,
|
||||
Location: fmt.Sprintf("/fetch/%d", id),
|
||||
})
|
||||
@ -410,7 +416,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
|
||||
if !present {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprint(w, "No url supplied")
|
||||
_, _ = fmt.Fprint(w, "No url supplied")
|
||||
return
|
||||
}
|
||||
|
||||
@ -418,7 +424,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
templateData := map[string]interface{}{"config": cs.Config, "url": url[0], "Version": vm.GetInfo()}
|
||||
@ -427,7 +433,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@ -441,13 +447,14 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
log.Printf("bulkHandler")
|
||||
|
||||
method := r.Method
|
||||
if method == "GET" {
|
||||
switch method {
|
||||
case "GET":
|
||||
|
||||
t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/bulk.tmpl")
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
templateData := map[string]interface{}{"config": cs.Config, "Version": vm.GetInfo()}
|
||||
@ -456,13 +463,12 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
} else if method == "POST" {
|
||||
case "POST":
|
||||
type reqBulkType struct {
|
||||
URLs string `json:"urls"`
|
||||
ProfileChosen string `json:"profile"`
|
||||
@ -470,13 +476,19 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
}
|
||||
|
||||
req := reqBulkType{}
|
||||
json.NewDecoder(r.Body).Decode(&req)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
log.Printf("error decoding request body: %s", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("bulk POST request: %#v", req)
|
||||
|
||||
if req.URLs == "" {
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
_ = json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: "No URLs supplied",
|
||||
})
|
||||
@ -486,7 +498,7 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
if req.ProfileChosen == "" {
|
||||
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
_ = json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: "you must choose a profile",
|
||||
})
|
||||
@ -496,7 +508,7 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
profile := cs.Config.ProfileCalled(req.ProfileChosen)
|
||||
if profile == nil {
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
_ = json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("no such profile: '%s'", req.ProfileChosen),
|
||||
})
|
||||
@ -522,7 +534,7 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(successResponse{
|
||||
_ = json.NewEncoder(w).Encode(successResponse{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("queued %d downloads", count),
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user