mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-12-14 17:33:48 +01:00
Merge pull request #457 from mautrix/sumner/bri-2349
segment: add for provisioning API
This commit is contained in:
commit
4fd07a2646
5 changed files with 122 additions and 7 deletions
|
@ -46,6 +46,7 @@ type Config struct {
|
|||
Provisioning struct {
|
||||
Prefix string `yaml:"prefix"`
|
||||
SharedSecret string `yaml:"shared_secret"`
|
||||
SegmentKey string `yaml:"segment_key"`
|
||||
} `yaml:"provisioning"`
|
||||
|
||||
ID string `yaml:"id"`
|
||||
|
|
|
@ -54,6 +54,7 @@ func (helper *UpgradeHelper) doUpgrade() {
|
|||
} else {
|
||||
helper.Copy(Str, "appservice", "provisioning", "shared_secret")
|
||||
}
|
||||
helper.Copy(Str|Null, "appservice", "provisioning", "segment_key")
|
||||
helper.Copy(Str, "appservice", "id")
|
||||
helper.Copy(Str, "appservice", "bot", "username")
|
||||
helper.Copy(Str, "appservice", "bot", "displayname")
|
||||
|
|
|
@ -48,6 +48,11 @@ appservice:
|
|||
# Shared secret for authentication. If set to "generate", a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled.
|
||||
shared_secret: generate
|
||||
# Segment API key to enable analytics tracking for web server
|
||||
# endpoints. Set to null to disable.
|
||||
# Currently the only events are login start, QR code retrieve, and login
|
||||
# success/failure.
|
||||
segment_key: null
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: whatsapp
|
||||
|
|
|
@ -43,10 +43,15 @@ import (
|
|||
type ProvisioningAPI struct {
|
||||
bridge *Bridge
|
||||
log log.Logger
|
||||
segment *Segment
|
||||
}
|
||||
|
||||
func (prov *ProvisioningAPI) Init() {
|
||||
prov.log = prov.bridge.Log.Sub("Provisioning")
|
||||
|
||||
// Set up segment
|
||||
prov.segment = NewSegment(prov.bridge.Config.AppService.Provisioning.SegmentKey, prov.log)
|
||||
|
||||
prov.log.Debugln("Enabling provisioning API at", prov.bridge.Config.AppService.Provisioning.Prefix)
|
||||
r := prov.bridge.AS.Router.PathPrefix(prov.bridge.Config.AppService.Provisioning.Prefix).Subrouter()
|
||||
r.Use(prov.AuthMiddleware)
|
||||
|
@ -490,6 +495,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
user.log.Debugln("Started login via provisioning API")
|
||||
prov.segment.Track(user.MXID, "$login_start")
|
||||
|
||||
for {
|
||||
select {
|
||||
|
@ -498,6 +504,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
|
|||
case whatsmeow.QRChannelSuccess.Event:
|
||||
jid := user.Client.Store.ID
|
||||
user.log.Debugln("Successful login as", jid, "via provisioning API")
|
||||
prov.segment.Track(user.MXID, "$login_success")
|
||||
_ = c.WriteJSON(map[string]interface{}{
|
||||
"success": true,
|
||||
"jid": jid,
|
||||
|
@ -505,34 +512,45 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
case whatsmeow.QRChannelTimeout.Event:
|
||||
user.log.Debugln("Login via provisioning API timed out")
|
||||
errCode := "login timed out"
|
||||
prov.segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
|
||||
_ = c.WriteJSON(Error{
|
||||
Error: "QR code scan timed out. Please try again.",
|
||||
ErrCode: "login timed out",
|
||||
ErrCode: errCode,
|
||||
})
|
||||
case whatsmeow.QRChannelErrUnexpectedEvent.Event:
|
||||
user.log.Debugln("Login via provisioning API failed due to unexpected event")
|
||||
errCode := "unexpected event"
|
||||
prov.segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
|
||||
_ = c.WriteJSON(Error{
|
||||
Error: "Got unexpected event while waiting for QRs, perhaps you're already logged in?",
|
||||
ErrCode: "unexpected event",
|
||||
ErrCode: errCode,
|
||||
})
|
||||
case whatsmeow.QRChannelClientOutdated.Event:
|
||||
user.log.Debugln("Login via provisioning API failed due to outdated client")
|
||||
errCode := "bridge outdated"
|
||||
prov.segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
|
||||
_ = c.WriteJSON(Error{
|
||||
Error: "Got client outdated error while waiting for QRs. The bridge must be updated to continue.",
|
||||
ErrCode: "bridge outdated",
|
||||
ErrCode: errCode,
|
||||
})
|
||||
case whatsmeow.QRChannelScannedWithoutMultidevice.Event:
|
||||
errCode := "multidevice not enabled"
|
||||
prov.segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
|
||||
_ = c.WriteJSON(Error{
|
||||
Error: "Please enable the WhatsApp multidevice beta and scan the QR code again.",
|
||||
ErrCode: "multidevice not enabled",
|
||||
ErrCode: errCode,
|
||||
})
|
||||
continue
|
||||
case "error":
|
||||
errCode := "fatal error"
|
||||
prov.segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
|
||||
_ = c.WriteJSON(Error{
|
||||
Error: "Fatal error while logging in",
|
||||
ErrCode: "fatal error",
|
||||
ErrCode: errCode,
|
||||
})
|
||||
case "code":
|
||||
prov.segment.Track(user.MXID, "$qrcode_retrieved")
|
||||
_ = c.WriteJSON(map[string]interface{}{
|
||||
"code": evt.Code,
|
||||
"timeout": int(evt.Timeout.Seconds()),
|
||||
|
|
90
segment.go
Normal file
90
segment.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
||||
// Copyright (C) 2022 Tulir Asokan, Sumner Evans
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
const SegmentURL = "https://api.segment.io/v1/track"
|
||||
|
||||
type Segment struct {
|
||||
segmentKey string
|
||||
log log.Logger
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewSegment(segmentKey string, parentLogger log.Logger) *Segment {
|
||||
return &Segment{
|
||||
segmentKey: segmentKey,
|
||||
log: parentLogger.Sub("Segment"),
|
||||
client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (segment *Segment) track(userID id.UserID, event string, properties map[string]interface{}) error {
|
||||
data := map[string]interface{}{
|
||||
"userId": userID,
|
||||
"event": event,
|
||||
"properties": properties,
|
||||
}
|
||||
json_data, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", SegmentURL, bytes.NewBuffer(json_data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.SetBasicAuth(segment.segmentKey, "")
|
||||
resp, err := segment.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (segment *Segment) Track(userID id.UserID, event string, properties ...map[string]interface{}) {
|
||||
if segment.segmentKey == "" {
|
||||
return
|
||||
}
|
||||
if len(properties) > 1 {
|
||||
segment.log.Fatalf("Track should be called with at most one property map")
|
||||
}
|
||||
|
||||
go (func() error {
|
||||
props := map[string]interface{}{}
|
||||
if len(properties) > 0 {
|
||||
props = properties[0]
|
||||
}
|
||||
props["bridge"] = "whatsapp"
|
||||
err := segment.track(userID, event, props)
|
||||
if err != nil {
|
||||
segment.log.Errorf("Error tracking %s: %v+", event, err)
|
||||
return err
|
||||
}
|
||||
segment.log.Debug("Tracked ", event)
|
||||
return nil
|
||||
})()
|
||||
}
|
Loading…
Reference in a new issue