Skip to content

Commit

Permalink
User can now control WhatsApp presence via Purple status.
Browse files Browse the repository at this point in the history
Other devices will display notifications while plug-in connection is unavailable.
Plug-in connection will not receive contact presence updates while unavailable.
Other side effects may occur.
  • Loading branch information
hoehermann committed Feb 6, 2022
1 parent 156a953 commit ef1901e
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 32 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ Other improvements:

* Contact presence is regarded (buddies are online and offline).
* Typing notifications are handled.
* There is an "away" state.
* Logging happens via purple.
* There is an "away" state.
* WhatsApp does not send contact presence updates while being "away".
* Other devices (i.e. the main phone) display notifications while plug-in connection is "away".
* Caveat emptor: Other side-effects may occur while using "away" state.

Features which are present in the go-whatsapp version but missing here:

Expand Down
2 changes: 2 additions & 0 deletions src/c/blist.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ gowhatsapp_assume_buddy_online(PurpleAccount *account, PurpleBuddy *buddy)
}
// TODO: move somewhere else so function names are not misleading
// this is only here because gowhatsapp_assume_buddy_online is alredy being called in all relevant situations
// NOTE: subscriptions are valid while unavailable
// but WhatsApp requires you to be available to receive presence updates
gowhatsapp_go_subscribe_presence(account, buddy->name);

if (purple_account_get_bool(account, GOWHATSAPP_GET_ICONS_OPTION, FALSE)) {
Expand Down
9 changes: 5 additions & 4 deletions src/c/gowhatsapp.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
#define GOWHATSAPP_NAME "whatsmeow" // name to refer to this plug-in (in logs)
#define GOWHATSAPP_PRPL_ID "prpl-hehoe-whatsmeow"

#define GOWHATSAPP_STATUS_STR_ONLINE "online"
#define GOWHATSAPP_STATUS_STR_OFFLINE "offline"
#define GOWHATSAPP_STATUS_STR_MOBILE "mobile"
#define GOWHATSAPP_STATUS_STR_AWAY "away"
#define GOWHATSAPP_STATUS_STR_AVAILABLE "available" // this must match whatsmeow's types.PresenceAvailable
#define GOWHATSAPP_STATUS_STR_AWAY "unavailable" // this must match whatsmeow's types.PresenceUnavailable
#define GOWHATSAPP_STATUS_STR_OFFLINE "offline"
#define GOWHATSAPP_STATUS_STR_MOBILE "mobile"

// options
GList *gowhatsapp_add_account_options(GList *account_options);
Expand Down Expand Up @@ -65,6 +65,7 @@ void gowhatsapp_send_file(PurpleConnection *pc, const gchar *who, const gchar *f
void gowhatsapp_handle_presence(PurpleAccount *account, char *remoteJid, char available, time_t last_seen);
void gowhatsapp_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *info, gboolean full);
void gowhatsapp_handle_profile_picture(gowhatsapp_message_t *gwamsg);
void gowhatsapp_set_presence(PurpleAccount *account, PurpleStatus *status);

// receipts
void gowhatsapp_receipts_init(PurpleConnection *pc);
9 changes: 5 additions & 4 deletions src/c/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ status_types(PurpleAccount *account)
GList *types = NULL;
PurpleStatusType *status;

status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, GOWHATSAPP_STATUS_STR_ONLINE, NULL, TRUE, TRUE, FALSE);
types = g_list_append(types, status);

status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, GOWHATSAPP_STATUS_STR_OFFLINE, NULL, TRUE, TRUE, FALSE);
status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, GOWHATSAPP_STATUS_STR_AVAILABLE, NULL, TRUE, TRUE, FALSE);
types = g_list_append(types, status);

status = purple_status_type_new_full(PURPLE_STATUS_AWAY, GOWHATSAPP_STATUS_STR_AWAY, NULL, TRUE, TRUE, FALSE);
types = g_list_prepend(types, status);

status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, GOWHATSAPP_STATUS_STR_OFFLINE, NULL, TRUE, TRUE, FALSE);
types = g_list_append(types, status);

status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, GOWHATSAPP_STATUS_STR_MOBILE, NULL, FALSE, FALSE, TRUE);
types = g_list_prepend(types, status);

Expand Down Expand Up @@ -97,6 +97,7 @@ plugin_init(PurplePlugin *plugin)
prpl_info->protocol_options = gowhatsapp_add_account_options(prpl_info->protocol_options);
prpl_info->list_icon = list_icon;
prpl_info->status_types = status_types; // this actually needs to exist, else the protocol cannot be set to "online"
prpl_info->set_status = gowhatsapp_set_presence;
prpl_info->login = gowhatsapp_login;
prpl_info->close = gowhatsapp_close;
prpl_info->send_im = gowhatsapp_send_im;
Expand Down
9 changes: 8 additions & 1 deletion src/c/presence.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#include "gowhatsapp.h"
#include "constants.h"
#include "purple-go-whatsapp.h" // for gowhatsapp_go_send_presence

