2020-06-04 23:00:41 +00:00
|
|
|
package input
|
|
|
|
|
|
|
|
import (
|
2020-06-19 22:40:06 +00:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/binary"
|
2020-06-17 22:58:38 +00:00
|
|
|
"fmt"
|
2020-06-13 07:29:32 +00:00
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
"github.com/diamondburned/cchat"
|
2020-06-13 07:29:32 +00:00
|
|
|
"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"
|
2020-06-13 07:29:32 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-06-19 22:40:06 +00:00
|
|
|
"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.
|
2020-06-13 07:29:32 +00:00
|
|
|
var globalID uint64
|
|
|
|
|
2020-06-19 22:40:06 +00:00
|
|
|
// 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.
|
2020-06-17 22:58:38 +00:00
|
|
|
func (f *Field) generateNonce() string {
|
2020-06-19 22:40:06 +00:00
|
|
|
raw := fmt.Sprintf(
|
2020-06-17 22:58:38 +00:00
|
|
|
"cchat-gtk/%s/%X/%X",
|
|
|
|
f.UserID, time.Now().UnixNano(), atomic.AddUint64(&globalID, 1),
|
|
|
|
)
|
2020-06-19 22:40:06 +00:00
|
|
|
|
|
|
|
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)
|
2020-06-17 22:58:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Field) sendInput() {
|
2020-06-13 07:29:32 +00:00
|
|
|
if f.Sender == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-10 23:26:07 +00:00
|
|
|
// Get the input text.
|
|
|
|
var text = f.getText()
|
2020-06-13 07:29:32 +00:00
|
|
|
|
2020-06-17 22:58:38 +00:00
|
|
|
// Are we editing anything?
|
2020-06-28 23:01:08 +00:00
|
|
|
if id := f.editingID; f.Editable(id) && id != "" {
|
2020-06-17 22:58:38 +00:00
|
|
|
go func() {
|
|
|
|
if err := f.editor.EditMessage(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
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
f.SendMessage(SendMessageData{
|
2020-06-29 02:55:13 +00:00
|
|
|
time: time.Now().UTC(),
|
2020-06-13 07:29:32 +00:00
|
|
|
content: text,
|
2020-07-03 03:22:48 +00:00
|
|
|
author: f.Username.GetLabel(),
|
2020-06-13 07:29:32 +00:00
|
|
|
authorID: f.UserID,
|
2020-07-03 03:22:48 +00:00
|
|
|
authorURL: f.Username.GetIconURL(),
|
2020-06-17 22:58:38 +00:00
|
|
|
nonce: f.generateNonce(),
|
2020-07-10 23:26:07 +00:00
|
|
|
files: attachments,
|
2020-06-13 07:29:32 +00:00
|
|
|
})
|
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()
|
2020-06-13 07:29:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-06-13 07:29:32 +00:00
|
|
|
if err := sender.SendMessage(data); err != nil {
|
2020-08-31 04:47:58 +00:00
|
|
|
return func() { onErr(err) }, errors.Wrap(err, "Failed to send message")
|
2020-06-13 07:29:32 +00:00
|
|
|
}
|
2020-08-31 04:47:58 +00:00
|
|
|
return nil, nil
|
|
|
|
})
|
2020-06-13 07:29:32 +00:00
|
|
|
}
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
type SendMessageData struct {
|
2020-06-13 07:29:32 +00:00
|
|
|
time time.Time
|
2020-06-07 07:06:13 +00:00
|
|
|
content string
|
|
|
|
author text.Rich
|
|
|
|
authorID string
|
|
|
|
authorURL string // avatar
|
|
|
|
nonce string
|
2020-07-10 23:26:07 +00:00
|
|
|
files []attachment.File
|
2020-06-04 23:00:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type PresendMessage interface {
|
2020-06-13 07:29:32 +00:00
|
|
|
cchat.MessageHeader // returns nonce and time
|
2020-06-04 23:00:41 +00:00
|
|
|
cchat.SendableMessage
|
|
|
|
cchat.MessageNonce
|
2020-07-10 23:26:07 +00:00
|
|
|
cchat.SendableMessageAttachments
|
|
|
|
|
|
|
|
// These methods are reserved for internal use.
|
2020-06-04 23:00:41 +00:00
|
|
|
|
|
|
|
Author() text.Rich
|
|
|
|
AuthorID() string
|
2020-06-07 07:06:13 +00:00
|
|
|
AuthorAvatarURL() string // may be empty
|
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
|
|
|
|
2020-06-13 07:29:32 +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
|
|
|
|
}
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
func (s SendMessageData) Content() string {
|
|
|
|
return s.content
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s SendMessageData) Author() text.Rich {
|
|
|
|
return s.author
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s SendMessageData) AuthorID() string {
|
|
|
|
return s.authorID
|
|
|
|
}
|
|
|
|
|
2020-06-07 07:06:13 +00:00
|
|
|
func (s SendMessageData) AuthorAvatarURL() string {
|
|
|
|
return s.authorURL
|
|
|
|
}
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
func (s SendMessageData) Nonce() string {
|
|
|
|
return s.nonce
|
|
|
|
}
|
2020-07-10 23:26:07 +00:00
|
|
|
|
|
|
|
func (s SendMessageData) Files() []attachment.File {
|
|
|
|
return s.files
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s SendMessageData) Attachments() []cchat.MessageAttachment {
|
|
|
|
var attachments = make([]cchat.MessageAttachment, len(s.files))
|
|
|
|
for i, file := range s.files {
|
|
|
|
attachments[i] = file.AsAttachment()
|
|
|
|
}
|
|
|
|
return attachments
|
|
|
|
}
|