aboutsummaryrefslogtreecommitdiff
path: root/forged/internal/irc
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-08-17 03:09:52 +0800
committerRunxi Yu <me@runxiyu.org>2025-08-17 03:09:52 +0800
commit009a6e397651a9540b6c6bb74ef2230eeda9b577 (patch)
tree0a808670d95aba5804a73a558cc335a49d230aa6 /forged/internal/irc
parentRemove HTML templates from main server (diff)
downloadforge-009a6e397651a9540b6c6bb74ef2230eeda9b577.tar.gz
forge-009a6e397651a9540b6c6bb74ef2230eeda9b577.tar.zst
forge-009a6e397651a9540b6c6bb74ef2230eeda9b577.zip
Some mass renaming
Diffstat (limited to 'forged/internal/irc')
-rw-r--r--forged/internal/irc/bot.go175
-rw-r--r--forged/internal/irc/conn.go49
-rw-r--r--forged/internal/irc/doc.go2
-rw-r--r--forged/internal/irc/errors.go8
-rw-r--r--forged/internal/irc/message.go126
-rw-r--r--forged/internal/irc/source.go50
6 files changed, 0 insertions, 410 deletions
diff --git a/forged/internal/irc/bot.go b/forged/internal/irc/bot.go
deleted file mode 100644
index 3ebac89..0000000
--- a/forged/internal/irc/bot.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-only
-// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-
-package irc
-
-import (
- "crypto/tls"
- "log/slog"
- "net"
-
- "go.lindenii.runxiyu.org/forge/forged/internal/misc"
-)
-
-// Config contains IRC connection and identity settings for the bot.
-// This should usually be a part of the primary config struct.
-type Config struct {
- Net string `scfg:"net"`
- Addr string `scfg:"addr"`
- TLS bool `scfg:"tls"`
- SendQ uint `scfg:"sendq"`
- Nick string `scfg:"nick"`
- User string `scfg:"user"`
- Gecos string `scfg:"gecos"`
-}
-
-// Bot represents an IRC bot client that handles events and allows for sending messages.
-type Bot struct {
- config *Config
- ircSendBuffered chan string
- ircSendDirectChan chan misc.ErrorBack[string]
-}
-
-// NewBot creates a new Bot instance using the provided configuration.
-func NewBot(c *Config) (b *Bot) {
- b = &Bot{
- config: c,
- }
- return
-}
-
-// Connect establishes a new IRC session and starts handling incoming and outgoing messages.
-// This method blocks until an error occurs or the connection is closed.
-func (b *Bot) Connect() error {
- var err error
- var underlyingConn net.Conn
- if b.config.TLS {
- underlyingConn, err = tls.Dial(b.config.Net, b.config.Addr, nil)
- } else {
- underlyingConn, err = net.Dial(b.config.Net, b.config.Addr)
- }
- if err != nil {
- return err
- }
- defer underlyingConn.Close()
-
- conn := NewConn(underlyingConn)
-
- logAndWriteLn := func(s string) (n int, err error) {
- slog.Debug("irc tx", "line", s)
- return conn.WriteString(s + "\r\n")
- }
-
- _, err = logAndWriteLn("NICK " + b.config.Nick)
- if err != nil {
- return err
- }
- _, err = logAndWriteLn("USER " + b.config.User + " 0 * :" + b.config.Gecos)
- 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
- }
-
- slog.Debug("irc rx", "line", line)
-
- switch msg.Command {
- case "001":
- _, err = logAndWriteLn("JOIN #chat")
- 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.(Client)
- if !ok {
- slog.Error("unable to convert source of JOIN to client")
- }
- if c.Nick != b.config.Nick {
- continue
- }
- default:
- }
- }
- }()
-
- for {
- select {
- case err = <-readLoopError:
- return err
- case line := <-b.ircSendBuffered:
- _, err = logAndWriteLn(line)
- if err != nil {
- select {
- case b.ircSendBuffered <- line:
- default:
- slog.Error("unable to requeue message", "line", line)
- }
- writeLoopAbort <- struct{}{}
- return err
- }
- case lineErrorBack := <-b.ircSendDirectChan:
- _, err = logAndWriteLn(lineErrorBack.Content)
- lineErrorBack.ErrorChan <- err
- if err != nil {
- writeLoopAbort <- struct{}{}
- return err
- }
- }
- }
-}
-
-// SendDirect sends an IRC message directly to the connection and bypasses
-// the buffering system.
-func (b *Bot) SendDirect(line string) error {
- ech := make(chan error, 1)
-
- b.ircSendDirectChan <- misc.ErrorBack[string]{
- Content: line,
- ErrorChan: ech,
- }
-
- return <-ech
-}
-
-// Send queues a message to be sent asynchronously via the buffered send queue.
-// If the queue is full, the message is dropped and an error is logged.
-func (b *Bot) Send(line string) {
- select {
- case b.ircSendBuffered <- line:
- default:
- slog.Error("irc sendq full", "line", line)
- }
-}
-
-// ConnectLoop continuously attempts to maintain an IRC session.
-// If the connection drops, it automatically retries with no delay.
-func (b *Bot) ConnectLoop() {
- b.ircSendBuffered = make(chan string, b.config.SendQ)
- b.ircSendDirectChan = make(chan misc.ErrorBack[string])
-
- for {
- err := b.Connect()
- slog.Error("irc session error", "error", err)
- }
-}
diff --git a/forged/internal/irc/conn.go b/forged/internal/irc/conn.go
deleted file mode 100644
index b975b72..0000000
--- a/forged/internal/irc/conn.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package irc
-
-import (
- "bufio"
- "net"
- "slices"
-
- "go.lindenii.runxiyu.org/forge/forged/internal/misc"
-)
-
-type Conn struct {
- netConn net.Conn
- bufReader *bufio.Reader
-}
-
-func NewConn(netConn net.Conn) Conn {
- return Conn{
- netConn: netConn,
- bufReader: bufio.NewReader(netConn),
- }
-}
-
-func (c *Conn) ReadMessage() (msg Message, line string, err error) {
- raw, err := c.bufReader.ReadSlice('\n')
- if err != nil {
- return
- }
-
- if raw[len(raw)-1] == '\n' {
- raw = raw[:len(raw)-1]
- }
- if raw[len(raw)-1] == '\r' {
- raw = raw[:len(raw)-1]
- }
-
- lineBytes := slices.Clone(raw)
- line = misc.BytesToString(lineBytes)
- msg, err = Parse(lineBytes)
-
- return
-}
-
-func (c *Conn) Write(p []byte) (n int, err error) {
- return c.netConn.Write(p)
-}
-
-func (c *Conn) WriteString(s string) (n int, err error) {
- return c.netConn.Write(misc.StringToBytes(s))
-}
diff --git a/forged/internal/irc/doc.go b/forged/internal/irc/doc.go
deleted file mode 100644
index dcfca82..0000000
--- a/forged/internal/irc/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package irc provides basic IRC bot functionality.
-package irc
diff --git a/forged/internal/irc/errors.go b/forged/internal/irc/errors.go
deleted file mode 100644
index 3506c70..0000000
--- a/forged/internal/irc/errors.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package irc
-
-import "errors"
-
-var (
- ErrInvalidIRCv3Tag = errors.New("invalid ircv3 tag")
- ErrMalformedMsg = errors.New("malformed irc message")
-)
diff --git a/forged/internal/irc/message.go b/forged/internal/irc/message.go
deleted file mode 100644
index 84b6867..0000000
--- a/forged/internal/irc/message.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2018-2024 luk3yx <https://luk3yx.github.io>
-// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-
-package irc
-
-import (
- "bytes"
-
- "go.lindenii.runxiyu.org/forge/forged/internal/misc"
-)
-
-type Message struct {
- Command string
- Source Source
- Tags map[string]string
- Args []string
-}
-
-// All strings returned are borrowed from the input byte slice.
-func Parse(raw []byte) (msg Message, err error) {
- sp := bytes.Split(raw, []byte{' '}) // TODO: Use bytes.Cut instead here
-
- if bytes.HasPrefix(sp[0], []byte{'@'}) { // TODO: Check size manually
- if len(sp[0]) < 2 {
- err = ErrMalformedMsg
- return
- }
- sp[0] = sp[0][1:]
-
- msg.Tags, err = tagsToMap(sp[0])
- if err != nil {
- return
- }
-
- if len(sp) < 2 {
- err = ErrMalformedMsg
- return
- }
- sp = sp[1:]
- } else {
- msg.Tags = nil // TODO: Is a nil map the correct thing to use here?
- }
-
- if bytes.HasPrefix(sp[0], []byte{':'}) { // TODO: Check size manually
- if len(sp[0]) < 2 {
- err = ErrMalformedMsg
- return
- }
- sp[0] = sp[0][1:]
-
- msg.Source = parseSource(sp[0])
-
- if len(sp) < 2 {
- err = ErrMalformedMsg
- return
- }
- sp = sp[1:]
- }
-
- msg.Command = misc.BytesToString(sp[0])
- if len(sp) < 2 {
- return
- }
- sp = sp[1:]
-
- for i := 0; i < len(sp); i++ {
- if len(sp[i]) == 0 {
- continue
- }
- if sp[i][0] == ':' {
- if len(sp[i]) < 2 {
- sp[i] = []byte{}
- } else {
- sp[i] = sp[i][1:]
- }
- msg.Args = append(msg.Args, misc.BytesToString(bytes.Join(sp[i:], []byte{' '})))
- // TODO: Avoid Join by not using sp in the first place
- break
- }
- msg.Args = append(msg.Args, misc.BytesToString(sp[i]))
- }
-
- return
-}
-
-var ircv3TagEscapes = map[byte]byte{ //nolint:gochecknoglobals
- ':': ';',
- 's': ' ',
- 'r': '\r',
- 'n': '\n',
-}
-
-func tagsToMap(raw []byte) (tags map[string]string, err error) {
- tags = make(map[string]string)
- for rawTag := range bytes.SplitSeq(raw, []byte{';'}) {
- key, value, found := bytes.Cut(rawTag, []byte{'='})
- if !found {
- err = ErrInvalidIRCv3Tag
- return
- }
- if len(value) == 0 {
- tags[misc.BytesToString(key)] = ""
- } else {
- if !bytes.Contains(value, []byte{'\\'}) {
- tags[misc.BytesToString(key)] = misc.BytesToString(value)
- } else {
- valueUnescaped := bytes.NewBuffer(make([]byte, 0, len(value)))
- for i := 0; i < len(value); i++ {
- if value[i] == '\\' {
- i++
- byteUnescaped, ok := ircv3TagEscapes[value[i]]
- if !ok {
- byteUnescaped = value[i]
- }
- valueUnescaped.WriteByte(byteUnescaped)
- } else {
- valueUnescaped.WriteByte(value[i])
- }
- }
- tags[misc.BytesToString(key)] = misc.BytesToString(valueUnescaped.Bytes())
- }
- }
- }
- return
-}
diff --git a/forged/internal/irc/source.go b/forged/internal/irc/source.go
deleted file mode 100644
index d955f45..0000000
--- a/forged/internal/irc/source.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// SPDX-License-Identifier: MIT
-// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-
-package irc
-
-import (
- "bytes"
-
- "go.lindenii.runxiyu.org/forge/forged/internal/misc"
-)
-
-type Source interface {
- AsSourceString() string
-}
-
-func parseSource(s []byte) Source {
- nick, userhost, found := bytes.Cut(s, []byte{'!'})
- if !found {
- return Server{name: misc.BytesToString(s)}
- }
-
- user, host, found := bytes.Cut(userhost, []byte{'@'})
- if !found {
- return Server{name: misc.BytesToString(s)}
- }
-
- return Client{
- Nick: misc.BytesToString(nick),
- User: misc.BytesToString(user),
- Host: misc.BytesToString(host),
- }
-}
-
-type Server struct {
- name string
-}
-
-func (s Server) AsSourceString() string {
- return s.name
-}
-
-type Client struct {
- Nick string
- User string
- Host string
-}
-
-func (c Client) AsSourceString() string {
- return c.Nick + "!" + c.User + "@" + c.Host
-}