diff options
| author | Mistivia <i@mistivia.com> | 2025-10-24 00:11:55 +0800 |
|---|---|---|
| committer | Mistivia <i@mistivia.com> | 2025-10-24 00:11:55 +0800 |
| commit | ffa71fb8b2e5521f93d8599279af2b28a0795a66 (patch) | |
| tree | acd96fd3e03cb39c8a648c6298b96b9c847e716f /webircgateway/pkg/irc | |
| parent | 5c71d2a538a93fd4a4fc06cb7941231cc5c0b104 (diff) | |
add web irc
Diffstat (limited to 'webircgateway/pkg/irc')
| -rw-r--r-- | webircgateway/pkg/irc/isupport.go | 56 | ||||
| -rw-r--r-- | webircgateway/pkg/irc/message.go | 217 | ||||
| -rw-r--r-- | webircgateway/pkg/irc/state.go | 79 |
3 files changed, 352 insertions, 0 deletions
diff --git a/webircgateway/pkg/irc/isupport.go b/webircgateway/pkg/irc/isupport.go new file mode 100644 index 0000000..fdb7bee --- /dev/null +++ b/webircgateway/pkg/irc/isupport.go @@ -0,0 +1,56 @@ +package irc + +import ( + "strings" + "sync" +) + +type ISupport struct { + Received bool + Injected bool + Tags map[string]string + tokens map[string]string + tokensMutex sync.RWMutex +} + +func (m *ISupport) ClearTokens() { + m.tokensMutex.Lock() + m.tokens = make(map[string]string) + m.tokensMutex.Unlock() +} + +func (m *ISupport) AddToken(tokenPair string) { + m.tokensMutex.Lock() + m.addToken(tokenPair) + m.tokensMutex.Unlock() +} + +func (m *ISupport) AddTokens(tokenPairs []string) { + m.tokensMutex.Lock() + for _, tp := range tokenPairs { + m.addToken(tp) + } + m.tokensMutex.Unlock() +} + +func (m *ISupport) HasToken(key string) (ok bool) { + m.tokensMutex.RLock() + _, ok = m.tokens[strings.ToUpper(key)] + m.tokensMutex.RUnlock() + return +} + +func (m *ISupport) GetToken(key string) (val string) { + m.tokensMutex.RLock() + val = m.tokens[strings.ToUpper(key)] + m.tokensMutex.RUnlock() + return +} + +func (m *ISupport) addToken(tokenPair string) { + kv := strings.Split(tokenPair, "=") + if len(kv) == 1 { + kv = append(kv, "") + } + m.tokens[strings.ToUpper(kv[0])] = kv[1] +} diff --git a/webircgateway/pkg/irc/message.go b/webircgateway/pkg/irc/message.go new file mode 100644 index 0000000..18477d6 --- /dev/null +++ b/webircgateway/pkg/irc/message.go @@ -0,0 +1,217 @@ +package irc + +import ( + "errors" + "strings" +) + +type Mask struct { + Nick string + Username string + Hostname string + Mask string +} +type Message struct { + Raw string + Tags map[string]string + Prefix *Mask + Command string + Params []string +} + +func NewMessage() *Message { + return &Message{ + Tags: make(map[string]string), + Prefix: &Mask{}, + } +} + +// GetParam - Get a param value, returning a default value if it doesn't exist +func (m *Message) GetParam(idx int, def string) string { + if idx < 0 || idx > len(m.Params)-1 { + return def + } + + return m.Params[idx] +} + +// GetParamU - Get a param value in uppercase, returning a default value if it doesn't exist +func (m *Message) GetParamU(idx int, def string) string { + return strings.ToUpper(m.GetParam(idx, def)) +} + +// ToLine - Convert the Message struct to its raw IRC line +func (m *Message) ToLine() string { + line := "" + + if len(m.Tags) > 0 { + line += "@" + tagCount := 0 + for tagName, tagVal := range m.Tags { + tagCount++ + line += tagName + if tagVal != "" { + line += "=" + tagVal + } + if tagCount < len(m.Tags) { + line += ";" + } + } + } + + if m.Prefix != nil && (m.Prefix.Nick != "" || m.Prefix.Username != "" || m.Prefix.Hostname != "") { + prefix := "" + + if m.Prefix.Nick != "" { + prefix += m.Prefix.Nick + } + + if m.Prefix.Username != "" && m.Prefix.Nick != "" { + prefix += "!" + m.Prefix.Username + } else if m.Prefix.Username != "" { + prefix += m.Prefix.Username + } + + if m.Prefix.Hostname != "" && prefix != "" { + prefix += "@" + m.Prefix.Username + } else if m.Prefix.Hostname != "" { + prefix += m.Prefix.Hostname + } + + if line != "" { + line += " :" + prefix + } else { + line += ":" + prefix + } + } + + if line != "" { + line += " " + m.Command + } else { + line += m.Command + } + + paramLen := len(m.Params) + for idx, param := range m.Params { + if idx == paramLen-1 && (strings.Contains(param, " ") || strings.HasPrefix(param, ":")) { + line += " :" + param + } else { + line += " " + param + } + } + + return line +} + +func createMask(maskStr string) *Mask { + mask := &Mask{ + Mask: maskStr, + } + + usernameStart := strings.Index(maskStr, "!") + hostStart := strings.Index(maskStr, "@") + + if usernameStart == -1 && hostStart == -1 { + mask.Nick = maskStr + } else if usernameStart > -1 && hostStart > -1 { + mask.Nick = maskStr[0:usernameStart] + mask.Username = maskStr[usernameStart+1 : hostStart] + mask.Hostname = maskStr[hostStart+1:] + } else if usernameStart > -1 && hostStart == -1 { + mask.Nick = maskStr[0:usernameStart] + mask.Username = maskStr[usernameStart+1:] + } else if usernameStart == -1 && hostStart > -1 { + mask.Username = maskStr[0:hostStart] + mask.Hostname = maskStr[hostStart+1:] + } + + return mask +} + +// ParseLine - Turn a raw IRC line into a message +func ParseLine(input string) (*Message, error) { + line := strings.Trim(input, "\r\n") + + message := NewMessage() + message.Raw = line + + token := "" + rest := "" + + token, rest = nextToken(line, false) + if token == "" { + return message, errors.New("Empty line") + } + + // Tags. Starts with "@" + if token[0] == 64 { + tagsRaw := token[1:] + tags := strings.Split(tagsRaw, ";") + for _, tag := range tags { + parts := strings.Split(tag, "=") + if len(parts) > 0 && parts[0] == "" { + continue + } + + if len(parts) == 1 { + message.Tags[parts[0]] = "" + } else { + message.Tags[parts[0]] = parts[1] + } + } + + token, rest = nextToken(rest, false) + } + + // Prefix. Starts with ":" + if token != "" && token[0] == 58 { + message.Prefix = createMask(token[1:]) + token, rest = nextToken(rest, false) + } else { + message.Prefix = createMask("") + } + + // Command + if token == "" { + return message, errors.New("Missing command") + } + + message.Command = token + + // Params + for { + token, rest = nextToken(rest, true) + if token == "" { + break + } + + message.Params = append(message.Params, token) + } + + return message, nil +} + +func nextToken(s string, allowTrailing bool) (string, string) { + s = strings.TrimLeft(s, " ") + + if len(s) == 0 { + return "", "" + } + + // The last token (trailing) start with : + if allowTrailing && s[0] == 58 { + return s[1:], "" + } + + token := "" + spaceIdx := strings.Index(s, " ") + if spaceIdx > -1 { + token = s[:spaceIdx] + s = s[spaceIdx+1:] + } else { + token = s + s = "" + } + + return token, s +} diff --git a/webircgateway/pkg/irc/state.go b/webircgateway/pkg/irc/state.go new file mode 100644 index 0000000..69480fc --- /dev/null +++ b/webircgateway/pkg/irc/state.go @@ -0,0 +1,79 @@ +package irc + +import ( + "strings" + "sync" + "time" +) + +type State struct { + LocalPort int + RemotePort int + Username string + Nick string + RealName string + Password string + Account string + Modes map[string]string + + channelsMutex sync.Mutex + Channels map[string]*StateChannel + ISupport *ISupport +} + +type StateChannel struct { + Name string + Modes map[string]string + Joined time.Time +} + +func NewState() *State { + return &State{ + Channels: make(map[string]*StateChannel), + ISupport: &ISupport{ + tokens: make(map[string]string), + }, + } +} + +func NewStateChannel(name string) *StateChannel { + return &StateChannel{ + Name: name, + Modes: make(map[string]string), + Joined: time.Now(), + } +} + +func (m *State) HasChannel(name string) (ok bool) { + m.channelsMutex.Lock() + _, ok = m.Channels[strings.ToLower(name)] + m.channelsMutex.Unlock() + return +} + +func (m *State) GetChannel(name string) (channel *StateChannel) { + m.channelsMutex.Lock() + channel = m.Channels[strings.ToLower(name)] + m.channelsMutex.Unlock() + return +} + +func (m *State) SetChannel(channel *StateChannel) { + m.channelsMutex.Lock() + m.Channels[strings.ToLower(channel.Name)] = channel + m.channelsMutex.Unlock() +} + +func (m *State) RemoveChannel(name string) { + m.channelsMutex.Lock() + delete(m.Channels, strings.ToLower(name)) + m.channelsMutex.Unlock() +} + +func (m *State) ClearChannels() { + m.channelsMutex.Lock() + for i := range m.Channels { + delete(m.Channels, i) + } + m.channelsMutex.Unlock() +} |
