aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-03-25 00:36:32 +0800
committerRunxi Yu <me@runxiyu.org>2025-03-25 10:13:59 +0800
commit5a82b5fcd173247ab8ae83bbb957ca58abfc8f3f (patch)
treee6d58c2d29647f5abcdbe1b5aa4933e986d16b31
parentLighter color for the selection (diff)
downloadpowxy-5a82b5fcd173247ab8ae83bbb957ca58abfc8f3f.tar.gz
powxy-5a82b5fcd173247ab8ae83bbb957ca58abfc8f3f.tar.zst
powxy-5a82b5fcd173247ab8ae83bbb957ca58abfc8f3f.zip
IRC logging thing
-rw-r--r--README.md3
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--irc.go143
-rw-r--r--irclog.go124
5 files changed, 274 insertions, 0 deletions
diff --git a/README.md b/README.md
index 4f6ab2e..afd89af 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
+**Branch information:** This branch contains a version that uses IRC for
+logging. Connection details are hardcoded. You should probably not use this.
+
# Powxy &ndash; anti-scraper reverse proxy
Powxy is a reverse proxy that protects your upstream service by challenging
diff --git a/go.mod b/go.mod
index 62c2e32..3eecbfe 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
module go.lindenii.runxiyu.org/powxy
go 1.24.1
+
+require go.lindenii.runxiyu.org/lindenii-irc v0.0.0-20250322030600-1e47f911f1fa
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..f5afd47
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+go.lindenii.runxiyu.org/lindenii-irc v0.0.0-20250322030600-1e47f911f1fa h1:LU3ZN/9xVUOEHyUCa5d+lvrL2sqhy/PR2iM2DuAQDqs=
+go.lindenii.runxiyu.org/lindenii-irc v0.0.0-20250322030600-1e47f911f1fa/go.mod h1:fE6Ks8GK7PHZGAPkTWG593UmF7FmyugcRcqmey3Nvy0=
diff --git a/irc.go b/irc.go
new file mode 100644
index 0000000..14e4a12
--- /dev/null
+++ b/irc.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "math/rand"
+
+ irc "go.lindenii.runxiyu.org/lindenii-irc"
+)
+
+var (
+ ircSendBuffered chan string
+ ircSendDirectChan chan errorBack[string]
+)
+
+type errorBack[T any] struct {
+ content T
+ errorBack chan error
+}
+
+func ircBotSession() error {
+ underlyingConn, err := tls.Dial("tcp", "irc.runxiyu.org:6697", nil)
+ if err != nil {
+ return err
+ }
+ defer underlyingConn.Close()
+
+ conn := irc.NewConn(underlyingConn)
+
+ logAndWriteLn := func(s string) (n int, err error) {
+ return conn.WriteString(s + "\r\n")
+ }
+
+ suffix := ""
+ for i := 0; i < 5; i++ {
+ suffix += string(rand.Intn(26) + 97)
+ }
+
+ _, err = logAndWriteLn("NICK :powxy-" + suffix)
+ if err != nil {
+ return err
+ }
+ _, err = logAndWriteLn("USER powxy 0 * :powxy")
+ if err != nil {
+ return err
+ }
+
+ readLoopError := make(chan error)
+ writeLoopAbort := make(chan struct{})
+ go func() {
+ for {
+ select {
+ case <-writeLoopAbort:
+ return
+ default:
+ }
+
+ msg, line, err := conn.ReadMessage()
+ if err != nil {
+ readLoopError <- err
+ return
+ }
+
+ fmt.Println(line)
+
+ switch msg.Command {
+ case "001":
+ _, err = logAndWriteLn("JOIN #logs")
+ if err != nil {
+ readLoopError <- err
+ return
+ }
+ case "PING":
+ _, err = logAndWriteLn("PONG :" + msg.Args[0])
+ if err != nil {
+ readLoopError <- err
+ return
+ }
+ case "JOIN":
+ c, ok := msg.Source.(irc.Client)
+ if !ok {
+ }
+ if c.Nick != "powxy"+suffix {
+ continue
+ }
+ _, err = logAndWriteLn("PRIVMSG #logs :test")
+ if err != nil {
+ readLoopError <- err
+ return
+ }
+ default:
+ }
+ }
+ }()
+
+ for {
+ select {
+ case err = <-readLoopError:
+ return err
+ case line := <-ircSendBuffered:
+ _, err = logAndWriteLn(line)
+ if err != nil {
+ select {
+ case ircSendBuffered <- line:
+ default:
+ }
+ writeLoopAbort <- struct{}{}
+ return err
+ }
+ case lineErrorBack := <-ircSendDirectChan:
+ _, err = logAndWriteLn(lineErrorBack.content)
+ lineErrorBack.errorBack <- err
+ if err != nil {
+ writeLoopAbort <- struct{}{}
+ return err
+ }
+ }
+ }
+}
+
+func ircSendDirect(s string) error {
+ ech := make(chan error, 1)
+
+ ircSendDirectChan <- errorBack[string]{
+ content: s,
+ errorBack: ech,
+ }
+
+ return <-ech
+}
+
+func ircBotLoop() {
+ ircSendBuffered = make(chan string, 3000)
+ ircSendDirectChan = make(chan errorBack[string])
+
+ for {
+ _ = ircBotSession()
+ }
+}
+
+func init() {
+ go ircBotLoop()
+}
diff --git a/irclog.go b/irclog.go
new file mode 100644
index 0000000..eef95a3
--- /dev/null
+++ b/irclog.go
@@ -0,0 +1,124 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "os"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+type IRCLogHandler struct {
+ level slog.Level
+}
+
+func NewIRCLogHandler(level slog.Level) *IRCLogHandler {
+ return &IRCLogHandler{
+ level: level,
+ }
+}
+
+func (h *IRCLogHandler) Enabled(_ context.Context, level slog.Level) bool {
+ return level >= h.level
+}
+
+func (h *IRCLogHandler) Handle(_ context.Context, r slog.Record) error {
+ var sb strings.Builder
+
+ sb.WriteString("PRIVMSG #logs :")
+
+ sb.WriteString(r.Level.String())
+ sb.WriteString(" ")
+ sb.WriteString(r.Message)
+
+ r.Attrs(func(a slog.Attr) bool {
+ sb.WriteString(" ")
+
+ key := a.Key
+ if needsQuoting(key) {
+ key = strconv.Quote(key)
+ }
+ sb.WriteString(key)
+ sb.WriteString("=")
+
+ val := attrValueToString(a.Value)
+ if needsQuoting(val) {
+ val = strconv.Quote(val)
+ }
+ sb.WriteString(val)
+
+ return true
+ })
+
+ str := sb.String()
+
+ select {
+ case ircSendBuffered <- str:
+ default:
+ fmt.Fprintln(os.Stderr, "DROP")
+ }
+
+ fmt.Fprintln(os.Stderr, str)
+
+ return nil
+}
+
+func (h *IRCLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ return h
+}
+
+func (h *IRCLogHandler) WithGroup(_ string) slog.Handler {
+ return h
+}
+
+func attrValueToString(v slog.Value) string {
+ return v.String()
+}
+
+func init() {
+ slog.SetDefault(slog.New(NewIRCLogHandler(slog.LevelInfo)))
+}
+
+// copied from slog
+func needsQuoting(s string) bool {
+ if len(s) == 0 {
+ return true
+ }
+ for i := 0; i < len(s); {
+ b := s[i]
+ if b < utf8.RuneSelf {
+ if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
+ return true
+ }
+ i++
+ continue
+ }
+ r, size := utf8.DecodeRuneInString(s[i:])
+ if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
+ return true
+ }
+ i += size
+ }
+ return false
+}
+
+var safeSet = [256]bool{
+ '!': true, '#': true, '$': true, '%': true, '&': true, '\'': true,
+ '*': true, '+': true, ',': true, '-': true, '.': true, '/': true,
+ '0': true, '1': true, '2': true, '3': true, '4': true,
+ '5': true, '6': true, '7': true, '8': true, '9': true,
+ ':': true, ';': true, '<': true, '>': true, '?': true, '@': true,
+ 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true,
+ 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true,
+ 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true,
+ 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true,
+ 'Y': true, 'Z': true, '[': true, ']': true, '^': true, '_': true,
+ 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true,
+ 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true,
+ 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true,
+ 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true,
+ 'y': true, 'z': true, '{': true, '|': true, '}': true, '~': true,
+}