From ed2e56948eaa6d43d3f7e9d99d157a8e1ede6238 Mon Sep 17 00:00:00 2001 From: Dario Piombo Date: Sun, 3 May 2020 17:00:41 +0200 Subject: [PATCH 1/8] start of DCC implementation (server side) small refactoring and added speed calculation fixup! small refactoring and added speed calculation download progress fixup! download progress --- config.default.toml | 3 + config/config.go | 4 +- pkg/irc/client.go | 157 ++++++++++++++++++++++++++++++++++++++++++ pkg/irc/conn.go | 5 ++ pkg/irc/message.go | 75 ++++++++++++++++++++ server/irc.go | 3 + server/irc_handler.go | 2 + 7 files changed, 248 insertions(+), 1 deletion(-) diff --git a/config.default.toml b/config.default.toml index 4b44ae4c..20e3f620 100644 --- a/config.default.toml +++ b/config.default.toml @@ -5,6 +5,9 @@ port = 80 hexIP = false verify_certificates = true +download_folder = "" +autoget = false + # Defaults for the client connect form [defaults] name = "freenode" diff --git a/config/config.go b/config/config.go index 83288b90..72515fa3 100644 --- a/config/config.go +++ b/config/config.go @@ -13,7 +13,9 @@ type Config struct { Port string Dev bool HexIP bool - VerifyCertificates bool `mapstructure:"verify_certificates"` + VerifyCertificates bool `mapstructure:"verify_certificates"` + DownloadFolder string `mapstructure:"download_folder"` + Autoget bool Headers map[string]string Defaults Defaults HTTPS HTTPS diff --git a/pkg/irc/client.go b/pkg/irc/client.go index f6839f2e..e3d2ed38 100644 --- a/pkg/irc/client.go +++ b/pkg/irc/client.go @@ -3,7 +3,13 @@ package irc import ( "bufio" "crypto/tls" + "encoding/binary" + "fmt" + "io" + "math" "net" + "os" + "path/filepath" "strings" "sync" "time" @@ -21,8 +27,12 @@ type Client struct { Realname string HandleNickInUse func(string) string + DownloadFolder string + Autoget bool + Messages chan *Message ConnectionChanged chan ConnectionState + Progress chan DownloadProgress Features *Features nick string channels []string @@ -50,6 +60,7 @@ func NewClient(nick, username string) *Client { Realname: nick, Messages: make(chan *Message, 32), ConnectionChanged: make(chan ConnectionState, 16), + Progress: make(chan DownloadProgress, 16), out: make(chan string, 32), quit: make(chan struct{}), reconnect: make(chan struct{}), @@ -207,3 +218,149 @@ func (c *Client) flushChannels() { } c.lock.Unlock() } + +func byteRead(totalBytes uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, totalBytes) + return b +} + +func round2(source float64) float64 { + return math.Round(100*source) / 100 +} + +const ( + _ = 1.0 << (10 * iota) + kibibyte + mebibyte + gibibyte +) + +func humanReadableByteCount(b float64, speed bool) string { + unit := "" + value := b + + switch { + case b >= gibibyte: + unit = "GiB" + value = value / gibibyte + case b >= mebibyte: + unit = "MiB" + value = value / mebibyte + case b >= kibibyte: + unit = "KiB" + value = value / kibibyte + case b > 1 || b == 0: + unit = "bytes" + case b == 1: + unit = "byte" + } + + if speed { + unit = unit + "/s" + } + + stringValue := strings.TrimSuffix( + fmt.Sprintf("%.2f", value), ".00", + ) + + return fmt.Sprintf("%s %s", stringValue, unit) +} + +func (c *Client) Download(pack *CTCP) { + if !c.Autoget { + // TODO: ask user if he/she wants to download the file + return + } + c.Progress <- DownloadProgress{ + PercCompletion: 0, + File: pack.File, + } + file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } + return + } + defer file.Close() + + con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", pack.IP, pack.Port)) + + if err != nil { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } + return + } + + defer con.Close() + + var avgSpeed float64 + var prevTime int64 = -1 + secondsElapsed := int64(0) + totalBytes := uint64(0) + buf := make([]byte, 0, 4*1024) + start := time.Now().UnixNano() + for { + n, err := con.Read(buf[:cap(buf)]) + buf = buf[:n] + if n == 0 { + if err == nil { + continue + } + if err == io.EOF { + break + } + } + + if _, err := file.Write(buf); err != nil { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } + return + } + + cycleBytes := uint64(len(buf)) + totalBytes += cycleBytes + percentage := round2(100 * float64(totalBytes) / float64(pack.Length)) + + now := time.Now().UnixNano() + secondsElapsed = (now - start) / 1e9 + avgSpeed = round2(float64(totalBytes) / (float64(secondsElapsed))) + speed := 0.0 + + if prevTime < 0 { + speed = avgSpeed + } else { + speed = round2(1e9 * float64(cycleBytes) / (float64(now - prevTime))) + } + secondsToGo := (float64(pack.Length) - float64(totalBytes)) / speed + prevTime = now + con.Write(byteRead(totalBytes)) + c.Progress <- DownloadProgress{ + InstSpeed: humanReadableByteCount(speed, true), + AvgSpeed: humanReadableByteCount(avgSpeed, true), + PercCompletion: percentage, + BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false), + BytesCompleted: humanReadableByteCount(float64(totalBytes), false), + SecondsElapsed: secondsElapsed, + SecondsToGo: secondsToGo, + File: pack.File, + } + } + con.Write(byteRead(totalBytes)) + c.Progress <- DownloadProgress{ + AvgSpeed: humanReadableByteCount(avgSpeed, true), + PercCompletion: 100, + BytesCompleted: humanReadableByteCount(float64(totalBytes), false), + SecondsElapsed: secondsElapsed, + File: pack.File, + } +} diff --git a/pkg/irc/conn.go b/pkg/irc/conn.go index 935a55f4..ae819a2b 100644 --- a/pkg/irc/conn.go +++ b/pkg/irc/conn.go @@ -239,6 +239,11 @@ func (c *Client) recv() { if c.HandleNickInUse != nil { go c.writeNick(c.HandleNickInUse(msg.Params[1])) } + + } + + if ctcp := msg.ToCTCP(); ctcp != nil { + go c.Download(ctcp) } c.Messages <- msg diff --git a/pkg/irc/message.go b/pkg/irc/message.go index cc64d55e..b40fecd7 100644 --- a/pkg/irc/message.go +++ b/pkg/irc/message.go @@ -1,7 +1,12 @@ package irc import ( + "encoding/json" + "fmt" + "path" + "strconv" "strings" + "unicode" ) type Message struct { @@ -12,6 +17,26 @@ type Message struct { Params []string } +type DownloadProgress struct { + File string `json:"file"` + Error error `json:"error"` + BytesCompleted string `json:"bytes_completed"` + BytesRemaining string `json:"bytes_remaining"` + PercCompletion float64 `json:"perc_completion"` + AvgSpeed string `json:"avg_speed"` + InstSpeed string `json:"speed"` + SecondsElapsed int64 `json:"elapsed"` + SecondsToGo float64 `json:"eta"` +} + +// CTCP is used to parse a message into a CTCP message +type CTCP struct { + File string `json:"file"` + IP string `json:"ip"` + Port uint16 `json:"port"` + Length uint64 `json:"length"` +} + func (m *Message) LastParam() string { if len(m.Params) > 0 { return m.Params[len(m.Params)-1] @@ -19,6 +44,40 @@ func (m *Message) LastParam() string { return "" } +// ToCTCP tries to parse the message parameters into a CTCP message +func (m *Message) ToCTCP() *CTCP { + params := strings.Join(m.Params, " ") + if strings.Contains(params, "DCC SEND") { + // to be extra sure that there are non-printable characters + params = strings.TrimFunc(params, func(r rune) bool { + return !unicode.IsPrint(r) + }) + parts := strings.Split(params, " ") + ip, err := strconv.Atoi(parts[4]) + port, err := strconv.Atoi(parts[5]) + length, err := strconv.Atoi(parts[6]) + + if err != nil { + return nil + } + + ip3 := uint32ToIP(ip) + + filename := path.Base(parts[3]) + if filename == "/" || filename == "." { + filename = "" + } + + return &CTCP{ + File: filename, + IP: ip3, + Port: uint16(port), + Length: uint64(length), + } + } + return nil +} + func ParseMessage(line string) *Message { msg := Message{} @@ -112,3 +171,19 @@ var unescapeTagReplacer = strings.NewReplacer( func unescapeTag(s string) string { return unescapeTagReplacer.Replace(s) } + +func uint32ToIP(n int) string { + var byte1 = n & 255 + var byte2 = ((n >> 8) & 255) + var byte3 = ((n >> 16) & 255) + var byte4 = ((n >> 24) & 255) + return fmt.Sprintf("%d.%d.%d.%d", byte4, byte3, byte2, byte1) +} + +func (p DownloadProgress) ToJSON() string { + progress, err := json.Marshal(p) + if err != nil { + return "" + } + return string(progress) +} diff --git a/server/irc.go b/server/irc.go index 8e67c96a..f708e9d6 100644 --- a/server/irc.go +++ b/server/irc.go @@ -70,6 +70,9 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client } } + i.DownloadFolder = cfg.DownloadFolder + i.Autoget = cfg.Autoget + state.setIRC(server.Host, i) i.Connect(address) go newIRCHandler(i, state).run() diff --git a/server/irc_handler.go b/server/irc_handler.go index 9a8c1ed0..27b2aeb0 100644 --- a/server/irc_handler.go +++ b/server/irc_handler.go @@ -63,6 +63,8 @@ func (i *ircHandler) run() { } else if state.Connected { i.log("Connected") } + case progress := <-i.client.Progress: + i.state.sendJSON("progress", progress.ToJSON()) } } } From 2509420ba504cde98260f47b02c0165da6d6ee7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Tue, 12 May 2020 04:13:05 +0200 Subject: [PATCH 2/8] Make ToCTCP handle any type of CTCP message, move DCC handling to separate file --- pkg/irc/client.go | 152 ------------------------------ pkg/irc/conn.go | 20 +++- pkg/irc/dcc.go | 227 +++++++++++++++++++++++++++++++++++++++++++++ pkg/irc/message.go | 83 ++++------------- 4 files changed, 261 insertions(+), 221 deletions(-) create mode 100644 pkg/irc/dcc.go diff --git a/pkg/irc/client.go b/pkg/irc/client.go index e3d2ed38..83dd40b0 100644 --- a/pkg/irc/client.go +++ b/pkg/irc/client.go @@ -3,13 +3,7 @@ package irc import ( "bufio" "crypto/tls" - "encoding/binary" - "fmt" - "io" - "math" "net" - "os" - "path/filepath" "strings" "sync" "time" @@ -218,149 +212,3 @@ func (c *Client) flushChannels() { } c.lock.Unlock() } - -func byteRead(totalBytes uint64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, totalBytes) - return b -} - -func round2(source float64) float64 { - return math.Round(100*source) / 100 -} - -const ( - _ = 1.0 << (10 * iota) - kibibyte - mebibyte - gibibyte -) - -func humanReadableByteCount(b float64, speed bool) string { - unit := "" - value := b - - switch { - case b >= gibibyte: - unit = "GiB" - value = value / gibibyte - case b >= mebibyte: - unit = "MiB" - value = value / mebibyte - case b >= kibibyte: - unit = "KiB" - value = value / kibibyte - case b > 1 || b == 0: - unit = "bytes" - case b == 1: - unit = "byte" - } - - if speed { - unit = unit + "/s" - } - - stringValue := strings.TrimSuffix( - fmt.Sprintf("%.2f", value), ".00", - ) - - return fmt.Sprintf("%s %s", stringValue, unit) -} - -func (c *Client) Download(pack *CTCP) { - if !c.Autoget { - // TODO: ask user if he/she wants to download the file - return - } - c.Progress <- DownloadProgress{ - PercCompletion: 0, - File: pack.File, - } - file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - c.Progress <- DownloadProgress{ - PercCompletion: -1, - File: pack.File, - Error: err, - } - return - } - defer file.Close() - - con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", pack.IP, pack.Port)) - - if err != nil { - c.Progress <- DownloadProgress{ - PercCompletion: -1, - File: pack.File, - Error: err, - } - return - } - - defer con.Close() - - var avgSpeed float64 - var prevTime int64 = -1 - secondsElapsed := int64(0) - totalBytes := uint64(0) - buf := make([]byte, 0, 4*1024) - start := time.Now().UnixNano() - for { - n, err := con.Read(buf[:cap(buf)]) - buf = buf[:n] - if n == 0 { - if err == nil { - continue - } - if err == io.EOF { - break - } - } - - if _, err := file.Write(buf); err != nil { - c.Progress <- DownloadProgress{ - PercCompletion: -1, - File: pack.File, - Error: err, - } - return - } - - cycleBytes := uint64(len(buf)) - totalBytes += cycleBytes - percentage := round2(100 * float64(totalBytes) / float64(pack.Length)) - - now := time.Now().UnixNano() - secondsElapsed = (now - start) / 1e9 - avgSpeed = round2(float64(totalBytes) / (float64(secondsElapsed))) - speed := 0.0 - - if prevTime < 0 { - speed = avgSpeed - } else { - speed = round2(1e9 * float64(cycleBytes) / (float64(now - prevTime))) - } - secondsToGo := (float64(pack.Length) - float64(totalBytes)) / speed - prevTime = now - con.Write(byteRead(totalBytes)) - c.Progress <- DownloadProgress{ - InstSpeed: humanReadableByteCount(speed, true), - AvgSpeed: humanReadableByteCount(avgSpeed, true), - PercCompletion: percentage, - BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false), - BytesCompleted: humanReadableByteCount(float64(totalBytes), false), - SecondsElapsed: secondsElapsed, - SecondsToGo: secondsToGo, - File: pack.File, - } - } - con.Write(byteRead(totalBytes)) - c.Progress <- DownloadProgress{ - AvgSpeed: humanReadableByteCount(avgSpeed, true), - PercCompletion: 100, - BytesCompleted: humanReadableByteCount(float64(totalBytes), false), - SecondsElapsed: secondsElapsed, - File: pack.File, - } -} diff --git a/pkg/irc/conn.go b/pkg/irc/conn.go index ae819a2b..fc8b698a 100644 --- a/pkg/irc/conn.go +++ b/pkg/irc/conn.go @@ -223,6 +223,11 @@ func (c *Client) recv() { c.setNick(msg.LastParam()) } + case Privmsg: + if ctcp := msg.ToCTCP(); ctcp != nil { + c.handleCTCP(ctcp) + } + case ReplyWelcome: c.setNick(msg.Params[0]) c.setRegistered(true) @@ -242,10 +247,17 @@ func (c *Client) recv() { } - if ctcp := msg.ToCTCP(); ctcp != nil { - go c.Download(ctcp) - } - c.Messages <- msg } } + +func (c *Client) handleCTCP(ctcp *CTCP) { + switch ctcp.Command { + case "DCC": + if strings.HasPrefix(ctcp.Params, "SEND") { + if dccSend := ParseDCCSend(ctcp); dccSend != nil { + go c.Download(dccSend) + } + } + } +} diff --git a/pkg/irc/dcc.go b/pkg/irc/dcc.go new file mode 100644 index 00000000..014a24c2 --- /dev/null +++ b/pkg/irc/dcc.go @@ -0,0 +1,227 @@ +package irc + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "io" + "math" + "net" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" +) + +type DCCSend struct { + File string `json:"file"` + IP string `json:"ip"` + Port uint16 `json:"port"` + Length uint64 `json:"length"` +} + +func ParseDCCSend(ctcp *CTCP) *DCCSend { + params := strings.Split(ctcp.Params, " ") + + if len(params) > 4 { + ip, err := strconv.Atoi(params[2]) + port, err := strconv.Atoi(params[3]) + length, err := strconv.Atoi(params[4]) + + if err != nil { + return nil + } + + ip3 := uint32ToIP(ip) + + filename := path.Base(params[1]) + if filename == "/" || filename == "." { + filename = "" + } + + return &DCCSend{ + File: filename, + IP: ip3, + Port: uint16(port), + Length: uint64(length), + } + } + + return nil +} + +func (c *Client) Download(pack *DCCSend) { + if !c.Autoget { + // TODO: ask user if he/she wants to download the file + return + } + c.Progress <- DownloadProgress{ + PercCompletion: 0, + File: pack.File, + } + file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } + return + } + defer file.Close() + + con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", pack.IP, pack.Port)) + + if err != nil { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } + return + } + + defer con.Close() + + var avgSpeed float64 + var prevTime int64 = -1 + secondsElapsed := int64(0) + totalBytes := uint64(0) + buf := make([]byte, 0, 4*1024) + start := time.Now().UnixNano() + for { + n, err := con.Read(buf[:cap(buf)]) + buf = buf[:n] + if n == 0 { + if err == nil { + continue + } + if err == io.EOF { + break + } + } + + if _, err := file.Write(buf); err != nil { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } + return + } + + cycleBytes := uint64(len(buf)) + totalBytes += cycleBytes + percentage := round2(100 * float64(totalBytes) / float64(pack.Length)) + + now := time.Now().UnixNano() + secondsElapsed = (now - start) / 1e9 + avgSpeed = round2(float64(totalBytes) / (float64(secondsElapsed))) + speed := 0.0 + + if prevTime < 0 { + speed = avgSpeed + } else { + speed = round2(1e9 * float64(cycleBytes) / (float64(now - prevTime))) + } + secondsToGo := (float64(pack.Length) - float64(totalBytes)) / speed + prevTime = now + con.Write(byteRead(totalBytes)) + c.Progress <- DownloadProgress{ + InstSpeed: humanReadableByteCount(speed, true), + AvgSpeed: humanReadableByteCount(avgSpeed, true), + PercCompletion: percentage, + BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false), + BytesCompleted: humanReadableByteCount(float64(totalBytes), false), + SecondsElapsed: secondsElapsed, + SecondsToGo: secondsToGo, + File: pack.File, + } + } + con.Write(byteRead(totalBytes)) + c.Progress <- DownloadProgress{ + AvgSpeed: humanReadableByteCount(avgSpeed, true), + PercCompletion: 100, + BytesCompleted: humanReadableByteCount(float64(totalBytes), false), + SecondsElapsed: secondsElapsed, + File: pack.File, + } +} + +type DownloadProgress struct { + File string `json:"file"` + Error error `json:"error"` + BytesCompleted string `json:"bytes_completed"` + BytesRemaining string `json:"bytes_remaining"` + PercCompletion float64 `json:"perc_completion"` + AvgSpeed string `json:"avg_speed"` + InstSpeed string `json:"speed"` + SecondsElapsed int64 `json:"elapsed"` + SecondsToGo float64 `json:"eta"` +} + +func (p DownloadProgress) ToJSON() string { + progress, err := json.Marshal(p) + if err != nil { + return "" + } + return string(progress) +} + +func uint32ToIP(n int) string { + var byte1 = n & 255 + var byte2 = ((n >> 8) & 255) + var byte3 = ((n >> 16) & 255) + var byte4 = ((n >> 24) & 255) + return fmt.Sprintf("%d.%d.%d.%d", byte4, byte3, byte2, byte1) +} + +func byteRead(totalBytes uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, totalBytes) + return b +} + +func round2(source float64) float64 { + return math.Round(100*source) / 100 +} + +const ( + _ = 1.0 << (10 * iota) + kibibyte + mebibyte + gibibyte +) + +func humanReadableByteCount(b float64, speed bool) string { + unit := "" + value := b + + switch { + case b >= gibibyte: + unit = "GiB" + value = value / gibibyte + case b >= mebibyte: + unit = "MiB" + value = value / mebibyte + case b >= kibibyte: + unit = "KiB" + value = value / kibibyte + case b > 1 || b == 0: + unit = "bytes" + case b == 1: + unit = "byte" + } + + if speed { + unit = unit + "/s" + } + + stringValue := strings.TrimSuffix( + fmt.Sprintf("%.2f", value), ".00", + ) + + return fmt.Sprintf("%s %s", stringValue, unit) +} diff --git a/pkg/irc/message.go b/pkg/irc/message.go index b40fecd7..6327fec4 100644 --- a/pkg/irc/message.go +++ b/pkg/irc/message.go @@ -1,12 +1,7 @@ package irc import ( - "encoding/json" - "fmt" - "path" - "strconv" "strings" - "unicode" ) type Message struct { @@ -17,26 +12,6 @@ type Message struct { Params []string } -type DownloadProgress struct { - File string `json:"file"` - Error error `json:"error"` - BytesCompleted string `json:"bytes_completed"` - BytesRemaining string `json:"bytes_remaining"` - PercCompletion float64 `json:"perc_completion"` - AvgSpeed string `json:"avg_speed"` - InstSpeed string `json:"speed"` - SecondsElapsed int64 `json:"elapsed"` - SecondsToGo float64 `json:"eta"` -} - -// CTCP is used to parse a message into a CTCP message -type CTCP struct { - File string `json:"file"` - IP string `json:"ip"` - Port uint16 `json:"port"` - Length uint64 `json:"length"` -} - func (m *Message) LastParam() string { if len(m.Params) > 0 { return m.Params[len(m.Params)-1] @@ -44,37 +19,31 @@ func (m *Message) LastParam() string { return "" } -// ToCTCP tries to parse the message parameters into a CTCP message -func (m *Message) ToCTCP() *CTCP { - params := strings.Join(m.Params, " ") - if strings.Contains(params, "DCC SEND") { - // to be extra sure that there are non-printable characters - params = strings.TrimFunc(params, func(r rune) bool { - return !unicode.IsPrint(r) - }) - parts := strings.Split(params, " ") - ip, err := strconv.Atoi(parts[4]) - port, err := strconv.Atoi(parts[5]) - length, err := strconv.Atoi(parts[6]) +type CTCP struct { + Command string + Params string +} - if err != nil { +func (m *Message) ToCTCP() *CTCP { + lp := m.LastParam() + + if len(lp) > 1 && lp[0] == 0x01 { + parts := strings.SplitN(strings.Trim(lp, "\x01"), " ", 2) + ctcp := CTCP{} + + if parts[0] != "" { + ctcp.Command = parts[0] + } else { return nil } - ip3 := uint32ToIP(ip) - - filename := path.Base(parts[3]) - if filename == "/" || filename == "." { - filename = "" + if len(parts) == 2 { + ctcp.Params = parts[1] } - return &CTCP{ - File: filename, - IP: ip3, - Port: uint16(port), - Length: uint64(length), - } + return &ctcp } + return nil } @@ -171,19 +140,3 @@ var unescapeTagReplacer = strings.NewReplacer( func unescapeTag(s string) string { return unescapeTagReplacer.Replace(s) } - -func uint32ToIP(n int) string { - var byte1 = n & 255 - var byte2 = ((n >> 8) & 255) - var byte3 = ((n >> 16) & 255) - var byte4 = ((n >> 24) & 255) - return fmt.Sprintf("%d.%d.%d.%d", byte4, byte3, byte2, byte1) -} - -func (p DownloadProgress) ToJSON() string { - progress, err := json.Marshal(p) - if err != nil { - return "" - } - return string(progress) -} From 1532b2a8c8e9c3ad5ef32c6d6cef852c4e71943d Mon Sep 17 00:00:00 2001 From: Dario Piombo Date: Tue, 12 May 2020 15:09:14 +0200 Subject: [PATCH 3/8] changed sendJson param and removed not needed file mode removed instantaneous speed; update only if percentage delta > 0.1 removed instantaneous speed; update only if percentage delta > 0.1 --- pkg/irc/dcc.go | 43 ++++++++++++++++++------------------------- server/irc_handler.go | 2 +- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/pkg/irc/dcc.go b/pkg/irc/dcc.go index 014a24c2..9461cd7f 100644 --- a/pkg/irc/dcc.go +++ b/pkg/irc/dcc.go @@ -61,7 +61,7 @@ func (c *Client) Download(pack *DCCSend) { PercCompletion: 0, File: pack.File, } - file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_CREATE|os.O_WRONLY, 0644) if err != nil { c.Progress <- DownloadProgress{ PercCompletion: -1, @@ -85,8 +85,8 @@ func (c *Client) Download(pack *DCCSend) { defer con.Close() - var avgSpeed float64 - var prevTime int64 = -1 + var speed float64 + var prevPerc float64 secondsElapsed := int64(0) totalBytes := uint64(0) buf := make([]byte, 0, 4*1024) @@ -118,31 +118,25 @@ func (c *Client) Download(pack *DCCSend) { now := time.Now().UnixNano() secondsElapsed = (now - start) / 1e9 - avgSpeed = round2(float64(totalBytes) / (float64(secondsElapsed))) - speed := 0.0 - - if prevTime < 0 { - speed = avgSpeed - } else { - speed = round2(1e9 * float64(cycleBytes) / (float64(now - prevTime))) - } - secondsToGo := (float64(pack.Length) - float64(totalBytes)) / speed - prevTime = now + speed = round2(float64(totalBytes) / (float64(secondsElapsed))) + secondsToGo := round2((float64(pack.Length) - float64(totalBytes)) / speed) con.Write(byteRead(totalBytes)) - c.Progress <- DownloadProgress{ - InstSpeed: humanReadableByteCount(speed, true), - AvgSpeed: humanReadableByteCount(avgSpeed, true), - PercCompletion: percentage, - BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false), - BytesCompleted: humanReadableByteCount(float64(totalBytes), false), - SecondsElapsed: secondsElapsed, - SecondsToGo: secondsToGo, - File: pack.File, + if percentage-prevPerc >= 0.1 { + prevPerc = percentage + c.Progress <- DownloadProgress{ + Speed: humanReadableByteCount(speed, true), + PercCompletion: percentage, + BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false), + BytesCompleted: humanReadableByteCount(float64(totalBytes), false), + SecondsElapsed: secondsElapsed, + SecondsToGo: secondsToGo, + File: pack.File, + } } } con.Write(byteRead(totalBytes)) c.Progress <- DownloadProgress{ - AvgSpeed: humanReadableByteCount(avgSpeed, true), + Speed: humanReadableByteCount(speed, true), PercCompletion: 100, BytesCompleted: humanReadableByteCount(float64(totalBytes), false), SecondsElapsed: secondsElapsed, @@ -156,8 +150,7 @@ type DownloadProgress struct { BytesCompleted string `json:"bytes_completed"` BytesRemaining string `json:"bytes_remaining"` PercCompletion float64 `json:"perc_completion"` - AvgSpeed string `json:"avg_speed"` - InstSpeed string `json:"speed"` + Speed string `json:"speed"` SecondsElapsed int64 `json:"elapsed"` SecondsToGo float64 `json:"eta"` } diff --git a/server/irc_handler.go b/server/irc_handler.go index 27b2aeb0..fa793456 100644 --- a/server/irc_handler.go +++ b/server/irc_handler.go @@ -64,7 +64,7 @@ func (i *ircHandler) run() { i.log("Connected") } case progress := <-i.client.Progress: - i.state.sendJSON("progress", progress.ToJSON()) + i.state.sendJSON("pm", Message{Server: i.client.Host, From: "@dcc", Content: progress.ToJSON()}) } } } From 6b5bf4ced13857a3a3d5ee805482005dc61b37fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Sun, 17 May 2020 02:14:35 +0200 Subject: [PATCH 4/8] Add per-user download directory --- config.default.toml | 1 - config/config.go | 3 +-- server/irc.go | 2 +- storage/directory.go | 4 ++++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config.default.toml b/config.default.toml index 20e3f620..cfe5b29b 100644 --- a/config.default.toml +++ b/config.default.toml @@ -5,7 +5,6 @@ port = 80 hexIP = false verify_certificates = true -download_folder = "" autoget = false # Defaults for the client connect form diff --git a/config/config.go b/config/config.go index 72515fa3..7bc67f99 100644 --- a/config/config.go +++ b/config/config.go @@ -13,8 +13,7 @@ type Config struct { Port string Dev bool HexIP bool - VerifyCertificates bool `mapstructure:"verify_certificates"` - DownloadFolder string `mapstructure:"download_folder"` + VerifyCertificates bool `mapstructure:"verify_certificates"` Autoget bool Headers map[string]string Defaults Defaults diff --git a/server/irc.go b/server/irc.go index f708e9d6..23336a64 100644 --- a/server/irc.go +++ b/server/irc.go @@ -70,7 +70,7 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client } } - i.DownloadFolder = cfg.DownloadFolder + i.DownloadFolder = storage.Path.Downloads(state.user.Username) i.Autoget = cfg.Autoget state.setIRC(server.Host, i) diff --git a/storage/directory.go b/storage/directory.go index 0aced016..55ad3272 100644 --- a/storage/directory.go +++ b/storage/directory.go @@ -52,6 +52,10 @@ func (d directory) Key(username string) string { return filepath.Join(d.User(username), "key.pem") } +func (d directory) Downloads(username string) string { + return filepath.Join(d.User(username), "downloads") +} + func (d directory) Config() string { return filepath.Join(d.ConfigRoot(), "config.toml") } From fa99a96733828fc7fe65a10cc2ad14d6d2bf935f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Sun, 17 May 2020 02:31:50 +0200 Subject: [PATCH 5/8] Create download directory in NewUser --- storage/user.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/storage/user.go b/storage/user.go index 66386862..cf86f379 100644 --- a/storage/user.go +++ b/storage/user.go @@ -37,6 +37,11 @@ func NewUser(store Store) (*User, error) { return nil, err } + err = os.Mkdir(Path.Downloads(user.Username), 0700) + if err != nil { + return nil, err + } + return user, nil } From b92f5cfb43c1d885c6a375332141e959cff2ed86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Sun, 17 May 2020 03:55:46 +0200 Subject: [PATCH 6/8] Send download link on completion --- client/js/sw.js | 6 +++++- pkg/irc/dcc.go | 12 ++++++++---- server/irc_handler.go | 17 ++++++++++++++++- server/server.go | 28 ++++++++++++++++++++++++++++ server/websocket_handler.go | 6 ++++++ storage/directory.go | 4 ++++ 6 files changed, 67 insertions(+), 6 deletions(-) diff --git a/client/js/sw.js b/client/js/sw.js index cc70f733..d628dbdb 100644 --- a/client/js/sw.js +++ b/client/js/sw.js @@ -10,4 +10,8 @@ precacheAndRoute(self.__WB_MANIFEST, { }); const handler = createHandlerBoundToURL('/'); -registerRoute(new NavigationRoute(handler)); +registerRoute( + new NavigationRoute(handler, { + denylist: [new RegExp('/downloads/')] + }) +); diff --git a/pkg/irc/dcc.go b/pkg/irc/dcc.go index 9461cd7f..f2e269cc 100644 --- a/pkg/irc/dcc.go +++ b/pkg/irc/dcc.go @@ -73,7 +73,6 @@ func (c *Client) Download(pack *DCCSend) { defer file.Close() con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", pack.IP, pack.Port)) - if err != nil { c.Progress <- DownloadProgress{ PercCompletion: -1, @@ -86,7 +85,7 @@ func (c *Client) Download(pack *DCCSend) { defer con.Close() var speed float64 - var prevPerc float64 + var prevUpdate time.Time secondsElapsed := int64(0) totalBytes := uint64(0) buf := make([]byte, 0, 4*1024) @@ -120,9 +119,12 @@ func (c *Client) Download(pack *DCCSend) { secondsElapsed = (now - start) / 1e9 speed = round2(float64(totalBytes) / (float64(secondsElapsed))) secondsToGo := round2((float64(pack.Length) - float64(totalBytes)) / speed) + con.Write(byteRead(totalBytes)) - if percentage-prevPerc >= 0.1 { - prevPerc = percentage + + if time.Since(prevUpdate) >= time.Second { + prevUpdate = time.Now() + c.Progress <- DownloadProgress{ Speed: humanReadableByteCount(speed, true), PercCompletion: percentage, @@ -134,7 +136,9 @@ func (c *Client) Download(pack *DCCSend) { } } } + con.Write(byteRead(totalBytes)) + c.Progress <- DownloadProgress{ Speed: humanReadableByteCount(speed, true), PercCompletion: 100, diff --git a/server/irc_handler.go b/server/irc_handler.go index fa793456..c5d1ab8d 100644 --- a/server/irc_handler.go +++ b/server/irc_handler.go @@ -63,8 +63,23 @@ func (i *ircHandler) run() { } else if state.Connected { i.log("Connected") } + case progress := <-i.client.Progress: - i.state.sendJSON("pm", Message{Server: i.client.Host, From: "@dcc", Content: progress.ToJSON()}) + if progress.PercCompletion == 100 { + i.state.sendJSON("pm", Message{ + Server: i.client.Host, + From: "@dcc", + Content: fmt.Sprintf("%s://%s/downloads/%s/%s", i.state.String("scheme"), + i.state.String("host"), i.state.user.Username, progress.File), + }) + } else { + i.state.sendJSON("pm", Message{ + Server: i.client.Host, + From: "@dcc", + Content: fmt.Sprintf("%s: %.2f%% %s remaining, %.2fs left", progress.File, + progress.PercCompletion, progress.BytesRemaining, progress.SecondsToGo), + }) + } } } } diff --git a/server/server.go b/server/server.go index 75c1bff2..4aec34b7 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package server import ( "log" "net/http" + "strconv" "strings" "sync" @@ -180,6 +181,33 @@ func (d *Dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) { } d.upgradeWS(w, r, state) + } else if strings.HasPrefix(r.URL.Path, "/downloads") { + state := d.handleAuth(w, r, false, false) + if state == nil { + log.Println("[Auth] No state") + fail(w, http.StatusInternalServerError) + return + } + + params := strings.Split(strings.Trim(r.URL.Path, "/"), "/") + + if len(params) == 3 { + userID, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + fail(w, http.StatusBadRequest) + } + + if userID != state.user.ID { + fail(w, http.StatusUnauthorized) + } + + filename := params[2] + + w.Header().Set("Content-Disposition", "attachment; filename="+filename) + http.ServeFile(w, r, storage.Path.DownloadedFile(state.user.Username, filename)) + } else { + fail(w, http.StatusNotFound) + } } else { d.serveFiles(w, r) } diff --git a/server/websocket_handler.go b/server/websocket_handler.go index f6207e77..0ce34557 100644 --- a/server/websocket_handler.go +++ b/server/websocket_handler.go @@ -63,6 +63,12 @@ func (h *wsHandler) dispatchRequest(req WSRequest) { func (h *wsHandler) init(r *http.Request) { h.state.setWS(h.addr.String(), h.ws) h.state.user.SetLastIP(addrToIPBytes(h.addr)) + if r.TLS != nil { + h.state.Set("scheme", "https") + } else { + h.state.Set("scheme", "http") + } + h.state.Set("host", r.Host) log.Println(h.addr, "[State] User ID:", h.state.user.ID, "|", h.state.numIRC(), "IRC connections |", diff --git a/storage/directory.go b/storage/directory.go index 55ad3272..4b99ac83 100644 --- a/storage/directory.go +++ b/storage/directory.go @@ -56,6 +56,10 @@ func (d directory) Downloads(username string) string { return filepath.Join(d.User(username), "downloads") } +func (d directory) DownloadedFile(username string, file string) string { + return filepath.Join(d.Downloads(username), file) +} + func (d directory) Config() string { return filepath.Join(d.ConfigRoot(), "config.toml") } From a90e8d4b2f859db956a6767a422cf061bf1d6b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Mon, 18 May 2020 03:21:34 +0200 Subject: [PATCH 7/8] Improve speed calculation, clean some things up --- pkg/irc/dcc.go | 126 +++++++++++++++++++++--------------------- server/irc_handler.go | 30 +++++----- 2 files changed, 81 insertions(+), 75 deletions(-) diff --git a/pkg/irc/dcc.go b/pkg/irc/dcc.go index f2e269cc..62449771 100644 --- a/pkg/irc/dcc.go +++ b/pkg/irc/dcc.go @@ -18,7 +18,7 @@ import ( type DCCSend struct { File string `json:"file"` IP string `json:"ip"` - Port uint16 `json:"port"` + Port string `json:"port"` Length uint64 `json:"length"` } @@ -27,14 +27,14 @@ func ParseDCCSend(ctcp *CTCP) *DCCSend { if len(params) > 4 { ip, err := strconv.Atoi(params[2]) - port, err := strconv.Atoi(params[3]) - length, err := strconv.Atoi(params[4]) - if err != nil { return nil } - ip3 := uint32ToIP(ip) + length, err := strconv.ParseUint(params[4], 10, 64) + if err != nil { + return nil + } filename := path.Base(params[1]) if filename == "/" || filename == "." { @@ -43,9 +43,9 @@ func ParseDCCSend(ctcp *CTCP) *DCCSend { return &DCCSend{ File: filename, - IP: ip3, - Port: uint16(port), - Length: uint64(length), + IP: intToIP(ip), + Port: params[3], + Length: length, } } @@ -57,97 +57,99 @@ func (c *Client) Download(pack *DCCSend) { // TODO: ask user if he/she wants to download the file return } + c.Progress <- DownloadProgress{ - PercCompletion: 0, - File: pack.File, + File: pack.File, } + file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - c.Progress <- DownloadProgress{ - PercCompletion: -1, - File: pack.File, - Error: err, - } + c.downloadFailed(pack, err) return } defer file.Close() - con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", pack.IP, pack.Port)) + conn, err := net.Dial("tcp", net.JoinHostPort(pack.IP, pack.Port)) if err != nil { - c.Progress <- DownloadProgress{ - PercCompletion: -1, - File: pack.File, - Error: err, - } + c.downloadFailed(pack, err) return } + defer conn.Close() - defer con.Close() - - var speed float64 - var prevUpdate time.Time - secondsElapsed := int64(0) totalBytes := uint64(0) - buf := make([]byte, 0, 4*1024) - start := time.Now().UnixNano() + accBytes := uint64(0) + averageSpeed := float64(0) + buf := make([]byte, 4*1024) + start := time.Now() + prevUpdate := start + for { - n, err := con.Read(buf[:cap(buf)]) - buf = buf[:n] - if n == 0 { - if err == nil { - continue + n, err := conn.Read(buf) + if err != nil { + if err != io.EOF { + c.downloadFailed(pack, err) + return } - if err == io.EOF { + if n == 0 { break } } - if _, err := file.Write(buf); err != nil { - c.Progress <- DownloadProgress{ - PercCompletion: -1, - File: pack.File, - Error: err, - } + if _, err := file.Write(buf[:n]); err != nil { + c.downloadFailed(pack, err) return } - cycleBytes := uint64(len(buf)) - totalBytes += cycleBytes - percentage := round2(100 * float64(totalBytes) / float64(pack.Length)) + accBytes += uint64(n) + totalBytes += uint64(n) - now := time.Now().UnixNano() - secondsElapsed = (now - start) / 1e9 - speed = round2(float64(totalBytes) / (float64(secondsElapsed))) - secondsToGo := round2((float64(pack.Length) - float64(totalBytes)) / speed) + conn.Write(uint64Bytes(totalBytes)) - con.Write(byteRead(totalBytes)) - - if time.Since(prevUpdate) >= time.Second { + if dt := time.Since(prevUpdate); dt >= time.Second { prevUpdate = time.Now() + speed := float64(accBytes) / dt.Seconds() + if averageSpeed == 0 { + averageSpeed = speed + } else { + averageSpeed = 0.2*speed + 0.8*averageSpeed + } + accBytes = 0 + + bytesRemaining := float64(pack.Length - totalBytes) + percentage := 100 * (float64(totalBytes) / float64(pack.Length)) + c.Progress <- DownloadProgress{ - Speed: humanReadableByteCount(speed, true), + Speed: humanReadableByteCount(averageSpeed, true), PercCompletion: percentage, - BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false), + BytesRemaining: humanReadableByteCount(bytesRemaining, false), BytesCompleted: humanReadableByteCount(float64(totalBytes), false), - SecondsElapsed: secondsElapsed, - SecondsToGo: secondsToGo, + SecondsElapsed: secondsSince(start), + SecondsToGo: bytesRemaining / averageSpeed, File: pack.File, } } } - con.Write(byteRead(totalBytes)) + // TODO: is this needed? + conn.Write(uint64Bytes(totalBytes)) c.Progress <- DownloadProgress{ - Speed: humanReadableByteCount(speed, true), PercCompletion: 100, BytesCompleted: humanReadableByteCount(float64(totalBytes), false), - SecondsElapsed: secondsElapsed, + SecondsElapsed: secondsSince(start), File: pack.File, } } +func (c *Client) downloadFailed(pack *DCCSend, err error) { + c.Progress <- DownloadProgress{ + PercCompletion: -1, + File: pack.File, + Error: err, + } +} + type DownloadProgress struct { File string `json:"file"` Error error `json:"error"` @@ -167,7 +169,7 @@ func (p DownloadProgress) ToJSON() string { return string(progress) } -func uint32ToIP(n int) string { +func intToIP(n int) string { var byte1 = n & 255 var byte2 = ((n >> 8) & 255) var byte3 = ((n >> 16) & 255) @@ -175,14 +177,14 @@ func uint32ToIP(n int) string { return fmt.Sprintf("%d.%d.%d.%d", byte4, byte3, byte2, byte1) } -func byteRead(totalBytes uint64) []byte { +func uint64Bytes(i uint64) []byte { b := make([]byte, 8) - binary.BigEndian.PutUint64(b, totalBytes) + binary.BigEndian.PutUint64(b, i) return b } -func round2(source float64) float64 { - return math.Round(100*source) / 100 +func secondsSince(t time.Time) int64 { + return int64(math.Round(time.Since(t).Seconds())) } const ( diff --git a/server/irc_handler.go b/server/irc_handler.go index c5d1ab8d..a7a00853 100644 --- a/server/irc_handler.go +++ b/server/irc_handler.go @@ -65,20 +65,16 @@ func (i *ircHandler) run() { } case progress := <-i.client.Progress: - if progress.PercCompletion == 100 { - i.state.sendJSON("pm", Message{ - Server: i.client.Host, - From: "@dcc", - Content: fmt.Sprintf("%s://%s/downloads/%s/%s", i.state.String("scheme"), - i.state.String("host"), i.state.user.Username, progress.File), - }) + if progress.Error != nil { + i.sendDCCInfo("%s: Download failed (%s)", progress.File, progress.Error) + } else if progress.PercCompletion == 100 { + i.sendDCCInfo("Download finished, get it here: %s://%s/downloads/%s/%s", + i.state.String("scheme"), i.state.String("host"), i.state.user.Username, progress.File) + } else if progress.PercCompletion == 0 { + i.sendDCCInfo("%s: Starting download", progress.File) } else { - i.state.sendJSON("pm", Message{ - Server: i.client.Host, - From: "@dcc", - Content: fmt.Sprintf("%s: %.2f%% %s remaining, %.2fs left", progress.File, - progress.PercCompletion, progress.BytesRemaining, progress.SecondsToGo), - }) + i.sendDCCInfo("%s: %.1f%%, %s, %s remaining, %.1fs left", progress.File, + progress.PercCompletion, progress.Speed, progress.BytesRemaining, progress.SecondsToGo) } } } @@ -435,6 +431,14 @@ func (i *ircHandler) log(v ...interface{}) { log.Println("[IRC]", i.state.user.ID, i.client.Host, s[:len(s)-1]) } +func (i *ircHandler) sendDCCInfo(message string, a ...interface{}) { + i.state.sendJSON("pm", Message{ + Server: i.client.Host, + From: "@dcc", + Content: fmt.Sprintf(message, a...), + }) +} + func parseMode(mode string) *Mode { m := Mode{} add := false From 84a10efe3674558316c312d8553f339c5640d244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Tue, 19 May 2020 10:37:20 +0200 Subject: [PATCH 8/8] Log key dcc info messages, keep dcc tab open --- pkg/irc/dcc.go | 3 --- server/irc_handler.go | 20 +++++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pkg/irc/dcc.go b/pkg/irc/dcc.go index 62449771..bc8f7032 100644 --- a/pkg/irc/dcc.go +++ b/pkg/irc/dcc.go @@ -131,9 +131,6 @@ func (c *Client) Download(pack *DCCSend) { } } - // TODO: is this needed? - conn.Write(uint64Bytes(totalBytes)) - c.Progress <- DownloadProgress{ PercCompletion: 100, BytesCompleted: humanReadableByteCount(float64(totalBytes), false), diff --git a/server/irc_handler.go b/server/irc_handler.go index a7a00853..1875f5cb 100644 --- a/server/irc_handler.go +++ b/server/irc_handler.go @@ -66,14 +66,14 @@ func (i *ircHandler) run() { case progress := <-i.client.Progress: if progress.Error != nil { - i.sendDCCInfo("%s: Download failed (%s)", progress.File, progress.Error) + i.sendDCCInfo("%s: Download failed (%s)", true, progress.File, progress.Error) } else if progress.PercCompletion == 100 { - i.sendDCCInfo("Download finished, get it here: %s://%s/downloads/%s/%s", + i.sendDCCInfo("Download finished, get it here: %s://%s/downloads/%s/%s", true, i.state.String("scheme"), i.state.String("host"), i.state.user.Username, progress.File) } else if progress.PercCompletion == 0 { - i.sendDCCInfo("%s: Starting download", progress.File) + i.sendDCCInfo("%s: Starting download", true, progress.File) } else { - i.sendDCCInfo("%s: %.1f%%, %s, %s remaining, %.1fs left", progress.File, + i.sendDCCInfo("%s: %.1f%%, %s, %s remaining, %.1fs left", false, progress.File, progress.PercCompletion, progress.Speed, progress.BytesRemaining, progress.SecondsToGo) } } @@ -431,12 +431,18 @@ func (i *ircHandler) log(v ...interface{}) { log.Println("[IRC]", i.state.user.ID, i.client.Host, s[:len(s)-1]) } -func (i *ircHandler) sendDCCInfo(message string, a ...interface{}) { - i.state.sendJSON("pm", Message{ +func (i *ircHandler) sendDCCInfo(message string, log bool, a ...interface{}) { + msg := Message{ Server: i.client.Host, From: "@dcc", Content: fmt.Sprintf(message, a...), - }) + } + i.state.sendJSON("pm", msg) + + if log { + i.state.user.AddOpenDM(msg.Server, msg.From) + i.state.user.LogMessage(betterguid.New(), msg.Server, msg.From, msg.From, msg.Content) + } } func parseMode(mode string) *Mode {