|
|
@@ -47,8 +47,6 @@ type messageInput struct {
|
|
|
lastSearch time.Time
|
|
|
}
|
|
|
|
|
|
-type memberList []discord.Member
|
|
|
-
|
|
|
func newMessageInput(cfg *config.Config) *messageInput {
|
|
|
mi := &messageInput{
|
|
|
TextArea: tview.NewTextArea(),
|
|
|
@@ -96,7 +94,7 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|
|
|
|
|
case mi.cfg.Keys.MessageInput.Send:
|
|
|
if app.chatView.GetVisibile(mentionsListPageName) {
|
|
|
- mi.tabComplete(false)
|
|
|
+ mi.tabComplete()
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -119,7 +117,7 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|
|
|
|
|
return nil
|
|
|
case mi.cfg.Keys.MessageInput.TabComplete:
|
|
|
- go app.QueueUpdateDraw(func() { mi.tabComplete(false) })
|
|
|
+ go app.QueueUpdateDraw(func() { mi.tabComplete() })
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -135,7 +133,7 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- go app.QueueUpdateDraw(func() { mi.tabComplete(true) })
|
|
|
+ go app.QueueUpdateDraw(func() { mi.tabSuggestion() })
|
|
|
}
|
|
|
|
|
|
return event
|
|
|
@@ -149,7 +147,7 @@ func (mi *messageInput) paste() {
|
|
|
}
|
|
|
|
|
|
func (mi *messageInput) send() {
|
|
|
- if !app.chatView.selectedChannelID.IsValid() {
|
|
|
+ if app.chatView.selectedChannel == nil {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -158,7 +156,7 @@ func (mi *messageInput) send() {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- text = processText(app.chatView.selectedChannelID, []byte(text))
|
|
|
+ text = processText(app.chatView.selectedChannel, []byte(text))
|
|
|
|
|
|
if mi.edit {
|
|
|
m, err := app.chatView.messagesList.selectedMessage()
|
|
|
@@ -176,8 +174,8 @@ func (mi *messageInput) send() {
|
|
|
} else {
|
|
|
data := mi.sendMessageData
|
|
|
data.Content = text
|
|
|
- if _, err := discordState.SendMessageComplex(app.chatView.selectedChannelID, *data); err != nil {
|
|
|
- slog.Error("failed to send message in channel", "channel_id", app.chatView.selectedChannelID, "err", err)
|
|
|
+ if _, err := discordState.SendMessageComplex(app.chatView.selectedChannel.ID, *data); err != nil {
|
|
|
+ slog.Error("failed to send message in channel", "channel_id", app.chatView.selectedChannel.ID, "err", err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -193,7 +191,7 @@ func (mi *messageInput) send() {
|
|
|
app.chatView.messagesList.ScrollToEnd()
|
|
|
}
|
|
|
|
|
|
-func processText(cID discord.ChannelID, src []byte) string {
|
|
|
+func processText(channel *discord.Channel, src []byte) string {
|
|
|
var (
|
|
|
ranges [][2]int
|
|
|
canMention = true
|
|
|
@@ -217,19 +215,36 @@ func processText(cID discord.ChannelID, src []byte) string {
|
|
|
})
|
|
|
|
|
|
for _, rng := range ranges {
|
|
|
- src = slices.Replace(src, rng[0], rng[1], expandMentions(cID, src[rng[0]:rng[1]])...)
|
|
|
+ src = slices.Replace(src, rng[0], rng[1], expandMentions(channel, src[rng[0]:rng[1]])...)
|
|
|
}
|
|
|
|
|
|
return string(src)
|
|
|
}
|
|
|
|
|
|
-func expandMentions(cID discord.ChannelID, src []byte) []byte {
|
|
|
+func expandMentions(c *discord.Channel, src []byte) []byte {
|
|
|
return mentionRegex.ReplaceAllFunc(src, func(input []byte) []byte {
|
|
|
output := input
|
|
|
name := string(input[1:])
|
|
|
- discordState.MemberStore.Each(app.chatView.selectedGuildID, func(m *discord.Member) bool {
|
|
|
- if strings.EqualFold(m.User.Username, name) && channelHasUser(cID, m.User.ID) {
|
|
|
- output = []byte(m.User.ID.Mention())
|
|
|
+ if c.Type == discord.DirectMessage || c.Type == discord.GroupDM {
|
|
|
+ for _, user := range c.DMRecipients {
|
|
|
+ if strings.EqualFold(user.Username, name) {
|
|
|
+ return []byte(user.ID.Mention())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // self ping
|
|
|
+ me, err := discordState.Cabinet.Me()
|
|
|
+ if err != nil {
|
|
|
+ slog.Error("failed to get client user (me)", "err", err)
|
|
|
+ } else if strings.EqualFold(me.Username, name) {
|
|
|
+ return []byte(me.ID.Mention())
|
|
|
+ }
|
|
|
+ return output
|
|
|
+ }
|
|
|
+ discordState.MemberStore.Each(c.GuildID, func(m *discord.Member) bool {
|
|
|
+ if strings.EqualFold(m.User.Username, name) {
|
|
|
+ if channelHasUser(c.ID, m.User.ID) {
|
|
|
+ output = []byte(m.User.ID.Mention())
|
|
|
+ }
|
|
|
return true
|
|
|
}
|
|
|
return false
|
|
|
@@ -238,7 +253,7 @@ func expandMentions(cID discord.ChannelID, src []byte) []byte {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-func (mi *messageInput) tabComplete(isAuto bool) {
|
|
|
+func (mi *messageInput) tabComplete() {
|
|
|
posEnd, name, r := mi.GetWordUnderCursor(func(r rune) bool {
|
|
|
return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '.'
|
|
|
})
|
|
|
@@ -248,13 +263,17 @@ func (mi *messageInput) tabComplete(isAuto bool) {
|
|
|
}
|
|
|
pos := posEnd - (len(name) + 1)
|
|
|
|
|
|
- gID := app.chatView.selectedGuildID
|
|
|
- cID := app.chatView.selectedChannelID
|
|
|
+ gID := app.chatView.selectedChannel.GuildID
|
|
|
|
|
|
- if !isAuto {
|
|
|
- if mi.cfg.AutocompleteLimit == 0 {
|
|
|
+ if mi.cfg.AutocompleteLimit == 0 {
|
|
|
+ if !gID.IsValid() {
|
|
|
+ users := app.chatView.selectedChannel.DMRecipients
|
|
|
+ res := fuzzy.FindFrom(name, userList(users))
|
|
|
+ if len(res) > 0 {
|
|
|
+ mi.Replace(pos, posEnd, "@"+users[res[0].Index].Username+" ")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
mi.searchMember(gID, name)
|
|
|
-
|
|
|
members, err := discordState.Cabinet.Members(gID)
|
|
|
if err != nil {
|
|
|
slog.Error("failed to get members from state", "guild_id", gID, "err", err)
|
|
|
@@ -263,45 +282,93 @@ func (mi *messageInput) tabComplete(isAuto bool) {
|
|
|
|
|
|
res := fuzzy.FindFrom(name, memberList(members))
|
|
|
for _, r := range res {
|
|
|
- if channelHasUser(cID, members[r.Index].User.ID) {
|
|
|
+ if channelHasUser(app.chatView.selectedChannel.ID, members[r.Index].User.ID) {
|
|
|
mi.Replace(pos, posEnd, "@"+members[r.Index].User.Username+" ")
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
- return
|
|
|
}
|
|
|
- if mi.mentionsList.GetItemCount() == 0 {
|
|
|
- return
|
|
|
- }
|
|
|
- _, name = mi.mentionsList.GetItemText(mi.mentionsList.GetCurrentItem())
|
|
|
- mi.Replace(pos, posEnd, "@"+name+" ")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if mi.mentionsList.GetItemCount() == 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ _, name = mi.mentionsList.GetItemText(mi.mentionsList.GetCurrentItem())
|
|
|
+ mi.Replace(pos, posEnd, "@"+name+" ")
|
|
|
+ mi.stopTabCompletion()
|
|
|
+}
|
|
|
+
|
|
|
+func (mi *messageInput) tabSuggestion() {
|
|
|
+ _, name, r := mi.GetWordUnderCursor(func(r rune) bool {
|
|
|
+ return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '.'
|
|
|
+ })
|
|
|
+ if r != '@' {
|
|
|
mi.stopTabCompletion()
|
|
|
return
|
|
|
}
|
|
|
+ gID := app.chatView.selectedChannel.GuildID
|
|
|
+ cID := app.chatView.selectedChannel.ID
|
|
|
+ mi.mentionsList.Clear()
|
|
|
|
|
|
- // Special case, show recent messages' authors
|
|
|
+ var shown map[string]struct{}
|
|
|
+ var userDone struct{}
|
|
|
if name == "" {
|
|
|
+ shown = make(map[string]struct{})
|
|
|
+ // Don't show @me in the list of recent authors
|
|
|
+ me, err := discordState.Cabinet.Me()
|
|
|
+ if err != nil {
|
|
|
+ slog.Error("failed to get client user (me)", "err", err)
|
|
|
+ } else {
|
|
|
+ shown[me.Username] = userDone
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // DMs have recipients, not members
|
|
|
+ if !gID.IsValid() {
|
|
|
+ if name == "" { // show recent messages' authors
|
|
|
+ msgs, err := discordState.Cabinet.Messages(cID)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ for _, m := range msgs {
|
|
|
+ if _, ok := shown[m.Author.Username]; ok {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ shown[m.Author.Username] = userDone
|
|
|
+ mi.addMentionUser(&m.Author)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ users := app.chatView.selectedChannel.DMRecipients
|
|
|
+ me, err := discordState.Cabinet.Me()
|
|
|
+ if err != nil {
|
|
|
+ slog.Error("failed to get client user (me)", "err", err)
|
|
|
+ } else {
|
|
|
+ users = append(users, *me)
|
|
|
+ }
|
|
|
+ res := fuzzy.FindFrom(name, userList(users))
|
|
|
+ for _, r := range res {
|
|
|
+ mi.addMentionUser(&users[r.Index])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if name == "" { // show recent messages' authors
|
|
|
msgs, err := discordState.Cabinet.Messages(cID)
|
|
|
if err != nil {
|
|
|
return
|
|
|
}
|
|
|
- shown := make(map[string]bool)
|
|
|
- mi.mentionsList.Clear()
|
|
|
for _, m := range msgs {
|
|
|
- if shown[m.Author.Username] {
|
|
|
+ if _, ok := shown[m.Author.Username]; ok {
|
|
|
continue
|
|
|
}
|
|
|
- shown[m.Author.Username] = true
|
|
|
+ shown[m.Author.Username] = userDone
|
|
|
discordState.MemberState.RequestMember(gID, m.Author.ID)
|
|
|
if mem, err := discordState.Cabinet.Member(gID, m.Author.ID); err == nil {
|
|
|
- if mi.addMentionItem(gID, mem) {
|
|
|
+ if mi.addMentionMember(gID, mem) {
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
mi.searchMember(gID, name)
|
|
|
- mi.mentionsList.Clear()
|
|
|
mems, err := discordState.Cabinet.Members(gID)
|
|
|
if err != nil {
|
|
|
slog.Error("fetching members failed", "err", err)
|
|
|
@@ -313,7 +380,7 @@ func (mi *messageInput) tabComplete(isAuto bool) {
|
|
|
}
|
|
|
for _, r := range res {
|
|
|
if channelHasUser(cID, mems[r.Index].User.ID) &&
|
|
|
- mi.addMentionItem(gID, &mems[r.Index]) {
|
|
|
+ mi.addMentionMember(gID, &mems[r.Index]) {
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
@@ -324,12 +391,27 @@ func (mi *messageInput) tabComplete(isAuto bool) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- _, col, _, _ := mi.GetCursor()
|
|
|
- mi.showMentionList(col - 1)
|
|
|
+ mi.showMentionList()
|
|
|
+}
|
|
|
+
|
|
|
+type memberList []discord.Member
|
|
|
+type userList []discord.User
|
|
|
+
|
|
|
+func (ml memberList) String(i int) string {
|
|
|
+ return ml[i].Nick + ml[i].User.DisplayName + ml[i].User.Tag()
|
|
|
}
|
|
|
|
|
|
-func (m memberList) String(i int) string { return m[i].Nick + m[i].User.DisplayName + m[i].User.Tag() }
|
|
|
-func (m memberList) Len() int { return len(m) }
|
|
|
+func (ml memberList) Len() int {
|
|
|
+ return len(ml)
|
|
|
+}
|
|
|
+
|
|
|
+func (ul userList) String(i int) string {
|
|
|
+ return ul[i].DisplayName + ul[i].Tag()
|
|
|
+}
|
|
|
+
|
|
|
+func (ul userList) Len() int {
|
|
|
+ return len(ul)
|
|
|
+}
|
|
|
|
|
|
// channelHasUser checks if a user has permission to view the specified channel
|
|
|
func channelHasUser(channelID discord.ChannelID, userID discord.UserID) bool {
|
|
|
@@ -369,7 +451,7 @@ func (mi *messageInput) searchMember(gID discord.GuildID, name string) {
|
|
|
mi.cache.Create(key, app.chatView.messagesList.waitForChunkEvent())
|
|
|
}
|
|
|
|
|
|
-func (mi *messageInput) showMentionList(col int) {
|
|
|
+func (mi *messageInput) showMentionList() {
|
|
|
borders := 0
|
|
|
if mi.cfg.Theme.Border.Enabled {
|
|
|
borders = 1
|
|
|
@@ -394,6 +476,7 @@ func (mi *messageInput) showMentionList(col int) {
|
|
|
}
|
|
|
|
|
|
w = min(w+borders*2, maxW)
|
|
|
+ _, col, _, _ := mi.GetCursor()
|
|
|
x += min(col, maxW-w)
|
|
|
}
|
|
|
|
|
|
@@ -405,7 +488,7 @@ func (mi *messageInput) showMentionList(col int) {
|
|
|
app.SetFocus(mi)
|
|
|
}
|
|
|
|
|
|
-func (mi *messageInput) addMentionItem(gID discord.GuildID, m *discord.Member) bool {
|
|
|
+func (mi *messageInput) addMentionMember(gID discord.GuildID, m *discord.Member) bool {
|
|
|
if m == nil {
|
|
|
return false
|
|
|
}
|
|
|
@@ -427,9 +510,7 @@ func (mi *messageInput) addMentionItem(gID discord.GuildID, m *discord.Member) b
|
|
|
presence, err := discordState.Cabinet.Presence(gID, m.User.ID)
|
|
|
if err != nil {
|
|
|
slog.Info("failed to get presence from state", "guild_id", gID, "user_id", m.User.ID, "err", err)
|
|
|
- }
|
|
|
-
|
|
|
- if presence != nil && presence.Status == discord.OfflineStatus {
|
|
|
+ } else if presence.Status == discord.OfflineStatus {
|
|
|
name = fmt.Sprintf("[::d]%s[::D]", name)
|
|
|
}
|
|
|
|
|
|
@@ -437,6 +518,24 @@ func (mi *messageInput) addMentionItem(gID discord.GuildID, m *discord.Member) b
|
|
|
return mi.mentionsList.GetItemCount() > int(mi.cfg.AutocompleteLimit)
|
|
|
}
|
|
|
|
|
|
+func (mi *messageInput) addMentionUser(user *discord.User) {
|
|
|
+ if user == nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ name := user.DisplayOrUsername()
|
|
|
+ presence, err := discordState.Cabinet.Presence(discord.NullGuildID, user.ID)
|
|
|
+ if err != nil {
|
|
|
+ slog.Info("failed to get presence from state", "user_id", user.ID, "err", err)
|
|
|
+ } else if presence.Status == discord.OfflineStatus {
|
|
|
+ name = fmt.Sprintf("[::d]%s[::D]", name)
|
|
|
+ }
|
|
|
+
|
|
|
+ mi.mentionsList.AddItem(name, user.Username, 0, nil)
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// used by chatView
|
|
|
func (mi *messageInput) removeMentionsList() {
|
|
|
app.chatView.
|
|
|
RemovePage(mentionsListPageName).
|
|
|
@@ -494,7 +593,7 @@ func (mi *messageInput) addTitle(s string) {
|
|
|
}
|
|
|
|
|
|
func (mi *messageInput) openFilePicker() {
|
|
|
- if !app.chatView.selectedChannelID.IsValid() {
|
|
|
+ if app.chatView.selectedChannel == nil {
|
|
|
return
|
|
|
}
|
|
|
|