zap-go

Repositório zap-go
git clone git://git.sivaldodavi.com/zap-go.git
Log | Files | Refs | README

commit 1cea5328c23781524d524c9f218ef76f7ee807a3
parent 37755d0c4d77f4df4c19a5d504df36255655713a
Author: sivaldodavi <sivaldodavi@disroot.org>
Date:   Fri, 20 Mar 2026 13:05:17 +0100

Melhorias de interface: mostrar nome de contato

Diffstat:
Mmain.go | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 103 insertions(+), 35 deletions(-)

diff --git a/main.go b/main.go @@ -22,6 +22,7 @@ import ( "google.golang.org/protobuf/proto" ) +// --- Estilos --- var ( senderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Bold(true) receiverStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("4")).Bold(true) @@ -42,8 +43,86 @@ type model struct { target string name string mode string + contacts map[string]string } +// --- Lógica de Identificação de Contatos --- + +// cleanNumber remove tudo que não é dígito +func cleanNumber(phone string) string { + return strings.Map(func(r rune) rune { + if r >= '0' && r <= '9' { + return r + } + return -1 + }, phone) +} + +// findContactName busca o nome comparando o final do número (últimos 8 dígitos) +// Isso evita problemas com prefixos 55, +55 ou nono dígito variável. +func findContactName(jidNumber string, contacts map[string]string) string { + cleanJid := cleanNumber(jidNumber) + if len(cleanJid) < 8 { + return jidNumber + } + + suffixJid := cleanJid[len(cleanJid)-8:] + + for phone, name := range contacts { + cleanStored := cleanNumber(phone) + if len(cleanStored) >= 8 { + suffixStored := cleanStored[len(cleanStored)-8:] + if suffixJid == suffixStored { + return name + } + } + } + return jidNumber +} + +func loadContactsMap() map[string]string { + contacts := make(map[string]string) + home, _ := os.UserHomeDir() + path := filepath.Join(home, ".local/share/contatos") + + data, err := os.ReadFile(path) + if err != nil { + return contacts + } + + lines := strings.Split(string(data), "\n") + for _, line := range lines { + parts := strings.Split(line, ",") + if len(parts) >= 2 { + name := strings.TrimSpace(parts[0]) + phone := strings.TrimSpace(parts[1]) + if name != "" && phone != "" && name != "Nome" { + contacts[phone] = name + } + } + } + return contacts +} + +func getContactViaFzf() (string, string) { + home, _ := os.UserHomeDir() + path := filepath.Join(home, ".local/share/contatos") + + cmdStr := fmt.Sprintf("tail -n +2 %s | fzf --header 'Selecionar Contato' --delimiter ',' --with-nth 1", path) + out, err := exec.Command("sh", "-c", cmdStr).Output() + if err != nil { + return "", "" + } + + parts := strings.Split(strings.TrimSpace(string(out)), ",") + if len(parts) < 2 { + return "", "" + } + return parts[0], cleanNumber(parts[1]) +} + +// --- Bubble Tea Model --- + func (m model) Init() tea.Cmd { return textinput.Blink } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -88,36 +167,14 @@ func (m model) View() string { view := systemStyle.Render(header) + "\n" + boxStyle.Width(65).Height(15).Render(body.String()) footer := systemStyle.Render("\n Ctrl+C para encerrar") - + if m.mode == "send" { return view + footer + "\n" + m.textInput.View() } return view + footer } -func getContactViaFzf() (string, string) { - home, _ := os.UserHomeDir() - path := filepath.Join(home, ".local/share/contatos") - - if _, err := os.Stat(path); os.IsNotExist(err) { - os.MkdirAll(filepath.Dir(path), 0755) - os.WriteFile(path, []byte("Nome,Telefone,Email\nExemplo,+5500000000000,\n"), 0644) - } - - cmdStr := fmt.Sprintf("tail -n +2 %s | fzf --header 'Selecionar Contato' --delimiter ',' --with-nth 1", path) - out, err := exec.Command("sh", "-c", cmdStr).Output() - if err != nil { return "", "" } - - parts := strings.Split(strings.TrimSpace(string(out)), ",") - if len(parts) < 2 { return "", "" } - - num := strings.Map(func(r rune) rune { - if r >= '0' && r <= '9' { return r } - return -1 - }, parts[1]) - - return parts[0], num -} +// --- Main --- func main() { if len(os.Args) < 2 { @@ -126,25 +183,24 @@ func main() { } mode := os.Args[1] + contactsMap := loadContactsMap() var contactName, contactPhone string if mode == "send" { contactName, contactPhone = getContactViaFzf() - if contactPhone == "" { return } + if contactPhone == "" { + return + } } - // Localiza o diretório real do binário para persistência do DB exePath, _ := os.Executable() - realPath, err := filepath.EvalSymlinks(exePath) - if err != nil { - realPath = exePath - } + realPath, _ := filepath.EvalSymlinks(exePath) dbPath := filepath.Join(filepath.Dir(realPath), "whatsapp.db") dbURI := fmt.Sprintf("file:%s?_foreign_keys=on", dbPath) container, err := sqlstore.New(context.Background(), "sqlite3", dbURI, waLog.Stdout("Database", "ERROR", true)) if err != nil { - panic(fmt.Errorf("erro ao abrir banco de dados (%s): %w", dbPath, err)) + panic(err) } device, _ := container.GetFirstDevice(context.Background()) @@ -160,6 +216,7 @@ func main() { target: contactPhone, name: contactName, mode: mode, + contacts: contactsMap, }) client.AddEventHandler(func(evt interface{}) { @@ -169,7 +226,19 @@ func main() { txt = v.Message.GetExtendedTextMessage().GetText() } if txt != "" { - p.Send(chatMsg{sender: v.Info.Sender.User, content: txt, isMe: false}) + // Usa a nova lógica de comparação parcial para identificar o remetente + senderName := findContactName(v.Info.Sender.User, contactsMap) + + // Se não achou no CSV, tenta o PushName do WhatsApp + if senderName == v.Info.Sender.User && v.Info.PushName != "" { + senderName = v.Info.PushName + } + + p.Send(chatMsg{ + sender: senderName, + content: txt, + isMe: v.Info.IsFromMe, + }) } } }) @@ -187,9 +256,7 @@ func main() { } if _, err := p.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Erro: %v\n", err) os.Exit(1) } - client.Disconnect() -} +} +\ No newline at end of file