cchat-gtk/internal/ui/messages/input/sendable.go

173 lines
4.5 KiB
Go
Raw Normal View History

2020-06-04 23:00:41 +00:00
package input
import (
"encoding/base64"
"encoding/binary"
"fmt"
"sync/atomic"
"time"
2020-06-04 23:00:41 +00:00
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
2020-07-10 23:26:07 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input/attachment"
2020-06-04 23:00:41 +00:00
"github.com/diamondburned/cchat/text"
"github.com/pkg/errors"
"github.com/twmb/murmur3"
2020-06-04 23:00:41 +00:00
)
2020-07-10 23:26:07 +00:00
// globalID used for atomically generating nonces.
var globalID uint64
// generateNonce creates a nonce that should prevent collision. This function
2020-08-31 04:47:58 +00:00
// will always return a 16-byte long string.
func (f *Field) generateNonce() string {
raw := fmt.Sprintf(
"cchat-gtk/%s/%X/%X",
f.UserID, time.Now().UnixNano(), atomic.AddUint64(&globalID, 1),
)
h1, h2 := murmur3.StringSum128(raw)
nonce := make([]byte, 8*2)
binary.LittleEndian.PutUint64(nonce[0:8], h1)
binary.LittleEndian.PutUint64(nonce[8:16], h2)
return base64.RawURLEncoding.EncodeToString(nonce)
}
func (f *Field) sendInput() {
if f.Sender == nil {
return
}
// Get the input text and the reply ID.
text := f.getText()
// Are we editing anything?
2020-06-28 23:01:08 +00:00
if id := f.editingID; f.Editable(id) && id != "" {
go func() {
2020-10-15 06:32:11 +00:00
if err := f.editor.Edit(id, text); err != nil {
log.Error(errors.Wrap(err, "Failed to edit message"))
}
}()
f.StopEditing()
return
}
2020-07-10 23:26:07 +00:00
// Get the attachments.
var attachments = f.Attachments.Files()
// Don't send if the message is empty.
if text == "" && len(attachments) == 0 {
return
}
2021-01-05 08:02:26 +00:00
// Derive the author. Prefer the author of the current user from the message
// buffer over the one in the username feed, unless we can't find any.
var author = f.ctrl.Author(f.UserID)
2021-01-05 08:02:26 +00:00
if author == nil {
author = newAuthor(f)
}
f.SendMessage(SendMessageData{
2021-01-05 02:05:33 +00:00
time: time.Now().UTC(),
content: text,
2021-01-05 08:02:26 +00:00
author: author,
2021-01-05 02:05:33 +00:00
nonce: f.generateNonce(),
replyID: f.replyingID,
files: attachments,
})
2020-07-10 23:26:07 +00:00
// Clear the input field after sending.
f.clearText()
2020-08-31 04:47:58 +00:00
// Refocus the textbox.
f.text.GrabFocus()
}
func (f *Field) SendMessage(data PresendMessage) {
// presend message into the container through the controller
var onErr = f.ctrl.AddPresendMessage(data)
2020-08-31 04:47:58 +00:00
// Copy the sender to prevent race conditions.
var sender = f.Sender
gts.Async(func() (func(), error) {
2020-10-15 06:32:11 +00:00
if err := sender.Send(data); err != nil {
2020-08-31 04:47:58 +00:00
return func() { onErr(err) }, errors.Wrap(err, "Failed to send message")
}
2020-08-31 04:47:58 +00:00
return nil, nil
})
}
// Files is a list of attachments.
type Files []attachment.File
// Attachments returns the list of files as a list of cchat attachments.
func (files Files) Attachments() []cchat.MessageAttachment {
var attachments = make([]cchat.MessageAttachment, len(files))
for i, file := range files {
attachments[i] = file.AsAttachment()
}
return attachments
}
// SendMessageData contains what is to be sent in a message. It behaves
// similarly to a regular CreateMessage.
2020-06-04 23:00:41 +00:00
type SendMessageData struct {
2021-01-05 02:05:33 +00:00
time time.Time
content string
author cchat.Author
nonce string
replyID cchat.ID
files Files
2020-06-04 23:00:41 +00:00
}
2020-10-15 06:32:11 +00:00
var _ cchat.SendableMessage = (*SendMessageData)(nil)
// PresendMessage is an interface for any message about to be sent.
2020-06-04 23:00:41 +00:00
type PresendMessage interface {
cchat.MessageHeader // returns nonce and time
2020-06-04 23:00:41 +00:00
cchat.SendableMessage
2020-10-15 06:32:11 +00:00
cchat.Noncer
2020-07-10 23:26:07 +00:00
// These methods are reserved for internal use.
2020-06-04 23:00:41 +00:00
2021-01-05 02:05:33 +00:00
Author() cchat.Author
2020-07-10 23:26:07 +00:00
Files() []attachment.File
2020-06-04 23:00:41 +00:00
}
2020-06-07 07:25:13 +00:00
var _ PresendMessage = (*SendMessageData)(nil)
2020-06-04 23:00:41 +00:00
// ID returns a pseudo ID for internal use.
func (s SendMessageData) ID() string { return s.nonce }
func (s SendMessageData) Time() time.Time { return s.time }
func (s SendMessageData) Content() string { return s.content }
2021-01-05 02:05:33 +00:00
func (s SendMessageData) Author() cchat.Author { return s.author }
func (s SendMessageData) AsNoncer() cchat.Noncer { return s }
func (s SendMessageData) Nonce() string { return s.nonce }
func (s SendMessageData) Files() []attachment.File { return s.files }
func (s SendMessageData) AsAttacher() cchat.Attacher { return s.files }
func (s SendMessageData) AsReplier() cchat.Replier { return s }
func (s SendMessageData) ReplyingTo() cchat.ID { return s.replyID }
2021-01-05 02:05:33 +00:00
type sendableAuthor struct {
id cchat.ID
name text.Rich
avatarURL string
}
func newAuthor(f *Field) sendableAuthor {
return sendableAuthor{
f.UserID,
f.Username.GetLabel(),
f.Username.GetIconURL(),
}
}
var _ cchat.Author = (*sendableAuthor)(nil)
func (a sendableAuthor) ID() string { return a.id }
func (a sendableAuthor) Name() text.Rich { return a.name }
func (a sendableAuthor) Avatar() string { return a.avatarURL }