Compare commits
20 Commits
99c1061a18
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ac13fd57 | ||
|
|
375fc1000a | ||
|
|
47c7aa74d3 | ||
|
|
a6aad2820d | ||
|
|
b26afcb325 | ||
|
|
613bfdf96e | ||
|
|
81507319dd | ||
|
|
50ad3bb3db | ||
|
|
07992b6636 | ||
|
|
9583234df5 | ||
|
|
2943362587 | ||
|
|
cbd4e32022 | ||
|
|
727b4fdea6 | ||
|
|
fd7dcafd4e | ||
|
|
4fbc96b96f | ||
|
|
0afb6535b6 | ||
|
|
10a40d4a54 | ||
|
|
4bdfbd1f06 | ||
|
|
44bcc0ba2e | ||
|
|
b17801060e |
3
install_scratch.sh
Normal file
3
install_scratch.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
CGO_ENABLED=1 CC=x86_64-linux-musl-gcc go install -ldflags="-linkmode external -extldflags '-static'"
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Entrypoint(ctx context.Context, p string) error {
|
func Entrypoint(ctx context.Context, p string) error {
|
||||||
@@ -46,69 +47,19 @@ func deport(ctx context.Context, p string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asses, err := filepath.Glob(path.Join(
|
if err := BestAssToSRT(ctx, p); err != nil {
|
||||||
path.Dir(p),
|
|
||||||
fmt.Sprintf(".%s.*.ass", path.Base(p)),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, ass := range asses {
|
|
||||||
srt := fmt.Sprintf("%s.srt", strings.TrimSuffix(ass, ".ass"))
|
|
||||||
if _, err := os.Stat(srt); err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ffmpeg(ctx, "-y", "-i", ass, srt); err != nil {
|
|
||||||
if ctx.Err() == nil {
|
|
||||||
log.Printf("ffmpeg failed to process %s; removing", ass)
|
|
||||||
os.Remove(ass)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := os.ReadFile(srt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
before := len(b)
|
|
||||||
b = regexp.MustCompile(`size="[^"]*"`).ReplaceAll(b, []byte{})
|
|
||||||
if after := len(b); before == after {
|
|
||||||
} else if err := os.WriteFile(srt, b, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
srts, err := filepath.Glob(path.Join(
|
|
||||||
path.Dir(p),
|
|
||||||
fmt.Sprintf(".%s.*.srt", path.Base(p)),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slices.SortFunc(srts, func(a, b string) int {
|
|
||||||
// if skip a { return 1 }
|
|
||||||
// if skip b { return -1 }
|
|
||||||
// return -1 * (wc(a) - wc(b))
|
|
||||||
return strings.Compare(a, b)
|
|
||||||
})
|
|
||||||
for i := range srts {
|
|
||||||
if i == 0 {
|
|
||||||
base := path.Base(p)
|
base := path.Base(p)
|
||||||
withoutExt := strings.TrimSuffix(base, path.Ext(base))
|
withoutExt := strings.TrimSuffix(base, path.Ext(base))
|
||||||
|
|
||||||
srt := path.Join(path.Dir(p), fmt.Sprintf("%s.srt", withoutExt))
|
|
||||||
if err := os.Rename(srts[i], srt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p2 := path.Join(path.Dir(p), fmt.Sprintf("%s.subless.mkv", withoutExt))
|
p2 := path.Join(path.Dir(p), fmt.Sprintf("%s.subless.mkv", withoutExt))
|
||||||
args := []string{
|
args := []string{
|
||||||
"-i", p,
|
"-i", p,
|
||||||
"-map", "0",
|
"-map", "0",
|
||||||
}
|
}
|
||||||
for _, assStreamID := range assStreamIDs {
|
for _, assStream := range assStreams {
|
||||||
args = append(args, "-map", "-"+assStreamID)
|
args = append(args, "-map", "-"+assStream.id)
|
||||||
}
|
}
|
||||||
args = append(args,
|
args = append(args,
|
||||||
"-c", "copy",
|
"-c", "copy",
|
||||||
@@ -119,10 +70,6 @@ func deport(ctx context.Context, p string) error {
|
|||||||
} else if err := os.Rename(p2, p); err != nil {
|
} else if err := os.Rename(p2, p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
os.Remove(srts[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -186,3 +133,115 @@ func execc(ctx context.Context, bin string, args ...string) (string, error) {
|
|||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
return string(stdout.Bytes()), err
|
return string(stdout.Bytes()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BestAssToSRT(ctx context.Context, p string) error {
|
||||||
|
asses, err := filepath.Glob(path.Join(
|
||||||
|
path.Dir(p),
|
||||||
|
fmt.Sprintf(".%s.*.ass", path.Base(p)),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srts := []string{}
|
||||||
|
for _, ass := range asses {
|
||||||
|
srt, err := assToSRT(ctx, ass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srts = append(srts, srt)
|
||||||
|
}
|
||||||
|
|
||||||
|
srts = SRTsByGoodness(srts)
|
||||||
|
|
||||||
|
for i := range srts {
|
||||||
|
if i == 0 {
|
||||||
|
base := path.Base(p)
|
||||||
|
withoutExt := strings.TrimSuffix(base, path.Ext(base))
|
||||||
|
|
||||||
|
srt := path.Join(path.Dir(p), fmt.Sprintf("%s.srt", withoutExt))
|
||||||
|
if err := os.Rename(srts[i], srt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.Remove(srts[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assToSRT(ctx context.Context, ass string) (string, error) {
|
||||||
|
ctx, can := context.WithTimeout(ctx, 30*time.Second)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
srt := fmt.Sprintf("%s.srt", strings.TrimSuffix(ass, ".ass"))
|
||||||
|
if _, err := os.Stat(srt); err == nil {
|
||||||
|
return srt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ffmpeg(ctx, "-y", "-i", ass, srt); err != nil {
|
||||||
|
if ctx.Err() == nil {
|
||||||
|
log.Printf("ffmpeg failed to process %s; removing", ass)
|
||||||
|
os.Remove(ass)
|
||||||
|
}
|
||||||
|
return srt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(srt)
|
||||||
|
if err != nil {
|
||||||
|
return srt, err
|
||||||
|
}
|
||||||
|
before := len(b)
|
||||||
|
b = regexp.MustCompile(`size="[^"]*"`).ReplaceAll(b, []byte{})
|
||||||
|
if after := len(b); before == after {
|
||||||
|
} else if err := os.WriteFile(srt, b, os.ModePerm); err != nil {
|
||||||
|
return srt, err
|
||||||
|
}
|
||||||
|
return srt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SRTsByGoodness(srts []string) []string {
|
||||||
|
skippers := []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(?i)lat.*amer`),
|
||||||
|
regexp.MustCompile(`(?i)signs`),
|
||||||
|
regexp.MustCompile(`(?i)rus`),
|
||||||
|
regexp.MustCompile(`(?i)por`),
|
||||||
|
regexp.MustCompile(`(?i)ita`),
|
||||||
|
regexp.MustCompile(`(?i)fre`),
|
||||||
|
regexp.MustCompile(`(?i)spa`),
|
||||||
|
regexp.MustCompile(`(?i)ger`),
|
||||||
|
regexp.MustCompile(`(?i)ara`),
|
||||||
|
regexp.MustCompile(`(?i)jpn`),
|
||||||
|
regexp.MustCompile(`(?i)urop`),
|
||||||
|
regexp.MustCompile(`(?i)razil`),
|
||||||
|
regexp.MustCompile(`(?i)Deu`),
|
||||||
|
regexp.MustCompile(`(?i)ara`),
|
||||||
|
}
|
||||||
|
|
||||||
|
keepers := []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(?i)^eng$`),
|
||||||
|
}
|
||||||
|
|
||||||
|
srts = slices.Clone(srts)
|
||||||
|
slices.SortFunc(srts, func(a, b string) int {
|
||||||
|
a = strings.ToLower(a)
|
||||||
|
b = strings.ToLower(b)
|
||||||
|
for _, skipper := range skippers {
|
||||||
|
if skipper.MatchString(b) {
|
||||||
|
return -1
|
||||||
|
} else if skipper.MatchString(a) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, keeper := range keepers {
|
||||||
|
if keeper.MatchString(a) {
|
||||||
|
return -1
|
||||||
|
} else if keeper.MatchString(b) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Compare(a, b)
|
||||||
|
})
|
||||||
|
return srts
|
||||||
|
}
|
||||||
|
|||||||
47
src/asses/deport_test.go
Normal file
47
src/asses/deport_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package asses_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"show-rss/src/asses"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSRTsByGoodness(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
given []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
"eng": {
|
||||||
|
given: []string{"a", "eng"},
|
||||||
|
want: "eng",
|
||||||
|
},
|
||||||
|
"eng nocap": {
|
||||||
|
given: []string{"A", "eng"},
|
||||||
|
want: "eng",
|
||||||
|
},
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:9.ita.ass": {
|
||||||
|
given: []string{
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:10.rus.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:2.eng.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:3.por.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:4.spa.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:5.spa.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:6.ara.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:7.fre.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:8.ger.srt",
|
||||||
|
".Apothecary_Diaries_S02E19.mkv.0:9.ita.srt",
|
||||||
|
},
|
||||||
|
want: ".Apothecary_Diaries_S02E19.mkv.0:2.eng.srt",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
name := name
|
||||||
|
c := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := asses.SRTsByGoodness(c.given)
|
||||||
|
if got[0] != c.want {
|
||||||
|
t.Errorf("expected %s but got %s (%+v)", c.want, got[0], got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,9 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"show-rss/src/slow"
|
"show-rss/src/slow"
|
||||||
"strconv"
|
"strconv"
|
||||||
"golang.org/x/time/rate"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var EnvCksumBPS = func() int {
|
var EnvCksumBPS = func() int {
|
||||||
@@ -36,7 +37,7 @@ func One(ctx context.Context, p string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
threshold := 20 + rand.New(rand.NewSource(func() int64{
|
threshold := 20 + rand.New(rand.NewSource(func() int64 {
|
||||||
b := md5.New().Sum([]byte(p))
|
b := md5.New().Sum([]byte(p))
|
||||||
var sum int64
|
var sum int64
|
||||||
for _, c := range b {
|
for _, c := range b {
|
||||||
@@ -45,7 +46,7 @@ func One(ctx context.Context, p string) error {
|
|||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
}())).Int()%10
|
}())).Int()%10
|
||||||
if daysSince := int(time.Since(last.T).Hours()/24); daysSince > threshold {
|
if daysSince := int(time.Since(last.T).Hours() / 24); daysSince > threshold {
|
||||||
log.Printf("asses.One(%s) // no modified check as %vd since last check", shortp, daysSince)
|
log.Printf("asses.One(%s) // no modified check as %vd since last check", shortp, daysSince)
|
||||||
} else if stat, err := os.Stat(p); err != nil {
|
} else if stat, err := os.Stat(p); err != nil {
|
||||||
return fmt.Errorf("cannot stat %s: %w", p, err)
|
return fmt.Errorf("cannot stat %s: %w", p, err)
|
||||||
@@ -81,6 +82,11 @@ func One(ctx context.Context, p string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("asses.transcode(%s)...", shortp)
|
||||||
|
if err := transcode(ctx, p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}(); err != nil {
|
}(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
func TestOne(t *testing.T) {
|
func TestOne(t *testing.T) {
|
||||||
ctx := db.Test(t, context.Background())
|
ctx := db.Test(t, context.Background())
|
||||||
|
|
||||||
|
os.Setenv("DO_TRANSCODE", "true")
|
||||||
|
|
||||||
d := t.TempDir()
|
d := t.TempDir()
|
||||||
b, _ := os.ReadFile(path.Join("testdata", "survivor_au_S11E12.smoller.mkv"))
|
b, _ := os.ReadFile(path.Join("testdata", "survivor_au_S11E12.smoller.mkv"))
|
||||||
p := path.Join(d, "f.mkv")
|
p := path.Join(d, "f.mkv")
|
||||||
|
|||||||
64
src/asses/transcode.go
Normal file
64
src/asses/transcode.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package asses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EntrypointTranscode(ctx context.Context, p string) error {
|
||||||
|
return transcode(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transcode(ctx context.Context, p string) error {
|
||||||
|
if os.Getenv("NO_TRANSCODE") != "" || os.Getenv("DO_TRANSCODE") == "" {
|
||||||
|
log.Printf("would transcode %s but $NO_TRANSCODE=x or $DO_TRANSCODE=", p)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := ffprobe(ctx, "-i", p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h264 := slices.ContainsFunc(strings.Split(output, "\n"), func(line string) bool {
|
||||||
|
return strings.Contains(line, "tream #") && strings.Contains(line, "Video: ") && strings.Contains(line, "h264")
|
||||||
|
})
|
||||||
|
aac := slices.ContainsFunc(strings.Split(output, "\n"), func(line string) bool {
|
||||||
|
return strings.Contains(line, "tream #") && strings.Contains(line, "Audio: ") && strings.Contains(line, "aac")
|
||||||
|
})
|
||||||
|
|
||||||
|
if h264 && aac {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p2 := p + ".en" + path.Ext(p)
|
||||||
|
if err := ffmpeg(ctx, "-y",
|
||||||
|
"-i", p,
|
||||||
|
"-vcodec", "libx264",
|
||||||
|
"-acodec", "aac",
|
||||||
|
p2,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
output2, err := ffprobe(ctx, "-i", p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
df := func(line string) bool {
|
||||||
|
return !strings.Contains(line, "tream #")
|
||||||
|
}
|
||||||
|
originalStreams := slices.DeleteFunc(strings.Split(output, "\n"), df)
|
||||||
|
newStreams := slices.DeleteFunc(strings.Split(output2, "\n"), df)
|
||||||
|
if len(originalStreams) != len(newStreams) {
|
||||||
|
return fmt.Errorf("stream count changed from transcode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(p2, p)
|
||||||
|
}
|
||||||
@@ -47,6 +47,27 @@ func Main(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
case BestAssToSRT:
|
||||||
|
errs := []string{}
|
||||||
|
for _, pos := range flags.Pos {
|
||||||
|
if err := inass.BestAssToSRT(ctx, pos); err != nil {
|
||||||
|
err = fmt.Errorf("[%s] %w", pos, err)
|
||||||
|
log.Println(err)
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("errors: %+v", errs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case Transcode:
|
||||||
|
for _, pos := range flags.Pos {
|
||||||
|
log.Printf("transcoding %q...", pos)
|
||||||
|
if err := inass.EntrypointTranscode(ctx, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
panic(flags.Entrypoint.String())
|
panic(flags.Entrypoint.String())
|
||||||
}
|
}
|
||||||
@@ -58,6 +79,8 @@ type Entrypoint int
|
|||||||
const (
|
const (
|
||||||
Defacto Entrypoint = iota
|
Defacto Entrypoint = iota
|
||||||
DeportAss
|
DeportAss
|
||||||
|
BestAssToSRT
|
||||||
|
Transcode
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *Entrypoint) Set(s string) error {
|
func (e *Entrypoint) Set(s string) error {
|
||||||
@@ -66,10 +89,16 @@ func (e *Entrypoint) Set(s string) error {
|
|||||||
*e = Defacto
|
*e = Defacto
|
||||||
case DeportAss.String():
|
case DeportAss.String():
|
||||||
*e = DeportAss
|
*e = DeportAss
|
||||||
|
case BestAssToSRT.String():
|
||||||
|
*e = BestAssToSRT
|
||||||
|
case Transcode.String():
|
||||||
|
*e = Transcode
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s nin (%s)", s, strings.Join([]string{
|
return fmt.Errorf("%s nin (%s)", s, strings.Join([]string{
|
||||||
Defacto.String(),
|
Defacto.String(),
|
||||||
DeportAss.String(),
|
DeportAss.String(),
|
||||||
|
BestAssToSRT.String(),
|
||||||
|
Transcode.String(),
|
||||||
}, ", "))
|
}, ", "))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -81,6 +110,10 @@ func (e Entrypoint) String() string {
|
|||||||
return ""
|
return ""
|
||||||
case DeportAss:
|
case DeportAss:
|
||||||
return "deport-ass"
|
return "deport-ass"
|
||||||
|
case BestAssToSRT:
|
||||||
|
return "best-ass-to-srt"
|
||||||
|
case Transcode:
|
||||||
|
return "transcode"
|
||||||
}
|
}
|
||||||
panic("cannot serialize entrypoint")
|
panic("cannot serialize entrypoint")
|
||||||
}
|
}
|
||||||
@@ -99,6 +132,5 @@ func runner(ctx context.Context, k string, foo func(context.Context) error) func
|
|||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,26 @@ import (
|
|||||||
|
|
||||||
func (h Handler) feeds(w http.ResponseWriter, r *http.Request) error {
|
func (h Handler) feeds(w http.ResponseWriter, r *http.Request) error {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
case http.MethodDelete:
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := h.feedsDelete(r.Context(), r.URL.Query().Get("id")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case http.MethodPost, http.MethodPut:
|
case http.MethodPost, http.MethodPut:
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if r.URL.Query().Has("delete") {
|
||||||
|
if err := h.feedsDelete(r.Context(), r.URL.Query().Get("id")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := h.feedsUpsert(r.Context(), r.URL.Query().Get("id"), r.Form); err != nil {
|
if err := h.feedsUpsert(r.Context(), r.URL.Query().Get("id"), r.Form); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return nil
|
return nil
|
||||||
@@ -29,6 +42,10 @@ func (h Handler) feeds(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h Handler) feedsDelete(ctx context.Context, id string) error {
|
||||||
|
return feeds.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (h Handler) feedsUpsert(ctx context.Context, id string, form url.Values) error {
|
func (h Handler) feedsUpsert(ctx context.Context, id string, form url.Values) error {
|
||||||
var req feeds.Version
|
var req feeds.Version
|
||||||
for k, v := range map[string]*string{
|
for k, v := range map[string]*string{
|
||||||
|
|||||||
@@ -23,7 +23,13 @@
|
|||||||
{{ if eq "" .editing.ID }}
|
{{ if eq "" .editing.ID }}
|
||||||
New
|
New
|
||||||
{{ else }}
|
{{ else }}
|
||||||
Update <code><a target="_blank" href="{{ .editing_url }}">{{ .editing.URL }}</a></code> (<a href="?">clear</a>)
|
Updating <code><a target="_blank" href="{{ .editing_url }}">{{ .editing.URL }}</a></code> (<a href="?">clear</a>)
|
||||||
|
<br>
|
||||||
|
<div style="scale: 0.85">
|
||||||
|
<form method="POST" action="/v1/feeds?id={{ .editing.ID }}&delete">
|
||||||
|
<button type="submit">DELETE</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</h3>
|
</h3>
|
||||||
<form method="POST" action="/v1/feeds?id={{ .editing.ID }}">
|
<form method="POST" action="/v1/feeds?id={{ .editing.ID }}">
|
||||||
@@ -17,17 +17,17 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed testdata/index.tmpl
|
//go:embed public/index.tmpl
|
||||||
var embeddedIndexTMPL string
|
var embeddedIndexTMPL string
|
||||||
|
|
||||||
//go:embed testdata/*
|
//go:embed public/*
|
||||||
var embeddedDir embed.FS
|
var embeddedDir embed.FS
|
||||||
|
|
||||||
var dir = func() string {
|
var dir = func() string {
|
||||||
if v := os.Getenv("UI_D"); v != "" {
|
if v := os.Getenv("UI_D"); v != "" {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
return "./src/cmd/server/handler/testdata"
|
return "./src/cmd/server/handler/public"
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func (h Handler) ui(w http.ResponseWriter, r *http.Request) error {
|
func (h Handler) ui(w http.ResponseWriter, r *http.Request) error {
|
||||||
@@ -37,7 +37,7 @@ func (h Handler) ui(w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
w.Header().Set("Cache-Control", "max-age=2592000")
|
w.Header().Set("Cache-Control", "max-age=2592000")
|
||||||
fs := http.FileServer(http.FS(embeddedDir))
|
fs := http.FileServer(http.FS(embeddedDir))
|
||||||
r.URL.Path = fmt.Sprintf("/testdata/%s", strings.TrimPrefix(r.URL.Path, "/experimental/ui"))
|
r.URL.Path = fmt.Sprintf("/public/%s", strings.TrimPrefix(r.URL.Path, "/experimental/ui"))
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,9 @@ func (h Handler) uiIndex(w http.ResponseWriter, r *http.Request) error {
|
|||||||
editing.Version.WebhookURL = "vpntor:///data/completed-rss/TITLE"
|
editing.Version.WebhookURL = "vpntor:///data/completed-rss/TITLE"
|
||||||
all := []feeds.Feed{}
|
all := []feeds.Feed{}
|
||||||
if err := feeds.ForEach(ctx, func(f feeds.Feed) error {
|
if err := feeds.ForEach(ctx, func(f feeds.Feed) error {
|
||||||
|
if deleted := f.Version.URL == ""; !deleted {
|
||||||
all = append(all, f)
|
all = append(all, f)
|
||||||
|
}
|
||||||
if f.Entry.ID == r.URL.Query().Get("edit") {
|
if f.Entry.ID == r.URL.Query().Get("edit") {
|
||||||
editing.Entry = f.Entry
|
editing.Entry = f.Entry
|
||||||
editing.Version = f.Version
|
editing.Version = f.Version
|
||||||
|
|||||||
@@ -72,18 +72,27 @@ func ForEach(ctx context.Context, cb func(Feed) error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errs []string
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
feed, err := Get(ctx, id.ID)
|
feed, err := Get(ctx, id.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err := cb(feed); err != nil {
|
} else if err := cb(feed); err != nil {
|
||||||
return err
|
errs = append(errs, fmt.Sprintf(`failed to fetch %s: %v`, id.ID, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("failed some callbacks: %+v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Delete(ctx context.Context, id string) error {
|
||||||
|
return Update(ctx, id, "", "", "", "", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
func Get(ctx context.Context, id string) (Feed, error) {
|
func Get(ctx context.Context, id string) (Feed, error) {
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return Feed{}, err
|
return Feed{}, err
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (feed Feed) ShouldExecute() (bool, error) {
|
func (feed Feed) ShouldExecute() (bool, error) {
|
||||||
if !feed.Entry.Deleted.IsZero() {
|
if !feed.Entry.Deleted.IsZero() || feed.Version.URL == "" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
next, err := feed.Next()
|
next, err := feed.Next()
|
||||||
|
|||||||
Reference in New Issue
Block a user