void
gowhatsapp_handle_presence(PurpleAccount *account, char *remoteJid, char online, time_t last_seen) {
char *status = GOWHATSAPP_STATUS_STR_ONLINE;
char *status = GOWHATSAPP_STATUS_STR_AVAILABLE;
if (online == 0) {
if (purple_account_get_bool(account, GOWHATSAPP_FAKE_ONLINE_OPTION, TRUE)) {
status = GOWHATSAPP_STATUS_STR_AWAY;
Expand Down Expand Up @@ -52,3 +53,9 @@ gowhatsapp_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *info, gboolean
purple_notify_user_info_add_pair(info, "Published name", published_name);
}
}

void
gowhatsapp_set_presence(PurpleAccount *account, PurpleStatus *status) {
const char *status_id = purple_status_get_id(status);
gowhatsapp_go_send_presence(account, (char *)status_id); // cgo does not support const
}
1 change: 1 addition & 0 deletions src/c/process_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ gowhatsapp_process_message(gowhatsapp_message_t *gwamsg)
case gowhatsapp_message_type_connected:
gowhatsapp_close_qrcode(gwamsg->account);
purple_connection_set_state(pc, PURPLE_CONNECTION_CONNECTED);
gowhatsapp_set_presence(gwamsg->account, purple_account_get_active_status(gwamsg->account));
gowhatsapp_assume_all_buddies_online(gwamsg->account);
break;
case gowhatsapp_message_type_disconnected:
Expand Down
10 changes: 10 additions & 0 deletions src/go/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ func gowhatsapp_go_mark_read_conversation(account *PurpleAccount, who *C.char) {
}
}

//export gowhatsapp_go_send_presence
func gowhatsapp_go_send_presence(account *PurpleAccount, presence *C.char) {
handler, ok := handlers[account]
if ok {
handler.send_presence(C.GoString(presence))
} else {
purple_error(account, "Cannot set presence: Not connected.", ERROR_TRANSIENT)
}
}

//export gowhatsapp_go_subscribe_presence
func gowhatsapp_go_subscribe_presence(account *PurpleAccount, who *C.char) {
handler, ok := handlers[account]
Expand Down
25 changes: 10 additions & 15 deletions src/go/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/appstate"
"go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
waLog "go.mau.fi/whatsmeow/util/log"
"net/http"
Expand Down Expand Up @@ -41,27 +40,23 @@ func (handler *Handler) eventHandler(rawEvt interface{}) {
// this happens after initial logon via QR code (before HistorySync)
if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock {
log.Infof("AppStateSyncComplete")
err := handler.send_presence(types.PresenceAvailable)
if err == nil {
purple_connected(handler.account)
// connected – start downloading profile pictures now.
go handler.profile_picture_downloader()
}
purple_connected(handler.account)
// connected – start downloading profile pictures now.
go handler.profile_picture_downloader()
}
case *events.PushNameSetting:
if len(cli.Store.PushName) == 0 {
return
}
// Send presence "available" when the pushname is changed remotely.
// Send presence when the pushname is changed remotely.
// This makes sure that outgoing messages always have the right pushname.
handler.send_presence(types.PresenceAvailable)
// This is making a round-trip through purple so user can decide to
// opt out of this feature by being "away" instead of "online"
purple_connected(handler.account)
case *events.Connected:
err := handler.send_presence(types.PresenceAvailable)
if err == nil {
purple_connected(handler.account)
// connected – start downloading profile pictures now.
go handler.profile_picture_downloader()
}
purple_connected(handler.account)
// connected – start downloading profile pictures now.
go handler.profile_picture_downloader()
case *events.StreamReplaced:
// TODO: test this
purple_error(handler.account, fmt.Sprintf("StreamReplaced: %+v", evt), ERROR_FATAL)
Expand Down
2 changes: 1 addition & 1 deletion src/go/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import "C"
import (
"context"
"fmt"
_ "github.com/go-sql-driver/mysql"
_ "github.com/jackc/pgx"
_ "github.com/mattn/go-sqlite3"
_ "github.com/go-sql-driver/mysql"
"github.com/mdp/qrterminal/v3"
"github.com/skip2/go-qrcode"
"go.mau.fi/whatsmeow"
Expand Down
21 changes: 15 additions & 6 deletions src/go/presence.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
package main

import (
"fmt"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
"time"
)

func (handler *Handler) send_presence(presence types.Presence) error {
err := handler.client.SendPresence(presence)
if err != nil {
handler.log.Warnf("Failed to send presence: %v", err)
func (handler *Handler) send_presence(presence_str string) {
presenceMap := map[string]types.Presence{
"available": types.PresenceAvailable,
"unavailable": types.PresenceUnavailable,
}
presence, ok := presenceMap[presence_str]
if ok {
err := handler.client.SendPresence(presence)
if err != nil {
purple_error(handler.account, fmt.Sprintf("Failed to send presence: %v", err), ERROR_FATAL)
} else {
handler.log.Infof("Set presence to %v", presence)
}
} else {
handler.log.Infof("Set presence to %v", presence)
purple_error(handler.account, fmt.Sprintf("Unknown presence %s (this is a bug).", presence_str), ERROR_FATAL)
}
return err
}

func (handler *Handler) handle_chat_presence(evt *events.ChatPresence) {
Expand Down

0 comments on commit ef1901e

Please sign in to comment.