Compare commits
	
		
			5 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6a832cbcf3 | ||
|   | c2f3a012dc | ||
|   | 147feb90ec | ||
|   | b39d4891e0 | ||
|   | 19455b0baa | 
| @@ -32,8 +32,9 @@ A command line interface for whatsapp, based on [go-whatsapp](https://github.com | ||||
|  | ||||
| Things that work. | ||||
|  | ||||
| - Connects through the Web App API without a browser | ||||
| - Allows sending and receiving WhatsApp messages in a command line app | ||||
| - Connects through the Web App API without browser | ||||
| - Allows downloading and opening image/video/audio/document attachments | ||||
| - Uses QR code for simple setup | ||||
| - Binaries for Windows, Mac, Linux and RaspBerry Pi | ||||
|  | ||||
| @@ -43,7 +44,6 @@ This is a WIP. Heres some things you might expect to work that don't. Plus some | ||||
|  | ||||
| - Only shows existing chats | ||||
| - Only fetches a few old messages | ||||
| - No support for images, videos, documents etc. | ||||
| - No incoming message notification / count | ||||
| - No proper connection drop handling | ||||
| - Not configurable at all | ||||
|   | ||||
							
								
								
									
										32
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								main.go
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ type waMsg struct { | ||||
| 	Text string | ||||
| } | ||||
|  | ||||
| var VERSION string = "v0.6.0" | ||||
| var VERSION string = "v0.6.2" | ||||
|  | ||||
| var sendChannel chan waMsg | ||||
| var textChannel chan whatsapp.TextMessage | ||||
| @@ -77,7 +77,11 @@ func main() { | ||||
| 		} | ||||
| 		if event.Key() == tcell.KeyTab { | ||||
| 			app.SetFocus(textInput) | ||||
| 			return event | ||||
| 			return nil | ||||
| 		} | ||||
| 		if event.Key() == tcell.KeyEsc { | ||||
| 			textView.Highlight("") | ||||
| 			return nil | ||||
| 		} | ||||
| 		if curRegions == nil || len(curRegions) == 0 { | ||||
| 			return event | ||||
| @@ -122,6 +126,7 @@ func main() { | ||||
| 			hls := textView.GetHighlights() | ||||
| 			if len(hls) > 0 { | ||||
| 				DownloadMessageId(hls[0], false) | ||||
| 				textView.Highlight("") | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| @@ -129,6 +134,15 @@ func main() { | ||||
| 			hls := textView.GetHighlights() | ||||
| 			if len(hls) > 0 { | ||||
| 				DownloadMessageId(hls[0], true) | ||||
| 				textView.Highlight("") | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| 		if event.Rune() == 'i' { | ||||
| 			hls := textView.GetHighlights() | ||||
| 			if len(hls) > 0 { | ||||
| 				fmt.Fprintf(textView, msgStore.GetMessageInfo(hls[0])) | ||||
| 				textView.Highlight("") | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| @@ -336,6 +350,7 @@ func EnterCommand(key tcell.Key) { | ||||
| 	textInput.SetText("") | ||||
| } | ||||
|  | ||||
| // get the next message id to select (highlighted + offset) | ||||
| func GetOffsetMsgId(curId string, offset int) string { | ||||
| 	if curRegions == nil || len(curRegions) == 0 { | ||||
| 		return "" | ||||
| @@ -349,9 +364,9 @@ func GetOffsetMsgId(curId string, offset int) string { | ||||
| 		} | ||||
| 	} | ||||
| 	if offset > 0 { | ||||
| 		return curRegions[len(curRegions)-1] | ||||
| 	} else { | ||||
| 		return curRegions[0] | ||||
| 	} else { | ||||
| 		return curRegions[len(curRegions)-1] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -403,7 +418,7 @@ func StartTextReceiver() error { | ||||
| 		case msg := <-sendChannel: | ||||
| 			SendText(msg.Wid, msg.Text) | ||||
| 		case rcvd := <-textChannel: | ||||
| 			if msgStore.AddTextMessage(rcvd) { | ||||
| 			if msgStore.AddTextMessage(&rcvd) { | ||||
| 				app.QueueUpdateDraw(LoadContacts) | ||||
| 			} | ||||
| 		case other := <-otherChannel: | ||||
| @@ -433,11 +448,12 @@ func SendText(wid string, text string) { | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintln(textView, "[red]error sending message: ", err, "[-]") | ||||
| 	} else { | ||||
| 		msgStore.AddTextMessage(msg) | ||||
| 		msgStore.AddTextMessage(&msg) | ||||
| 		PrintTextMessage(msg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // initiates a download of a specific message attachment in a new go routine | ||||
| func DownloadMessageId(id string, open bool) { | ||||
| 	fmt.Fprintln(textView, "[::d]..attempt download of #", id, "[::-]") | ||||
| 	go func() { | ||||
| @@ -449,6 +465,7 @@ func DownloadMessageId(id string, open bool) { | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| // notifies about a new message if its recent | ||||
| func NotifyMsg(msg whatsapp.TextMessage) { | ||||
| 	if int64(msg.Info.Timestamp) > time.Now().Unix()-30 { | ||||
| 		//fmt.Print("\a") | ||||
| @@ -545,12 +562,13 @@ func (t textHandler) HandleAudioMessage(message whatsapp.AudioMessage) { | ||||
| 	otherChannel <- message | ||||
| } | ||||
|  | ||||
| // add contact info to database TODO: when are these sent?? | ||||
| // add contact info to database (not needed, internal db of connection is used) | ||||
| func (t textHandler) HandleNewContact(contact whatsapp.Contact) { | ||||
| 	// redundant, wac has contacts | ||||
| 	//contactChannel <- contact | ||||
| } | ||||
|  | ||||
| // handle battery messages | ||||
| //func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) { | ||||
| //  app.QueueUpdate(func() { | ||||
| //    infoBar.SetText("🔋: " + string(msg.Percentage) + "%") | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import ( | ||||
| var contacts map[string]string | ||||
| var connection *whatsapp.Conn | ||||
|  | ||||
| // loads custom contacts from disk | ||||
| func LoadContacts() { | ||||
| 	contacts = make(map[string]string) | ||||
| 	file, err := os.Open(GetHomeDir() + ".whatscli.contacts") | ||||
| @@ -26,6 +27,7 @@ func LoadContacts() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // saves custom contacts to disk | ||||
| func SaveContacts() { | ||||
| 	file, err := os.Create(GetHomeDir() + ".whatscli.contacts") | ||||
| 	if err != nil { | ||||
| @@ -40,11 +42,13 @@ func SaveContacts() { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // sets a new name for a whatsapp id | ||||
| func SetIdName(id string, name string) { | ||||
| 	contacts[id] = name | ||||
| 	SaveContacts() | ||||
| } | ||||
|  | ||||
| // gets a pretty name for a whatsapp id | ||||
| func GetIdName(id string) string { | ||||
| 	if _, ok := contacts[id]; ok { | ||||
| 		return contacts[id] | ||||
| @@ -61,6 +65,7 @@ func GetIdName(id string) string { | ||||
| 	return strings.TrimSuffix(id, CONTACTSUFFIX) | ||||
| } | ||||
|  | ||||
| // gets a short name for a whatsapp id | ||||
| func GetIdShort(id string) string { | ||||
| 	if val, ok := connection.Store.Contacts[id]; ok { | ||||
| 		if val.Short != "" { | ||||
| @@ -77,6 +82,7 @@ func GetIdShort(id string) string { | ||||
| 	return strings.TrimSuffix(id, CONTACTSUFFIX) | ||||
| } | ||||
|  | ||||
| // gets the OS home dir with a path separator at the end | ||||
| func GetHomeDir() string { | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -11,13 +11,14 @@ import ( | ||||
| 	"github.com/normen/whatscli/qrcode" | ||||
| ) | ||||
|  | ||||
| // TODO: remove this circular dependeny in favor of a better way | ||||
| var textView *tview.TextView | ||||
|  | ||||
| // TODO: remove this circular dependeny in favor of a better way | ||||
| func SetTextView(tv *tview.TextView) { | ||||
| 	textView = tv | ||||
| } | ||||
|  | ||||
| // gets an existing connection or creates one | ||||
| func GetConnection() *whatsapp.Conn { | ||||
| 	var wac *whatsapp.Conn | ||||
| 	if connection == nil { | ||||
| @@ -82,6 +83,7 @@ func Logout() error { | ||||
| 	return removeSession() | ||||
| } | ||||
|  | ||||
| // reads the session file from disk | ||||
| func readSession() (whatsapp.Session, error) { | ||||
| 	session := whatsapp.Session{} | ||||
| 	file, err := os.Open(GetHomeDir() + ".whatscli.session") | ||||
| @@ -97,6 +99,7 @@ func readSession() (whatsapp.Session, error) { | ||||
| 	return session, nil | ||||
| } | ||||
|  | ||||
| // saves the session file to disk | ||||
| func writeSession(session whatsapp.Session) error { | ||||
| 	file, err := os.Create(GetHomeDir() + ".whatscli.session") | ||||
| 	if err != nil { | ||||
| @@ -111,6 +114,7 @@ func writeSession(session whatsapp.Session) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // deletes the session file from disk | ||||
| func removeSession() error { | ||||
| 	return os.Remove(GetHomeDir() + ".whatscli.session") | ||||
| } | ||||
|   | ||||
| @@ -17,26 +17,28 @@ const GROUPSUFFIX = "@g.us" | ||||
| const CONTACTSUFFIX = "@s.whatsapp.net" | ||||
|  | ||||
| type MessageDatabase struct { | ||||
| 	textMessages  map[string][]whatsapp.TextMessage // text messages stored by RemoteJid | ||||
| 	latestMessage map[string]uint64                 // last message from RemoteJid | ||||
| 	otherMessages map[string]interface{}            // other non-text messages, stored by ID | ||||
| 	textMessages  map[string][]*whatsapp.TextMessage // text messages stored by RemoteJid | ||||
| 	messagesById  map[string]*whatsapp.TextMessage   // text messages stored by message ID | ||||
| 	latestMessage map[string]uint64                  // last message from RemoteJid | ||||
| 	otherMessages map[string]*interface{}            // other non-text messages, stored by ID | ||||
| } | ||||
|  | ||||
| // initialize the database | ||||
| func (db *MessageDatabase) Init() { | ||||
| 	//var this = *db | ||||
| 	(*db).textMessages = make(map[string][]whatsapp.TextMessage) | ||||
| 	(*db).otherMessages = make(map[string]interface{}) | ||||
| 	(*db).textMessages = make(map[string][]*whatsapp.TextMessage) | ||||
| 	(*db).messagesById = make(map[string]*whatsapp.TextMessage) | ||||
| 	(*db).otherMessages = make(map[string]*interface{}) | ||||
| 	(*db).latestMessage = make(map[string]uint64) | ||||
| } | ||||
|  | ||||
| // add a text message to the database, stored by RemoteJid | ||||
| func (db *MessageDatabase) AddTextMessage(msg whatsapp.TextMessage) bool { | ||||
| func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool { | ||||
| 	//var this = *db | ||||
| 	var didNew = false | ||||
| 	var wid = msg.Info.RemoteJid | ||||
| 	if (*db).textMessages[wid] == nil { | ||||
| 		var newArr = []whatsapp.TextMessage{} | ||||
| 		var newArr = []*whatsapp.TextMessage{} | ||||
| 		(*db).textMessages[wid] = newArr | ||||
| 		(*db).latestMessage[wid] = msg.Info.Timestamp | ||||
| 		didNew = true | ||||
| @@ -45,6 +47,7 @@ func (db *MessageDatabase) AddTextMessage(msg whatsapp.TextMessage) bool { | ||||
| 		didNew = true | ||||
| 	} | ||||
| 	(*db).textMessages[wid] = append((*db).textMessages[wid], msg) | ||||
| 	(*db).messagesById[msg.Info.Id] = msg | ||||
| 	sort.Slice((*db).textMessages[wid], func(i, j int) bool { | ||||
| 		return (*db).textMessages[wid][i].Info.Timestamp < (*db).textMessages[wid][j].Info.Timestamp | ||||
| 	}) | ||||
| @@ -66,7 +69,7 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) { | ||||
| 		id = v.Info.Id | ||||
| 	} | ||||
| 	if id != "" { | ||||
| 		(*db).otherMessages[id] = (*msg) | ||||
| 		(*db).otherMessages[id] = msg | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -85,13 +88,29 @@ func (db *MessageDatabase) GetContactIds() []string { | ||||
| 	return keys | ||||
| } | ||||
|  | ||||
| func (db *MessageDatabase) GetMessageInfo(id string) string { | ||||
| 	if _, ok := (*db).otherMessages[id]; ok { | ||||
| 		return "[yellow]OtherMessage[-]" | ||||
| 	} | ||||
| 	out := "" | ||||
| 	if msg, ok := (*db).messagesById[id]; ok { | ||||
| 		out += "[yellow]ID: " + msg.Info.Id + "[-]\n" | ||||
| 		out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n" | ||||
| 		out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n" | ||||
| 		out += "[yellow]SenderJid: " + msg.Info.SenderJid + "[-]\n" | ||||
| 		out += "[yellow]Participant: " + msg.ContextInfo.Participant + "[-]\n" | ||||
| 		out += "[yellow]QuotedMessageID: " + msg.ContextInfo.QuotedMessageID + "[-]\n" | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| // get a string containing all messages for a chat by chat id | ||||
| func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) { | ||||
| 	//var this = *db | ||||
| 	var out = "" | ||||
| 	var arr = []string{} | ||||
| 	for _, element := range (*db).textMessages[wid] { | ||||
| 		out += GetTextMessageString(&element) | ||||
| 		out += GetTextMessageString(element) | ||||
| 		out += "\n" | ||||
| 		arr = append(arr, element.Info.Id) | ||||
| 	} | ||||
| @@ -122,7 +141,7 @@ func GetTextMessageString(msg *whatsapp.TextMessage) string { | ||||
| func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error) { | ||||
| 	if msg, ok := (*db).otherMessages[wid]; ok { | ||||
| 		var fileName string = "" | ||||
| 		switch v := msg.(type) { | ||||
| 		switch v := (*msg).(type) { | ||||
| 		default: | ||||
| 		case whatsapp.ImageMessage: | ||||
| 			if data, err := v.Download(); err == nil { | ||||
| @@ -161,6 +180,7 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error | ||||
| 	return "", errors.New("No attachments found") | ||||
| } | ||||
|  | ||||
| // helper to save an attachment and open it if specified | ||||
| func saveAttachment(data []byte, fileName string, openIt bool) error { | ||||
| 	path := GetHomeDir() + "Downloads" + string(os.PathSeparator) + fileName | ||||
| 	err := ioutil.WriteFile(path, data, 0644) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user