serverless-ircd 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +28 -19
  3. package/apps/cf-worker/package.json +1 -1
  4. package/apps/cf-worker/scripts/smoke.mjs +0 -0
  5. package/apps/cf-worker/src/worker.ts +8 -2
  6. package/apps/cf-worker/tests/smoke.test.ts +1 -0
  7. package/apps/cf-worker/wrangler.test.toml +5 -1
  8. package/apps/cf-worker/wrangler.toml +41 -17
  9. package/apps/local-cli/package.json +1 -1
  10. package/apps/local-cli/src/config-loader.ts +1 -0
  11. package/apps/local-cli/src/main.ts +1 -1
  12. package/apps/local-cli/src/server.ts +28 -2
  13. package/apps/local-cli/tests/e2e.test.ts +52 -0
  14. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  15. package/docs/ADR-002-location-of-authority.md +82 -0
  16. package/docs/ADR-003-durable-object-sharding.md +93 -0
  17. package/docs/ADR-004-dynamodb-schema.md +96 -0
  18. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  19. package/docs/ADR-006-sasl-mechanism-scope.md +86 -0
  20. package/docs/ADR-007-deterministic-ports.md +82 -0
  21. package/docs/ADR-008-monorepo-tooling.md +60 -0
  22. package/docs/AWS-Adapter-Architecture.md +6 -4
  23. package/docs/AWS-Deployment.md +86 -7
  24. package/docs/Cloudflare-Deployment-Guide.md +5 -5
  25. package/docs/Home.md +11 -1
  26. package/docs/Release-Process.md +9 -6
  27. package/package.json +14 -15
  28. package/packages/aws-adapter/package.json +1 -1
  29. package/packages/aws-adapter/src/account-store.ts +121 -0
  30. package/packages/aws-adapter/src/aws-runtime.ts +118 -30
  31. package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
  32. package/packages/aws-adapter/src/config-loader.ts +30 -0
  33. package/packages/aws-adapter/src/dynamo-account-store.ts +156 -0
  34. package/packages/aws-adapter/src/handlers/default.ts +8 -0
  35. package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
  36. package/packages/aws-adapter/src/handlers/index.ts +51 -6
  37. package/packages/aws-adapter/src/index.ts +7 -0
  38. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +171 -0
  39. package/packages/aws-adapter/tests/account-store.test.ts +280 -0
  40. package/packages/aws-adapter/tests/config-loader.test.ts +55 -0
  41. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +336 -0
  42. package/packages/aws-adapter/tests/handlers.test.ts +46 -0
  43. package/packages/cf-adapter/package.json +1 -1
  44. package/packages/cf-adapter/src/cf-runtime.ts +42 -22
  45. package/packages/cf-adapter/src/channel-do.ts +40 -1
  46. package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
  47. package/packages/cf-adapter/src/config-loader.ts +29 -0
  48. package/packages/cf-adapter/src/connection-do.ts +85 -0
  49. package/packages/cf-adapter/src/env.ts +27 -1
  50. package/packages/cf-adapter/src/index.ts +2 -1
  51. package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
  52. package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
  53. package/packages/cf-adapter/tests/worker/main.ts +2 -1
  54. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
  55. package/packages/cf-adapter/wrangler.test.toml +10 -1
  56. package/packages/in-memory-runtime/package.json +1 -1
  57. package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
  58. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
  59. package/packages/irc-core/package.json +3 -2
  60. package/packages/irc-core/src/case-fold.ts +64 -0
  61. package/packages/irc-core/src/commands/index.ts +2 -0
  62. package/packages/irc-core/src/commands/invite.ts +6 -9
  63. package/packages/irc-core/src/commands/isupport.ts +1 -1
  64. package/packages/irc-core/src/commands/join.ts +4 -3
  65. package/packages/irc-core/src/commands/kick.ts +4 -11
  66. package/packages/irc-core/src/commands/list.ts +5 -4
  67. package/packages/irc-core/src/commands/mode.ts +5 -9
  68. package/packages/irc-core/src/commands/motd-lines.ts +127 -0
  69. package/packages/irc-core/src/commands/motd.ts +6 -110
  70. package/packages/irc-core/src/commands/oper.ts +151 -0
  71. package/packages/irc-core/src/commands/registration.ts +8 -7
  72. package/packages/irc-core/src/commands/tagmsg.ts +205 -0
  73. package/packages/irc-core/src/config.ts +26 -3
  74. package/packages/irc-core/src/index.ts +2 -0
  75. package/packages/irc-core/src/ports.ts +68 -4
  76. package/packages/irc-core/src/types.ts +22 -0
  77. package/packages/irc-core/tests/account-store.test.ts +88 -0
  78. package/packages/irc-core/tests/case-fold.test.ts +80 -0
  79. package/packages/irc-core/tests/commands/kick.test.ts +15 -0
  80. package/packages/irc-core/tests/commands/oper.test.ts +257 -0
  81. package/packages/irc-core/tests/commands/registration.test.ts +106 -14
  82. package/packages/irc-core/tests/commands/tagmsg.test.ts +688 -0
  83. package/packages/irc-core/tests/commands/who.test.ts +22 -4
  84. package/packages/irc-core/tests/commands/whois.test.ts +8 -1
  85. package/packages/irc-core/tests/config.test.ts +75 -0
  86. package/packages/irc-server/package.json +1 -1
  87. package/packages/irc-server/src/actor.ts +24 -0
  88. package/packages/irc-server/src/routing.ts +9 -0
  89. package/packages/irc-server/tests/actor.test.ts +220 -1
  90. package/packages/irc-server/tests/routing.test.ts +3 -0
  91. package/packages/irc-test-support/package.json +1 -1
  92. package/packages/irc-test-support/src/in-memory-harness.ts +5 -4
  93. package/pnpm-workspace.yaml +1 -0
  94. package/tools/seed-aws-accounts.ts +80 -0
  95. package/AGENTS.md +0 -5
  96. package/MOTD.txt +0 -3
  97. package/PLAN-FIXES.md +0 -358
  98. package/PLAN.md +0 -420
  99. package/dashboards/cloudwatch-irc.json +0 -139
  100. package/progress.md +0 -107
  101. package/tickets.md +0 -2485
  102. package/webircgateway/LICENSE +0 -201
  103. package/webircgateway/Makefile +0 -44
  104. package/webircgateway/README.md +0 -134
  105. package/webircgateway/config.conf.example +0 -135
  106. package/webircgateway/go.mod +0 -16
  107. package/webircgateway/go.sum +0 -89
  108. package/webircgateway/main.go +0 -118
  109. package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
  110. package/webircgateway/pkg/identd/identd.go +0 -86
  111. package/webircgateway/pkg/identd/rpcclient.go +0 -59
  112. package/webircgateway/pkg/irc/isupport.go +0 -56
  113. package/webircgateway/pkg/irc/message.go +0 -217
  114. package/webircgateway/pkg/irc/state.go +0 -79
  115. package/webircgateway/pkg/proxy/proxy.go +0 -129
  116. package/webircgateway/pkg/proxy/server.go +0 -237
  117. package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
  118. package/webircgateway/pkg/webircgateway/client.go +0 -741
  119. package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
  120. package/webircgateway/pkg/webircgateway/config.go +0 -385
  121. package/webircgateway/pkg/webircgateway/gateway.go +0 -278
  122. package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
  123. package/webircgateway/pkg/webircgateway/hooks.go +0 -152
  124. package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
  125. package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
  126. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
  127. package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
  128. package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
  129. package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
  130. package/webircgateway/pkg/webircgateway/utils.go +0 -147
  131. package/webircgateway/plugins/example/plugin.go +0 -11
  132. package/webircgateway/plugins/stats/plugin.go +0 -52
  133. package/webircgateway/staticcheck.conf +0 -1
  134. package/webircgateway/webircgateway.svg +0 -3
