major: added CHATHISTORY, fixed bug in sendmessage
This commit is contained in:
parent
e49701af15
commit
44da1e17a6
50
http.go
50
http.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ type ModeResponse struct {
|
|||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
type KickResponse struct {
|
type KickResponse struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
@ -19,24 +21,33 @@ type KickResponse struct {
|
|||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHTTP(cfg Config) {
|
func startHTTP(cfg Config) {
|
||||||
htport := cfg.Webapiport
|
htport := cfg.Webapiport
|
||||||
http.HandleFunc("/admin/kick", handleKick)
|
addAuthMiddleware := func(path string, next http.HandlerFunc) {
|
||||||
http.HandleFunc("/admin/mode", handleModeAPI)
|
http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.HandleFunc("/admin/list", handleListAPI)
|
guardHandlerAuth(w, r, next)
|
||||||
http.HandleFunc("/admin/reloadmotd", handleMOTDreload)
|
})
|
||||||
fmt.Printf("[web] starting API on port %d\n", htport)
|
}
|
||||||
|
addAuthMiddleware("/admin/kick", handleKick)
|
||||||
|
addAuthMiddleware("/admin/mode", handleModeAPI)
|
||||||
|
addAuthMiddleware("/admin/list", handleListAPI)
|
||||||
|
addAuthMiddleware("/admin/reloadmotd", handleMOTDreload)
|
||||||
|
log.Printf("[web] starting API on port %d\n", htport)
|
||||||
http.ListenAndServe(fmt.Sprintf(":%d", htport), nil)
|
http.ListenAndServe(fmt.Sprintf(":%d", htport), nil)
|
||||||
}
|
}
|
||||||
func handleKick(w http.ResponseWriter, r *http.Request) {
|
func guardHandlerAuth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
user, pass, ok := r.BasicAuth()
|
user, pass, ok := r.BasicAuth()
|
||||||
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
|
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="API"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="API"`)
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
func handleKick(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
@ -82,17 +93,12 @@ func handleKick(w http.ResponseWriter, r *http.Request) {
|
|||||||
Channel: channelName,
|
Channel: channelName,
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
|
Error: "",
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(resp)
|
json.NewEncoder(w).Encode(resp)
|
||||||
}
|
}
|
||||||
func handleModeAPI(w http.ResponseWriter, r *http.Request) {
|
func handleModeAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
user, pass, ok := r.BasicAuth()
|
|
||||||
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="API"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
@ -142,6 +148,7 @@ func handleModeAPI(w http.ResponseWriter, r *http.Request) {
|
|||||||
Target: targetNick,
|
Target: targetNick,
|
||||||
Channel: channelName,
|
Channel: channelName,
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
|
Error: "",
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
@ -156,12 +163,6 @@ type ChannelInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleListAPI(w http.ResponseWriter, r *http.Request) {
|
func handleListAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
user, pass, ok := r.BasicAuth()
|
|
||||||
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="API"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chMu.Lock()
|
chMu.Lock()
|
||||||
defer chMu.Unlock()
|
defer chMu.Unlock()
|
||||||
@ -189,18 +190,9 @@ func handleListAPI(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func handleMOTDreload(w http.ResponseWriter, r *http.Request) {
|
func handleMOTDreload(w http.ResponseWriter, r *http.Request) {
|
||||||
user, pass, ok := r.BasicAuth()
|
|
||||||
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="API"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
loadMOTD(motdFile)
|
loadMOTD(motdFile)
|
||||||
type htresp struct {
|
resp := map[string]any{
|
||||||
Status string `json:"status"`
|
"status": "ok",
|
||||||
}
|
|
||||||
resp := htresp{
|
|
||||||
Status: "ok",
|
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
|
|||||||
179
main.go
179
main.go
@ -14,6 +14,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -38,8 +39,18 @@ type Channel struct {
|
|||||||
description string
|
description string
|
||||||
members map[string]*Client
|
members map[string]*Client
|
||||||
ops map[string]bool
|
ops map[string]bool
|
||||||
|
history []HistoryMessage
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
type HistoryMessage struct {
|
||||||
|
MsgID string
|
||||||
|
Time time.Time
|
||||||
|
Prefix string
|
||||||
|
Command string
|
||||||
|
Params []string
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
type Channelconfig struct {
|
type Channelconfig struct {
|
||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
@ -162,6 +173,12 @@ func handleConn(conn net.Conn) {
|
|||||||
delete(clients, oldNick)
|
delete(clients, oldNick)
|
||||||
clients[newNick] = client
|
clients[newNick] = client
|
||||||
cliMu.Unlock()
|
cliMu.Unlock()
|
||||||
|
for _, ch := range client.channels {
|
||||||
|
ch.mu.Lock()
|
||||||
|
delete(ch.members, oldNick)
|
||||||
|
ch.members[newNick] = client
|
||||||
|
ch.mu.Unlock()
|
||||||
|
}
|
||||||
client.nick = newNick
|
client.nick = newNick
|
||||||
for _, ch := range client.channels {
|
for _, ch := range client.channels {
|
||||||
broadcastNames(ch)
|
broadcastNames(ch)
|
||||||
@ -188,8 +205,8 @@ func handleConn(conn net.Conn) {
|
|||||||
subcmd := strings.ToUpper(msg.Params[0])
|
subcmd := strings.ToUpper(msg.Params[0])
|
||||||
switch subcmd {
|
switch subcmd {
|
||||||
case "LS":
|
case "LS":
|
||||||
// explicitly tell we only support PLAIN SASL
|
// explicitly tell we only support PLAIN SASL, as well as chathistory
|
||||||
client.fwrite(":server CAP * LS :sasl=PLAIN\r\n")
|
client.fwrite(":server CAP * LS :sasl=PLAIN 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") {
|
||||||
@ -373,6 +390,15 @@ 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)
|
||||||
}
|
}
|
||||||
|
case "CHATHISTORY":
|
||||||
|
if len(msg.Params) < 3 {
|
||||||
|
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])
|
||||||
|
client.serveHistory(subcmd, target, count, msg.Params)
|
||||||
default:
|
default:
|
||||||
if client.nick != "" {
|
if client.nick != "" {
|
||||||
fmt.Fprintf(conn, ":server 421 %s %s :Unknown command or command is not implemented\r\n", client.nick, msg.Command)
|
fmt.Fprintf(conn, ":server 421 %s %s :Unknown command or command is not implemented\r\n", client.nick, msg.Command)
|
||||||
@ -398,6 +424,110 @@ func (c *Client) sendNames(ch *Channel) {
|
|||||||
fmt.Fprintf(c.conn, ":server 353 %s = %s :%s\r\n", c.nick, ch.name, strings.Join(nicks, " "))
|
fmt.Fprintf(c.conn, ":server 353 %s = %s :%s\r\n", c.nick, ch.name, strings.Join(nicks, " "))
|
||||||
fmt.Fprintf(c.conn, ":server 366 %s %s :End of /NAMES list.\r\n", c.nick, ch.name)
|
fmt.Fprintf(c.conn, ":server 366 %s %s :End of /NAMES list.\r\n", c.nick, ch.name)
|
||||||
}
|
}
|
||||||
|
func (c *Client) serveHistory(subcmd, target string, count int, params []string) {
|
||||||
|
chMu.Lock()
|
||||||
|
ch, ok := 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 msgs []HistoryMessage
|
||||||
|
|
||||||
|
switch subcmd {
|
||||||
|
case "LATEST":
|
||||||
|
if count > len(ch.history) {
|
||||||
|
count = len(ch.history)
|
||||||
|
}
|
||||||
|
msgs = ch.history[len(ch.history)-count:]
|
||||||
|
|
||||||
|
case "BEFORE":
|
||||||
|
if len(params) < 4 {
|
||||||
|
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 {
|
||||||
|
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]
|
||||||
|
|
||||||
|
case "AFTER":
|
||||||
|
if len(params) < 4 {
|
||||||
|
c.fwrite(":server 461 %s CHATHISTORY :Not enough parameters\r\n", c.nick)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
end := idx + 1 + count
|
||||||
|
if end > len(ch.history) {
|
||||||
|
end = len(ch.history)
|
||||||
|
}
|
||||||
|
msgs = ch.history[idx+1 : end]
|
||||||
|
|
||||||
|
case "AROUND":
|
||||||
|
if len(params) < 4 {
|
||||||
|
c.fwrite(":server 461 %s CHATHISTORY :Not enough parameters\r\n", c.nick)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
end := idx + half + 1
|
||||||
|
if end > len(ch.history) {
|
||||||
|
end = len(ch.history)
|
||||||
|
}
|
||||||
|
msgs = ch.history[start:end]
|
||||||
|
}
|
||||||
|
batchID := fmt.Sprintf("hist-%d", time.Now().UnixNano())
|
||||||
|
c.fwrite(":server BATCH +%s chathistory %s\r\n", batchID, target)
|
||||||
|
|
||||||
|
for _, m := range msgs {
|
||||||
|
tags := fmt.Sprintf("@msgid=%s;time=%s",
|
||||||
|
m.MsgID,
|
||||||
|
m.Time.UTC().Format(time.RFC3339))
|
||||||
|
c.fwrite("%s :%s %s %s :%s\r\n",
|
||||||
|
tags,
|
||||||
|
m.Prefix,
|
||||||
|
m.Command,
|
||||||
|
strings.Join(m.Params, " "),
|
||||||
|
m.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
func kickUser(sender *Client, channelName, targetNick, reason string) {
|
func kickUser(sender *Client, channelName, targetNick, reason string) {
|
||||||
chMu.Lock()
|
chMu.Lock()
|
||||||
ch, ok := channels[channelName]
|
ch, ok := channels[channelName]
|
||||||
@ -482,6 +612,7 @@ 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
|
||||||
@ -501,6 +632,12 @@ func joinChannel(c *Client, name string) {
|
|||||||
for _, member := range ch.members {
|
for _, member := range ch.members {
|
||||||
fmt.Fprintf(member.conn, ":%s JOIN %s\r\n", c.nick, name)
|
fmt.Fprintf(member.conn, ":%s JOIN %s\r\n", c.nick, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ch.ops[c.nick] {
|
||||||
|
for _, member := range ch.members {
|
||||||
|
member.fwrite(":server MODE %s +o %s\r\n", name, c.nick)
|
||||||
|
}
|
||||||
|
}
|
||||||
c.sendNames(ch)
|
c.sendNames(ch)
|
||||||
}
|
}
|
||||||
func (c *Client) partChannel(name string) {
|
func (c *Client) partChannel(name string) {
|
||||||
@ -518,7 +655,7 @@ func (c *Client) partChannel(name string) {
|
|||||||
fmt.Fprintf(member.conn, ":%s PART %s\r\n", c.nick, name)
|
fmt.Fprintf(member.conn, ":%s PART %s\r\n", c.nick, name)
|
||||||
}
|
}
|
||||||
ch.mu.Unlock()
|
ch.mu.Unlock()
|
||||||
broadcastNames(ch)
|
// broadcastNames(ch)
|
||||||
}
|
}
|
||||||
func addChannel(name string, description string) {
|
func addChannel(name string, description string) {
|
||||||
name = normalizeChannelName(name)
|
name = normalizeChannelName(name)
|
||||||
@ -535,6 +672,7 @@ func addChannel(name string, description string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func sendMessage(sender *Client, target, text string) {
|
func sendMessage(sender *Client, target, text string) {
|
||||||
|
// TODO: handle when a user sends a message to a channel theyre not in
|
||||||
cliMu.Lock()
|
cliMu.Lock()
|
||||||
if c, ok := clients[target]; ok {
|
if c, ok := clients[target]; ok {
|
||||||
c.fwrite(":%s!%s@%s PRIVMSG %s :%s\r\n", sender.nick, sender.user, sender.host, target, text)
|
c.fwrite(":%s!%s@%s PRIVMSG %s :%s\r\n", sender.nick, sender.user, sender.host, target, text)
|
||||||
@ -548,6 +686,13 @@ func sendMessage(sender *Client, target, text string) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_, member := ch.members[sender.nick]
|
||||||
|
ch.mu.RUnlock()
|
||||||
|
if !member {
|
||||||
|
sender.fwrite(":server 404 %s %s :Cannot send to channel\r\n",
|
||||||
|
sender.nick, target)
|
||||||
|
return
|
||||||
|
}
|
||||||
ch.mu.Lock()
|
ch.mu.Lock()
|
||||||
defer ch.mu.Unlock()
|
defer ch.mu.Unlock()
|
||||||
for _, member := range ch.members {
|
for _, member := range ch.members {
|
||||||
@ -555,9 +700,9 @@ func sendMessage(sender *Client, target, text string) {
|
|||||||
fmt.Fprintf(member.conn, ":%s!%s@%s PRIVMSG %s :%s\r\n", sender.nick, sender.user, sender.host, target, text)
|
fmt.Fprintf(member.conn, ":%s!%s@%s PRIVMSG %s :%s\r\n", sender.nick, sender.user, sender.host, target, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
storeMessage(ch, sender, target, text)
|
||||||
}
|
}
|
||||||
func (c *Client) checkRegistration() {
|
func (c *Client) checkRegistration() {
|
||||||
//log.Printf("checkRegistration called: nick=%q user=%q registered=%v", c.nick, c.user, c.registered)
|
|
||||||
if c.nick != "" && c.user != "" && !c.registered {
|
if c.nick != "" && c.user != "" && !c.registered {
|
||||||
if c.saslRequired && !c.saslComplete {
|
if c.saslRequired && !c.saslComplete {
|
||||||
return
|
return
|
||||||
@ -568,9 +713,13 @@ func (c *Client) checkRegistration() {
|
|||||||
c.fwrite(":server 003 %s :This server was created just now\r\n", c.nick)
|
c.fwrite(":server 003 %s :This server was created just now\r\n", c.nick)
|
||||||
c.fwrite(":server 004 %s server irc 0.1\r\n", c.nick)
|
c.fwrite(":server 004 %s server irc 0.1\r\n", c.nick)
|
||||||
fmt.Fprintf(c.conn, ":server 375 %s :- Message of the Day -\r\n", c.nick)
|
fmt.Fprintf(c.conn, ":server 375 %s :- Message of the Day -\r\n", c.nick)
|
||||||
|
if motdString != nil {
|
||||||
for _, i := range motdString {
|
for _, i := range motdString {
|
||||||
c.fwrite(":server 372 %s :- %s\r\n", c.nick, i)
|
c.fwrite(":server 372 %s :- %s\r\n", c.nick, i)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
c.fwrite("%s :MOTD File is missing", c.nick)
|
||||||
|
}
|
||||||
fmt.Fprintf(c.conn, ":server 376 %s :End of MOTD\r\n", c.nick)
|
fmt.Fprintf(c.conn, ":server 376 %s :End of MOTD\r\n", c.nick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,6 +782,20 @@ func (c *Client) handleList() {
|
|||||||
// End of list
|
// End of list
|
||||||
c.fwrite(":server 323 %s :End of /LIST\r\n", c.nick)
|
c.fwrite(":server 323 %s :End of /LIST\r\n", c.nick)
|
||||||
}
|
}
|
||||||
|
func storeMessage(ch *Channel, sender *Client, target, text string) {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
ch.history = append(ch.history, msg)
|
||||||
|
if len(ch.history) > 1000 {
|
||||||
|
ch.history = ch.history[1:] // drop oldest
|
||||||
|
}
|
||||||
|
}
|
||||||
func nicknameExists(nick string) bool {
|
func nicknameExists(nick string) bool {
|
||||||
cliMu.RLock()
|
cliMu.RLock()
|
||||||
defer cliMu.RUnlock()
|
defer cliMu.RUnlock()
|
||||||
@ -646,11 +809,13 @@ func (c *Client) cleanup() {
|
|||||||
for name, ch := range c.channels {
|
for name, ch := range c.channels {
|
||||||
ch.mu.Lock()
|
ch.mu.Lock()
|
||||||
delete(ch.members, c.nick)
|
delete(ch.members, c.nick)
|
||||||
for _, member := range ch.members {
|
for nick, member := range ch.members {
|
||||||
member.fwrite(":%s PART %s\r\n", c.nick, name)
|
member.fwrite(":%s PART %s\r\n", c.nick, name)
|
||||||
|
if member == c {
|
||||||
|
delete(ch.members, nick)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ch.mu.Unlock()
|
ch.mu.Unlock()
|
||||||
broadcastNames(ch)
|
|
||||||
delete(c.channels, name)
|
delete(c.channels, name)
|
||||||
}
|
}
|
||||||
cliMu.RLock()
|
cliMu.RLock()
|
||||||
@ -702,7 +867,7 @@ func normalizeChannelName(name string) string {
|
|||||||
func checkCredentials(user, pass string) bool {
|
func checkCredentials(user, pass string) bool {
|
||||||
file, err := os.Open("accounts.txt")
|
file, err := os.Open("accounts.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("checkCredentials: open error: %v", err)
|
log.Printf("open error: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user