From c2551972a5283be3ff22d323b8ae20e2e626e9d6 Mon Sep 17 00:00:00 2001 From: meera Date: Sun, 19 Jul 2026 01:57:38 +0300 Subject: [PATCH] p --- main.go | 108 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/main.go b/main.go index 99d275b..8961f57 100644 --- a/main.go +++ b/main.go @@ -236,12 +236,14 @@ func handleConn(conn net.Conn) { switch subcmd { case "LS": // explicitly tell we only support PLAIN SASL, as well as chathistory - client.fwrite(":server CAP * LS :sasl=PLAIN sasl chathistory\r\n") + client.fwrite(":server CAP * LS :sasl=PLAIN sasl chathistory server-time message-tags draft/chathistory batch\r\n") case "REQ": if len(msg.Params) >= 2 && strings.Contains(strings.ToLower(msg.Params[1]), "sasl") { client.saslRequired = true client.fwrite(":server CAP * ACK :sasl\r\n") + } else if len(msg.Params) >= 2 { + client.fwrite(":server CAP * ACK :%s\r\n", msg.Params[1]) } else { client.fwrite(":server CAP * NAK :%s\r\n", msg.Params[1]) } @@ -461,13 +463,17 @@ func handleConn(conn net.Conn) { } // ircv3 stuff case "CHATHISTORY": - if len(msg.Params) < 3 { + if len(msg.Params) < 4 { client.fwrite(":server 461 %s CHATHISTORY :Not enough parameters\r\n", client.nick) continue } subcmd := strings.ToUpper(msg.Params[0]) - target := normalizeChannelName(msg.Params[1]) - count, _ := strconv.Atoi(msg.Params[2]) + target := historyTargetName(msg.Params[1]) + count, convErr := strconv.Atoi(msg.Params[3]) + if convErr != nil || count <= 0 { + client.fwrite(":server 461 %s CHATHISTORY :Invalid message count\r\n", client.nick) + continue + } client.serveHistory(subcmd, target, count, msg.Params) default: if client.nick != "" { @@ -478,6 +484,17 @@ func handleConn(conn net.Conn) { } } } + +func historyTargetName(name string) string { + if strings.HasPrefix(name, "#") || strings.HasPrefix(name, "&") { + return name + } + if nicknameExists(name) { + return name + } + return name +} + func (c *Client) sendNames(ch *Channel) { ch.mu.Lock() defer ch.mu.Unlock() @@ -496,24 +513,29 @@ func (c *Client) sendNames(ch *Channel) { } func (c *Client) serveHistory(subcmd, target string, count int, params []string) { chMu.Lock() - ch, ok := channels[target] + ch, isChannel := channels[target] chMu.Unlock() - if !ok { - c.fwrite(":server 403 %s %s :No such channel\r\n", c.nick, target) - return - } - ch.mu.RLock() - defer ch.mu.RUnlock() + var lockCh func() + var unlockCh func() + if isChannel { + lockCh = ch.mu.RLock + unlockCh = ch.mu.RUnlock + lockCh() + defer unlockCh() + } var msgs []HistoryMessage switch subcmd { case "LATEST": - if count <= len(ch.history) { + if isChannel && count <= len(ch.history) { msgs = ch.history[len(ch.history)-count:] } else { - extra := count - len(ch.history) + extra := count + if isChannel { + extra = count - len(ch.history) + } rows, err := db.Query(`SELECT msgid, timestamp, prefix, command, msg FROM messages WHERE channel=? @@ -525,7 +547,14 @@ func (c *Client) serveHistory(subcmd, target string, count int, params []string) } defer rows.Close() dbMsgs := scanRows(rows, target) - msgs = append(dbMsgs, ch.history...) + if isChannel { + msgs = append(dbMsgs, ch.history...) + } else { + for i, j := 0, len(dbMsgs)-1; i < j; i, j = i+1, j-1 { + dbMsgs[i], dbMsgs[j] = dbMsgs[j], dbMsgs[i] + } + msgs = dbMsgs + } } case "BEFORE": @@ -570,7 +599,7 @@ func (c *Client) serveHistory(subcmd, target string, count int, params []string) c.fwrite(":server 461 %s CHATHISTORY :Invalid timestamp\r\n", c.nick) return } - rows, err = db.Query(`SELECT msgid, nick, timestamp, channel, prefix, command, msg + rows, err = db.Query(`SELECT msgid, timestamp, prefix, command, msg FROM messages WHERE channel=? AND timestamp > ? ORDER BY timestamp ASC LIMIT ?`, @@ -662,7 +691,7 @@ func kickUser(sender *Client, channelName, targetNick, reason string) { ch.mu.Lock() defer ch.mu.Unlock() - if !ch.ops[sender.nick] { + if !ch.ops[sender.nick] && !globalOps[sender.nick] { sender.fwrite(":server 482 %s %s :You're not channel operator\r\n", sender.nick, channelName) return } @@ -696,7 +725,7 @@ func handleMode(sender *Client, channelName, mode, targetNick string) { ch.mu.Lock() defer ch.mu.Unlock() - if !ch.ops[sender.nick] { + if !ch.ops[sender.nick] && !globalOps[sender.nick] { sender.fwrite(":server 482 %s %s :You're not channel operator\r\n", sender.nick, channelName) return } @@ -787,6 +816,7 @@ func joinChannel(c *Client, name string) { } func (c *Client) partChannel(name string) { + name = normalizeChannelName(name) chMu.Lock() ch, ok := channels[name] chMu.Unlock() @@ -801,7 +831,7 @@ func (c *Client) partChannel(name string) { fmt.Fprintf(member.conn, ":%s PART %s\r\n", c.nick, name) } ch.mu.Unlock() - // broadcastNames(ch) + broadcastNames(ch) } func addChannel(name string, description string) { name = normalizeChannelName(name) @@ -818,11 +848,11 @@ func addChannel(name string, description string) { } } func sendMessage(sender *Client, target, text string) { - // TODO: handle when a user sends a message to a channel theyre not in cliMu.Lock() if c, ok := clients[target]; ok { c.fwrite(":%s!%s@%s PRIVMSG %s :%s\r\n", sender.nick, sender.user, sender.host, target, text) cliMu.Unlock() + storeDirectMessage(sender, target, text) return } cliMu.Unlock() @@ -830,6 +860,7 @@ func sendMessage(sender *Client, target, text string) { ch, ok := channels[target] chMu.Unlock() if !ok { + sender.fwrite(":server 401 %s %s :No such nick/channel\r\n", sender.nick, target) return } ch.mu.RLock() @@ -851,7 +882,8 @@ func sendMessage(sender *Client, target, text string) { } func (c *Client) checkRegistration() { if c.nick != "" && c.user != "" && !c.registered { - if c.saslRequired && !c.saslComplete { + if !c.saslComplete { + c.fwrite(":server 464 %s :You must authenticate before registering\r\n", c.nick) return } c.registered = true @@ -896,7 +928,7 @@ func (c *Client) performWhois(nick string) { c.fwrite(":server 318 %s %s :End of WHOIS list\r\n", c.nick, nick) } func (c *Client) sendUnregisteredNotice() { - fmt.Fprintf(c.conn, ":server 451 * :You have not registered\r\n") + c.fwrite(":server 451 * :You have not registered\r\n") } func showWho(c *Client, name string) { name = normalizeChannelName(name) @@ -909,8 +941,7 @@ func showWho(c *Client, name string) { ch.mu.Lock() defer ch.mu.Unlock() for _, member := range ch.members { - fmt.Fprintf(c.conn, ":server 352 %s %s %s server %s H :0 %s\r\n", - c.nick, name, member.host, member.nick, member.nick) + c.fwrite(":server 352 %s %s %s server %s H :0 %s\r\n", c.nick, name, member.host, member.nick, member.nick) } fmt.Fprintf(c.conn, ":server 315 %s %s :End of WHO list\r\n", c.nick, name) } @@ -931,9 +962,9 @@ func (c *Client) handleList() { } func storeMessage(ch *Channel, sender *Client, target, text string) { msg := HistoryMessage{ - MsgID: fmt.Sprintf("%d", time.Now().UnixNano()), + MsgID: fmt.Sprintf("%019d", time.Now().UnixNano()), Time: time.Now(), - Prefix: fmt.Sprintf("%s!%s@%s", sender.nick, sender.user, "host"), // for privacy reasons + Prefix: fmt.Sprintf("%s!%s@%s", sender.nick, sender.user, sender.host), Command: "PRIVMSG", Params: []string{target}, Text: text, @@ -942,10 +973,26 @@ func storeMessage(ch *Channel, sender *Client, target, text string) { if len(ch.history) > 1000 { ch.history = ch.history[1:] // drop oldest } + persistMessage(msg, sender.nick, target) +} + +func storeDirectMessage(sender *Client, target, text string) { + msg := HistoryMessage{ + MsgID: fmt.Sprintf("%019d", time.Now().UnixNano()), + Time: time.Now(), + Prefix: fmt.Sprintf("%s!%s@%s", sender.nick, sender.user, sender.host), + Command: "PRIVMSG", + Params: []string{target}, + Text: text, + } + persistMessage(msg, sender.nick, target) +} + +func persistMessage(msg HistoryMessage, nick, target string) { _, err := db.Exec(`INSERT INTO messages(msgid, nick, timestamp, channel, prefix, command, msg) VALUES(?, ?, ?, ?, ?, ?, ?)`, msg.MsgID, - sender.nick, + nick, msg.Time.UnixNano(), target, msg.Prefix, @@ -953,7 +1000,7 @@ func storeMessage(ch *Channel, sender *Client, target, text string) { msg.Text, ) if err != nil { - log.Printf("storeMessage error: %v", err) + log.Printf("persistMessage error: %v", err) } } func nicknameExists(nick string) bool { @@ -969,13 +1016,12 @@ func (c *Client) cleanup() { for name, ch := range c.channels { ch.mu.Lock() delete(ch.members, c.nick) + ch.mu.Unlock() for nick, member := range ch.members { - member.fwrite(":%s PART %s\r\n", c.nick, name) - if member == c { - delete(ch.members, nick) + if nick != c.nick { + member.fwrite(":%s PART %s\r\n", c.nick, name) } } - ch.mu.Unlock() delete(c.channels, name) } cliMu.RLock()