diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-10-25 14:06:42 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-10-25 14:06:42 -0400 |
| commit | afa4bc6c01297af78885bf0562e2dae7ff83605b (patch) | |
| tree | 97a1149d5646cf9b1c7aa2892d6e849c589219cc /cmd/_old/sanguisuga | |
| parent | 797eec6d94e193ae684db977179ea4a422b2499f (diff) | |
| download | x-afa4bc6c01297af78885bf0562e2dae7ff83605b.tar.xz x-afa4bc6c01297af78885bf0562e2dae7ff83605b.zip | |
cmd: add amano and stealthmountain
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/_old/sanguisuga')
| -rw-r--r-- | cmd/_old/sanguisuga/.gitignore | 2 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/admin.go | 10 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/config.default.ts | 114 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/config.go | 99 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/config_test.go | 14 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/dcc.go | 426 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/internal/dcc/dcc.go | 190 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/js/scripts/iptorrents.js | 39 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/js/scripts/subsplease.js | 50 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/js/scripts/torrentleech.js | 39 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/main.go | 443 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/plex/plex.go | 106 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/sanguisuga.templ | 346 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/sanguisuga_templ.go | 199 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/static/alpine.js | 5 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/static/styles.css | 1 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/tailwind.config.js | 14 | ||||
| -rw-r--r-- | cmd/_old/sanguisuga/tv.go | 74 |
18 files changed, 2171 insertions, 0 deletions
diff --git a/cmd/_old/sanguisuga/.gitignore b/cmd/_old/sanguisuga/.gitignore new file mode 100644 index 0000000..5841fb6 --- /dev/null +++ b/cmd/_old/sanguisuga/.gitignore @@ -0,0 +1,2 @@ +config.ts +data.json diff --git a/cmd/_old/sanguisuga/admin.go b/cmd/_old/sanguisuga/admin.go new file mode 100644 index 0000000..65f231f --- /dev/null +++ b/cmd/_old/sanguisuga/admin.go @@ -0,0 +1,10 @@ +package main + +import ( + "embed" +) + +var ( + //go:embed static + static embed.FS +) diff --git a/cmd/_old/sanguisuga/config.default.ts b/cmd/_old/sanguisuga/config.default.ts new file mode 100644 index 0000000..77d887e --- /dev/null +++ b/cmd/_old/sanguisuga/config.default.ts @@ -0,0 +1,114 @@ +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: boolean; + rpcURI: string; +}; + +export type Tailscale = { + hostname: string; + authkey: string; + dataDir?: string; +}; + +export type Telegram = { + token: string; + mentionUser: number; +}; + +export type WireGuardPeer = { + publicKey: string; + endpoint: string; + allowedIPs: string[]; +}; + +export type WireGuard = { + privateKey: string; + address: string[]; + dns: string; + peers: WireGuardPeer[]; +}; + +export type Config = { + irc: IRC; + xdcc: IRC; + transmission: Transmission; + shows: Show[]; + rssKey: string; + tailscale: Tailscale; + baseDiskPath: string; + telegram: Telegram; + wireguard: WireGuard; +}; + +export default { + irc: { + server: "", + password: "", + channel: "", + nick: "", + user: "", + real: "" + }, + xdcc: { + 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: "", + tailscale: { + hostname: "sanguisuga-dev", + authkey: "", + dataDir: undefined, + }, + baseDiskPath: "/data/TV/", + telegram: { + token: "", + mentionUser: 0, + }, + wireguard: { // for downloading files over DCC (XDCC) + privateKey: "", + address: [], + dns: "", + peers: [ + { + publicKey: "", + allowedIPs: [], + endpoint: "", + }, + ], + }, +} satisfies Config; diff --git a/cmd/_old/sanguisuga/config.go b/cmd/_old/sanguisuga/config.go new file mode 100644 index 0000000..29e6b07 --- /dev/null +++ b/cmd/_old/sanguisuga/config.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "io" + "log/slog" + "net/netip" + + "within.website/x/internal/key2hex" +) + +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"` +} + +func (s Show) LogValue() slog.Value { + return slog.GroupValue( + slog.String("title", s.Title), + slog.String("disk_path", s.DiskPath), + slog.String("quality", s.Quality), + ) +} + +type Transmission struct { + URL string `json:"url"` + User string `json:"user"` + Password string `json:"password"` +} + +type Tailscale struct { + Hostname string `json:"hostname"` + Authkey string `json:"authkey"` + DataDir *string `json:"dataDir,omitempty"` +} + +type Telegram struct { + Token string `json:"token"` + MentionUser int64 `json:"mentionUser"` +} + +type WireGuard struct { + PrivateKey string `json:"privateKey"` + Address []netip.Addr `json:"address"` + DNS netip.Addr `json:"dns"` + Peers []WireGuardPeer `json:"peers"` +} + +type WireGuardPeer struct { + PublicKey string `json:"publicKey"` + AllowedIPs []string `json:"allowedIPs"` + Endpoint string `json:"endpoint"` +} + +func (w WireGuard) UAPI(out io.Writer) error { + pkey, err := key2hex.Convert(w.PrivateKey) + if err != nil { + return err + } + fmt.Fprintf(out, "private_key=%s\n", pkey) + fmt.Fprintln(out, "listen_port=0") + fmt.Fprintln(out, "replace_peers=true") + for _, peer := range w.Peers { + pkey, err := key2hex.Convert(peer.PublicKey) + if err != nil { + return err + } + fmt.Fprintf(out, "public_key=%s\n", pkey) + fmt.Fprintf(out, "endpoint=%s\n", peer.Endpoint) + for _, ip := range peer.AllowedIPs { + fmt.Fprintf(out, "allowed_ip=%s\n", ip) + } + fmt.Fprintln(out, "persistent_keepalive_interval=25") + } + return nil +} + +type Config struct { + IRC IRC `json:"irc"` + XDCC IRC `json:"xdcc"` + Transmission Transmission `json:"transmission"` + Shows []Show `json:"shows"` + RSSKey string `json:"rssKey"` + Tailscale Tailscale `json:"tailscale"` + BaseDiskPath string `json:"baseDiskPath"` + Telegram Telegram `json:"telegram"` + WireGuard WireGuard `json:"wireguard"` +} diff --git a/cmd/_old/sanguisuga/config_test.go b/cmd/_old/sanguisuga/config_test.go new file mode 100644 index 0000000..f7d0f8c --- /dev/null +++ b/cmd/_old/sanguisuga/config_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "testing" + + "go.jetpack.io/tyson" +) + +func TestDefaultConfig(t *testing.T) { + var c Config + if err := tyson.Unmarshal("./config.default.ts", &c); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/_old/sanguisuga/dcc.go b/cmd/_old/sanguisuga/dcc.go new file mode 100644 index 0000000..defab31 --- /dev/null +++ b/cmd/_old/sanguisuga/dcc.go @@ -0,0 +1,426 @@ +package main + +import ( + "context" + "encoding/binary" + "encoding/json" + "errors" + "expvar" + "fmt" + "hash/crc32" + "io" + "log" + "log/slog" + "net" + "net/http" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + irc "github.com/thoj/go-ircevent" + "tailscale.com/metrics" + "within.website/x/cmd/sanguisuga/internal/dcc" +) + +var ( + subspleaseAnnounceRegex = regexp.MustCompile(`^.*\* (?P<fname>\[SubsPlease\] (?P<showName>.*) - (?P<episode>[0-9]+) \((?P<resolution>[0-9]{3,4})p\) \[(?P<crc32>[0-9A-Fa-f]{8})\]\.mkv) \* /MSG (?P<botName>[^ ]+) XDCC SEND (?P<packID>[0-9]+)$`) + dccCommand = regexp.MustCompile(`^DCC SEND "(.*)" ([0-9]+) ([0-9]+) ([0-9]+)$`) + + bytesDownloaded = &metrics.LabelMap{Label: "filename"} +) + +func init() { + expvar.Publish("gauge_sanguisuga_bytes_downloaded", bytesDownloaded) +} + +type SubspleaseAnnouncement struct { + Filename string `json:"fname"` + ShowName string `json:"showName"` + Episode string `json:"episode"` + Resolution string `json:"resolution"` + CRC32 string `json:"crc32"` + BotName string `json:"botName"` + PackID string `json:"packID"` +} + +func (sa SubspleaseAnnouncement) Key() string { + return fmt.Sprintf("%s %s %s", sa.BotName, sa.ShowName, sa.Episode) +} + +func (sa SubspleaseAnnouncement) LogValue() slog.Value { + return slog.GroupValue( + slog.String("showname", sa.ShowName), + slog.String("episode", sa.Episode), + slog.String("resolution", sa.Resolution), + slog.String("botName", sa.BotName), + slog.String("crc32", sa.CRC32), + ) +} + +func ParseSubspleaseAnnouncement(input string) (*SubspleaseAnnouncement, error) { + re := subspleaseAnnounceRegex + matches := subspleaseAnnounceRegex.FindStringSubmatch(input) + + if matches == nil { + return nil, errors.New("invalid annoucement format") + } + + return &SubspleaseAnnouncement{ + Filename: matches[re.SubexpIndex("fname")], + ShowName: matches[re.SubexpIndex("showName")], + Episode: matches[re.SubexpIndex("episode")], + Resolution: matches[re.SubexpIndex("resolution")], + CRC32: matches[re.SubexpIndex("crc32")], + BotName: matches[re.SubexpIndex("botName")], + PackID: matches[re.SubexpIndex("packID")], + }, nil +} + +func int2ip(nn uint32) string { + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, nn) + return ip.String() +} + +func (s *Sanguisuga) XDCC() { + ircCli := irc.IRC(s.Config.XDCC.Nick, s.Config.XDCC.User) + ircCli.Password = s.Config.XDCC.Password + ircCli.RealName = s.Config.XDCC.Real + ircCli.AddCallback("001", func(ev *irc.Event) { + ircCli.Join(s.Config.XDCC.Channel) + }) + ircCli.AddCallback("PRIVMSG", s.ScrapeSubsplease) + ircCli.AddCallback("CTCP", s.SubspleaseDCC) + + ircCli.Log = slog.NewLogLogger(slog.Default().Handler().WithAttrs([]slog.Attr{slog.String("from", "ircevent"), slog.String("for", "anime")}), slog.LevelInfo) + ircCli.Timeout = 5 * time.Second + + if err := ircCli.Connect(s.Config.XDCC.Server); err != nil { + log.Fatalf("can't connect to XDCC server %s: %v", s.Config.XDCC.Server, err) + } + + ircCli.Loop() +} + +func remove[T any](l []T, remove func(T) bool) []T { + out := make([]T, 0) + for _, element := range l { + if !remove(element) { + out = append(out, element) + } + } + return out +} + +func (s *Sanguisuga) UntrackAnime(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + var show Show + err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096)).Decode(&show) + if err != nil { + slog.Error("can't read request body", "err", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + s.dbLock.Lock() + defer s.dbLock.Unlock() + + s.db.Data.AnimeWatch = remove(s.db.Data.AnimeWatch, func(s Show) bool { + return s.Title == show.Title + }) + + slog.Info("no longer tracking anime", "show", show) + + if err := s.db.Save(); err != nil { + slog.Error("can't save database", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (s *Sanguisuga) TrackAnime(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + var show Show + err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096)).Decode(&show) + if err != nil { + slog.Error("can't read request body", "err", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + s.dbLock.Lock() + defer s.dbLock.Unlock() + + s.db.Data.AnimeWatch = append(s.db.Data.AnimeWatch, show) + if err := s.db.Save(); err != nil { + slog.Error("can't save database", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(s.db.Data.AnimeWatch) +} + +func (s *Sanguisuga) ListAnimeSnatches(w http.ResponseWriter, r *http.Request) { + s.dbLock.Lock() + defer s.dbLock.Unlock() + + json.NewEncoder(w).Encode(s.db.Data.AnimeSnatches) +} + +func (s *Sanguisuga) ListAnime(w http.ResponseWriter, r *http.Request) { + s.dbLock.Lock() + defer s.dbLock.Unlock() + + json.NewEncoder(w).Encode(s.db.Data.AnimeWatch) +} + +func (s *Sanguisuga) ScrapeSubsplease(ev *irc.Event) { + slog.Debug("chat line", "code", ev.Code, "channel", ev.Arguments[0], "msg", ev.MessageWithoutFormat()) + if ev.Code != "PRIVMSG" { + return + } + + if ev.Arguments[0] != s.Config.XDCC.Channel { + return + } + + switch ev.Nick { + case "CR-ARUTHA|NEW", "CR-HOLLAND|NEW", "Belath": + default: + return + } + + ann, err := ParseSubspleaseAnnouncement(ev.MessageWithoutFormat()) + if err != nil { + slog.Debug("can't parse announcement", "input", ev.MessageWithoutFormat(), "err", err) + return + } + + if ann.Resolution != "1080" { + return + } + + lg := slog.Default().With("announcement", ann) + lg.Info("found announcement") + + s.dbLock.Lock() + defer s.dbLock.Unlock() + + s.aifLock.Lock() + defer s.aifLock.Unlock() + + if _, ok := s.db.Data.AnimeSnatches[ann.Filename]; ok { + return + } + + found := false + for _, show := range s.db.Data.AnimeWatch { + if ann.ShowName == show.Title { + found = true + } + } + + if !found { + return + } + + // if already being fetched, don't fetch again + if _, ok := s.animeInFlight[ann.Filename]; ok { + return + } + + ev.Connection.Privmsgf(ann.BotName, "XDCC SEND %s", ann.PackID) + + s.animeInFlight[ann.Filename] = ann + + s.db.Data.AnimeSnatches[ann.Filename] = *ann + if err := s.db.Save(); err != nil { + lg.Error("can't save database", "err", err) + return + } +} +func (s *Sanguisuga) SubspleaseDCC(ev *irc.Event) { + go s.subspleaseDCC(ev) +} + +func (s *Sanguisuga) subspleaseDCC(ev *irc.Event) { + matches := dccCommand.FindStringSubmatch(ev.MessageWithoutFormat()) + if matches == nil { + return + } + + s.aifLock.Lock() + defer s.aifLock.Unlock() + + if len(matches) != 5 { + slog.Error("wrong message from DCC bot", "botName", ev.Nick, "message", ev.Message()) + return + } + + fname := matches[1] + ipString := matches[2] + port := matches[3] + sizeString := matches[4] + + if strings.HasSuffix(fname, "\"") { + fname = fname[:len(fname)-2] + } + + var ann *SubspleaseAnnouncement + t := time.NewTicker(25 * time.Millisecond) + defer t.Stop() + i := 0 +waitLoop: + for { + select { + case <-t.C: + ann, _ = s.animeInFlight[fname] + + if ann == nil { + continue + } else { + slog.Debug("found announcement", "ann", ann) + break waitLoop + } + + default: + if i >= 30 { + slog.Error("wanted to download file but we aren't watching for it", "fname", fname) + return + } + + } + } + + // TODO(Xe): fix for IPv6 + ipUint, err := strconv.ParseUint(ipString, 10, 32) + if err != nil { + slog.Error("can't parse IP address", "addr", ipString, "err", err) + return + } + + ip := int2ip(uint32(ipUint)) + addr := net.JoinHostPort(ip, port) + + size, err := strconv.Atoi(sizeString) + if err != nil { + slog.Error("can't parse size", "size", sizeString, "err", err) + return + } + + lg := slog.Default().With("fname", fname, "botName", ev.Nick, "addr", addr) + lg.Info("fetching episode") + + baseDir := "" + for _, show := range s.db.Data.AnimeWatch { + if ann.ShowName == show.Title { + baseDir = show.DiskPath + } + } + + outFname := filepath.Join(baseDir, fname) + + os.MkdirAll(baseDir, 0777) + + fout, err := os.Create(outFname) + if err != nil { + lg.Error("can't create output file", "outFname", outFname, "err", err) + return + } + defer fout.Close() + + d := dcc.NewDCC(addr, size, fout, s.tnet.DialContext) + + ctx, cancel := context.WithTimeout(ev.Ctx, 120*time.Minute) + defer cancel() + + start := time.Now() + progc, errc := d.Run(ctx) + +outer: + for { + select { + case p := <-progc: + curr := bytesDownloaded.GetFloat(fname) + curr.Set(p.CurrentFileSize) + + if p.CurrentFileSize == p.FileSize { + break outer + } + + if p.Percentage >= 100 { + break outer + } + + lg.Debug("download progress", "progress", p) + case err := <-errc: + lg.Error("error in DCC thread, giving up", "err", err) + delete(s.animeInFlight, fname) + return + } + } + + delete(s.animeInFlight, fname) + dur := time.Since(start) + + lg.Info("finished downloading", "dur", dur.String()) + + s.Notify(fmt.Sprintf("Fetched %s episode %s", ann.ShowName, ann.Episode)) + + _, err = crcCheck(outFname, ann.CRC32) + if err != nil { + lg.Error("got wrong hash", "err", err) + } + + lg.Debug("hash check passed") +} + +func crcCheck(fname, wantHash string) (bool, error) { + fin, err := os.Open(fname) + if err != nil { + return false, err + } + defer fin.Close() + + h := crc32.NewIEEE() + + if _, err := io.Copy(h, fin); err != nil { + return false, err + } + + gotHash := fmt.Sprintf("%X", h.Sum32()) + + if wantHash != gotHash { + return false, crcError{ + Want: wantHash, + Got: gotHash, + } + } + + return true, nil +} + +type crcError struct { + Want string + Got string +} + +func (c crcError) Error() string { + return fmt.Sprintf("crc32 didn't match: want %s, got %s", c.Want, c.Got) +} + +func (c crcError) LogValue() slog.Value { + return slog.GroupValue( + slog.String("type", "crc_error"), + slog.String("want", c.Want), + slog.String("got", c.Got), + ) +} diff --git a/cmd/_old/sanguisuga/internal/dcc/dcc.go b/cmd/_old/sanguisuga/internal/dcc/dcc.go new file mode 100644 index 0000000..82d2ff3 --- /dev/null +++ b/cmd/_old/sanguisuga/internal/dcc/dcc.go @@ -0,0 +1,190 @@ +package dcc + +import ( + "context" + "encoding/binary" + "io" + "log/slog" + "net" + "time" +) + +// Progress contains the progression of the +// download handled by the DCC client socket +type Progress struct { + Speed float64 + Percentage float64 + CurrentFileSize float64 + FileSize float64 +} + +func (p Progress) LogValue() slog.Value { + return slog.GroupValue( + slog.Float64("speed", p.Speed), + slog.Float64("percentage", p.Percentage), + slog.Float64("curr", p.CurrentFileSize), + slog.Float64("total", p.FileSize), + ) +} + +// DCC creates a new socket client instance where +// it'll download the DCC transaction into the +// specified io.Writer destination +type DCC struct { + // important properties + address string + size int + + // output channels used for the Run and the receiver methods() + // to avoid parameter passing + progressc chan Progress + done chan error + + // internal DCC socket connection + conn net.Conn + + // assigned context passed from the Run() method + ctx context.Context + + // destination writer + writer io.Writer + + // dial function + dialFunc func(ctx context.Context, network, address string) (net.Conn, error) +} + +// NewDCC creates a new DCC instance. +// the host, port are needed for the socket client connection +// the size is required so the download progress is calculated +// the writer is required to store the transaction fragments into +// the specified io.Writer +func NewDCC( + address string, + size int, + writer io.Writer, + dialFunc func(ctx context.Context, network, address string) (net.Conn, error), +) *DCC { + return &DCC{ + address: address, + size: size, + progressc: make(chan Progress, 1), + done: make(chan error, 1), + writer: writer, + dialFunc: dialFunc, + } +} + +func (d *DCC) progress(written float64, speed *float64) time.Time { + d.progressc <- Progress{ + Speed: written - *speed, + Percentage: (written / float64(d.size)) * 100, + CurrentFileSize: written, + FileSize: float64(d.size), + } + + *speed = float64(written) + + return time.Now() +} + +func (d *DCC) receive() { + defer func() { // close channels + close(d.done) + + // close the connection afterwards.. + d.conn.Close() + }() + + var ( + written int + speed float64 + buf = make([]byte, 30720) + reader = io.LimitReader(d.conn, int64(d.size)) + ticker = time.NewTicker(time.Second) + ) + + defer ticker.Stop() + +D: + for { + select { + case <-d.ctx.Done(): + d.done <- nil // send empty to notify the watchers that we're done + return // terminated.. + case <-ticker.C: + d.progress(float64(written), &speed) + // notify the other side about the state of the connection + writtenNetworkOrder := uint32(written) + if err := binary.Write(d.conn, binary.BigEndian, writtenNetworkOrder); err != nil { + if err == io.EOF { + err = nil + } + + d.progress(float64(written), &speed) + d.done <- err + + return + } + default: + n, err := reader.Read(buf) + + if err != nil { + if err == io.EOF { // empty out the error + err = nil + } + + d.progress(float64(written), &speed) + d.done <- err + + return + } + + if n > 0 { + _, err = d.writer.Write(buf[0:n]) + + if err != nil { + d.done <- err + return + } else if written >= d.size { // finished + break D + } + + written += n + } + } + } +} + +// Run established the connection with the DCC TCP socket +// and returns two channels, where one is used for the download progress +// and the other is used to return exceptions during our transaction. +// A context is required, where you have the ability to cancel and timeout +// a download. +// One should check the second value for the progress/error channels when +// receiving data as if the channels are closed, it means that the transaction +// is finished or got interrupted. +func (d *DCC) Run(ctx context.Context) ( + progressc <-chan Progress, + done <-chan error, +) { + // assign the output to the struct properties + progressc = d.progressc + done = d.done + + // assign the passed context + d.ctx = ctx + + conn, err := d.dialFunc(d.ctx, "tcp", d.address) + + if err != nil { + d.done <- err + return + } + + // setup the connection for the receiver + d.conn = conn + + go d.receive() + + return +} diff --git a/cmd/_old/sanguisuga/js/scripts/iptorrents.js b/cmd/_old/sanguisuga/js/scripts/iptorrents.js new file mode 100644 index 0000000..fd45abe --- /dev/null +++ b/cmd/_old/sanguisuga/js/scripts/iptorrents.js @@ -0,0 +1,39 @@ +const regex = + /^\[([^\]]*)]\s+(.*?(FREELEECH)?)\s+-\s+https?\:\/\/([^\/]+).*[&;\?]id=(\d+)\s*- (.*)$/; + +const genURL = (torrentName, baseURL, id, passkey) => + `https://${baseURL}/download.php/${id}/${torrentName}.torrent?torrent_pass=${passkey}`; + +export const allowLine = (nick, channel) => { + if (channel != "#ipt.announce") { + return false; + } + + if (nick !== "IPT") { + return false; + } + + return true; +}; + +export const parseLine = (msg) => { + const [ + _blank, + category, + torrentName, + freeleech, + baseURL, + id, + size, + ] = msg.split(regex); + + return { + torrent: { + category, + name: torrentName, + freeleech: freeleech !== "", + id: id, + url: genURL(torrentName, baseURL, id), + }, + }; +}; diff --git a/cmd/_old/sanguisuga/js/scripts/subsplease.js b/cmd/_old/sanguisuga/js/scripts/subsplease.js new file mode 100644 index 0000000..dd9566e --- /dev/null +++ b/cmd/_old/sanguisuga/js/scripts/subsplease.js @@ -0,0 +1,50 @@ +const regex = + /^.*\* (\[SubsPlease\] (.*) - ([0-9]+) \(([0-9]{3,4})p\) \[([0-9A-Fa-f]{8})\]\.mkv) \* .MSG ([^ ]+) XDCC SEND ([0-9]+)$/; + +const bots = [ + "CR-ARUTHA|NEW", + "CR-HOLLAND|NEW", +]; + +export const ircInfo = { + server: "irc.rizon.net:6697", + channel: "#subsplease", + downloadType: "DCC", +}; + +export const allowLine = (nick, channel) => { + if (channel != "#subsplease") { + return false; + } + + if (!bots.includes(nick)) { + return false; + } + + return true; +}; + +export const parseLine = (msg) => { + const [ + _blank, + fname, + showName, + episode, + resolution, + crc32, + botName, + packID, + ] = msg.split(regex); + + const result = { + fname, + showName, + episode, + resolution, + crc32, + botName, + packID, + }; + + return result; +}; diff --git a/cmd/_old/sanguisuga/js/scripts/torrentleech.js b/cmd/_old/sanguisuga/js/scripts/torrentleech.js new file mode 100644 index 0000000..7ddfa9e --- /dev/null +++ b/cmd/_old/sanguisuga/js/scripts/torrentleech.js @@ -0,0 +1,39 @@ +const regex = + /^New Torrent Announcement: <([^>]*)>\s+Name:'(.*)' uploaded by '.*' ?(freeleech)?\s+-\s+https:..\w+.\w+.\w+\/.\w+\/([0-9]+)$/; + +const genURL = (torrentName, baseURL, id, passkey) => + `https://www.torrentleech.org/rss/download/${id}/${passkey}/${torrentName}`; + +export const allowLine = (nick, channel) => { + if (channel !== "#tlannounces") { + return false; + } + + if (nick !== "_AnnounceBot_") { + return false; + } + + return true; +}; + +export const parseLine = (msg) => { + const [ + _blank, + category, + torrentName, + freeleech, + baseURL, + id, + size, + ] = msg.split(regex); + + return { + torrent: { + category, + name: torrentName, + freeleech: freeleech !== "", + id: id, + url: genURL(torrentName, baseURL, id), + }, + }; +}; diff --git a/cmd/_old/sanguisuga/main.go b/cmd/_old/sanguisuga/main.go new file mode 100644 index 0000000..06efced --- /dev/null +++ b/cmd/_old/sanguisuga/main.go @@ -0,0 +1,443 @@ +package main + +import ( + "bytes" + "encoding/base64" + "errors" + "expvar" + "flag" + "fmt" + "io" + "log" + "log/slog" + "net/http" + "net/netip" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + "time" + + "github.com/a-h/templ" + "github.com/mymmrac/telego" + tu "github.com/mymmrac/telego/telegoutil" + "github.com/tailscale/wireguard-go/conn" + "github.com/tailscale/wireguard-go/device" + "github.com/tailscale/wireguard-go/tun/netstack" + irc "github.com/thoj/go-ircevent" + "go.jetpack.io/tyson" + "honnef.co/go/transmission" + "tailscale.com/jsondb" + "tailscale.com/tsnet" + "within.website/x/internal" + "within.website/x/web/parsetorrentname" +) + +//go:generate tailwindcss --output static/styles.css --minify +//go:generate go run github.com/a-h/templ/cmd/templ@latest generate + +var ( + dbLoc = flag.String("db-loc", "./data.json", "path to data file") + tysonConfig = flag.String("tyson-config", "./config.ts", "path to configuration secrets (TySON)") + externalSeed = flag.Bool("external-seed", false, "try to external seed?") + + crcCheckCLI = flag.Bool("crc-check", false, "if true, check args[0] against hash args[1]") + + annRegex = regexp.MustCompile(`^New Torrent Announcement: <([^>]*)>\s+Name:'(.*)' uploaded by '.*' ?(freeleech)?\s+-\s+https://\w+.\w+.\w+./\w+./([0-9]+)$`) + + snatches = expvar.NewInt("gauge_sanguisuga_snatches") +) + +func ConvertURL(torrentID, rssKey, name string) string { + name = strings.ReplaceAll(name, " ", ".") + ".torrent" + return fmt.Sprintf("https://www.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, errors.New("invalid torrent announcement format") + } + + torrent := &TorrentAnnouncement{ + Category: match[1], + Name: strings.TrimSpace(match[2]), + Freeleech: match[3] != "", + TorrentID: match[4], + } +< |
