feat: attach image from clipboard

Closes #96
This commit is contained in:
ayn2op 2025-07-29 14:25:19 -04:00
parent a4c8787f1d
commit f91e7d39b5
5 changed files with 44 additions and 13 deletions

View file

@ -1,6 +1,7 @@
package cmd
import (
"bytes"
"fmt"
"io"
"log/slog"
@ -13,7 +14,6 @@ import (
"time"
"unicode"
"github.com/atotto/clipboard"
"github.com/ayn2op/discordo/internal/cache"
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/consts"
@ -28,6 +28,7 @@ import (
"github.com/ncruces/zenity"
"github.com/sahilm/fuzzy"
"github.com/yuin/goldmark/ast"
"golang.design/x/clipboard"
)
const tmpFilePattern = consts.Name + "_*.md"
@ -56,15 +57,20 @@ func newMessageInput(cfg *config.Config) *messageInput {
mentionsList: tview.NewList(),
}
if err := clipboard.Init(); err != nil {
slog.Warn("failed to init clipboard", "err", err)
} else {
mi.
SetClipboard(func(s string) {
clipboard.Write(clipboard.FmtText, []byte(s))
}, func() string {
data := clipboard.Read(clipboard.FmtText)
return string(data)
})
}
mi.Box = ui.ConfigureBox(mi.Box, &cfg.Theme)
mi.
SetClipboard(func(s string) {
clipboard.WriteAll(s)
}, func() string {
text, _ := clipboard.ReadAll()
return text
}).
SetInputCapture(mi.onInputCapture)
mi.SetInputCapture(mi.onInputCapture)
mi.mentionsList.Box = ui.ConfigureBox(mi.mentionsList.Box, &mi.cfg.Theme)
mi.mentionsList.
@ -88,6 +94,10 @@ func (mi *messageInput) reset() {
func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case mi.cfg.Keys.MessageInput.Paste:
mi.paste()
return tcell.NewEventKey(tcell.KeyCtrlV, 0, tcell.ModNone)
case mi.cfg.Keys.MessageInput.Send:
if app.pages.GetVisibile(mentionsListPageName) {
mi.tabComplete(false)
@ -140,6 +150,13 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
return event
}
func (mi *messageInput) paste() {
if data := clipboard.Read(clipboard.FmtImage); data != nil {
name := "clipboard.png"
mi.attach(name, sendpart.File{Name: name, Reader: bytes.NewReader(data)})
}
}
func (mi *messageInput) send() {
if !app.guildsTree.selectedChannelID.IsValid() {
return
@ -486,9 +503,11 @@ func (mi *messageInput) openFilePicker() {
}
name := filepath.Base(path)
mi.sendMessageData.Files = append(mi.sendMessageData.Files, sendpart.File{Name: name, Reader: file})
title := fmt.Sprintf("Attached %s", name)
mi.addTitle(title)
mi.attach(name, sendpart.File{Name: name, Reader: file})
}
}
func (mi *messageInput) attach(name string, file sendpart.File) {
mi.sendMessageData.Files = append(mi.sendMessageData.Files, file)
mi.addTitle("Attached " + name)
}

3
go.mod
View file

@ -18,6 +18,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/yuin/goldmark v1.7.13
github.com/zalando/go-keyring v0.2.6
golang.design/x/clipboard v0.7.1
)
require (
@ -47,7 +48,9 @@ require (
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp/shiny v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/image v0.29.0 // indirect
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect

6
go.sum
View file

@ -178,6 +178,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -194,6 +196,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp/shiny v0.0.0-20250718183923-645b1fa84792 h1:YkeFUC3wFzzmWTi5lYbb5af46Tpk9DLWHXXwGjLF3R0=
golang.org/x/exp/shiny v0.0.0-20250718183923-645b1fa84792/go.mod h1:DUdAjGCS1V5oj0c1HZTX5UNuMxBjfxuU/NIoy/wuiRw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
@ -209,6 +213,8 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 h1:Z6EFcPz8e1cx0ge5jWCwqafndPjdsDQf8fk4Kw3pJoI=
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0/go.mod h1:kqVs191xxTTCd39tk8zK1UD3jyCS1SPrMHTpJ9ujxZg=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=

View file

@ -81,6 +81,8 @@ yank_id = "Rune[i]"
# Only while typing a message
# Alt+Enter: Insert a new line to the current text.
[keys.message_input]
# paste from clipboard (supports both text and images)
paste = "Ctrl+V"
send = "Enter"
# Remove existing text or cancel reply.
cancel = "Esc"

View file

@ -50,6 +50,7 @@ type (
}
MessageInputKeys struct {
Paste string `toml:"paste"`
Send string `toml:"send"`
Cancel string `toml:"cancel"`
TabComplete string `toml:"tab_complete"`