major: fixed bug in joinchannel, added INVITE command

This commit is contained in:
meera 2026-07-18 17:17:51 +03:00
parent 0898143876
commit c859f99b1e
3 changed files with 95 additions and 24 deletions

5
go.mod
View File

@ -2,4 +2,7 @@ module ircserve
go 1.26.3 go 1.26.3
require github.com/ergochat/irc-go v0.6.0 require (
github.com/ergochat/irc-go v0.6.0
github.com/mattn/go-sqlite3 v1.14.48
)

2
go.sum
View File

@ -1,2 +1,4 @@
github.com/ergochat/irc-go v0.6.0 h1:Y0AGV76aeihJfCtLaQh+OyJKFiKGrYC0VTkeMZ6XW28= github.com/ergochat/irc-go v0.6.0 h1:Y0AGV76aeihJfCtLaQh+OyJKFiKGrYC0VTkeMZ6XW28=
github.com/ergochat/irc-go v0.6.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0= github.com/ergochat/irc-go v0.6.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs=
github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=

98
main.go
View File

@ -171,19 +171,25 @@ func handleConn(conn net.Conn) {
client.fwrite(":server 433 * %s :Nickname is already in use\r\n", newNick) client.fwrite(":server 433 * %s :Nickname is already in use\r\n", newNick)
} else { } else {
oldNick := client.nick oldNick := client.nick
if oldNick != "" { client.nick = newNick
// nick change
cliMu.Lock() cliMu.Lock()
if oldNick != "" {
delete(clients, oldNick) delete(clients, oldNick)
}
clients[newNick] = client clients[newNick] = client
cliMu.Unlock() cliMu.Unlock()
for _, ch := range client.channels { for _, ch := range client.channels {
ch.mu.Lock() ch.mu.Lock()
if oldNick != "" {
delete(ch.members, oldNick) delete(ch.members, oldNick)
} else {
delete(ch.members, "")
}
ch.members[newNick] = client ch.members[newNick] = client
ch.mu.Unlock() ch.mu.Unlock()
} }
client.nick = newNick if oldNick != "" {
for _, ch := range client.channels { for _, ch := range client.channels {
broadcastNames(ch) broadcastNames(ch)
} }
@ -192,15 +198,12 @@ func handleConn(conn net.Conn) {
other.fwrite(":%s NICK %s\r\n", oldNick, newNick) other.fwrite(":%s NICK %s\r\n", oldNick, newNick)
} }
} }
} else {
client.nick = newNick
cliMu.Lock()
clients[newNick] = client
cliMu.Unlock()
} }
client.checkRegistration() client.checkRegistration()
} }
} }
case "CAP": case "CAP":
if len(msg.Params) < 1 { if len(msg.Params) < 1 {
client.fwrite(":server 461 %s CAP :Not enough parameters\r\n", client.nick) client.fwrite(":server 461 %s CAP :Not enough parameters\r\n", client.nick)
@ -210,7 +213,7 @@ func handleConn(conn net.Conn) {
switch subcmd { switch subcmd {
case "LS": case "LS":
// explicitly tell we only support PLAIN SASL, as well as chathistory // explicitly tell we only support PLAIN SASL, as well as chathistory
client.fwrite(":server CAP * LS :sasl=PLAIN chathistory\r\n") client.fwrite(":server CAP * LS :sasl=PLAIN sasl chathistory\r\n")
case "REQ": case "REQ":
if len(msg.Params) >= 2 && strings.Contains(strings.ToLower(msg.Params[1]), "sasl") { if len(msg.Params) >= 2 && strings.Contains(strings.ToLower(msg.Params[1]), "sasl") {
@ -335,6 +338,44 @@ func handleConn(conn net.Conn) {
text := msg.Params[1] text := msg.Params[1]
sendNotice(client, target, text) sendNotice(client, target, text)
} }
case "INVITE":
if !client.registered {
client.sendUnregisteredNotice()
continue
}
if len(msg.Params) < 2 {
client.fwrite(":server 461 %s INVITE :Not enough parameters\r\n", client.nick)
continue
}
targetNick := msg.Params[0]
channelName := normalizeChannelName(msg.Params[1])
cliMu.RLock()
target, ok := clients[targetNick]
cliMu.RUnlock()
if !ok {
client.fwrite(":server 401 %s %s :No such nick\r\n", client.nick, targetNick)
continue
}
chMu.Lock()
ch, ok := channels[channelName]
chMu.Unlock()
if !ok {
client.fwrite(":server 403 %s %s :No such channel\r\n", client.nick, channelName)
continue
}
ch.mu.RLock()
_, inChannel := ch.members[client.nick]
ch.mu.RUnlock()
if !inChannel {
client.fwrite(":server 442 %s %s :You're not on that channel\r\n", client.nick, channelName)
continue
}
client.fwrite(":server 341 %s %s %s\r\n", client.nick, targetNick, channelName)
target.fwrite(":%s INVITE %s :%s\r\n", client.nick, targetNick, channelName)
case "QUIT": case "QUIT":
if !client.registered { if !client.registered {
client.sendUnregisteredNotice() client.sendUnregisteredNotice()
@ -394,6 +435,7 @@ func handleConn(conn net.Conn) {
} else { } else {
fmt.Fprintf(client.conn, ":server 461 %s NAMES :Not enough parameters\r\n", client.nick) fmt.Fprintf(client.conn, ":server 461 %s NAMES :Not enough parameters\r\n", client.nick)
} }
// ircv3 stuff
case "CHATHISTORY": case "CHATHISTORY":
if len(msg.Params) < 3 { if len(msg.Params) < 3 {
client.fwrite(":server 461 %s CHATHISTORY :Not enough parameters\r\n", client.nick) client.fwrite(":server 461 %s CHATHISTORY :Not enough parameters\r\n", client.nick)
@ -624,9 +666,10 @@ func handleMode(sender *Client, channelName, mode, targetNick string) {
sender.fwrite(":server 501 %s :Unknown MODE flag\r\n", sender.nick) sender.fwrite(":server 501 %s :Unknown MODE flag\r\n", sender.nick)
} }
} }
func joinChannel(c *Client, name string) { func joinChannel(c *Client, name string) {
name = normalizeChannelName(name) name = normalizeChannelName(name)
// get or create channel
chMu.Lock() chMu.Lock()
ch, ok := channels[name] ch, ok := channels[name]
if !ok { if !ok {
@ -634,13 +677,9 @@ func joinChannel(c *Client, name string) {
name: name, name: name,
members: make(map[string]*Client), members: make(map[string]*Client),
ops: make(map[string]bool), ops: make(map[string]bool),
history: nil,
description: "none", description: "none",
} }
channels[name] = ch channels[name] = ch
if globalOps[c.nick] {
ch.ops[c.nick] = true
}
} }
if globalOps[c.nick] { if globalOps[c.nick] {
ch.ops[c.nick] = true ch.ops[c.nick] = true
@ -648,20 +687,47 @@ func joinChannel(c *Client, name string) {
chMu.Unlock() chMu.Unlock()
ch.mu.Lock() ch.mu.Lock()
// reject duplicate join
if _, exists := ch.members[c.nick]; exists {
ch.mu.Unlock()
c.fwrite(":server 443 %s %s %s :is already on channel\r\n",
c.nick, c.nick, name)
return
}
// clean up stale empty nick
delete(ch.members, "")
// add new member
ch.members[c.nick] = c ch.members[c.nick] = c
c.channels[name] = ch c.channels[name] = ch
ch.mu.Unlock() ch.mu.Unlock()
// echo JOIN to client
fmt.Fprintf(c.conn, ":%s JOIN %s\r\n", c.nick, name)
// broadcast JOIN to others
for _, member := range ch.members { for _, member := range ch.members {
if member != c {
fmt.Fprintf(member.conn, ":%s JOIN %s\r\n", c.nick, name) fmt.Fprintf(member.conn, ":%s JOIN %s\r\n", c.nick, name)
} }
}
// topic
if ch.description != "" {
c.fwrite(":server 332 %s %s :%s\r\n", c.nick, name, ch.description)
} else {
c.fwrite(":server 331 %s %s :No topic is set\r\n", c.nick, name)
}
// names list
c.sendNames(ch)
// MODE if op
if ch.ops[c.nick] { if ch.ops[c.nick] {
for _, member := range ch.members { for _, member := range ch.members {
member.fwrite(":server MODE %s +o %s\r\n", name, c.nick) member.fwrite(":server MODE %s +o %s\r\n", name, c.nick)
} }
} }
c.sendNames(ch)
} }
func (c *Client) partChannel(name string) { func (c *Client) partChannel(name string) {
chMu.Lock() chMu.Lock()
ch, ok := channels[name] ch, ok := channels[name]
@ -809,7 +875,7 @@ func storeMessage(ch *Channel, sender *Client, target, text string) {
msg := HistoryMessage{ msg := HistoryMessage{
MsgID: fmt.Sprintf("%d", time.Now().UnixNano()), MsgID: fmt.Sprintf("%d", time.Now().UnixNano()),
Time: time.Now(), Time: time.Now(),
Prefix: fmt.Sprintf("%s!%s@%s", sender.nick, sender.user, sender.host), Prefix: fmt.Sprintf("%s!%s@%s", sender.nick, sender.user, "host"), // for privacy reasons
Command: "PRIVMSG", Command: "PRIVMSG",
Params: []string{target}, Params: []string{target},
Text: text, Text: text,