Skip to content

Commit f3b1546

Browse files
authored
handle large chat sends (#369)
* handle large chat sends * x
1 parent 94ff78f commit f3b1546

2 files changed

Lines changed: 63 additions & 59 deletions

File tree

‎base/output.go‎

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package base
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67
"time"
78

@@ -76,11 +77,65 @@ func (d *DebugOutput) ChatErrorf(convID chat1.ConvIDStr, msg string, args ...any
7677
}
7778

7879
func (d *DebugOutput) ChatEcho(convID chat1.ConvIDStr, msg string, args ...any) {
79-
if _, err := d.config.KBC.SendMessageByConvID(convID, msg, args...); err != nil && !IsIgnoredSendError(err) {
80+
if err := d.SendMessageByConvIDOrFile(convID, "", msg, args...); err != nil && !IsIgnoredSendError(err) {
8081
d.Errorf("ChatEcho: failed to send echo message: %s", err)
8182
}
8283
}
8384

85+
// SendMessageByConvIDOrFile sends a chat message, falling back to a file
86+
// attachment when the message exceeds Keybase's maximum length.
87+
func (d *DebugOutput) SendMessageByConvIDOrFile(convID chat1.ConvIDStr, title, msg string, args ...any) error {
88+
if d.config == nil || d.config.KBC == nil {
89+
return fmt.Errorf("chat not configured")
90+
}
91+
body := msg
92+
if len(args) > 0 {
93+
body = fmt.Sprintf(msg, args...)
94+
}
95+
if _, err := d.config.KBC.SendMessageByConvID(convID, "%s", body); err == nil ||
96+
IsIgnoredSendError(err) || !IsMessageTooLongError(err) {
97+
return err
98+
}
99+
100+
filePath, err := writeTempFile(d.name, body)
101+
if err != nil {
102+
return err
103+
}
104+
GoWithRecover(d, func() {
105+
defer func() {
106+
time.Sleep(time.Minute)
107+
d.Debug("cleaning up %s", filePath)
108+
if err := os.Remove(filePath); err != nil {
109+
d.Errorf("unable to clean up %s: %v", filePath, err)
110+
}
111+
}()
112+
if _, err := d.config.KBC.SendAttachmentByConvID(convID, filePath, title); err != nil {
113+
d.Errorf("failed to send attachment %s: %s", filePath, err)
114+
}
115+
})
116+
return nil
117+
}
118+
119+
func writeTempFile(namePrefix, content string) (string, error) {
120+
f, err := os.CreateTemp("", namePrefix+"-*.txt")
121+
if err != nil {
122+
return "", err
123+
}
124+
path := f.Name()
125+
defer func() {
126+
_ = f.Close()
127+
}()
128+
if _, err := f.WriteString(content); err != nil {
129+
_ = os.Remove(path)
130+
return "", err
131+
}
132+
if err := f.Sync(); err != nil {
133+
_ = os.Remove(path)
134+
return "", err
135+
}
136+
return path, nil
137+
}
138+
84139
func (d *DebugOutput) Trace(err *error, format string, args ...any) func() {
85140
msg := fmt.Sprintf(format, args...)
86141
start := time.Now()
@@ -94,6 +149,11 @@ func IsIgnoredSendError(err error) bool {
94149
return IsDeletedConvError(err) || IsEphemeralKeyError(err)
95150
}
96151

152+
func IsMessageTooLongError(err error) bool {
153+
// error created in https://github.com/keybase/client/blob/7d6aa64f3fba66adba7a5dd1cc7c523d5086a548/go/chat/msgchecker/plaintext_checker.go#L50
154+
return err != nil && strings.Contains(err.Error(), "exceeds the maximum length")
155+
}
156+
97157
func IsDeletedConvError(err error) bool {
98158
// error created in https://github.com/keybase/client/blob/1985b18c4e7659bede1d4a2e68e4f68467acebc6/go/client/chat_svc_handler.go#L1407
99159
// error created in https://github.com/keybase/keybase/blob/9a82c96231ea2c6132532002e58bac80849265e6/go/chatbase/storage/sql_chat.go#L2324

‎webhookbot/webhookbot/http.go‎

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10-
"os"
11-
"strings"
12-
"time"
1310

1411
"github.com/didip/tollbooth/v8"
1512
"github.com/gorilla/mux"
@@ -68,30 +65,6 @@ func (h *HTTPSrv) getMessage(r *http.Request) (string, error) {
6865
return "`Error: no body found. To use a webhook URL, supply a 'msg' URL parameter, or a JSON POST body with a field 'msg'`", nil
6966
}
7067

71-
func (h *HTTPSrv) safeWriteToFile(hookName, content string) (string, error) {
72-
pattern := fmt.Sprintf("webhookbot-%s-*.txt", hookName)
73-
file, err := os.CreateTemp("", pattern)
74-
if err != nil {
75-
return "", fmt.Errorf("failed to create temp file: %w", err)
76-
}
77-
defer func() {
78-
if cerr := file.Close(); cerr != nil {
79-
h.Errorf("safeWriteToFile: failed to close temp file: %s", cerr)
80-
}
81-
}()
82-
83-
if _, err := file.Write([]byte(content)); err != nil {
84-
return "", fmt.Errorf("failed to write file: %w", err)
85-
}
86-
87-
// Explicitly sync to ensure all data is written to disk before upload
88-
if err := file.Sync(); err != nil {
89-
return "", fmt.Errorf("failed to sync file: %w", err)
90-
}
91-
92-
return file.Name(), nil
93-
}
94-
9568
func (h *HTTPSrv) handleHook(w http.ResponseWriter, r *http.Request) {
9669
vars := mux.Vars(r)
9770
id := vars["id"]
@@ -114,37 +87,8 @@ func (h *HTTPSrv) handleHook(w http.ResponseWriter, r *http.Request) {
11487
return
11588
}
11689
h.Stats.Count("handle - success")
117-
if _, err := h.Config().KBC.SendMessageByConvID(hook.ConvID, "[hook: *%s*]\n\n%s", hook.Name, msg); err != nil {
118-
if base.IsDeletedConvError(err) {
119-
h.Debug("handleHook: failed to send message: %s", err)
120-
return
121-
}
122-
123-
// error created in https://github.com/keybase/client/blob/7d6aa64f3fba66adba7a5dd1cc7c523d5086a548/go/chat/msgchecker/plaintext_checker.go#L50
124-
if strings.Contains(err.Error(), "exceeds the maximum length") {
125-
filePath, err := h.safeWriteToFile(hook.Name, msg)
126-
if err != nil {
127-
h.Errorf("failed to write attachment file: %s", err)
128-
return
129-
}
130-
base.GoWithRecover(h.DebugOutput, func() {
131-
defer func() {
132-
// Cleanup after the file is sent.
133-
time.Sleep(time.Minute)
134-
h.Debug("cleaning up %s", filePath)
135-
if err = os.Remove(filePath); err != nil {
136-
h.Errorf("unable to clean up %s: %v", filePath, err)
137-
}
138-
}()
139-
title := fmt.Sprintf("[hook: *%s*]", hook.Name)
140-
if _, err := h.Config().KBC.SendAttachmentByConvID(hook.ConvID, filePath, title); err != nil {
141-
h.Errorf("failed to send attachment %s: %s", filePath, err)
142-
return
143-
}
144-
})
145-
return
146-
}
147-
90+
title := fmt.Sprintf("[hook: *%s*]", hook.Name)
91+
if err := h.SendMessageByConvIDOrFile(hook.ConvID, title, "%s\n\n%s", title, msg); err != nil {
14892
h.Debug("handleHook: failed to send message: %s", err)
14993
}
15094
}

0 commit comments

Comments
 (0)