Move whatsapp-ext to go-whatsapp

This commit is contained in:
Tulir Asokan 2021-02-17 01:21:30 +02:00
parent ebf2072025
commit 69dd7f803a
40 changed files with 203 additions and 1732 deletions

View file

@ -35,7 +35,6 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
type CommandHandler struct { type CommandHandler struct {
@ -737,12 +736,12 @@ const cmdListHelp = `list <contacts|groups> [page] [items per page] - Get a list
func formatContacts(contacts bool, input map[string]whatsapp.Contact) (result []string) { func formatContacts(contacts bool, input map[string]whatsapp.Contact) (result []string) {
for jid, contact := range input { for jid, contact := range input {
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) != contacts { if strings.HasSuffix(jid, whatsapp.NewUserSuffix) != contacts {
continue continue
} }
if contacts { if contacts {
result = append(result, fmt.Sprintf("* %s / %s - `%s`", contact.Name, contact.Notify, contact.Jid[:len(contact.Jid)-len(whatsappExt.NewUserSuffix)])) result = append(result, fmt.Sprintf("* %s / %s - `%s`", contact.Name, contact.Notify, contact.Jid[:len(contact.Jid)-len(whatsapp.NewUserSuffix)]))
} else { } else {
result = append(result, fmt.Sprintf("* %s - `%s`", contact.Name, contact.Jid)) result = append(result, fmt.Sprintf("* %s - `%s`", contact.Name, contact.Jid))
} }
@ -818,8 +817,8 @@ func (handler *CommandHandler) CommandOpen(ce *CommandEvent) {
user := ce.User user := ce.User
jid := ce.Args[0] jid := ce.Args[0]
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) { if strings.HasSuffix(jid, whatsapp.NewUserSuffix) {
ce.Reply("That looks like a user JID. Did you mean `pm %s`?", jid[:len(jid)-len(whatsappExt.NewUserSuffix)]) ce.Reply("That looks like a user JID. Did you mean `pm %s`?", jid[:len(jid)-len(whatsapp.NewUserSuffix)])
return return
} }
@ -865,7 +864,7 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
return return
} }
} }
jid := number + whatsappExt.NewUserSuffix jid := number + whatsapp.NewUserSuffix
handler.log.Debugln("Importing", jid, "for", user) handler.log.Debugln("Importing", jid, "for", user)

View file

@ -26,8 +26,6 @@ import (
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/types"
) )
type BridgeConfig struct { type BridgeConfig struct {
@ -185,7 +183,7 @@ func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8
return buf.String(), quality return buf.String(), quality
} }
func (bc BridgeConfig) FormatUsername(userID types.WhatsAppID) string { func (bc BridgeConfig) FormatUsername(userID whatsapp.JID) string {
var buf bytes.Buffer var buf bytes.Buffer
bc.usernameTemplate.Execute(&buf, userID) bc.usernameTemplate.Execute(&buf, userID)
return buf.String() return buf.String()

View file

@ -22,11 +22,11 @@ import (
"encoding/json" "encoding/json"
"strings" "strings"
"github.com/Rhymen/go-whatsapp"
waProto "github.com/Rhymen/go-whatsapp/binary/proto" waProto "github.com/Rhymen/go-whatsapp/binary/proto"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
@ -54,18 +54,18 @@ func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) {
return return
} }
func (mq *MessageQuery) GetByJID(chat PortalKey, jid types.WhatsAppMessageID) *Message { func (mq *MessageQuery) GetByJID(chat PortalKey, jid whatsapp.MessageID) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content " + return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content "+
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", chat.JID, chat.Receiver, jid) "FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", chat.JID, chat.Receiver, jid)
} }
func (mq *MessageQuery) GetByMXID(mxid id.EventID) *Message { func (mq *MessageQuery) GetByMXID(mxid id.EventID) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content " + return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content "+
"FROM message WHERE mxid=$1", mxid) "FROM message WHERE mxid=$1", mxid)
} }
func (mq *MessageQuery) GetLastInChat(chat PortalKey) *Message { func (mq *MessageQuery) GetLastInChat(chat PortalKey) *Message {
msg := mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content " + msg := mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content "+
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 ORDER BY timestamp DESC LIMIT 1", chat.JID, chat.Receiver) "FROM message WHERE chat_jid=$1 AND chat_receiver=$2 ORDER BY timestamp DESC LIMIT 1", chat.JID, chat.Receiver)
if msg == nil || msg.Timestamp == 0 { if msg == nil || msg.Timestamp == 0 {
// Old db, we don't know what the last message is. // Old db, we don't know what the last message is.
@ -87,9 +87,9 @@ type Message struct {
log log.Logger log log.Logger
Chat PortalKey Chat PortalKey
JID types.WhatsAppMessageID JID whatsapp.MessageID
MXID id.EventID MXID id.EventID
Sender types.WhatsAppID Sender whatsapp.JID
Timestamp uint64 Timestamp uint64
Content *waProto.Message Content *waProto.Message
} }
@ -134,7 +134,7 @@ func (msg *Message) encodeBinaryContent() []byte {
} }
func (msg *Message) Insert() { func (msg *Message) Insert() {
_, err := msg.db.Exec("INSERT INTO message (chat_jid, chat_receiver, jid, mxid, sender, timestamp, content) " + _, err := msg.db.Exec("INSERT INTO message (chat_jid, chat_receiver, jid, mxid, sender, timestamp, content) "+
"VALUES ($1, $2, $3, $4, $5, $6, $7)", "VALUES ($1, $2, $3, $4, $5, $6, $7)",
msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, msg.Sender, msg.Timestamp, msg.encodeBinaryContent()) msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, msg.Sender, msg.Timestamp, msg.encodeBinaryContent())
if err != nil { if err != nil {

View file

@ -22,24 +22,24 @@ import (
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/id" "github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix-whatsapp/types" "maunium.net/go/mautrix/id"
) )
type PortalKey struct { type PortalKey struct {
JID types.WhatsAppID JID whatsapp.JID
Receiver types.WhatsAppID Receiver whatsapp.JID
} }
func GroupPortalKey(jid types.WhatsAppID) PortalKey { func GroupPortalKey(jid whatsapp.JID) PortalKey {
return PortalKey{ return PortalKey{
JID: jid, JID: jid,
Receiver: jid, Receiver: jid,
} }
} }
func NewPortalKey(jid, receiver types.WhatsAppID) PortalKey { func NewPortalKey(jid, receiver whatsapp.JID) PortalKey {
if strings.HasSuffix(jid, "@g.us") { if strings.HasSuffix(jid, "@g.us") {
receiver = jid receiver = jid
} }
@ -80,11 +80,11 @@ func (pq *PortalQuery) GetByMXID(mxid id.RoomID) *Portal {
return pq.get("SELECT * FROM portal WHERE mxid=$1", mxid) return pq.get("SELECT * FROM portal WHERE mxid=$1", mxid)
} }
func (pq *PortalQuery) GetAllByJID(jid types.WhatsAppID) []*Portal { func (pq *PortalQuery) GetAllByJID(jid whatsapp.JID) []*Portal {
return pq.getAll("SELECT * FROM portal WHERE jid=$1", jid) return pq.getAll("SELECT * FROM portal WHERE jid=$1", jid)
} }
func (pq *PortalQuery) FindPrivateChats(receiver types.WhatsAppID) []*Portal { func (pq *PortalQuery) FindPrivateChats(receiver whatsapp.JID) []*Portal {
return pq.getAll("SELECT * FROM portal WHERE receiver=$1 AND jid LIKE '%@s.whatsapp.net'", receiver) return pq.getAll("SELECT * FROM portal WHERE receiver=$1 AND jid LIKE '%@s.whatsapp.net'", receiver)
} }

View file

@ -21,9 +21,9 @@ import (
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/id" "github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix-whatsapp/types" "maunium.net/go/mautrix/id"
) )
type PuppetQuery struct { type PuppetQuery struct {
@ -53,7 +53,7 @@ func (pq *PuppetQuery) GetAll() (puppets []*Puppet) {
return return
} }
func (pq *PuppetQuery) Get(jid types.WhatsAppID) *Puppet { func (pq *PuppetQuery) Get(jid whatsapp.JID) *Puppet {
row := pq.db.QueryRow("SELECT jid, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE jid=$1", jid) row := pq.db.QueryRow("SELECT jid, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE jid=$1", jid)
if row == nil { if row == nil {
return nil return nil
@ -85,7 +85,7 @@ type Puppet struct {
db *Database db *Database
log log.Logger log log.Logger
JID types.WhatsAppID JID whatsapp.JID
Avatar string Avatar string
AvatarURL id.ContentURI AvatarURL id.ContentURI
Displayname string Displayname string

View file

@ -26,8 +26,6 @@ import (
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
@ -63,7 +61,7 @@ func (uq *UserQuery) GetByMXID(userID id.UserID) *User {
return uq.New().Scan(row) return uq.New().Scan(row)
} }
func (uq *UserQuery) GetByJID(userID types.WhatsAppID) *User { func (uq *UserQuery) GetByJID(userID whatsapp.JID) *User {
row := uq.db.QueryRow(`SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key FROM "user" WHERE jid=$1`, stripSuffix(userID)) row := uq.db.QueryRow(`SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key FROM "user" WHERE jid=$1`, stripSuffix(userID))
if row == nil { if row == nil {
return nil return nil
@ -76,7 +74,7 @@ type User struct {
log log.Logger log log.Logger
MXID id.UserID MXID id.UserID
JID types.WhatsAppID JID whatsapp.JID
ManagementRoom id.RoomID ManagementRoom id.RoomID
Session *whatsapp.Session Session *whatsapp.Session
LastConnection uint64 LastConnection uint64
@ -93,14 +91,14 @@ func (user *User) Scan(row Scannable) *User {
return nil return nil
} }
if len(jid.String) > 0 && len(clientID.String) > 0 { if len(jid.String) > 0 && len(clientID.String) > 0 {
user.JID = jid.String + whatsappExt.NewUserSuffix user.JID = jid.String + whatsapp.NewUserSuffix
user.Session = &whatsapp.Session{ user.Session = &whatsapp.Session{
ClientId: clientID.String, ClientId: clientID.String,
ClientToken: clientToken.String, ClientToken: clientToken.String,
ServerToken: serverToken.String, ServerToken: serverToken.String,
EncKey: encKey, EncKey: encKey,
MacKey: macKey, MacKey: macKey,
Wid: jid.String + whatsappExt.OldUserSuffix, Wid: jid.String + whatsapp.OldUserSuffix,
} }
} else { } else {
user.Session = nil user.Session = nil
@ -108,7 +106,7 @@ func (user *User) Scan(row Scannable) *User {
return user return user
} }
func stripSuffix(jid types.WhatsAppID) string { func stripSuffix(jid whatsapp.JID) string {
if len(jid) == 0 { if len(jid) == 0 {
return jid return jid
} }

View file

@ -22,12 +22,11 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)") var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)")
@ -58,9 +57,9 @@ func NewFormatter(bridge *Bridge) *Formatter {
if mxid[0] == '@' { if mxid[0] == '@' {
puppet := bridge.GetPuppetByMXID(id.UserID(mxid)) puppet := bridge.GetPuppetByMXID(id.UserID(mxid))
if puppet != nil { if puppet != nil {
jids, ok := ctx[mentionedJIDsContextKey].([]types.WhatsAppID) jids, ok := ctx[mentionedJIDsContextKey].([]whatsapp.JID)
if !ok { if !ok {
ctx[mentionedJIDsContextKey] = []types.WhatsAppID{puppet.JID} ctx[mentionedJIDsContextKey] = []whatsapp.JID{puppet.JID}
} else { } else {
ctx[mentionedJIDsContextKey] = append(jids, puppet.JID) ctx[mentionedJIDsContextKey] = append(jids, puppet.JID)
} }
@ -105,7 +104,7 @@ func NewFormatter(bridge *Bridge) *Formatter {
return formatter return formatter
} }
func (formatter *Formatter) getMatrixInfoByJID(jid types.WhatsAppID) (mxid id.UserID, displayname string) { func (formatter *Formatter) getMatrixInfoByJID(jid whatsapp.JID) (mxid id.UserID, displayname string) {
if user := formatter.bridge.GetUserByJID(jid); user != nil { if user := formatter.bridge.GetUserByJID(jid); user != nil {
mxid = user.MXID mxid = user.MXID
displayname = string(user.MXID) displayname = string(user.MXID)
@ -116,7 +115,7 @@ func (formatter *Formatter) getMatrixInfoByJID(jid types.WhatsAppID) (mxid id.Us
return return
} }
func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []types.WhatsAppID) { func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []whatsapp.JID) {
output := html.EscapeString(content.Body) output := html.EscapeString(content.Body)
for regex, replacement := range formatter.waReplString { for regex, replacement := range formatter.waReplString {
output = regex.ReplaceAllString(output, replacement) output = regex.ReplaceAllString(output, replacement)
@ -126,7 +125,7 @@ func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, me
} }
for _, jid := range mentionedJIDs { for _, jid := range mentionedJIDs {
mxid, displayname := formatter.getMatrixInfoByJID(jid) mxid, displayname := formatter.getMatrixInfoByJID(jid)
number := "@" + strings.Replace(jid, whatsappExt.NewUserSuffix, "", 1) number := "@" + strings.Replace(jid, whatsapp.NewUserSuffix, "", 1)
output = strings.Replace(output, number, fmt.Sprintf(`<a href="https://matrix.to/#/%s">%s</a>`, mxid, displayname), -1) output = strings.Replace(output, number, fmt.Sprintf(`<a href="https://matrix.to/#/%s">%s</a>`, mxid, displayname), -1)
content.Body = strings.Replace(content.Body, number, displayname, -1) content.Body = strings.Replace(content.Body, number, displayname, -1)
} }
@ -140,9 +139,9 @@ func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, me
} }
} }
func (formatter *Formatter) ParseMatrix(html string) (string, []types.WhatsAppID) { func (formatter *Formatter) ParseMatrix(html string) (string, []whatsapp.JID) {
ctx := make(format.Context) ctx := make(format.Context)
result := formatter.matrixHTMLParser.Parse(html, ctx) result := formatter.matrixHTMLParser.Parse(html, ctx)
mentionedJIDs, _ := ctx[mentionedJIDsContextKey].([]types.WhatsAppID) mentionedJIDs, _ := ctx[mentionedJIDsContextKey].([]whatsapp.JID)
return result, mentionedJIDs return result, mentionedJIDs
} }

View file

@ -1,2 +0,0 @@
[*.{yaml,yml}]
indent_size = 2

View file

@ -1,2 +0,0 @@
charts/*
!*.yaml

View file

@ -1,22 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -1,14 +0,0 @@
apiVersion: v1
name: mautrix-whatsapp
version: 0.1.0
appVersion: "0.1.0"
description: A Matrix-Whatsapp puppeting bridge.
keywords:
- matrix
- bridge
- whatsapp
maintainers:
- name: Tulir Asokan
email: tulir@maunium.net
sources:
- https://github.com/tulir/mautrix-whatsapp

View file

@ -1,6 +0,0 @@
dependencies:
- name: postgresql
repository: https://kubernetes-charts.storage.googleapis.com/
version: 6.5.0
digest: sha256:85139e9d4207e49c11c5f84d7920d0135cffd3d427f3f3638d4e51258990de2a
generated: "2019-10-23T22:11:37.005827507+03:00"

View file

@ -1,5 +0,0 @@
dependencies:
- name: postgresql
version: 6.5.0
repository: https://kubernetes-charts.storage.googleapis.com/
condition: postgresql.enabled

View file

@ -1,12 +0,0 @@
Your registration file is below. Save it into a YAML file and give the path to that file to synapse:
id: {{ .Values.appservice.id }}
as_token: {{ .Values.appservice.asToken }}
hs_token: {{ .Values.appservice.hsToken }}
namespaces:
users:
- exclusive: true
regex: "@{{ .Values.bridge.username_template | replace "{{.}}" ".+"}}:{{ .Values.homeserver.domain }}"
url: {{ .Values.appservice.address }}
sender_localpart: {{ .Values.appservice.botUsername }}
rate_limited: false

View file

@ -1,55 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mautrix-whatsapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mautrix-whatsapp.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mautrix-whatsapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "mautrix-whatsapp.labels" -}}
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
helm.sh/chart: {{ include "mautrix-whatsapp.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "mautrix-whatsapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "mautrix-whatsapp.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

View file

@ -1,45 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "mautrix-whatsapp.fullname" . }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ template "mautrix-whatsapp.name" . }}
data:
config.yaml: |
homeserver:
address: {{ .Values.homeserver.address }}
domain: {{ .Values.homeserver.domain }}
appservice:
address: http://{{ include "mautrix-whatsapp.fullname" . }}:{{ .Values.service.port }}
hostname: 0.0.0.0
port: {{ .Values.service.port }}
{{- if .Values.postgresql.enabled }}
database:
type: postgres
uri: "postgres://postgres:{{ .Values.postgresql.postgresqlPassword }}@{{ .Release.Name }}-postgresql/{{ .Values.postgresql.postgresqlDatabase }}?sslmode=disable"
{{- else }}
database:
{{- toYaml .Values.appservice.database | nindent 8 }}
{{- end }}
id: {{ .Values.appservice.id }}
bot:
username: {{ .Values.appservice.botUsername }}
displayname: {{ .Values.appservice.botDisplayname }}
avatar: {{ .Values.appservice.botAvatar }}
as_token: {{ .Values.appservice.asToken }}
hs_token: {{ .Values.appservice.hsToken }}
bridge:
{{- toYaml .Values.bridge | nindent 6 }}
logging:
{{- toYaml .Values.logging | nindent 6 }}
registration.yaml: ""

View file

@ -1,69 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mautrix-whatsapp.fullname" . }}
labels:
{{- include "mautrix-whatsapp.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
{{- if .Values.podAnnotations }}
annotations:
{{- toYaml .Values.podAnnotations | nindent 6 }}
{{- end }}
metadata:
labels:
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: {{ template "mautrix-whatsapp.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- mountPath: /data
name: config-volume
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
# livenessProbe:
# httpGet:
# path: /_matrix/mau/live
# port: http
# initialDelaySeconds: 60
# periodSeconds: 5
# readinessProbe:
# httpGet:
# path: /_matrix/mau/ready
# port: http
# initialDelaySeconds: 60
# periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: config-volume
configMap:
name: {{ template "mautrix-whatsapp.fullname" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View file

@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mautrix-whatsapp.fullname" . }}
labels:
{{ include "mautrix-whatsapp.labels" . | indent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

View file

@ -1,8 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "mautrix-whatsapp.serviceAccountName" . }}
labels:
{{ include "mautrix-whatsapp.labels" . | indent 4 }}
{{- end -}}

View file

@ -1,137 +0,0 @@
image:
repository: dock.mau.dev/tulir/mautrix-whatsapp
tag: latest
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name:
service:
type: ClusterIP
port: 29318
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
# Postgres pod configs
postgresql:
enabled: true
postgresqlDatabase: mxwa
postgresqlPassword: SET TO RANDOM STRING
persistence:
size: 2Gi
resources:
requests:
memory: 256Mi
cpu: 100m
# Homeserver details
homeserver:
# The address that this appservice can use to connect to the homeserver.
address: https://example.com
# The domain of the homeserver (for MXIDs, etc).
domain: example.com
# Application service host/registration related details
# Changing these values requires regeneration of the registration.
appservice:
id: whatsapp
botUsername: whatsappbot
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
# to leave display name/avatar as-is.
botDisplayname: WhatsApp bridge bot
botAvatar: mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr
# Authentication tokens for AS <-> HS communication.
asToken: SET TO RANDOM STRING
hsToken: SET TO RANDOM STRING
# The keys below can be used to override the configs in the base config:
# https://github.com/tulir/mautrix-whatsapp/blob/master/example-config.yaml
# Note that the "appservice" and "homeserver" sections are above and slightly different than the base.
# Bridge config
bridge:
# Localpart template of MXIDs for WhatsApp users.
# {{.}} is replaced with the phone number of the WhatsApp user.
username_template: whatsapp_{{.}}
# Number of chats to sync for new users.
initial_chat_sync_count: 10
# Number of old messages to fill when creating new portal rooms.
initial_history_fill_count: 20
# Maximum number of chats to sync when recovering from downtime.
# Set to -1 to sync all new chats during downtime.
recovery_chat_sync_limit: -1
# Whether or not to sync history when recovering from downtime.
recovery_history_backfill: true
# Maximum number of seconds since last message in chat to skip
# syncing the chat in any case. This setting will take priority
# over both recovery_chat_sync_limit and initial_chat_sync_count.
# Default is 3 days = 259200 seconds
sync_max_chat_age: 259200
# Whether or not to explicitly set the avatar and room name for private
# chat portal rooms. This can be useful if the previous field works fine,
# but causes room avatar/name bugs.
private_chat_portal_meta: true
# Allow invite permission for user. User can invite any bots to room with whatsapp
# users (private chat and groups)
allow_user_invite: true
# Permissions for using the bridge.
# Permitted values:
# relaybot - Talk through the relaybot (if enabled), no access otherwise
# user - Access to use the bridge to chat with a WhatsApp account.
# admin - User level and some additional administration tools
# Permitted keys:
# * - All Matrix users
# domain - All users on that homeserver
# mxid - Specific user
permissions:
"*": relaybot
"example.com": user
"@admin:example.com": admin
relaybot:
# Whether or not relaybot support is enabled.
enabled: false
# The management room for the bot. This is where all status notifications are posted and
# in this room, you can use `!wa <command>` instead of `!wa relaybot <command>`. Omitting
# the command prefix completely like in user management rooms is not possible.
management: !foo:example.com
# List of users to invite to all created rooms that include the relaybot.
invites: []
# The formats to use when sending messages to WhatsApp via the relaybot.
message_formats:
m.text: "<b>{{ .Sender.Displayname }}</b>: {{ .Message }}"
m.notice: "<b>{{ .Sender.Displayname }}</b>: {{ .Message }}"
m.emote: "* <b>{{ .Sender.Displayname }}</b> {{ .Message }}"
m.file: "<b>{{ .Sender.Displayname }}</b> sent a file"
m.image: "<b>{{ .Sender.Displayname }}</b> sent an image"
m.audio: "<b>{{ .Sender.Displayname }}</b> sent an audio file"
m.video: "<b>{{ .Sender.Displayname }}</b> sent a video"
m.location: "<b>{{ .Sender.Displayname }}</b> sent a location"
logging:
timestamp_format: Jan _2, 2006 15:04:05
print_level: debug

11
main.go
View file

@ -25,6 +25,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/Rhymen/go-whatsapp"
flag "maunium.net/go/mauflag" flag "maunium.net/go/mauflag"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
@ -36,7 +38,6 @@ import (
"maunium.net/go/mautrix-whatsapp/config" "maunium.net/go/mautrix-whatsapp/config"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/database/upgrades" "maunium.net/go/mautrix-whatsapp/database/upgrades"
"maunium.net/go/mautrix-whatsapp/types"
) )
var ( var (
@ -137,14 +138,14 @@ type Bridge struct {
Metrics *MetricsHandler Metrics *MetricsHandler
usersByMXID map[id.UserID]*User usersByMXID map[id.UserID]*User
usersByJID map[types.WhatsAppID]*User usersByJID map[whatsapp.JID]*User
usersLock sync.Mutex usersLock sync.Mutex
managementRooms map[id.RoomID]*User managementRooms map[id.RoomID]*User
managementRoomsLock sync.Mutex managementRoomsLock sync.Mutex
portalsByMXID map[id.RoomID]*Portal portalsByMXID map[id.RoomID]*Portal
portalsByJID map[database.PortalKey]*Portal portalsByJID map[database.PortalKey]*Portal
portalsLock sync.Mutex portalsLock sync.Mutex
puppets map[types.WhatsAppID]*Puppet puppets map[whatsapp.JID]*Puppet
puppetsByCustomMXID map[id.UserID]*Puppet puppetsByCustomMXID map[id.UserID]*Puppet
puppetsLock sync.Mutex puppetsLock sync.Mutex
} }
@ -163,11 +164,11 @@ type Crypto interface {
func NewBridge() *Bridge { func NewBridge() *Bridge {
bridge := &Bridge{ bridge := &Bridge{
usersByMXID: make(map[id.UserID]*User), usersByMXID: make(map[id.UserID]*User),
usersByJID: make(map[types.WhatsAppID]*User), usersByJID: make(map[whatsapp.JID]*User),
managementRooms: make(map[id.RoomID]*User), managementRooms: make(map[id.RoomID]*User),
portalsByMXID: make(map[id.RoomID]*Portal), portalsByMXID: make(map[id.RoomID]*Portal),
portalsByJID: make(map[database.PortalKey]*Portal), portalsByJID: make(map[database.PortalKey]*Portal),
puppets: make(map[types.WhatsAppID]*Puppet), puppets: make(map[whatsapp.JID]*Puppet),
puppetsByCustomMXID: make(map[id.UserID]*Puppet), puppetsByCustomMXID: make(map[id.UserID]*Puppet),
} }

View file

@ -254,6 +254,10 @@ func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
return return
} }
if mx.shouldIgnoreEvent(evt) {
return
}
user := mx.bridge.GetUserByMXID(evt.Sender) user := mx.bridge.GetUserByMXID(evt.Sender)
if user == nil || !user.Whitelisted || !user.IsConnected() { if user == nil || !user.Whitelisted || !user.IsConnected() {
return return

View file

@ -27,11 +27,12 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
) )
type MetricsHandler struct { type MetricsHandler struct {
@ -56,11 +57,11 @@ type MetricsHandler struct {
unencryptedPrivateCount prometheus.Gauge unencryptedPrivateCount prometheus.Gauge
connected prometheus.Gauge connected prometheus.Gauge
connectedState map[types.WhatsAppID]bool connectedState map[whatsapp.JID]bool
loggedIn prometheus.Gauge loggedIn prometheus.Gauge
loggedInState map[types.WhatsAppID]bool loggedInState map[whatsapp.JID]bool
syncLocked prometheus.Gauge syncLocked prometheus.Gauge
syncLockedState map[types.WhatsAppID]bool syncLockedState map[whatsapp.JID]bool
bufferLength *prometheus.GaugeVec bufferLength *prometheus.GaugeVec
} }
@ -109,17 +110,17 @@ func NewMetricsHandler(address string, log log.Logger, db *database.Database) *M
Name: "bridge_logged_in", Name: "bridge_logged_in",
Help: "Users logged into the bridge", Help: "Users logged into the bridge",
}), }),
loggedInState: make(map[types.WhatsAppID]bool), loggedInState: make(map[whatsapp.JID]bool),
connected: promauto.NewGauge(prometheus.GaugeOpts{ connected: promauto.NewGauge(prometheus.GaugeOpts{
Name: "bridge_connected", Name: "bridge_connected",
Help: "Bridge users connected to WhatsApp", Help: "Bridge users connected to WhatsApp",
}), }),
connectedState: make(map[types.WhatsAppID]bool), connectedState: make(map[whatsapp.JID]bool),
syncLocked: promauto.NewGauge(prometheus.GaugeOpts{ syncLocked: promauto.NewGauge(prometheus.GaugeOpts{
Name: "bridge_sync_locked", Name: "bridge_sync_locked",
Help: "Bridge users locked in post-login sync", Help: "Bridge users locked in post-login sync",
}), }),
syncLockedState: make(map[types.WhatsAppID]bool), syncLockedState: make(map[whatsapp.JID]bool),
bufferLength: promauto.NewGaugeVec(prometheus.GaugeOpts{ bufferLength: promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "bridge_buffer_size", Name: "bridge_buffer_size",
Help: "Number of messages in buffer", Help: "Number of messages in buffer",
@ -149,7 +150,7 @@ func (mh *MetricsHandler) TrackDisconnection(userID id.UserID) {
mh.disconnections.With(prometheus.Labels{"user_id": string(userID)}).Inc() mh.disconnections.With(prometheus.Labels{"user_id": string(userID)}).Inc()
} }
func (mh *MetricsHandler) TrackLoginState(jid types.WhatsAppID, loggedIn bool) { func (mh *MetricsHandler) TrackLoginState(jid whatsapp.JID, loggedIn bool) {
if !mh.running { if !mh.running {
return return
} }
@ -164,7 +165,7 @@ func (mh *MetricsHandler) TrackLoginState(jid types.WhatsAppID, loggedIn bool) {
} }
} }
func (mh *MetricsHandler) TrackConnectionState(jid types.WhatsAppID, connected bool) { func (mh *MetricsHandler) TrackConnectionState(jid whatsapp.JID, connected bool) {
if !mh.running { if !mh.running {
return return
} }
@ -179,7 +180,7 @@ func (mh *MetricsHandler) TrackConnectionState(jid types.WhatsAppID, connected b
} }
} }
func (mh *MetricsHandler) TrackSyncLock(jid types.WhatsAppID, locked bool) { func (mh *MetricsHandler) TrackSyncLock(jid whatsapp.JID, locked bool) {
if !mh.running { if !mh.running {
return return
} }

View file

@ -40,23 +40,19 @@ import (
"sync" "sync"
"time" "time"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/crypto/attachment"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
waProto "github.com/Rhymen/go-whatsapp/binary/proto" waProto "github.com/Rhymen/go-whatsapp/binary/proto"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/crypto/attachment"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/pushrules" "maunium.net/go/mautrix/pushrules"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal { func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal {
@ -83,7 +79,7 @@ func (bridge *Bridge) GetAllPortals() []*Portal {
return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAll()) return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAll())
} }
func (bridge *Bridge) GetAllPortalsByJID(jid types.WhatsAppID) []*Portal { func (bridge *Bridge) GetAllPortalsByJID(jid whatsapp.JID) []*Portal {
return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAllByJID(jid)) return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAllByJID(jid))
} }
@ -131,7 +127,7 @@ func (bridge *Bridge) NewManualPortal(key database.PortalKey) *Portal {
bridge: bridge, bridge: bridge,
log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", key)), log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", key)),
recentlyHandled: [recentlyHandledLength]types.WhatsAppMessageID{}, recentlyHandled: [recentlyHandledLength]whatsapp.MessageID{},
messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer), messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer),
} }
@ -146,7 +142,7 @@ func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
bridge: bridge, bridge: bridge,
log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)), log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)),
recentlyHandled: [recentlyHandledLength]types.WhatsAppMessageID{}, recentlyHandled: [recentlyHandledLength]whatsapp.MessageID{},
messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer), messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer),
} }
@ -171,7 +167,7 @@ type Portal struct {
roomCreateLock sync.Mutex roomCreateLock sync.Mutex
recentlyHandled [recentlyHandledLength]types.WhatsAppMessageID recentlyHandled [recentlyHandledLength]whatsapp.MessageID
recentlyHandledLock sync.Mutex recentlyHandledLock sync.Mutex
recentlyHandledIndex uint8 recentlyHandledIndex uint8
@ -256,7 +252,7 @@ func (portal *Portal) handleMessage(msg PortalMessage, isBackfill bool) {
portal.HandleLocationMessage(msg.source, data) portal.HandleLocationMessage(msg.source, data)
case whatsapp.StubMessage: case whatsapp.StubMessage:
portal.HandleStubMessage(msg.source, data, isBackfill) portal.HandleStubMessage(msg.source, data, isBackfill)
case whatsappExt.MessageRevocation: case whatsapp.MessageRevocation:
portal.HandleMessageRevoke(msg.source, data) portal.HandleMessageRevoke(msg.source, data)
case FakeMessage: case FakeMessage:
portal.HandleFakeMessage(msg.source, data) portal.HandleFakeMessage(msg.source, data)
@ -265,7 +261,7 @@ func (portal *Portal) handleMessage(msg PortalMessage, isBackfill bool) {
} }
} }
func (portal *Portal) isRecentlyHandled(id types.WhatsAppMessageID) bool { func (portal *Portal) isRecentlyHandled(id whatsapp.MessageID) bool {
start := portal.recentlyHandledIndex start := portal.recentlyHandledIndex
for i := start; i != start; i = (i - 1) % recentlyHandledLength { for i := start; i != start; i = (i - 1) % recentlyHandledLength {
if portal.recentlyHandled[i] == id { if portal.recentlyHandled[i] == id {
@ -275,7 +271,7 @@ func (portal *Portal) isRecentlyHandled(id types.WhatsAppMessageID) bool {
return false return false
} }
func (portal *Portal) isDuplicate(id types.WhatsAppMessageID) bool { func (portal *Portal) isDuplicate(id whatsapp.MessageID) bool {
msg := portal.bridge.DB.Message.GetByJID(portal.Key, id) msg := portal.bridge.DB.Message.GetByJID(portal.Key, id)
if msg != nil { if msg != nil {
return true return true
@ -355,7 +351,7 @@ func (portal *Portal) finishHandling(source *User, message *waProto.WebMessageIn
portal.log.Debugln("Handled message", message.GetKey().GetId(), "->", mxid) portal.log.Debugln("Handled message", message.GetKey().GetId(), "->", mxid)
} }
func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) { func (portal *Portal) SyncParticipants(metadata *whatsapp.GroupInfo) {
changed := false changed := false
levels, err := portal.MainIntent().PowerLevels(portal.MXID) levels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil { if err != nil {
@ -413,7 +409,7 @@ func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) {
} }
} }
func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInfo, updateInfo bool) bool { func (portal *Portal) UpdateAvatar(user *User, avatar *whatsapp.ProfilePicInfo, updateInfo bool) bool {
if avatar == nil || (avatar.Status == 0 && avatar.Tag != "remove" && len(avatar.URL) == 0) { if avatar == nil || (avatar.Status == 0 && avatar.Tag != "remove" && len(avatar.URL) == 0) {
var err error var err error
avatar, err = user.Conn.GetProfilePicThumb(portal.Key.JID) avatar, err = user.Conn.GetProfilePicThumb(portal.Key.JID)
@ -464,7 +460,7 @@ func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInf
return true return true
} }
func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, intent *appservice.IntentAPI, updateInfo bool) bool { func (portal *Portal) UpdateName(name string, setBy whatsapp.JID, intent *appservice.IntentAPI, updateInfo bool) bool {
if portal.Name != name { if portal.Name != name {
portal.log.Debugfln("Updating name %s -> %s", portal.Name, name) portal.log.Debugfln("Updating name %s -> %s", portal.Name, name)
portal.Name = name portal.Name = name
@ -488,7 +484,7 @@ func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, intent *ap
return false return false
} }
func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID, intent *appservice.IntentAPI, updateInfo bool) bool { func (portal *Portal) UpdateTopic(topic string, setBy whatsapp.JID, intent *appservice.IntentAPI, updateInfo bool) bool {
if portal.Topic != topic { if portal.Topic != topic {
portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic) portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic)
portal.Topic = topic portal.Topic = topic
@ -974,7 +970,7 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
portal.log.Infoln("Creating Matrix room. Info source:", user.MXID) portal.log.Infoln("Creating Matrix room. Info source:", user.MXID)
var metadata *whatsappExt.GroupInfo var metadata *whatsapp.GroupInfo
if portal.IsPrivateChat() { if portal.IsPrivateChat() {
puppet := portal.bridge.GetPuppetByJID(portal.Key.JID) puppet := portal.bridge.GetPuppetByJID(portal.Key.JID)
if portal.bridge.Config.Bridge.PrivateChatPortalMeta { if portal.bridge.Config.Bridge.PrivateChatPortalMeta {
@ -1099,7 +1095,7 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
func (portal *Portal) IsPrivateChat() bool { func (portal *Portal) IsPrivateChat() bool {
if portal.isPrivate == nil { if portal.isPrivate == nil {
val := strings.HasSuffix(portal.Key.JID, whatsappExt.NewUserSuffix) val := strings.HasSuffix(portal.Key.JID, whatsapp.NewUserSuffix)
portal.isPrivate = &val portal.isPrivate = &val
} }
return *portal.isPrivate return *portal.isPrivate
@ -1152,7 +1148,7 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp
return return
} }
func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.MessageRevocation) { func (portal *Portal) HandleMessageRevoke(user *User, message whatsapp.MessageRevocation) {
msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id) msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id)
if msg == nil || msg.IsFakeMXID() { if msg == nil || msg.IsFakeMXID() {
return return
@ -1794,7 +1790,7 @@ func (portal *Portal) convertGifToVideo(gif []byte) ([]byte, error) {
func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsapp.MediaType) *MediaUpload { func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsapp.MediaType) *MediaUpload {
var caption string var caption string
var mentionedJIDs []types.WhatsAppID var mentionedJIDs []whatsapp.JID
if relaybotFormatted { if relaybotFormatted {
caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody) caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
} }
@ -1851,7 +1847,7 @@ func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool
type MediaUpload struct { type MediaUpload struct {
Caption string Caption string
MentionedJIDs []types.WhatsAppID MentionedJIDs []whatsapp.JID
URL string URL string
MediaKey []byte MediaKey []byte
FileEncSHA256 []byte FileEncSHA256 []byte

View file

@ -27,12 +27,11 @@ import (
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/id"
whatsappExt "maunium.net/go/mautrix-whatsapp/whatsapp-ext" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/id"
) )
type ProvisioningAPI struct { type ProvisioningAPI struct {
@ -433,7 +432,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
} }
user.log.Debugln("Successful login via provisioning API") user.log.Debugln("Successful login via provisioning API")
user.ConnectionErrors = 0 user.ConnectionErrors = 0
user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1) user.JID = strings.Replace(user.Conn.Info.Wid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
user.addToJIDMap() user.addToJIDMap()
user.SetSession(&session) user.SetSession(&session)
_ = c.WriteJSON(map[string]interface{}{ _ = c.WriteJSON(map[string]interface{}{

View file

@ -22,21 +22,18 @@ import (
"regexp" "regexp"
"strings" "strings"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
var userIDRegex *regexp.Regexp var userIDRegex *regexp.Regexp
func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.WhatsAppID, bool) { func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (whatsapp.JID, bool) {
if userIDRegex == nil { if userIDRegex == nil {
userIDRegex = regexp.MustCompile(fmt.Sprintf("^@%s:%s$", userIDRegex = regexp.MustCompile(fmt.Sprintf("^@%s:%s$",
bridge.Config.Bridge.FormatUsername("([0-9]+)"), bridge.Config.Bridge.FormatUsername("([0-9]+)"),
@ -47,7 +44,7 @@ func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.WhatsAppID, bool) {
return "", false return "", false
} }
jid := types.WhatsAppID(match[1] + whatsappExt.NewUserSuffix) jid := whatsapp.JID(match[1] + whatsapp.NewUserSuffix)
return jid, true return jid, true
} }
@ -60,7 +57,7 @@ func (bridge *Bridge) GetPuppetByMXID(mxid id.UserID) *Puppet {
return bridge.GetPuppetByJID(jid) return bridge.GetPuppetByJID(jid)
} }
func (bridge *Bridge) GetPuppetByJID(jid types.WhatsAppID) *Puppet { func (bridge *Bridge) GetPuppetByJID(jid whatsapp.JID) *Puppet {
bridge.puppetsLock.Lock() bridge.puppetsLock.Lock()
defer bridge.puppetsLock.Unlock() defer bridge.puppetsLock.Unlock()
puppet, ok := bridge.puppets[jid] puppet, ok := bridge.puppets[jid]
@ -125,12 +122,12 @@ func (bridge *Bridge) dbPuppetsToPuppets(dbPuppets []*database.Puppet) []*Puppet
return output return output
} }
func (bridge *Bridge) FormatPuppetMXID(jid types.WhatsAppID) id.UserID { func (bridge *Bridge) FormatPuppetMXID(jid whatsapp.JID) id.UserID {
return id.NewUserID( return id.NewUserID(
bridge.Config.Bridge.FormatUsername( bridge.Config.Bridge.FormatUsername(
strings.Replace( strings.Replace(
jid, jid,
whatsappExt.NewUserSuffix, "", 1)), whatsapp.NewUserSuffix, "", 1)),
bridge.Config.Homeserver.Domain) bridge.Config.Homeserver.Domain)
} }
@ -161,7 +158,7 @@ type Puppet struct {
} }
func (puppet *Puppet) PhoneNumber() string { func (puppet *Puppet) PhoneNumber() string {
return strings.Replace(puppet.JID, whatsappExt.NewUserSuffix, "", 1) return strings.Replace(puppet.JID, whatsapp.NewUserSuffix, "", 1)
} }
func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI { func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
@ -181,7 +178,7 @@ func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI {
return puppet.bridge.AS.Intent(puppet.MXID) return puppet.bridge.AS.Intent(puppet.MXID)
} }
func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsappExt.ProfilePicInfo) bool { func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsapp.ProfilePicInfo) bool {
if avatar == nil { if avatar == nil {
var err error var err error
avatar, err = source.Conn.GetProfilePicThumb(puppet.JID) avatar, err = source.Conn.GetProfilePicThumb(puppet.JID)

View file

@ -1,23 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package types
// WhatsAppID is a WhatsApp JID.
type WhatsAppID = string
// WhatsAppMessageID is the internal ID of a WhatsApp message.
type WhatsAppMessageID = string

238
user.go
View file

@ -40,13 +40,11 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
type User struct { type User struct {
*database.User *database.User
Conn *whatsappExt.ExtendedConn Conn *whatsapp.Conn
bridge *Bridge bridge *Bridge
log log.Logger log log.Logger
@ -91,7 +89,7 @@ func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
return user return user
} }
func (bridge *Bridge) GetUserByJID(userID types.WhatsAppID) *User { func (bridge *Bridge) GetUserByJID(userID whatsapp.JID) *User {
bridge.usersLock.Lock() bridge.usersLock.Lock()
defer bridge.usersLock.Unlock() defer bridge.usersLock.Unlock()
user, ok := bridge.usersByJID[userID] user, ok := bridge.usersByJID[userID]
@ -264,7 +262,7 @@ func (user *User) Connect(evenIfNoSession bool) bool {
user.connLock.Unlock() user.connLock.Unlock()
return false return false
} }
user.Conn = whatsappExt.ExtendConn(conn) user.Conn = conn
user.log.Debugln("WhatsApp connection successful") user.log.Debugln("WhatsApp connection successful")
user.Conn.AddHandler(user) user.Conn.AddHandler(user)
user.connLock.Unlock() user.connLock.Unlock()
@ -419,7 +417,7 @@ func (user *User) Login(ce *CommandEvent) {
// TODO there's a bit of duplication between this and the provisioning API login method // TODO there's a bit of duplication between this and the provisioning API login method
// Also between the two logout methods (commands.go and provisioning.go) // Also between the two logout methods (commands.go and provisioning.go)
user.ConnectionErrors = 0 user.ConnectionErrors = 0
user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1) user.JID = strings.Replace(user.Conn.Info.Wid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
user.addToJIDMap() user.addToJIDMap()
user.SetSession(&session) user.SetSession(&session)
ce.Reply("Successfully logged in, synchronizing chats...") ce.Reply("Successfully logged in, synchronizing chats...")
@ -501,7 +499,7 @@ func (user *User) sendMarkdownBridgeAlert(formatString string, args ...interface
} }
} }
func (user *User) postConnPing(conn *whatsappExt.ExtendedConn) bool { func (user *User) postConnPing(conn *whatsapp.Conn) bool {
if user.Conn != conn { if user.Conn != conn {
user.log.Warnln("Connection changed before scheduled post-connection ping, canceling ping") user.log.Warnln("Connection changed before scheduled post-connection ping, canceling ping")
return false return false
@ -528,7 +526,7 @@ func (user *User) postConnPing(conn *whatsappExt.ExtendedConn) bool {
} }
} }
func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) { func (user *User) intPostLogin(conn *whatsapp.Conn) {
defer user.syncWait.Done() defer user.syncWait.Done()
user.lastReconnection = time.Now().Unix() user.lastReconnection = time.Now().Unix()
user.createCommunity() user.createCommunity()
@ -559,8 +557,60 @@ func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) {
} }
} }
func (user *User) HandleStreamEvent(evt whatsappExt.StreamEvent) { type InfoGetter interface {
if evt.Type == whatsappExt.StreamSleep { GetInfo() whatsapp.MessageInfo
}
func (user *User) HandleEvent(event interface{}) {
switch v := event.(type) {
case whatsapp.TextMessage, whatsapp.ImageMessage, whatsapp.StickerMessage, whatsapp.VideoMessage,
whatsapp.AudioMessage, whatsapp.DocumentMessage, whatsapp.ContactMessage, whatsapp.StubMessage,
whatsapp.LocationMessage:
info := v.(InfoGetter).GetInfo()
user.messageInput <- PortalMessage{info.RemoteJid, user, v, info.Timestamp}
case whatsapp.MessageRevocation:
user.messageInput <- PortalMessage{v.RemoteJid, user, v, 0}
case whatsapp.StreamEvent:
user.HandleStreamEvent(v)
case []whatsapp.Chat:
user.HandleChatList(v)
case []whatsapp.Contact:
user.HandleContactList(v)
case error:
user.HandleError(v)
case whatsapp.Contact:
go user.HandleNewContact(v)
case whatsapp.BatteryMessage:
user.HandleBatteryMessage(v)
case whatsapp.CallInfo:
user.HandleCallInfo(v)
case whatsapp.PresenceEvent:
go user.HandlePresence(v)
case whatsapp.JSONMsgInfo:
go user.HandleMsgInfo(v)
case whatsapp.ReceivedMessage:
user.HandleReceivedMessage(v)
case whatsapp.ReadMessage:
user.HandleReadMessage(v)
case whatsapp.JSONCommand:
user.HandleCommand(v)
case whatsapp.ChatUpdate:
user.HandleChatUpdate(v)
case json.RawMessage:
user.HandleJSONMessage(v)
case *waProto.WebMessageInfo:
user.updateLastConnectionIfNecessary()
// TODO trace log
user.log.Debugfln("WebMessageInfo: %+v", v)
case *waBinary.Node:
user.log.Debugfln("Unknown binary message: %+v", v)
default:
user.log.Debugfln("Unknown type of event in HandleEvent: %T", v)
}
}
func (user *User) HandleStreamEvent(evt whatsapp.StreamEvent) {
if evt.Type == whatsapp.StreamSleep {
if user.lastReconnection+60 > time.Now().Unix() { if user.lastReconnection+60 > time.Now().Unix() {
user.lastReconnection = 0 user.lastReconnection = 0
user.log.Infoln("Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds") user.log.Infoln("Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds")
@ -738,7 +788,7 @@ func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) {
} }
user.log.Infoln("Syncing puppet info from contacts") user.log.Infoln("Syncing puppet info from contacts")
for jid, contact := range contacts { for jid, contact := range contacts {
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) { if strings.HasSuffix(jid, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(contact.Jid) puppet := user.bridge.GetPuppetByJID(contact.Jid)
puppet.Sync(user, contact) puppet.Sync(user, contact)
} }
@ -841,19 +891,11 @@ func (user *User) tryReconnect(msg string) {
} }
} }
func (user *User) ShouldCallSynchronously() bool { func (user *User) PortalKey(jid whatsapp.JID) database.PortalKey {
return true
}
func (user *User) HandleJSONParseError(err error) {
user.log.Errorln("WhatsApp JSON parse error:", err)
}
func (user *User) PortalKey(jid types.WhatsAppID) database.PortalKey {
return database.NewPortalKey(jid, user.JID) return database.NewPortalKey(jid, user.JID)
} }
func (user *User) GetPortalByJID(jid types.WhatsAppID) *Portal { func (user *User) GetPortalByJID(jid whatsapp.JID) *Portal {
return user.bridge.GetPortalByJID(user.PortalKey(jid)) return user.bridge.GetPortalByJID(user.PortalKey(jid))
} }
@ -888,13 +930,11 @@ func (user *User) handleMessageLoop() {
func (user *User) HandleNewContact(contact whatsapp.Contact) { func (user *User) HandleNewContact(contact whatsapp.Contact) {
user.log.Debugfln("Contact message: %+v", contact) user.log.Debugfln("Contact message: %+v", contact)
go func() { if strings.HasSuffix(contact.Jid, whatsapp.OldUserSuffix) {
if strings.HasSuffix(contact.Jid, whatsappExt.OldUserSuffix) { contact.Jid = strings.Replace(contact.Jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
contact.Jid = strings.Replace(contact.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1) }
} puppet := user.bridge.GetPuppetByJID(contact.Jid)
puppet := user.bridge.GetPuppetByJID(contact.Jid) puppet.UpdateName(user, contact)
puppet.UpdateName(user, contact)
}()
} }
func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) { func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) {
@ -914,53 +954,13 @@ func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) {
} }
} }
func (user *User) HandleTextMessage(message whatsapp.TextMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleContactMessage(message whatsapp.ContactMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleStubMessage(message whatsapp.StubMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleLocationMessage(message whatsapp.LocationMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleMessageRevoke(message whatsappExt.MessageRevocation) {
user.messageInput <- PortalMessage{message.RemoteJid, user, message, 0}
}
type FakeMessage struct { type FakeMessage struct {
Text string Text string
ID string ID string
Alert bool Alert bool
} }
func (user *User) HandleCallInfo(info whatsappExt.CallInfo) { func (user *User) HandleCallInfo(info whatsapp.CallInfo) {
if info.Data != nil { if info.Data != nil {
return return
} }
@ -968,19 +968,19 @@ func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
ID: info.ID, ID: info.ID,
} }
switch info.Type { switch info.Type {
case whatsappExt.CallOffer: case whatsapp.CallOffer:
if !user.bridge.Config.Bridge.CallNotices.Start { if !user.bridge.Config.Bridge.CallNotices.Start {
return return
} }
data.Text = "Incoming call" data.Text = "Incoming call"
data.Alert = true data.Alert = true
case whatsappExt.CallOfferVideo: case whatsapp.CallOfferVideo:
if !user.bridge.Config.Bridge.CallNotices.Start { if !user.bridge.Config.Bridge.CallNotices.Start {
return return
} }
data.Text = "Incoming video call" data.Text = "Incoming video call"
data.Alert = true data.Alert = true
case whatsappExt.CallTerminate: case whatsapp.CallTerminate:
if !user.bridge.Config.Bridge.CallNotices.End { if !user.bridge.Config.Bridge.CallNotices.End {
return return
} }
@ -995,7 +995,7 @@ func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
} }
} }
func (user *User) HandlePresence(info whatsappExt.Presence) { func (user *User) HandlePresence(info whatsapp.PresenceEvent) {
puppet := user.bridge.GetPuppetByJID(info.SenderJID) puppet := user.bridge.GetPuppetByJID(info.SenderJID)
switch info.Status { switch info.Status {
case whatsapp.PresenceUnavailable: case whatsapp.PresenceUnavailable:
@ -1023,33 +1023,31 @@ func (user *User) HandlePresence(info whatsappExt.Presence) {
} }
} }
func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) { func (user *User) HandleMsgInfo(info whatsapp.JSONMsgInfo) {
if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead { if (info.Command == whatsapp.MsgInfoCommandAck || info.Command == whatsapp.MsgInfoCommandAcks) && info.Acknowledgement == whatsapp.AckMessageRead {
portal := user.GetPortalByJID(info.ToJID) portal := user.GetPortalByJID(info.ToJID)
if len(portal.MXID) == 0 { if len(portal.MXID) == 0 {
return return
} }
go func() { intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal) for _, msgID := range info.IDs {
for _, msgID := range info.IDs { msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID)
msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID) if msg == nil || msg.IsFakeMXID() {
if msg == nil || msg.IsFakeMXID() { continue
continue
}
err := intent.MarkRead(portal.MXID, msg.MXID)
if err != nil {
user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
}
} }
}()
err := intent.MarkRead(portal.MXID, msg.MXID)
if err != nil {
user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
}
}
} }
} }
func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) { func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) {
if received.Type == "read" { if received.Type == "read" {
user.markSelfRead(received.Jid, received.Index) go user.markSelfRead(received.Jid, received.Index)
} else { } else {
user.log.Debugfln("Unknown received message type: %+v", received) user.log.Debugfln("Unknown received message type: %+v", received)
} }
@ -1057,12 +1055,12 @@ func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) {
func (user *User) HandleReadMessage(read whatsapp.ReadMessage) { func (user *User) HandleReadMessage(read whatsapp.ReadMessage) {
user.log.Debugfln("Received chat read message: %+v", read) user.log.Debugfln("Received chat read message: %+v", read)
user.markSelfRead(read.Jid, "") go user.markSelfRead(read.Jid, "")
} }
func (user *User) markSelfRead(jid, messageID string) { func (user *User) markSelfRead(jid, messageID string) {
if strings.HasSuffix(jid, whatsappExt.OldUserSuffix) { if strings.HasSuffix(jid, whatsapp.OldUserSuffix) {
jid = strings.Replace(jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1) jid = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
} }
puppet := user.bridge.GetPuppetByJID(user.JID) puppet := user.bridge.GetPuppetByJID(user.JID)
if puppet == nil { if puppet == nil {
@ -1096,17 +1094,17 @@ func (user *User) markSelfRead(jid, messageID string) {
} }
} }
func (user *User) HandleCommand(cmd whatsappExt.Command) { func (user *User) HandleCommand(cmd whatsapp.JSONCommand) {
switch cmd.Type { switch cmd.Type {
case whatsappExt.CommandPicture: case whatsapp.CommandPicture:
if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) { if strings.HasSuffix(cmd.JID, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(cmd.JID) puppet := user.bridge.GetPuppetByJID(cmd.JID)
go puppet.UpdateAvatar(user, cmd.ProfilePicInfo) go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
} else if user.bridge.Config.Bridge.ChatMetaSync { } else if user.bridge.Config.Bridge.ChatMetaSync {
portal := user.GetPortalByJID(cmd.JID) portal := user.GetPortalByJID(cmd.JID)
go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true) go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true)
} }
case whatsappExt.CommandDisconnect: case whatsapp.CommandDisconnect:
if cmd.Kind == "replaced" { if cmd.Kind == "replaced" {
user.cleanDisconnection = true user.cleanDisconnection = true
go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" + go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" +
@ -1119,14 +1117,14 @@ func (user *User) HandleCommand(cmd whatsappExt.Command) {
} }
} }
func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) { func (user *User) HandleChatUpdate(cmd whatsapp.ChatUpdate) {
if cmd.Command != whatsappExt.ChatUpdateCommandAction { if cmd.Command != whatsapp.ChatUpdateCommandAction {
return return
} }
portal := user.GetPortalByJID(cmd.JID) portal := user.GetPortalByJID(cmd.JID)
if len(portal.MXID) == 0 { if len(portal.MXID) == 0 {
if cmd.Data.Action == whatsappExt.ChatActionIntroduce || cmd.Data.Action == whatsappExt.ChatActionCreate { if cmd.Data.Action == whatsapp.ChatActionIntroduce || cmd.Data.Action == whatsapp.ChatActionCreate {
go func() { go func() {
err := portal.CreateMatrixRoom(user) err := portal.CreateMatrixRoom(user)
if err != nil { if err != nil {
@ -1139,11 +1137,11 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
// These don't come down the message history :( // These don't come down the message history :(
switch cmd.Data.Action { switch cmd.Data.Action {
case whatsappExt.ChatActionAddTopic: case whatsapp.ChatActionAddTopic:
go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil,true) go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionRemoveTopic: case whatsapp.ChatActionRemoveTopic:
go portal.UpdateTopic("", cmd.Data.SenderJID, nil,true) go portal.UpdateTopic("", cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionRemove: case whatsapp.ChatActionRemove:
// We ignore leaving groups in the message history to avoid accidentally leaving rejoined groups, // We ignore leaving groups in the message history to avoid accidentally leaving rejoined groups,
// but if we get a real-time command that says we left, it should be safe to bridge it. // but if we get a real-time command that says we left, it should be safe to bridge it.
if !user.bridge.Config.Bridge.ChatMetaSync { if !user.bridge.Config.Bridge.ChatMetaSync {
@ -1162,45 +1160,35 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
} }
switch cmd.Data.Action { switch cmd.Data.Action {
case whatsappExt.ChatActionNameChange: case whatsapp.ChatActionNameChange:
go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, nil, true) go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionPromote: case whatsapp.ChatActionPromote:
go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true) go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true)
case whatsappExt.ChatActionDemote: case whatsapp.ChatActionDemote:
go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false) go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false)
case whatsappExt.ChatActionAnnounce: case whatsapp.ChatActionAnnounce:
go portal.RestrictMessageSending(cmd.Data.Announce) go portal.RestrictMessageSending(cmd.Data.Announce)
case whatsappExt.ChatActionRestrict: case whatsapp.ChatActionRestrict:
go portal.RestrictMetadataChanges(cmd.Data.Restrict) go portal.RestrictMetadataChanges(cmd.Data.Restrict)
case whatsappExt.ChatActionRemove: case whatsapp.ChatActionRemove:
go portal.HandleWhatsAppKick(nil, cmd.Data.SenderJID, cmd.Data.UserChange.JIDs) go portal.HandleWhatsAppKick(nil, cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
case whatsappExt.ChatActionAdd: case whatsapp.ChatActionAdd:
go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs) go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs)
case whatsappExt.ChatActionIntroduce: case whatsapp.ChatActionIntroduce:
if cmd.Data.SenderJID != "unknown" { if cmd.Data.SenderJID != "unknown" {
go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID}) go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID})
} }
} }
} }
func (user *User) HandleJsonMessage(message string) { func (user *User) HandleJSONMessage(message json.RawMessage) {
var msg json.RawMessage if !json.Valid(message) {
err := json.Unmarshal([]byte(message), &msg)
if err != nil {
return return
} }
user.log.Debugln("JSON message:", message) user.log.Debugfln("JSON message: %s", message)
user.updateLastConnectionIfNecessary() user.updateLastConnectionIfNecessary()
} }
func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
user.updateLastConnectionIfNecessary()
}
func (user *User) HandleUnknownBinaryNode(node *waBinary.Node) {
user.log.Debugfln("Unknown binary message: %+v", node)
}
func (user *User) NeedsRelaybot(portal *Portal) bool { func (user *User) NeedsRelaybot(portal *Portal) bool {
return !user.HasSession() || !user.IsInPortal(portal.Key) return !user.HasSession() || !user.IsInPortal(portal.Key)
} }

View file

@ -1,72 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type CallInfoType string
const (
CallOffer CallInfoType = "offer"
CallOfferVideo CallInfoType = "offer_video"
CallTransport CallInfoType = "transport"
CallRelayLatency CallInfoType = "relaylatency"
CallTerminate CallInfoType = "terminate"
)
type CallInfo struct {
ID string `json:"id"`
Type CallInfoType `json:"type"`
From string `json:"from"`
Platform string `json:"platform"`
Version []int `json:"version"`
Data [][]interface{} `json:"data"`
}
type CallInfoHandler interface {
whatsapp.Handler
HandleCallInfo(CallInfo)
}
func (ext *ExtendedConn) handleMessageCall(message []byte) {
var event CallInfo
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.From = strings.Replace(event.From, OldUserSuffix, NewUserSuffix, 1)
for _, handler := range ext.handlers {
callInfoHandler, ok := handler.(CallInfoHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(callInfoHandler) {
callInfoHandler.HandleCallInfo(event)
} else {
go callInfoHandler.HandleCallInfo(event)
}
}
}

View file

@ -1,183 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type ChatUpdateCommand string
const (
ChatUpdateCommandAction ChatUpdateCommand = "action"
)
type ChatUpdate struct {
JID string `json:"id"`
Command ChatUpdateCommand `json:"cmd"`
Data ChatUpdateData `json:"data"`
}
type ChatActionType string
const (
ChatActionNameChange ChatActionType = "subject"
ChatActionAddTopic ChatActionType = "desc_add"
ChatActionRemoveTopic ChatActionType = "desc_remove"
ChatActionRestrict ChatActionType = "restrict"
ChatActionAnnounce ChatActionType = "announce"
ChatActionPromote ChatActionType = "promote"
ChatActionDemote ChatActionType = "demote"
ChatActionIntroduce ChatActionType = "introduce"
ChatActionCreate ChatActionType = "create"
ChatActionRemove ChatActionType = "remove"
ChatActionAdd ChatActionType = "add"
)
type ChatUpdateData struct {
Action ChatActionType
SenderJID string
NameChange struct {
Name string `json:"subject"`
SetAt int64 `json:"s_t"`
SetBy string `json:"s_o"`
}
AddTopic struct {
Topic string `json:"desc"`
ID string `json:"descId"`
SetAt int64 `json:"descTime"`
SetBy string `json:"descOwner"`
}
RemoveTopic struct {
ID string `json:"descId"`
}
Introduce struct {
CreationTime int64 `json:"creation"`
Admins []string `json:"admins"`
SuperAdmins []string `json:"superadmins"`
Regulars []string `json:"regulars"`
}
Restrict bool
Announce bool
UserChange struct {
JIDs []string `json:"participants"`
}
}
func (cud *ChatUpdateData) UnmarshalJSON(data []byte) error {
var arr []json.RawMessage
err := json.Unmarshal(data, &arr)
if err != nil {
return err
} else if len(arr) < 3 {
return nil
}
err = json.Unmarshal(arr[0], &cud.Action)
if err != nil {
return err
}
err = json.Unmarshal(arr[1], &cud.SenderJID)
if err != nil {
return err
}
cud.SenderJID = strings.Replace(cud.SenderJID, OldUserSuffix, NewUserSuffix, 1)
var unmarshalTo interface{}
switch cud.Action {
case ChatActionIntroduce, ChatActionCreate:
err = json.Unmarshal(arr[2], &cud.NameChange)
if err != nil {
return err
}
err = json.Unmarshal(arr[2], &cud.AddTopic)
if err != nil {
return err
}
unmarshalTo = &cud.Introduce
case ChatActionNameChange:
unmarshalTo = &cud.NameChange
case ChatActionAddTopic:
unmarshalTo = &cud.AddTopic
case ChatActionRemoveTopic:
unmarshalTo = &cud.RemoveTopic
case ChatActionRestrict:
unmarshalTo = &cud.Restrict
case ChatActionAnnounce:
unmarshalTo = &cud.Announce
case ChatActionPromote, ChatActionDemote, ChatActionRemove, ChatActionAdd:
unmarshalTo = &cud.UserChange
default:
return nil
}
err = json.Unmarshal(arr[2], unmarshalTo)
if err != nil {
return err
}
cud.NameChange.SetBy = strings.Replace(cud.NameChange.SetBy, OldUserSuffix, NewUserSuffix, 1)
for index, jid := range cud.UserChange.JIDs {
cud.UserChange.JIDs[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
for index, jid := range cud.Introduce.SuperAdmins {
cud.Introduce.SuperAdmins[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
for index, jid := range cud.Introduce.Admins {
cud.Introduce.Admins[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
for index, jid := range cud.Introduce.Regulars {
cud.Introduce.Regulars[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
return nil
}
type ChatUpdateHandler interface {
whatsapp.Handler
HandleChatUpdate(ChatUpdate)
}
func (ext *ExtendedConn) handleMessageChatUpdate(message []byte) {
var event ChatUpdate
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
for _, handler := range ext.handlers {
chatUpdateHandler, ok := handler.(ChatUpdateHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(chatUpdateHandler) {
chatUpdateHandler.HandleChatUpdate(event)
} else {
go chatUpdateHandler.HandleChatUpdate(event)
}
}
}

View file

@ -1,69 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type CommandType string
const (
CommandPicture CommandType = "picture"
CommandDisconnect CommandType = "disconnect"
)
type Command struct {
Type CommandType `json:"type"`
JID string `json:"jid"`
*ProfilePicInfo
Kind string `json:"kind"`
Raw json.RawMessage `json:"-"`
}
type CommandHandler interface {
whatsapp.Handler
HandleCommand(Command)
}
func (ext *ExtendedConn) handleMessageCommand(message []byte) {
var event Command
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.Raw = message
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
for _, handler := range ext.handlers {
commandHandler, ok := handler.(CommandHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(commandHandler) {
commandHandler.HandleCommand(event)
} else {
go commandHandler.HandleCommand(event)
}
}
}

View file

@ -1,65 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type ConnInfo struct {
ProtocolVersion []int `json:"protoVersion"`
BinaryVersion int `json:"binVersion"`
Phone struct {
WhatsAppVersion string `json:"wa_version"`
MCC string `json:"mcc"`
MNC string `json:"mnc"`
OSVersion string `json:"os_version"`
DeviceManufacturer string `json:"device_manufacturer"`
DeviceModel string `json:"device_model"`
OSBuildNumber string `json:"os_build_number"`
} `json:"phone"`
Features map[string]interface{} `json:"features"`
PushName string `json:"pushname"`
}
type ConnInfoHandler interface {
whatsapp.Handler
HandleConnInfo(ConnInfo)
}
func (ext *ExtendedConn) handleMessageConn(message []byte) {
var event ConnInfo
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
for _, handler := range ext.handlers {
connInfoHandler, ok := handler.(ConnInfoHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(connInfoHandler) {
connInfoHandler.HandleConnInfo(event)
} else {
go connInfoHandler.HandleConnInfo(event)
}
}
}

View file

@ -1,68 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"fmt"
"maunium.net/go/mautrix-whatsapp/types"
)
type CreateGroupResponse struct {
Status int `json:"status"`
GroupID types.WhatsAppID `json:"gid"`
Participants map[types.WhatsAppID]struct {
Code string `json:"code"`
} `json:"participants"`
Source string `json:"-"`
}
type actualCreateGroupResponse struct {
Status int `json:"status"`
GroupID types.WhatsAppID `json:"gid"`
Participants []map[types.WhatsAppID]struct {
Code string `json:"code"`
} `json:"participants"`
}
func (ext *ExtendedConn) CreateGroup(subject string, participants []types.WhatsAppID) (*CreateGroupResponse, error) {
respChan, err := ext.Conn.CreateGroup(subject, participants)
if err != nil {
return nil, err
}
var resp CreateGroupResponse
var actualResp actualCreateGroupResponse
resp.Source = <-respChan
fmt.Println(">>>>>>", resp.Source)
err = json.Unmarshal([]byte(resp.Source), &actualResp)
if err != nil {
return nil, err
}
resp.Status = actualResp.Status
resp.GroupID = actualResp.GroupID
resp.Participants = make(map[types.WhatsAppID]struct {
Code string `json:"code"`
})
for _, participantMap := range actualResp.Participants {
for jid, status := range participantMap {
resp.Participants[jid] = status
}
}
return &resp, nil
}

View file

@ -1,105 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type JSONMessage []json.RawMessage
type JSONMessageType string
const (
MessageMsgInfo JSONMessageType = "MsgInfo"
MessageMsg JSONMessageType = "Msg"
MessagePresence JSONMessageType = "Presence"
MessageStream JSONMessageType = "Stream"
MessageConn JSONMessageType = "Conn"
MessageProps JSONMessageType = "Props"
MessageCmd JSONMessageType = "Cmd"
MessageChat JSONMessageType = "Chat"
MessageCall JSONMessageType = "Call"
)
func (ext *ExtendedConn) HandleError(error) {}
type UnhandledJSONMessageHandler interface {
whatsapp.Handler
HandleUnhandledJSONMessage(string)
}
type JSONParseErrorHandler interface {
whatsapp.Handler
HandleJSONParseError(error)
}
func (ext *ExtendedConn) jsonParseError(err error) {
for _, handler := range ext.handlers {
errorHandler, ok := handler.(JSONParseErrorHandler)
if !ok {
continue
}
errorHandler.HandleJSONParseError(err)
}
}
func (ext *ExtendedConn) HandleJsonMessage(message string) {
msg := JSONMessage{}
err := json.Unmarshal([]byte(message), &msg)
if err != nil || len(msg) < 2 {
ext.jsonParseError(err)
return
}
var msgType JSONMessageType
json.Unmarshal(msg[0], &msgType)
switch msgType {
case MessagePresence:
ext.handleMessagePresence(msg[1])
case MessageStream:
ext.handleMessageStream(msg[1:])
case MessageConn:
ext.handleMessageConn(msg[1])
case MessageProps:
ext.handleMessageProps(msg[1])
case MessageMsgInfo, MessageMsg:
ext.handleMessageMsgInfo(msgType, msg[1])
case MessageCmd:
ext.handleMessageCommand(msg[1])
case MessageChat:
ext.handleMessageChatUpdate(msg[1])
case MessageCall:
ext.handleMessageCall(msg[1])
default:
for _, handler := range ext.handlers {
ujmHandler, ok := handler.(UnhandledJSONMessageHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(ujmHandler) {
ujmHandler.HandleUnhandledJSONMessage(message)
} else {
go ujmHandler.HandleUnhandledJSONMessage(message)
}
}
}
}

View file

@ -1,95 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type MsgInfoCommand string
const (
MsgInfoCommandAck MsgInfoCommand = "ack"
MsgInfoCommandAcks MsgInfoCommand = "acks"
)
type Acknowledgement int
const (
AckMessageSent Acknowledgement = 1
AckMessageDelivered Acknowledgement = 2
AckMessageRead Acknowledgement = 3
)
type JSONStringOrArray []string
func (jsoa *JSONStringOrArray) UnmarshalJSON(data []byte) error {
var str string
if json.Unmarshal(data, &str) == nil {
*jsoa = []string{str}
return nil
}
var strs []string
json.Unmarshal(data, &strs)
*jsoa = strs
return nil
}
type MsgInfo struct {
Command MsgInfoCommand `json:"cmd"`
IDs JSONStringOrArray `json:"id"`
Acknowledgement Acknowledgement `json:"ack"`
MessageFromJID string `json:"from"`
SenderJID string `json:"participant"`
ToJID string `json:"to"`
Timestamp int64 `json:"t"`
}
type MsgInfoHandler interface {
whatsapp.Handler
HandleMsgInfo(MsgInfo)
}
func (ext *ExtendedConn) handleMessageMsgInfo(msgType JSONMessageType, message []byte) {
var event MsgInfo
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.MessageFromJID = strings.Replace(event.MessageFromJID, OldUserSuffix, NewUserSuffix, 1)
event.SenderJID = strings.Replace(event.SenderJID, OldUserSuffix, NewUserSuffix, 1)
event.ToJID = strings.Replace(event.ToJID, OldUserSuffix, NewUserSuffix, 1)
if msgType == MessageMsg {
event.SenderJID = event.ToJID
}
for _, handler := range ext.handlers {
msgInfoHandler, ok := handler.(MsgInfoHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(msgInfoHandler) {
msgInfoHandler.HandleMsgInfo(event)
} else {
go msgInfoHandler.HandleMsgInfo(event)
}
}
}

View file

@ -1,64 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type Presence struct {
JID string `json:"id"`
SenderJID string `json:"participant"`
Status whatsapp.Presence `json:"type"`
Timestamp int64 `json:"t"`
Deny bool `json:"deny"`
}
type PresenceHandler interface {
whatsapp.Handler
HandlePresence(Presence)
}
func (ext *ExtendedConn) handleMessagePresence(message []byte) {
var event Presence
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
if len(event.SenderJID) == 0 {
event.SenderJID = event.JID
} else {
event.SenderJID = strings.Replace(event.SenderJID, OldUserSuffix, NewUserSuffix, 1)
}
for _, handler := range ext.handlers {
presenceHandler, ok := handler.(PresenceHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(presenceHandler) {
presenceHandler.HandlePresence(event)
} else {
go presenceHandler.HandlePresence(event)
}
}
}

View file

@ -1,73 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type ProtocolProps struct {
WebPresence bool `json:"webPresence"`
NotificationQuery bool `json:"notificationQuery"`
FacebookCrashLog bool `json:"fbCrashlog"`
Bucket string `json:"bucket"`
GIFSearch string `json:"gifSearch"`
Spam bool `json:"SPAM"`
SetBlock bool `json:"SET_BLOCK"`
MessageInfo bool `json:"MESSAGE_INFO"`
MaxFileSize int `json:"maxFileSize"`
Media int `json:"media"`
GroupNameLength int `json:"maxSubject"`
GroupDescriptionLength int `json:"groupDescLength"`
MaxParticipants int `json:"maxParticipants"`
VideoMaxEdge int `json:"videoMaxEdge"`
ImageMaxEdge int `json:"imageMaxEdge"`
ImageMaxKilobytes int `json:"imageMaxKBytes"`
Edit int `json:"edit"`
FwdUIStartTimestamp int `json:"fwdUiStartTs"`
GroupsV3 int `json:"groupsV3"`
RestrictGroups int `json:"restrictGroups"`
AnnounceGroups int `json:"announceGroups"`
}
type ProtocolPropsHandler interface {
whatsapp.Handler
HandleProtocolProps(ProtocolProps)
}
func (ext *ExtendedConn) handleMessageProps(message []byte) {
var event ProtocolProps
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
for _, handler := range ext.handlers {
protocolPropsHandler, ok := handler.(ProtocolPropsHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(protocolPropsHandler) {
protocolPropsHandler.HandleProtocolProps(event)
} else {
go protocolPropsHandler.HandleProtocolProps(event)
}
}
}

View file

@ -1,59 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"github.com/Rhymen/go-whatsapp"
"github.com/Rhymen/go-whatsapp/binary/proto"
)
type MessageRevokeHandler interface {
whatsapp.Handler
HandleMessageRevoke(key MessageRevocation)
}
type MessageRevocation struct {
Id string
RemoteJid string
FromMe bool
Participant string
}
func (ext *ExtendedConn) HandleRawMessage(message *proto.WebMessageInfo) {
protoMsg := message.GetMessage().GetProtocolMessage()
if protoMsg != nil && protoMsg.GetType() == proto.ProtocolMessage_REVOKE {
key := protoMsg.GetKey()
deletedMessage := MessageRevocation{
Id: key.GetId(),
RemoteJid: key.GetRemoteJid(),
FromMe: key.GetFromMe(),
Participant: key.GetParticipant(),
}
for _, handler := range ext.handlers {
mrHandler, ok := handler.(MessageRevokeHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(mrHandler) {
mrHandler.HandleMessageRevoke(deletedMessage)
} else {
go mrHandler.HandleMessageRevoke(deletedMessage)
}
}
}
}

View file

@ -1,76 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type StreamType string
const (
StreamUpdate = "update"
StreamSleep = "asleep"
)
type StreamEvent struct {
Type StreamType
IsOutdated bool
Version string
Extra []json.RawMessage
}
type StreamEventHandler interface {
whatsapp.Handler
HandleStreamEvent(StreamEvent)
}
func (ext *ExtendedConn) handleMessageStream(message []json.RawMessage) {
var event StreamEvent
err := json.Unmarshal(message[0], &event.Type)
if err != nil {
ext.jsonParseError(err)
return
}
if event.Type == StreamUpdate && len(message) >= 3 {
_ = json.Unmarshal(message[1], &event.IsOutdated)
_ = json.Unmarshal(message[2], &event.Version)
if len(message) >= 4 {
event.Extra = message[3:]
}
} else if len(message) >= 2 {
event.Extra = message[1:]
}
for _, handler := range ext.handlers {
streamHandler, ok := handler.(StreamEventHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(streamHandler) {
streamHandler.HandleStreamEvent(event)
} else {
go streamHandler.HandleStreamEvent(event)
}
}
}

View file

@ -1,164 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/Rhymen/go-whatsapp"
)
const (
OldUserSuffix = "@c.us"
NewUserSuffix = "@s.whatsapp.net"
)
type ExtendedConn struct {
*whatsapp.Conn
handlers []whatsapp.Handler
}
func ExtendConn(conn *whatsapp.Conn) *ExtendedConn {
ext := &ExtendedConn{
Conn: conn,
}
ext.Conn.AddHandler(ext)
return ext
}
func (ext *ExtendedConn) AddHandler(handler whatsapp.Handler) {
ext.Conn.AddHandler(handler)
ext.handlers = append(ext.handlers, handler)
}
func (ext *ExtendedConn) RemoveHandler(handler whatsapp.Handler) bool {
ext.Conn.RemoveHandler(handler)
for i, v := range ext.handlers {
if v == handler {
ext.handlers = append(ext.handlers[:i], ext.handlers[i+1:]...)
return true
}
}
return false
}
func (ext *ExtendedConn) RemoveHandlers() {
ext.Conn.RemoveHandlers()
ext.handlers = make([]whatsapp.Handler, 0)
}
func (ext *ExtendedConn) shouldCallSynchronously(handler whatsapp.Handler) bool {
sh, ok := handler.(whatsapp.SyncHandler)
return ok && sh.ShouldCallSynchronously()
}
func (ext *ExtendedConn) ShouldCallSynchronously() bool {
return true
}
type GroupInfo struct {
JID string `json:"jid"`
OwnerJID string `json:"owner"`
Name string `json:"subject"`
NameSetTime int64 `json:"subjectTime"`
NameSetBy string `json:"subjectOwner"`
Announce bool `json:"announce"` // Can only admins send messages?
Topic string `json:"desc"`
TopicID string `json:"descId"`
TopicSetAt int64 `json:"descTime"`
TopicSetBy string `json:"descOwner"`
GroupCreated int64 `json:"creation"`
Status int16 `json:"status"`
Participants []struct {
JID string `json:"id"`
IsAdmin bool `json:"isAdmin"`
IsSuperAdmin bool `json:"isSuperAdmin"`
} `json:"participants"`
}
func (ext *ExtendedConn) GetGroupMetaData(jid string) (*GroupInfo, error) {
data, err := ext.Conn.GetGroupMetaData(jid)
if err != nil {
return nil, fmt.Errorf("failed to get group metadata: %v", err)
}
content := <-data
info := &GroupInfo{}
err = json.Unmarshal([]byte(content), info)
if err != nil {
return info, fmt.Errorf("failed to unmarshal group metadata: %v", err)
}
for index, participant := range info.Participants {
info.Participants[index].JID = strings.Replace(participant.JID, OldUserSuffix, NewUserSuffix, 1)
}
info.NameSetBy = strings.Replace(info.NameSetBy, OldUserSuffix, NewUserSuffix, 1)
info.TopicSetBy = strings.Replace(info.TopicSetBy, OldUserSuffix, NewUserSuffix, 1)
return info, nil
}
type ProfilePicInfo struct {
URL string `json:"eurl"`
Tag string `json:"tag"`
Status int `json:"status"`
}
func (ppi *ProfilePicInfo) Download() (io.ReadCloser, error) {
resp, err := http.Get(ppi.URL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
func (ppi *ProfilePicInfo) DownloadBytes() ([]byte, error) {
body, err := ppi.Download()
if err != nil {
return nil, err
}
defer body.Close()
data, err := ioutil.ReadAll(body)
return data, err
}
func (ext *ExtendedConn) GetProfilePicThumb(jid string) (*ProfilePicInfo, error) {
data, err := ext.Conn.GetProfilePicThumb(jid)
if err != nil {
return nil, fmt.Errorf("failed to get avatar: %v", err)
}
content := <-data
info := &ProfilePicInfo{}
err = json.Unmarshal([]byte(content), info)
if err != nil {
return info, fmt.Errorf("failed to unmarshal avatar info: %v", err)
}
return info, nil
}