Whatsmeow conversation data [closed] - go

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 months ago.
Improve this question
I'm trying to build a tui client for WhatsApp using whatsmeow.
After half a day of searching and reading through the docs, I still can't find a way to get the conversation data of individual contacts. Any help is appreciated.
I found ParseWebMessage but I'm not really sure how to use this.
chatJID, err := types.ParseJID(conv.GetId())
for _, historyMsg := range conv.GetMessages() {
evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
yourNormalEventHandler(evt)
}
Matter of fact I'm not even sure if this is what I'm looking for

Well, you basically linked to the part of the docs that contains the information you're looking for. The return type of the ParseWebMessage call is events.Message, documented here. It contains an Info field of type MessageInfo (again, documented here). In turn, this MessageInfo type embeds the MessageSource type see docs here which looks like this:
type MessageSource struct {
Chat JID // The chat where the message was sent.
Sender JID // The user who sent the message.
IsFromMe bool // Whether the message was sent by the current user instead of someone else.
IsGroup bool // Whether the chat is a group chat or broadcast list.
// When sending a read receipt to a broadcast list message, the Chat is the broadcast list
// and Sender is you, so this field contains the recipient of the read receipt.
BroadcastListOwner JID
}
So to get the contact who sent a given message, given your code evt, err := cli.ParseWebMessage(), you need to check:
evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
if err != nil {
// handle error, of course
}
fmt.Printf("Sender ID: %s\nSent in Chat: %s\n", evt.Info.Sender, evt.Info.Chat)
if evt.Info.IsGroup {
fmt.Printf("%s is a group chat\n", evt.Info.Chat)
}
You can also skip messages you sent by simply doing this:
if evt.Info.IsFromMe {
continue
}
The evt.Info.Chat and evt.Info.Sender fields are all of type JID, documented here. There essentially are 2 variations of this ID type: user and server JID's and AD-JIDs (user, agent, and device). You can distinguish between the two by checking the JID.AD flag.
I haven't used this module at all, I only scanned through the docs briefly, but as I understand it, this module allows you to write a handler which will receive an events.Message type for everything you receive. By checking the evt.Info.IsGroup, you can work out whether the message we sent in a group chat, or in your person-to-person conversation thing. Based on evt.Info.Sender and evt.Info.Chat, you can work out who sent the message. The evt.Info.Sender being a JID in turn allows you to call the GetUserInfo method, passing in the JID, which gets you a UserInfo object in return as documented here, showing the name, picture, status, etc...
So I guess you're looking for something along these lines:
// some map of all messages from a given person, sent directly to you
contacts := cli.GetAllContacts() // returns map[JID]ContactInfo
personMsg := map[string][]*events.Message
evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
if err != nil {
// handle
}
if !evt.Info.IsFromMe && !evt.Info.IsGroup {// not a group, not sent by me
info, _ := cli.GetUserInfo([]types.JID{evt.Info.Sender})
if contact, ok := contacts[info[evt.Info.Sender]; ok {
msgs, ok := personMsg[contact.PushName]
if !ok {
msgs := []*events.Message{}
}
personMsg[contact.PushName] = append(msgs, evt)
}
}
Note the ContatInfo type didn't show up in the docs right away, but I stumbled across it in the repo.
Either way, I'm not quite sure what you're trying to do, and how/why you're stuck. All it took to find this information was to check the return type of the ParseWebMessage method you mentioned, check a couple of types, and scroll through some of the listed/documented methods to get a rough idea of how you can get all the data you could possibly need...

Related

Go-zabbix library does not find existing Event by id

I am trying to get an Event with a given event_id from Zabbix via the library github.com/cavaliercoder/go-zabbix.
An event with this id exists and is currently active. Connection to Zabbix is successful. Moreover, if I remove the EventIDs filter, it finds more than 600 events. But (!) I cannot access some of them directly from the Zabbix web interface under the same user.
Code:
session, err := zabbix.NewSession("zabbix_url/api_jsonrpc.php", "user", "password")
if err != nil {
panic(err)
}
var event_ids []string = []string{"2589270"}
params := zabbix.EventGetParams{
EventIDs: event_ids,
}
events, err := session.GetEvents(params)
fmt.Println(events)
Output:
[]
Errors:
2021/07/06 18:49:09 Error getting events: No results were found matching the given search
parameters
2021/07/06 18:49:09 No events found
2021/07/06 18:49:09 Validated 0 Events
Maybe I somehow incorrectly enter the id in the library?
In the source code of the library, the EventIDs parameter looks like this:
EventIDs []string `json:"eventids,omitempty"`
At the same time, everything works correctly from curl.
UPD
Code:
jparams, err := json.Marshal(params)
os.Stdout.Write(jparams)
Returns:
{"eventids":["2589270"],"object":0,"acknowledged":false}
UPD2:
It has been empirically established that the problem lies in the acknowledged parameter. If it is set to false, the event is not found. If it is set to true, the event is found. Curl exhibits the same behavior.
The problem is that the acknowledged parameter is set by default to either true or false. And I cannot remove it.
As planned by zabbix api (I guess), if this parameter is set to false, all events should be returned. If the parameter is set to true, then only acknowledged events should be returned. But it doesn't work as intended.

Fetching new Gmail emails after a timestamp

I am going through Gmail API docs(https://developers.google.com/gmail/api), I am able to read all the user emails which are present in the inbox.
eg snippet (reading complete list of emails):
for {
req := svc.Users.Messages.List("me")
r, _ := req.Do()
for _, m := range r.Messages {
msg, _ := svc.Users.Messages.Get("me", m.Id).Do()
date := ""
for _, h := range msg.Payload.Headers {
if h.Name == "Date" {
date = h.Value
break
}
}
msgs = append(msgs, message{
...
})
}
}
Now, when new emails come I want to read them as well (either immediately or after some time). I can write a scheduled job for that purpose, But I am not sure if I can fetch email after a particular timestamp or after an email identifier. I don't want to read a whole bunch of emails, again and again, to figure out the new emails, in this way, there is a lot of unnecessary computation being involved. Is there any way I can make this task easier?
Looking at the docs, it looks like it supports a query parameter, q.
The query parameter supports the same options as available in the Gmail search bar:
Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser#example.com rfc822msgid:somemsgid#example.com is:unread". Parameter cannot be used when accessing the api using the gmail.metadata scope.
This means you can do something like "after:YYYY/MM/DD" or with a timestamp "after:1616819452".
req := svc.Users.Messages.List("me").Q("after:2021/01/01")
See the full usage here https://pkg.go.dev/google.golang.org/api/gmail/v1#UsersMessagesListCall.Q

Should there be a new datastore.Client per HTTP request?

The official Go documentation on the datastore package (client library for the GCP datastore service) has the following code snippet for demonstartion:
type Entity struct {
Value string
}
func main() {
ctx := context.Background()
// Create a datastore client. In a typical application, you would create
// a single client which is reused for every datastore operation.
dsClient, err := datastore.NewClient(ctx, "my-project")
if err != nil {
// Handle error.
}
k := datastore.NameKey("Entity", "stringID", nil)
e := new(Entity)
if err := dsClient.Get(ctx, k, e); err != nil {
// Handle error.
}
old := e.Value
e.Value = "Hello World!"
if _, err := dsClient.Put(ctx, k, e); err != nil {
// Handle error.
}
fmt.Printf("Updated value from %q to %q\n", old, e.Value)
}
As one can see, it states that the datastore.Client should ideally only be instantiated once in an application. Now given that the datastore.NewClient function requires a context.Context object does it mean that it should get instantiated only once per HTTP request or can it safely be instantiated once globally with a context.Background() object?
Each operation requires a context.Context object again (e.g. dsClient.Get(ctx, k, e)) so is that the point where the HTTP request's context should be used?
I'm new to Go and can't really find any online resources which explain something like this very well with real world examples and actual best practice patterns.
You may use any context.Context for the datastore client creation, it may be context.Background(), that's completely fine. Client creation may be lengthy, it may require connecting to a remote server, authenticating, fetching configuration etc. If your use case has limited time, you may pass a context with timeout to abort the operation. Also if creation takes longer than the time you have, you may use a context with cancel and abort the mission at your will. These are just options which you may or may not use. But the "tools" are given via context.Context.
Later when you use the datastore.Client during serving (HTTP) client requests, then using the request's context is reasonable, so if a request gets cancelled, then so will its context, and so will the datastore operation you issue, rightfully, because if the client cannot see the result, then there's no point completing the query. Terminating the query early you might not end up using certain resources (e.g. datastore reads), and you may lower the server's load (by aborting jobs whose result will not be sent back to the client).

How do I properly post a message to a simple Go Chatserver using REST API

I am currently building a simple chat server that supports posting messages through a REST API.
example:
========
```
curl -X POST -H "Content-Type: application/json" --data '{"user":"alex", "text":"this is a message"}' http://localhost:8081/message
{
"ok": true
}
Right now, I'm just currently storing the messages in an array of messages. I'm pretty sure this is an inefficient way. So is there a simple, better way to get and store the messages using goroutines and channels that will make it thread-safe.
Here is what I currently have:
type Message struct {
Text string
User string
Timestamp time.Time
}
var Messages = []Message{}
func messagePost(c http.ResponseWriter, req *http.Request){
decoder := json.NewDecoder(req.Body)
var m Message
err := decoder.Decode(&m)
if err != nil {
panic(err)
}
if m.Timestamp == (time.Time{}) {
m.Timestamp = time.Now()
}
addUser(m.User)
Messages = append(Messages, m)
}
Thanks!
It could be made thread safe using mutex, as #ThunderCat suggested but I think this does not add concurrency. If two or more requests are made simultaneously, one will have to wait for the other to complete first, slowing the server down.
Adding Concurrency: You make it faster and handle more concurrent request by using a queue (which is a Go channel) and a worker that listens on that channel - it'll be a simple implementation. Every time a message comes in through a Post request, you add to the queue (this is instantaneous and the HTTP response can be sent immediately). In another goroutine, you detect that a message has been added to the queue, you take it out append it to your Messages slice. While you're appending to Messages, the HTTP requests don't have to wait.
Note: You can make it even better by having multiple goroutines listen on the queue, but we can leave that for later.
This is how the code will somewhat look like:
type Message struct {
Text string
User string
Timestamp time.Time
}
var Messages = []Message{}
// messageQueue is the queue that holds new messages until they are processed
var messageQueue chan Message
func init() { // need the init function to initialize the channel, and the listeners
// initialize the queue, choosing the buffer size as 8 (number of messages the channel can hold at once)
messageQueue = make(chan Message, 8)
// start a goroutine that listens on the queue/channel for new messages
go listenForMessages()
}
func listenForMessages() {
// whenever we detect a message in the queue, append it to Messages
for m := range messageQueue {
Messages = append(Messages, m)
}
}
func messagePost(c http.ResponseWriter, req *http.Request){
decoder := json.NewDecoder(req.Body)
var m Message
err := decoder.Decode(&m)
if err != nil {
panic(err)
}
if m.Timestamp == (time.Time{}) {
m.Timestamp = time.Now()
}
addUser(m.User)
// add the message to the channel, it'll only wait if the channel is full
messageQueue <- m
}
Storing Messages: As other users have suggested, storing messages in memory may not be the right choice since the messages won't persist if the application is restarted. If you're working on a small, proof-of-concept type project and don't want to figure out the DB, you could save the Messages variable as a flat file on the server and then read from it every time the application starts (*Note: this should not be done on a production system, of course, for that you should set up a Database). But yeah, database should be the way to go.
Use a mutex to make the program threadsafe.
var Messages = []Message{}
var messageMu sync.Mutex
...
messageMu.Lock()
Messages = append(Messages, m)
messageMu.Unlock()
There's no need to use channels and goroutines to make the program threadsafe.
A database is probably a better choice for storing messages than the in memory slice used in the question. Asking how to use a database to implement a chat program is too broad a question for SO.

Is it possible to dump golang db.Query() output to a string?

I have a small Heroku app in which i print out name and age from each rows after query execution.
I want to avoid looping rows.Next(),Scan().. and just want to show what database returned after query execution which may be some data or error.
Can we directly dump data to a string for printing?
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
if err != nil {
log.Fatal(err)
}
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d\n", name, age)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
Pretty much: No.
The Query method is going to return a pointer to a Rows struct:
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
If you print that (fmt.Printf("%#v\n", rows)) you'll see something such as:
&sql.Rows{dc:(*sql.driverConn)(0xc8201225a0), releaseConn:(func(error)(0x4802c0), rowsi:(*pq.rows)(0xc820166700), closed:false, lastcols:[]driver.Value(nil), lasterr:error(nil), closeStmt:driver.Stmt(nil)}
...probably not what you want.
Those correspond to the Rows struct from the sql package (you'll notice the fields are not exported):
type Rows struct {
dc *driverConn // owned; must call releaseConn when closed to release
releaseConn func(error)
rowsi driver.Rows
closed bool
lastcols []driver.Value
lasterr error // non-nil only if closed is true
closeStmt driver.Stmt // if non-nil, statement to Close on close
}
You'll see []driver.Value (an interface from the driver package), that looks like where we can expect to find some useful, maybe even human readable data. But when directly printed it doesn't appear useful, it's even empty... So you have to somehow get at the underlying information. The sql package gives us the Next method to start with:
Next prepares the next result row for reading with the Scan method.
It returns true on success, or false if there is no next
result row or an error happened while preparing it. Err
should be consulted to distinguish between the two cases.
Every call to Scan, even the first one, must be preceded by a call to Next.
Next is going to make a []driver.Value the same size as the number of columns I have, which is accessible (within the sql package) through driver.Rows (the rowsi field) and populate it with values from the query.
After calling rows.Next() if you did the same fmt.Printf("%#v\n", rows) you should now see that []diver.Value is no longer empty but it's still not going to be anything that you can read, more likely something resembling:[]diver.Value{[]uint8{0x47, 0x65...
And since the field isn't exported you can't even try and convert it to something more meaningful. But the sql package gives us a means to do something with the data, which is Scan.
The Scan method is pretty concise, with lengthy comments that I won't paste here, but the really important bit is that it ranges over the columns in the current row you get from the Next method and calls convertAssign(dest[i], sv), which you can see here:
https://golang.org/src/database/sql/convert.go
It's pretty long but actually relatively simple, it essentially switches on the type of the source and destination and converts where it can, and copies from source to destination; the function comments tell us:
convertAssign copies to dest the value in src, converting it if possible. An error is returned if the copy would result in loss of information. dest should be a pointer type.
So now you have a method (Scan) which you can call directly and which hands you back converted values. Your code sample above is fine (except maybe the call to Fatal() on a Scan error).
It's important to realize that the sql package has to work with a specific driver, which is in turn implemented for specific database software, so there is quite some work going on behind the scenes.
I think your best bet if you want to hide/generalize the whole Query() ---> Next() ---> Scan() idiom is to drop it into another function which does it behind the scenes... write a package in which you abstract away that higher level implementation, as the sql package abstracts away some of the driver-specific details, the converting and copying, populating the Rows, etc.

Resources