aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2023-07-26 11:57:32 -0400
committerXe Iaso <me@xeiaso.net>2023-07-26 11:57:32 -0400
commit1640e2c5b0b070cb2118aade61084e092947f85f (patch)
tree007d80a5b0fa429b388970d67c8780932522f63b
parent4b1553f221474f22a7176c1c9dab5e9be00ef47d (diff)
downloadx-1640e2c5b0b070cb2118aade61084e092947f85f.tar.xz
x-1640e2c5b0b070cb2118aade61084e092947f85f.zip
cmd: add new command sanguisuga
Signed-off-by: Xe Iaso <me@xeiaso.net>
-rw-r--r--cmd/sanguisuga/.gitignore2
-rw-r--r--cmd/sanguisuga/config.default.ts55
-rw-r--r--cmd/sanguisuga/config.go32
-rw-r--r--cmd/sanguisuga/main.go298
-rw-r--r--go.mod9
-rw-r--r--go.sum24
-rw-r--r--gomod2nix.toml25
7 files changed, 440 insertions, 5 deletions
diff --git a/cmd/sanguisuga/.gitignore b/cmd/sanguisuga/.gitignore
new file mode 100644
index 0000000..5841fb6
--- /dev/null
+++ b/cmd/sanguisuga/.gitignore
@@ -0,0 +1,2 @@
+config.ts
+data.json
diff --git a/cmd/sanguisuga/config.default.ts b/cmd/sanguisuga/config.default.ts
new file mode 100644
index 0000000..12b8632
--- /dev/null
+++ b/cmd/sanguisuga/config.default.ts
@@ -0,0 +1,55 @@
+export type IRC = {
+ server: string;
+ password: string;
+ channel: string;
+ nick: string;
+ user: string;
+ real: string;
+};
+
+export type Show = {
+ title: string;
+ diskPath: string;
+ quality: string;
+};
+
+export type Transmission = {
+ host: string;
+ user: string;
+ password: string;
+ https: bool;
+ rpcURI: string;
+};
+
+export type Config = {
+ irc: IRC;
+ transmission: Transmission;
+ shows: Show[];
+ rssKey: string;
+};
+
+export default {
+ irc: {
+ server: "",
+ password: "",
+ channel: "",
+ nick: "",
+ user: "",
+ real: ""
+ },
+ transmission: {
+ host: "",
+ user: "",
+ password: "",
+ https: true,
+ rpcURI: "/transmission/rpc"
+ },
+ shows: [
+ {
+ title: "Show Name",
+ diskPath: "/data/TV/Show Name",
+ quality: "1080p"
+ }
+ ],
+ rssKey: "",
+} satisfies Config;
diff --git a/cmd/sanguisuga/config.go b/cmd/sanguisuga/config.go
new file mode 100644
index 0000000..589501d
--- /dev/null
+++ b/cmd/sanguisuga/config.go
@@ -0,0 +1,32 @@
+package main
+
+type IRC struct {
+ Server string `json:"server"`
+ Password string `json:"password"`
+ Channel string `json:"channel"`
+ Regex string `json:"regex"`
+ Nick string `json:"nick"`
+ User string `json:"user"`
+ Real string `json:"real"`
+}
+
+type Show struct {
+ Title string `json:"title"`
+ DiskPath string `json:"diskPath"`
+ Quality string `json:"quality"`
+}
+
+type Transmission struct {
+ Host string `json:"host"`
+ User string `json:"user"`
+ Password string `json:"password"`
+ HTTPS bool `json:"https"`
+ RPCURI string `json:"rpcURI"`
+}
+
+type Config struct {
+ IRC IRC `json:"irc"`
+ Transmission Transmission `json:"transmission"`
+ Shows []Show `json:"shows"`
+ RSSKey string `json:"rssKey"`
+}
diff --git a/cmd/sanguisuga/main.go b/cmd/sanguisuga/main.go
new file mode 100644
index 0000000..e6f64f3
--- /dev/null
+++ b/cmd/sanguisuga/main.go
@@ -0,0 +1,298 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/hekmon/transmissionrpc/v2"
+ irc "github.com/thoj/go-ircevent"
+ "go.jetpack.io/tyson"
+ "golang.org/x/exp/slog"
+ "tailscale.com/hostinfo"
+ "tailscale.com/jsondb"
+ "within.website/x/internal"
+)
+
+var (
+ dbLoc = flag.String("db-loc", "./data.json", "path to data file")
+ tysonConfig = flag.String("tyson-config", "./config.ts", "path to configuration secrets (TySON)")
+ slogLevel = flag.String("slog-level", "INFO", "log level")
+
+ annRegex = regexp.MustCompile(`^New Torrent Announcement: <([^>]*)>\s+Name:'(.*)' uploaded by '.*' ?(freeleech)?\s+-\s+https://\w+.\w+.\w+./\w+./([0-9]+)$`)
+ showName = regexp.MustCompile(`^(.*)\s+(S[0-9]+E[0-9]+)\s+([0-9]+p)\s+(\w+)\s+(.*)$`)
+)
+
+type ShowMeta struct {
+ Name string
+ SeasonEpisode *SeasonEpisode
+ Quality string
+ Kind string
+ Group string
+}
+
+func (sm ShowMeta) StateKey() string {
+ return fmt.Sprintf("%s %s", sm.Name, sm.SeasonEpisode)
+}
+
+func ParseShowMeta(input string) (*ShowMeta, error) {
+ match := showName.FindStringSubmatch(input)
+
+ if match == nil {
+ return nil, fmt.Errorf("invalid input for TV show name: %q", input)
+ }
+
+ result := ShowMeta{
+ Name: strings.TrimSpace(match[1]),
+ Quality: match[3],
+ Kind: match[4],
+ Group: match[5],
+ }
+
+ se, err := ParseSeasonEpisode(match[2])
+ if err != nil {
+ return nil, err
+ }
+
+ result.SeasonEpisode = se
+
+ return &result, nil
+}
+
+type SeasonEpisode struct {
+ Season string
+ Episode string
+}
+
+func (se SeasonEpisode) GetFormattedSeason() string {
+ return "Season " + se.Season
+}
+
+func (se *SeasonEpisode) String() string {
+ return "S" + se.Season + "E" + se.Episode
+}
+
+func ParseSeasonEpisode(input string) (*SeasonEpisode, error) {
+ re := regexp.MustCompile(`S([0-9]+)E([0-9]+)`)
+ match := re.FindStringSubmatch(input)
+
+ if match == nil {
+ return nil, fmt.Errorf("invalid input for SeasonEpisode: %q", input)
+ }
+
+ season := match[1]
+ episode := match[2]
+ se := &SeasonEpisode{
+ Season: season,
+ Episode: episode,
+ }
+
+ return se, nil
+}
+
+func ConvertURL(torrentID, rssKey, name string) string {
+ name = strings.ReplaceAll(name, " ", ".") + ".torrent"
+ return fmt.Sprintf("https://torrentleech.org/rss/download/%s/%s/%s", torrentID, rssKey, name)
+}
+
+type TorrentAnnouncement struct {
+ Category string
+ Name string
+ Freeleech bool
+ TorrentID string
+}
+
+func ParseTorrentAnnouncement(input string) (*TorrentAnnouncement, error) {
+ match := annRegex.FindStringSubmatch(input)
+
+ if match == nil {
+ return nil, fmt.Errorf("invalid torrent announcement format")
+ }
+
+ torrent := &TorrentAnnouncement{
+ Category: match[1],
+ Name: strings.TrimSpace(match[2]),
+ Freeleech: match[3] != "",
+ TorrentID: match[4],
+ }
+
+ return torrent, nil
+}
+
+func main() {
+ internal.HandleStartup()
+ hostinfo.SetApp("within.website/x/cmd/sanguisuga")
+
+ var programLevel slog.Level
+ if err := (&programLevel).UnmarshalText([]byte(*slogLevel)); err != nil {
+ fmt.Fprintf(os.Stderr, "invalid log level %s: %v, using info\n", *slogLevel, err)
+ programLevel = slog.LevelInfo
+ }
+
+ h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
+ AddSource: true,
+ Level: programLevel,
+ })
+ slog.SetDefault(slog.New(h))
+
+ var c Config
+ if err := tyson.Unmarshal(*tysonConfig, &c); err != nil {
+ slog.Error("can't unmarshal config", "err", err)
+ os.Exit(1)
+ }
+
+ db, err := jsondb.Open[State](*dbLoc)
+ if err != nil {
+ slog.Error("can't set up database", "err", err)
+ os.Exit(1)
+ }
+ if db.Data == nil {
+ db.Data = &State{
+ Seen: map[string]TorrentAnnouncement{},
+ }
+ }
+ if err := db.Save(); err != nil {
+ slog.Error("can't ping database", "err", err)
+ os.Exit(1)
+ }
+
+ tm, err := transmissionrpc.New(c.Transmission.Host, c.Transmission.User, c.Transmission.Password, &transmissionrpc.AdvancedConfig{
+ Port: 443,
+ HTTPS: c.Transmission.HTTPS,
+ RPCURI: c.Transmission.RPCURI,
+ })
+ if err != nil {
+ slog.Error("can't connect to transmission", "err", err)
+ os.Exit(1)
+ }
+ _ = tm
+
+ portOpen, err := tm.PortTest(context.Background())
+ if err != nil {
+ slog.Error("can't test if port is open", "err", err)
+ os.Exit(1)
+ }
+
+ slog.Info("port status", "open", portOpen)
+
+ s := &Sanguisuga{
+ Config: c,
+ tc: tm,
+ db: db,
+ }
+
+ ircCli := irc.IRC(c.IRC.Nick, c.IRC.User)
+ ircCli.Password = c.IRC.Password
+ ircCli.RealName = c.IRC.Real
+ ircCli.AddCallback("PRIVMSG", s.HandleIRCMessage)
+ ircCli.AddCallback("001", func(ev *irc.Event) {
+ ircCli.Join(c.IRC.Channel)
+ })
+ ircCli.Log = slog.NewLogLogger(h.WithAttrs([]slog.Attr{slog.String("from", "ircevent")}), slog.LevelInfo)
+ ircCli.Timeout = 5 * time.Second
+
+ if err := ircCli.Connect(c.IRC.Server); err != nil {
+ slog.Error("can't connect to IRC server", "server", c.IRC.Server, "err", err)
+ os.Exit(1)
+ }
+
+ ircCli.Loop()
+}
+
+type Sanguisuga struct {
+ Config Config
+ tc *transmissionrpc.Client
+ db *jsondb.DB[State]
+}
+
+func (s *Sanguisuga) DelayedStartTorrent(tid int64) {
+ s.tc.TorrentStopIDs(context.Background(), []int64{tid})
+ time.Sleep(5 * time.Second) // delay a bit
+ s.tc.TorrentStartNowIDs(context.Background(), []int64{tid})
+}
+
+type State struct {
+ // Name + " " + SeasonEpisode -> TorrentAnnouncement
+ Seen map[string]TorrentAnnouncement
+}
+
+func (s *Sanguisuga) HandleIRCMessage(ev *irc.Event) {
+ // check if in channel
+ if ev.Code != "PRIVMSG" {
+ return
+ }
+
+ if ev.Arguments[0] != s.Config.IRC.Channel {
+ return
+ }
+
+ ta, err := ParseTorrentAnnouncement(ev.MessageWithoutFormat())
+ if err != nil {
+ slog.Debug("can't parse torrent announcment", "err", err, "msg", ev.MessageWithoutFormat())
+ return
+ }
+
+ slog.Debug("found torrent announcment", "category", ta.Category, "freeleech", ta.Freeleech, "name", ta.Name)
+
+ if ta.Category == "TV :: Episodes HD" {
+ sm, err := ParseShowMeta(ta.Name)
+ if err != nil {
+ slog.Debug("can't parse ShowMeta", "err", err, "name", ta.Name)
+ return
+ }
+ id := sm.SeasonEpisode.String()
+ slog.Debug("found ShowMeta", "title", sm.Name, "id", id, "quality", sm.Quality, "group", sm.Group)
+
+ for _, show := range s.Config.Shows {
+ if s.db.Data == nil {
+ s.db.Data = &State{
+ Seen: map[string]TorrentAnnouncement{},
+ }
+ }
+ if _, found := s.db.Data.Seen[sm.StateKey()]; found {
+ slog.Info("already snatched", "title", sm.Name, "id", id)
+ return
+ }
+
+ if show.Title != sm.Name {
+ slog.Debug("wrong name")
+ continue
+ }
+
+ if show.Quality != sm.Quality {
+ slog.Debug("wrong quality")
+ continue
+ }
+
+ torrentURL := ConvertURL(ta.TorrentID, s.Config.RSSKey, ta.Name)
+
+ slog.Debug("found url", "url", torrentURL)
+ downloadDir := filepath.Join(show.DiskPath, sm.SeasonEpisode.GetFormattedSeason())
+
+ t, err := s.tc.TorrentAdd(context.Background(), transmissionrpc.TorrentAddPayload{
+ DownloadDir: &downloadDir,
+ Filename: aws.String(torrentURL),
+ Paused: aws.Bool(false),
+ })
+ if err != nil {
+ slog.Error("error adding torrent", "err", err, "torrentID", ta.TorrentID)
+ return
+ }
+
+ go s.DelayedStartTorrent(*t.ID)
+
+ slog.Info("added torrent", "title", sm.Name, "id", id, "path", downloadDir)
+
+ s.db.Data.Seen[sm.StateKey()] = *ta
+ if err := s.db.Save(); err != nil {
+ slog.Error("error saving state", "err", err)
+ }
+ }
+ }
+}
diff --git a/go.mod b/go.mod
index fb4b73f..7961c6c 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/go-shiori/go-readability v0.0.0-20230421032831-c66949dfc0ad
github.com/google/go-github v17.0.0+incompatible
github.com/google/uuid v1.3.0
+ github.com/hekmon/transmissionrpc/v2 v2.0.1
github.com/hullerob/go.farbfeld v0.0.0-20181222022525-3661193c725f
github.com/jackc/pgx/v5 v5.4.2
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
@@ -36,6 +37,7 @@ require (
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
go.etcd.io/bbolt v1.3.7
+ go.jetpack.io/tyson v0.1.1
go4.org v0.0.0-20190313082347-94abd6928b1d
golang.org/x/crypto v0.11.0
golang.org/x/oauth2 v0.10.0
@@ -51,11 +53,16 @@ require (
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
+ github.com/dop251/goja v0.0.0-20211115154819-26ebff68a7d5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/evanw/esbuild v0.18.6 // indirect
github.com/fasthttp/router v1.4.19 // indirect
github.com/go-shiori/dom v0.0.0-20210627111528-4e4722cd0d65 // indirect
+ github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hekmon/cunits/v2 v2.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
@@ -157,7 +164,7 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 // indirect
- golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
+ golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb
golang.org/x/image v0.7.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.12.0 // indirect
diff --git a/go.sum b/go.sum
index 702b46f..f66cfb0 100644
--- a/go.sum
+++ b/go.sum
@@ -141,8 +141,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8 h1:vtIE3GO4hKplR58aTRx3yLPqAbfWyoyYrE8PXUv0Prw=
github.com/disintegration/imaging v1.6.1 h1:JnBbK6ECIZb1NsWIikP9pd8gIlTIRx7fuDNpU9fsxOE=
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dop251/goja v0.0.0-20211115154819-26ebff68a7d5 h1:7eyn0Cp9ezNbo2Vb4ttgJyWsFrRWP3oyHEw4PHKYlps=
+github.com/dop251/goja v0.0.0-20211115154819-26ebff68a7d5/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eaburns/peggy v0.0.0-20180405011029-d685ddd3cbcb/go.mod h1:5tfPwI6ukiK3W5vJzkj5MBQKHHY9Gcy2y6k1FC/23Xk=
@@ -162,6 +166,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanw/esbuild v0.18.6 h1:lu5hNfVlPZRwyXEcFvSUOgYNyG2GQ6vqKaL8oW+0AJU=
+github.com/evanw/esbuild v0.18.6/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
@@ -202,6 +208,8 @@ github.com/go-shiori/dom v0.0.0-20210627111528-4e4722cd0d65 h1:zx4B0AiwqKDQq+Agq
github.com/go-shiori/dom v0.0.0-20210627111528-4e4722cd0d65/go.mod h1:NPO1+buE6TYOWhUI98/hXLHHJhunIpXRuvDN4xjkCoE=
github.com/go-shiori/go-readability v0.0.0-20230421032831-c66949dfc0ad h1:3VP5Q8Mh165h2DHmXWFT4LJlwwvgTRlEuoe2vnsVnJ4=
github.com/go-shiori/go-readability v0.0.0-20230421032831-c66949dfc0ad/go.mod h1:2DpZlTJO/ycxp/vsc/C11oUyveStOgIXB88SYV1lncI=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
@@ -318,6 +326,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
@@ -325,6 +335,10 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
+github.com/hekmon/cunits/v2 v2.1.0 h1:k6wIjc4PlacNOHwKEMBgWV2/c8jyD4eRMs5mR1BBhI0=
+github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M=
+github.com/hekmon/transmissionrpc/v2 v2.0.1 h1:WkILCEdbNy3n/N/w7mi449waMPdH2AA1THyw7TfnN/w=
+github.com/hekmon/transmissionrpc/v2 v2.0.1/go.mod h1:+s96Pkg7dIP3h2PT3fzhXPvNb3OdLryh5J8PIvQg3aA=
github.com/hullerob/go.farbfeld v0.0.0-20181222022525-3661193c725f h1:1LkiAnH6RhOEbQAcfcEcixM5IsegqFi6IH0Nz0ZGqYs=
github.com/hullerob/go.farbfeld v0.0.0-20181222022525-3661193c725f/go.mod h1:mQEoc766DxPTAwQ54neWTK/lFqIeSO7OU6bqZsceglw=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -368,6 +382,7 @@ github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -527,6 +542,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
+go.jetpack.io/tyson v0.1.1 h1:9RCOnwvHkrlylVqTwY1IVm0/qp6oDfxHrfrnP5Cn0xA=
+go.jetpack.io/tyson v0.1.1/go.mod h1:YWQMHRMtrCpSrqT4F4J8JG+R3GYHe0NuLWJzhpVOo0U=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -560,8 +577,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
-golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us=
+golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 h1:w/MOPdQ1IoYoDou3L55ZbTx2Nhn7JAhX1BBZor8qChU=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -753,6 +770,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1030,11 +1048,13 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f h1:8GE2MRjGiFmfpon8dekPI08jEuNMQzSffVHgdupcO4E=
diff --git a/gomod2nix.toml b/gomod2nix.toml
index e25c650..124a40d 100644
--- a/gomod2nix.toml
+++ b/gomod2nix.toml
@@ -91,12 +91,18 @@ schema = 3
[mod."github.com/dlclark/regexp2"]
version = "v1.7.0"
hash = "sha256-Z/M62esiZ0fVwvueVQhwz18z0eS22LZ3DJ4O8FKp3AY="
+ [mod."github.com/dop251/goja"]
+ version = "v0.0.0-20211115154819-26ebff68a7d5"
+ hash = "sha256-ZNsIe01X2yoxJCuaeYOmtxXvSL8I8vgp7/e7ZEHv3tQ="
[mod."github.com/dustin/go-humanize"]
version = "v1.0.1"
hash = "sha256-yuvxYYngpfVkUg9yAmG99IUVmADTQA0tMbBXe0Fq0Mc="
[mod."github.com/eaburns/peggy"]
version = "v1.0.2"
hash = "sha256-toux6NpVog1DW+zEWX8dAj+hCZaeMG6z2fe4cAz2LDw="
+ [mod."github.com/evanw/esbuild"]
+ version = "v0.18.6"
+ hash = "sha256-duHCFPNOFFp6KlIzpnhpJwczR9ksfnxgmxHvlOL8neI="
[mod."github.com/facebookgo/ensure"]
version = "v0.0.0-20160127193407-b4ab57deab51"
hash = "sha256-jdCvva9KDH6KTTAtW0fBNw67xd0DhxLQbxU8PdhWLKY="
@@ -130,6 +136,9 @@ schema = 3
[mod."github.com/go-shiori/go-readability"]
version = "v0.0.0-20230421032831-c66949dfc0ad"
hash = "sha256-67CfGKvCghUq+1XMcAMF+O9xsbQNkwma4L9cxA4pLtw="
+ [mod."github.com/go-sourcemap/sourcemap"]
+ version = "v2.1.3+incompatible"
+ hash = "sha256-eXhXPPLnAy/rmt/zDgeqni2G3o58UtnHjR8vHLXvISI="
[mod."github.com/goccy/go-json"]
version = "v0.10.2"
hash = "sha256-6fMD2/Rku8HT0zDdeA23pX0YxbohiIOC8OJNYbylJTQ="
@@ -169,12 +178,21 @@ schema = 3
[mod."github.com/hashicorp/errwrap"]
version = "v1.1.0"
hash = "sha256-6lwuMQOfBq+McrViN3maJTIeh4f8jbEqvLy2c9FvvFw="
+ [mod."github.com/hashicorp/go-cleanhttp"]
+ version = "v0.5.2"
+ hash = "sha256-N9GOKYo7tK6XQUFhvhImtL7PZW/mr4C4Manx/yPVvcQ="
[mod."github.com/hashicorp/go-multierror"]
version = "v1.1.1"
hash = "sha256-ANzPEUJIZIlToxR89Mn7Db73d9LGI51ssy7eNnUgmlA="
[mod."github.com/hdevalence/ed25519consensus"]
version = "v0.1.0"
hash = "sha256-MkqFWnyXt653RaJQUMWWxcW6NCskIxou8VEfj+8vd3Y="
+ [mod."github.com/hekmon/cunits/v2"]
+ version = "v2.1.0"
+ hash = "sha256-NqaZD9wPP0z2x/An3c+o2GDAXuwRvfwMmOIe7TOb8xY="
+ [mod."github.com/hekmon/transmissionrpc/v2"]
+ version = "v2.0.1"
+ hash = "sha256-qKZ0uFDRyE10Zg0Aog+vB678u4q9quZzWBY+bwsh614="
[mod."github.com/hullerob/go.farbfeld"]
version = "v0.0.0-20181222022525-3661193c725f"
hash = "sha256-vUAM6JDuNV+W8bhOcY8lqZVevdQ7nIl7lwwntRBo/Sw="
@@ -382,6 +400,9 @@ schema = 3
[mod."go.etcd.io/bbolt"]
version = "v1.3.7"
hash = "sha256-poZk8tPLDWwW95oCOkTJcQtEvOJTD9UXAZ2TqGJutwk="
+ [mod."go.jetpack.io/tyson"]
+ version = "v0.1.1"
+ hash = "sha256-c4ROLn+BSX7v/4C9/IeU6HiE2YvnqDuXXGp2iZhAVk4="
[mod."go4.org"]
version = "v0.0.0-20190313082347-94abd6928b1d"
hash = "sha256-/hCsgDZzSnd5kPI5u6diLud6sEZm9e+tyCMVFRy5O1k="
@@ -395,8 +416,8 @@ schema = 3
version = "v0.11.0"
hash = "sha256-aFI8cpvSZRiQO2HQm6+a4e9OiSf7GUfj69SSta2iZ44="
[mod."golang.org/x/exp"]
- version = "v0.0.0-20230425010034-47ecfdc1ba53"
- hash = "sha256-wjUFZRfjU+lf/XqSZ4drxtbVDJbhML5FBMnNJ70RcrM="
+ version = "v0.0.0-20230711153332-06a737ee72cb"
+ hash = "sha256-Cbw10ZJ+jATPV232G47xZrn6ExO1FDtiT6nlMRCH7EI="
[mod."golang.org/x/image"]
version = "v0.7.0"
hash = "sha256-8ymOoG5nFlSCJOgn/apVzP4Zk5jtoYNLwd5TJKlP2X8="