@@ -1,118 +0,0 @@
1
- package main
2
-
3
- import (
4
- "flag"
5
- "fmt"
6
- "log"
7
- "os"
8
- "os/signal"
9
- "plugin"
10
- "sync"
11
- "syscall"
12
-
13
- "github.com/kiwiirc/webircgateway/pkg/webircgateway"
14
- )
15
-
16
- var VERSION = "1.1.0"
17
- var GITCOMMIT = "-"
18
- var BUILTWITHGO = "-"
19
-
20
- func init() {
21
- webircgateway.Version = VERSION
22
- }
23
-
24
- func main() {
25
- printVersion := flag.Bool("version", false, "Print the version")
26
- configFile := flag.String("config", "config.conf", "Config file location")
27
- startSection := flag.String("run", "gateway", "What type of server to run")
28
- flag.Parse()
29
-
30
- if *printVersion {
31
- fmt.Printf("Version: %s\n", webircgateway.Version)
32
- fmt.Printf("Git commit: %s\n", GITCOMMIT)
33
- fmt.Printf("Built with Go version: %s\n", BUILTWITHGO)
34
- os.Exit(0)
35
- }
36
-
37
- if *startSection != "gateway" && *startSection != "proxy" {
38
- fmt.Println("-run can either be 'gateway' or 'proxy'")
39
- os.Exit(1)
40
- }
41
-
42
- runGateway(*configFile, *startSection)
43
- }
44
-
45
- func runGateway(configFile string, function string) {
46
- gateway := webircgateway.NewGateway(function)
47
-
48
- log.SetFlags(log.Flags() | log.Lmicroseconds)
49
-
50
- // Print any webircgateway logout to STDOUT
51
- go printLogOutput(gateway)
52
-
53
- // Listen for process signals
54
- go watchForSignals(gateway)
55
-
56
- gateway.Config.SetConfigFile(configFile)
57
- log.Printf("Using config %s", gateway.Config.CurrentConfigFile())
58
-
59
- configErr := gateway.Config.Load()
60
- if configErr != nil {
61
- log.Printf("Config file error: %s", configErr.Error())
62
- os.Exit(1)
63
- }
64
-
65
- pluginsQuit := &sync.WaitGroup{}
66
- loadPlugins(gateway, pluginsQuit)
67
-
68
- gateway.Start()
69
-
70
- pluginsQuit.Wait()
71
- gateway.WaitClose()
72
- }
73
-
74
- func watchForSignals(gateway *webircgateway.Gateway) {
75
- c := make(chan os.Signal, 1)
76
- signal.Notify(c, syscall.SIGHUP, syscall.SIGINT)
77
-
78
- for {
79
- switch sig := <-c; sig {
80
- case syscall.SIGINT:
81
- fmt.Println("Received SIGINT, shutting down webircgateway")
82
- gateway.Close()
83
- case syscall.SIGHUP:
84
- fmt.Println("Recieved SIGHUP, reloading config file")
85
- gateway.Config.Load()
86
- }
87
- }
88
- }
89
-
90
- func printLogOutput(gateway *webircgateway.Gateway) {
91
- for {
92
- line, _ := <-gateway.LogOutput
93
- log.Println(line)
94
- }
95
- }
96
-
97
- func loadPlugins(gateway *webircgateway.Gateway, pluginsQuit *sync.WaitGroup) {
98
- for _, pluginPath := range gateway.Config.Plugins {
99
- pluginFullPath := gateway.Config.ResolvePath(pluginPath)
100
-
101
- gateway.Log(2, "Loading plugin "+pluginFullPath)
102
- p, err := plugin.Open(pluginFullPath)
103
- if err != nil {
104
- gateway.Log(3, "Error loading plugin: "+err.Error())
105
- continue
106
- }
107
-
108
- startSymbol, err := p.Lookup("Start")
109
- if err != nil {
110
- gateway.Log(3, "Plugin does not export a Start function! (%s)", pluginFullPath)
111
- continue
112
- }
113
-
114
- startFunc := startSymbol.(func(*webircgateway.Gateway, *sync.WaitGroup))
115
- pluginsQuit.Add(1)
116
- startFunc(gateway, pluginsQuit)
117
- }
118
- }
@@ -1,121 +0,0 @@
1
- package dnsbl
2
-
3
- import (
4
- "encoding/hex"
5
- "fmt"
6
- "net"
7
- "strings"
8
- )
9
-
10
- type ResultList struct {
11
- Listed bool
12
- Results []Result
13
- }
14
-
15
- /*
16
- Result holds the individual IP lookup results for each RBL search
17
- */
18
- type Result struct {
19
- // Blacklist is the DNSBL server that gave this result
20
- Blacklist string
21
- // Address is the IP address that was searched
22
- Address string
23
- // Listed indicates whether or not the IP was on the RBL
24
- Listed bool
25
- // RBL lists sometimes add extra information as a TXT record
26
- // if any info is present, it will be stored here.
27
- Text string
28
- // Error represents any error that was encountered (DNS timeout, host not
29
- // found, etc.) if any
30
- Error bool
31
- // ErrorType is the type of error encountered if any
32
- ErrorType error
33
- }
34
-
35
- /*
36
- Convert an IP to a hostname ready for a dnsbl lookup
37
- 127.0.0.1 becomes 1.0.0.127
38
- 1234:1234:1234:1234:1234:1234:1234:1234 becomes 4.3.2.1.4.3.2.1.4.3.2.1.4.3.2.1.4.3.2.1.4.3.2.1.4.3.2.1.4.3.2.1
39
- */
40
- func toDnsBlHostname(ip net.IP) string {
41
- if ip.To4() != nil {
42
- // IPv4
43
- // Reverse the complete octects
44
- splitAddress := strings.Split(ip.String(), ".")
45
- for i, j := 0, len(splitAddress)-1; i < len(splitAddress)/2; i, j = i+1, j-1 {
46
- splitAddress[i], splitAddress[j] = splitAddress[j], splitAddress[i]
47
- }
48
-
49
- return strings.Join(splitAddress, ".")
50
- }
51
-
52
- // IPv6
53
- // Remove all : from a full expanded address, then reverse all the hex characters
54
- ipv6Str := expandIPv6(ip)
55
- addrHexStr := strings.ReplaceAll(ipv6Str, ":", "")
56
- chars := []rune(addrHexStr)
57
- for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
58
- chars[i], chars[j] = chars[j], chars[i]
59
- }
60
-
61
- return strings.Join(strings.Split(string(chars), ""), ".")
62
- }
63
-
64
- func expandIPv6(ip net.IP) string {
65
- dst := make([]byte, hex.EncodedLen(len(ip)))
66
- _ = hex.Encode(dst, ip)
67
- return string(dst[0:4]) + ":" +
68
- string(dst[4:8]) + ":" +
69
- string(dst[8:12]) + ":" +
70
- string(dst[12:16]) + ":" +
71
- string(dst[16:20]) + ":" +
72
- string(dst[20:24]) + ":" +
73
- string(dst[24:28]) + ":" +
74
- string(dst[28:])
75
- }
76
-
77
- func query(rbl string, host string, r *Result) {
78
- r.Listed = false
79
-
80
- lookup := fmt.Sprintf("%s.%s", host, rbl)
81
- res, err := net.LookupHost(lookup)
82
-
83
- if len(res) > 0 {
84
- r.Listed = true
85
- txt, _ := net.LookupTXT(lookup)
86
- if len(txt) > 0 {
87
- r.Text = txt[0]
88
- }
89
- }
90
- if err != nil {
91
- r.Error = true
92
- r.ErrorType = err
93
- }
94
-
95
- return
96
- }
97
-
98
- func Lookup(dnsblList []string, targetHost string) (r ResultList) {
99
- ip, err := net.LookupIP(targetHost)
100
- if err != nil {
101
- return
102
- }
103
-
104
- for _, dnsbl := range dnsblList {
105
- for _, addr := range ip {
106
- res := Result{}
107
- res.Blacklist = dnsbl
108
- res.Address = addr.String()
109
-
110
- addr := toDnsBlHostname(addr)
111
- query(dnsbl, addr, &res)
112
- r.Results = append(r.Results, res)
113
-
114
- if res.Listed {
115
- r.Listed = true
116
- }
117
- }
118
- }
119
-
120
- return
121
- }
@@ -1,86 +0,0 @@
1
- package identd
2
-
3
- import (
4
- "fmt"
5
- "net"
6
- "net/textproto"
7
- "strings"
8
- "sync"
9
- )
10
-
11
- // Server - An IdentD server
12
- type Server struct {
13
- Entries map[string]string
14
- EntriesLock sync.Mutex
15
- }
16
-
17
- // NewIdentdServer - Create a new IdentdServer instance
18
- func NewIdentdServer() Server {
19
- return Server{
20
- Entries: make(map[string]string),
21
- }
22
- }
23
-
24
- // AddIdent - Add an ident to be looked up
25
- func (i *Server) AddIdent(localPort, remotePort int, ident string, iface string) {
26
- i.EntriesLock.Lock()
27
- i.Entries[fmt.Sprintf("%d-%d", localPort, remotePort)] = ident
28
- i.EntriesLock.Unlock()
29
- }
30
-
31
- // RemoveIdent - Remove an ident from being looked up
32
- func (i *Server) RemoveIdent(localPort, remotePort int, iface string) {
33
- i.EntriesLock.Lock()
34
- delete(i.Entries, fmt.Sprintf("%d-%d", localPort, remotePort))
35
- i.EntriesLock.Unlock()
36
- }
37
-
38
- // Run - Start listening for ident lookups
39
- func (i *Server) Run() error {
40
- serv, err := net.Listen("tcp", ":113")
41
- if err != nil {
42
- return err
43
- }
44
-
45
- go i.ListenForRequests(&serv)
46
- return nil
47
- }
48
-
49
- // ListenForRequests - Listen on a net.Listener for ident lookups
50
- func (i *Server) ListenForRequests(serverSocket *net.Listener) {
51
- for {
52
- serv := *serverSocket
53
- client, err := serv.Accept()
54
- if err != nil {
55
- break
56
- }
57
-
58
- go func(conn net.Conn) {
59
- tc := textproto.NewConn(conn)
60
-
61
- line, err := tc.ReadLine()
62
- if err != nil {
63
- conn.Close()
64
- return
65
- }
66
-
67
- // Remove all spaces, some servers like to send "%d , %d" but the spec examples use "%d, %d"
68
- line = strings.ReplaceAll(line, " ", "")
69
-
70
- var localPort, remotePort int
71
- fmt.Sscanf(line, "%d,%d", &localPort, &remotePort)
72
- if localPort > 0 && remotePort > 0 {
73
- i.EntriesLock.Lock()
74
- ident, ok := i.Entries[fmt.Sprintf("%d-%d", localPort, remotePort)]
75
- i.EntriesLock.Unlock()
76
- if !ok {
77
- fmt.Fprintf(conn, "%d, %d : ERROR : NO-USER\r\n", localPort, remotePort)
78
- } else {
79
- fmt.Fprintf(conn, "%d, %d : USERID : UNIX : %s\r\n", localPort, remotePort, ident)
80
- }
81
- }
82
-
83
- conn.Close()
84
- }(client)
85
- }
86
- }
@@ -1,59 +0,0 @@
1
- package identd
2
-
3
- import "net"
4
- import "fmt"
5
- import "time"
6
-
7
- func MakeRpcClient(appName string) *RpcClient {
8
- return &RpcClient{AppName: appName}
9
- }
10
-
11
- type RpcClient struct {
12
- AppName string
13
- Conn *net.Conn
14
- }
15
-
16
- func (rpc *RpcClient) ConnectAndReconnect(serverAddress string) {
17
- for {
18
- if rpc.Conn == nil {
19
- println("Connecting to identd RPC...")
20
- rpc.Connect(serverAddress)
21
- }
22
-
23
- time.Sleep(time.Second * 3)
24
- }
25
- }
26
-
27
- func (rpc *RpcClient) Connect(serverAddress string) error {
28
- conn, err := net.Dial("tcp", serverAddress)
29
- if err != nil {
30
- return err
31
- }
32
-
33
- rpc.Conn = &conn
34
- rpc.Write("id " + rpc.AppName)
35
-
36
- return nil
37
- }
38
-
39
- func (rpc *RpcClient) Write(line string) error {
40
- if rpc.Conn == nil {
41
- return fmt.Errorf("not connected")
42
- }
43
-
44
- conn := *rpc.Conn
45
- _, err := conn.Write([]byte(line + "\n"))
46
- if err != nil {
47
- rpc.Conn = nil
48
- conn.Close()
49
- }
50
- return err
51
- }
52
-
53
- func (rpc *RpcClient) AddIdent(lport int, rport int, username string, iface string) {
54
- rpc.Write(fmt.Sprintf("add %s %d %d %s", username, lport, rport, iface))
55
- }
56
-
57
- func (rpc *RpcClient) RemoveIdent(lport int, rport int, username string, iface string) {
58
- rpc.Write(fmt.Sprintf("del %d %d %s", lport, rport, iface))
59
- }
@@ -1,56 +0,0 @@
1
- package irc
2
-
3
- import (
4
- "strings"
5
- "sync"
6
- )
7
-
8
- type ISupport struct {
9
- Received bool
10
- Injected bool
11
- Tags map[string]string
12
- tokens map[string]string
13
- tokensMutex sync.RWMutex
14
- }
15
-
16
- func (m *ISupport) ClearTokens() {
17
- m.tokensMutex.Lock()
18
- m.tokens = make(map[string]string)
19
- m.tokensMutex.Unlock()
20
- }
21
-
22
- func (m *ISupport) AddToken(tokenPair string) {
23
- m.tokensMutex.Lock()
24
- m.addToken(tokenPair)
25
- m.tokensMutex.Unlock()
26
- }
27
-
28
- func (m *ISupport) AddTokens(tokenPairs []string) {
29
- m.tokensMutex.Lock()
30
- for _, tp := range tokenPairs {
31
- m.addToken(tp)
32
- }
33
- m.tokensMutex.Unlock()
34
- }
35
-
36
- func (m *ISupport) HasToken(key string) (ok bool) {
37
- m.tokensMutex.RLock()
38
- _, ok = m.tokens[strings.ToUpper(key)]
39
- m.tokensMutex.RUnlock()
40
- return
41
- }
42
-
43
- func (m *ISupport) GetToken(key string) (val string) {
44
- m.tokensMutex.RLock()
45
- val = m.tokens[strings.ToUpper(key)]
46
- m.tokensMutex.RUnlock()
47
- return
48
- }
49
-
50
- func (m *ISupport) addToken(tokenPair string) {
51
- kv := strings.Split(tokenPair, "=")
52
- if len(kv) == 1 {
53
- kv = append(kv, "")
54
- }
55
- m.tokens[strings.ToUpper(kv[0])] = kv[1]
56
- }
@@ -1,217 +0,0 @@
1
- package irc
2
-
3
- import (
4
- "errors"
5
- "strings"
6
- )
7
-
8
- type Mask struct {
9
- Nick string
10
- Username string
11
- Hostname string
12
- Mask string
13
- }
14
- type Message struct {
15
- Raw string
16
- Tags map[string]string
17
- Prefix *Mask
18
- Command string
19
- Params []string
20
- }
21
-
22
- func NewMessage() *Message {
23
- return &Message{
24
- Tags: make(map[string]string),
25
- Prefix: &Mask{},
26
- }
27
- }
28
-
29
- // GetParam - Get a param value, returning a default value if it doesn't exist
30
- func (m *Message) GetParam(idx int, def string) string {
31
- if idx < 0 || idx > len(m.Params)-1 {
32
- return def
33
- }
34
-
35
- return m.Params[idx]
36
- }
37
-
38
- // GetParamU - Get a param value in uppercase, returning a default value if it doesn't exist
39
- func (m *Message) GetParamU(idx int, def string) string {
40
- return strings.ToUpper(m.GetParam(idx, def))
41
- }
42
-
43
- // ToLine - Convert the Message struct to its raw IRC line
44
- func (m *Message) ToLine() string {
45
- line := ""
46
-
47
- if len(m.Tags) > 0 {
48
- line += "@"
49
- tagCount := 0
50
- for tagName, tagVal := range m.Tags {
51
- tagCount++
52
- line += tagName
53
- if tagVal != "" {
54
- line += "=" + tagVal
55
- }
56
- if tagCount < len(m.Tags) {
57
- line += ";"
58
- }
59
- }
60
- }
61
-
62
- if m.Prefix != nil && (m.Prefix.Nick != "" || m.Prefix.Username != "" || m.Prefix.Hostname != "") {
63
- prefix := ""
64
-
65
- if m.Prefix.Nick != "" {
66
- prefix += m.Prefix.Nick
67
- }
68
-
69
- if m.Prefix.Username != "" && m.Prefix.Nick != "" {
70
- prefix += "!" + m.Prefix.Username
71
- } else if m.Prefix.Username != "" {
72
- prefix += m.Prefix.Username
73
- }
74
-
75
- if m.Prefix.Hostname != "" && prefix != "" {
76
- prefix += "@" + m.Prefix.Username
77
- } else if m.Prefix.Hostname != "" {
78
- prefix += m.Prefix.Hostname
79
- }
80
-
81
- if line != "" {
82
- line += " :" + prefix
83
- } else {
84
- line += ":" + prefix
85
- }
86
- }
87
-
88
- if line != "" {
89
- line += " " + m.Command
90
- } else {
91
- line += m.Command
92
- }
93
-
94
- paramLen := len(m.Params)
95
- for idx, param := range m.Params {
96
- if idx == paramLen-1 && (strings.Contains(param, " ") || strings.HasPrefix(param, ":")) {
97
- line += " :" + param
98
- } else {
99
- line += " " + param
100
- }
101
- }
102
-
103
- return line
104
- }
105
-
106
- func createMask(maskStr string) *Mask {
107
- mask := &Mask{
108
- Mask: maskStr,
109
- }
110
-
111
- usernameStart := strings.Index(maskStr, "!")
112
- hostStart := strings.Index(maskStr, "@")
113
-
114
- if usernameStart == -1 && hostStart == -1 {
115
- mask.Nick = maskStr
116
- } else if usernameStart > -1 && hostStart > -1 {
117
- mask.Nick = maskStr[0:usernameStart]
118
- mask.Username = maskStr[usernameStart+1 : hostStart]
119
- mask.Hostname = maskStr[hostStart+1:]
120
- } else if usernameStart > -1 && hostStart == -1 {
121
- mask.Nick = maskStr[0:usernameStart]
122
- mask.Username = maskStr[usernameStart+1:]
123
- } else if usernameStart == -1 && hostStart > -1 {
124
- mask.Username = maskStr[0:hostStart]
125
- mask.Hostname = maskStr[hostStart+1:]
126
- }
127
-
128
- return mask
129
- }
130
-
131
- // ParseLine - Turn a raw IRC line into a message
132
- func ParseLine(input string) (*Message, error) {
133
- line := strings.Trim(input, "\r\n")
134
-
135
- message := NewMessage()
136
- message.Raw = line
137
-
138
- token := ""
139
- rest := ""
140
-
141
- token, rest = nextToken(line, false)
142
- if token == "" {
143
- return message, errors.New("Empty line")
144
- }
145
-
146
- // Tags. Starts with "@"
147
- if token[0] == 64 {
148
- tagsRaw := token[1:]
149
- tags := strings.Split(tagsRaw, ";")
150
- for _, tag := range tags {
151
- parts := strings.Split(tag, "=")
152
- if len(parts) > 0 && parts[0] == "" {
153
- continue
154
- }
155
-
156
- if len(parts) == 1 {
157
- message.Tags[parts[0]] = ""
158
- } else {
159
- message.Tags[parts[0]] = parts[1]
160
- }
161
- }
162
-
163
- token, rest = nextToken(rest, false)
164
- }
165
-
166
- // Prefix. Starts with ":"
167
- if token != "" && token[0] == 58 {
168
- message.Prefix = createMask(token[1:])
169
- token, rest = nextToken(rest, false)
170
- } else {
171
- message.Prefix = createMask("")
172
- }
173
-
174
- // Command
175
- if token == "" {
176
- return message, errors.New("Missing command")
177
- }
178
-
179
- message.Command = token
180
-
181
- // Params
182
- for {
183
- token, rest = nextToken(rest, true)
184
- if token == "" {
185
- break
186
- }
187
-
188
- message.Params = append(message.Params, token)
189
- }
190
-
191
- return message, nil
192
- }
193
-
194
- func nextToken(s string, allowTrailing bool) (string, string) {
195
- s = strings.TrimLeft(s, " ")
196
-
197
- if len(s) == 0 {
198
- return "", ""
199
- }
200
-
201
- // The last token (trailing) start with :
202
- if allowTrailing && s[0] == 58 {
203
- return s[1:], ""
204
- }
205
-
206
- token := ""
207
- spaceIdx := strings.Index(s, " ")
208
- if spaceIdx > -1 {
209
- token = s[:spaceIdx]
210
- s = s[spaceIdx+1:]
211
- } else {
212
- token = s
213
- s = ""
214
- }
215
-
216
- return token, s
217
- }