diff --git a/main.go b/main.go index f818c84..8244080 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ package main import ( "bufio" "crypto/sha256" + "database/sql" "encoding/base64" "encoding/hex" "encoding/json" @@ -20,6 +21,7 @@ import ( "time" "github.com/ergochat/irc-go/ircmsg" + _ "github.com/mattn/go-sqlite3" ) type Client struct { @@ -71,6 +73,7 @@ var ( cliMu sync.RWMutex // mutex for clients motdString []string motdFile string + db *sql.DB ) func loadConfig() (*Config, error) { @@ -123,6 +126,7 @@ func main() { if err != nil { log.Fatalf("Error listening: %v\n", err) } + sqlinitDB() log.Printf("IRC server listening on port %d\n", cfg.Port) go startHTTP(*cfg) for { @@ -450,18 +454,20 @@ func (c *Client) serveHistory(subcmd, target string, count int, params []string) c.fwrite(":server 461 %s CHATHISTORY :Not enough parameters\r\n", c.nick) return } - msgid := params[2] // format: msgid=12345 - msgid = strings.TrimPrefix(msgid, "msgid=") - idx := findMsgIndex(ch.history, msgid) - if idx == -1 { + msgid := strings.TrimPrefix(params[2], "msgid=") + + rows, err := db.Query(`SELECT msgid, timestamp, prefix, command, msg + FROM messages + WHERE channel=? AND msgid < ? + ORDER BY timestamp DESC LIMIT ?`, + target, msgid, count) + if err != nil { c.fwrite(":server 718 %s %s :No history available\r\n", c.nick, target) return } - start := idx - count - if start < 0 { - start = 0 - } - msgs = ch.history[start:idx] + defer rows.Close() + + msgs = scanRows(rows, target) case "AFTER": if len(params) < 4 { @@ -469,16 +475,19 @@ func (c *Client) serveHistory(subcmd, target string, count int, params []string) return } msgid := strings.TrimPrefix(params[2], "msgid=") - idx := findMsgIndex(ch.history, msgid) - if idx == -1 { + + rows, err := db.Query(`SELECT msgid, timestamp, prefix, command, msg + FROM messages + WHERE channel=? AND msgid > ? + ORDER BY timestamp ASC LIMIT ?`, + target, msgid, count) + if err != nil { c.fwrite(":server 718 %s %s :No history available\r\n", c.nick, target) return } - end := idx + 1 + count - if end > len(ch.history) { - end = len(ch.history) - } - msgs = ch.history[idx+1 : end] + defer rows.Close() + + msgs = scanRows(rows, target) case "AROUND": if len(params) < 4 { @@ -486,21 +495,27 @@ func (c *Client) serveHistory(subcmd, target string, count int, params []string) return } msgid := strings.TrimPrefix(params[2], "msgid=") - idx := findMsgIndex(ch.history, msgid) - if idx == -1 { - c.fwrite(":server 718 %s %s :No history available\r\n", c.nick, target) - return - } half := count / 2 - start := idx - half - if start < 0 { - start = 0 + beforeRows, _ := db.Query(`SELECT msgid, timestamp, prefix, command, msg + FROM messages + WHERE channel=? AND msgid < ? + ORDER BY timestamp DESC LIMIT ?`, + target, msgid, half) + beforeMsgs := scanRows(beforeRows, target) + anchor, err := findMsgIndex(target, msgid) + var anchorMsg []HistoryMessage + if err == nil && anchor != nil { + anchorMsg = []HistoryMessage{*anchor} } - end := idx + half + 1 - if end > len(ch.history) { - end = len(ch.history) - } - msgs = ch.history[start:end] + afterRows, _ := db.Query(`SELECT msgid, timestamp, prefix, command, msg + FROM messages + WHERE channel=? AND msgid > ? + ORDER BY timestamp ASC LIMIT ?`, + target, msgid, half) + afterMsgs := scanRows(afterRows, target) + + msgs = append(beforeMsgs, anchorMsg...) + msgs = append(msgs, afterMsgs...) } batchID := fmt.Sprintf("hist-%d", time.Now().UnixNano()) c.fwrite(":server BATCH +%s chathistory %s\r\n", batchID, target) @@ -519,13 +534,20 @@ func (c *Client) serveHistory(subcmd, target string, count int, params []string) c.fwrite(":server BATCH -%s\r\n", batchID) } -func findMsgIndex(history []HistoryMessage, msgid string) int { - for i, m := range history { - if m.MsgID == msgid { - return i - } +func findMsgIndex(channel, msgid string) (*HistoryMessage, error) { + row := db.QueryRow(`SELECT msgid, timestamp, prefix, command, msg + FROM messages + WHERE channel=? AND msgid=?`, channel, msgid) + + var m HistoryMessage + var ts string + err := row.Scan(&m.MsgID, &ts, &m.Prefix, &m.Command, &m.Text) + if err != nil { + return nil, err } - return -1 + m.Time, _ = time.Parse(time.RFC3339, ts) + m.Params = []string{channel} + return &m, nil } func kickUser(sender *Client, channelName, targetNick, reason string) { @@ -686,6 +708,7 @@ func sendMessage(sender *Client, target, text string) { if !ok { return } + ch.mu.RLock() _, member := ch.members[sender.nick] ch.mu.RUnlock() if !member { @@ -795,6 +818,19 @@ func storeMessage(ch *Channel, sender *Client, target, text string) { if len(ch.history) > 1000 { ch.history = ch.history[1:] // drop oldest } + _, err := db.Exec(`INSERT INTO messages(msgid, nick, timestamp, channel, prefix, command, msg) + VALUES(?, ?, ?, ?, ?, ?, ?)`, + msg.MsgID, + sender.nick, + msg.Time.UTC().Format(time.RFC3339), + target, + msg.Prefix, + msg.Command, + msg.Text, + ) + if err != nil { + log.Printf("storeMessage error: %v", err) + } } func nicknameExists(nick string) bool { cliMu.RLock() @@ -890,3 +926,45 @@ func checkCredentials(user, pass string) bool { } return false } + +// database stuff +func sqlinitDB() error { + var err error + db, err = sql.Open("sqlite3", "irc.db") + if err != nil { + return err + } + /* + msg := HistoryMessage{ + MsgID: fmt.Sprintf("%d", 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, + } + */ + stmt := `CREATE TABLE IF NOT EXISTS messages( + msgid TEXT PRIMARY KEY, + nick TEXT, + timestamp TEXT, + channel TEXT, + prefix TEXT, + command TEXT, + msg TEXT + )` + _, err = db.Exec(stmt) + return err +} +func scanRows(rows *sql.Rows, channel string) []HistoryMessage { + var msgs []HistoryMessage + for rows.Next() { + var m HistoryMessage + var ts string + rows.Scan(&m.MsgID, &ts, &m.Prefix, &m.Command, &m.Text) + m.Time, _ = time.Parse(time.RFC3339, ts) + m.Params = []string{channel} + msgs = append(msgs, m) + } + return msgs +}