p
This commit is contained in:
parent
330bd96cdf
commit
c2551972a5
110
main.go
110
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)
|
||||
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)
|
||||
for nick, member := range ch.members {
|
||||
member.fwrite(":%s PART %s\r\n", c.nick, name)
|
||||
if member == c {
|
||||
delete(ch.members, nick)
|
||||
}
|
||||
}
|
||||
ch.mu.Unlock()
|
||||
for nick, member := range ch.members {
|
||||
if nick != c.nick {
|
||||
member.fwrite(":%s PART %s\r\n", c.nick, name)
|
||||
}
|
||||
}
|
||||
delete(c.channels, name)
|
||||
}
|
||||
cliMu.RLock()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user