Merge branch 'tulir/google-account-pairing'
This commit is contained in:
commit
00488409ed
43 changed files with 4743 additions and 662 deletions
|
@ -27,6 +27,7 @@ const (
|
|||
GMListenError status.BridgeStateErrorCode = "gm-listen-error"
|
||||
GMFatalError status.BridgeStateErrorCode = "gm-listen-fatal-error"
|
||||
GMUnpaired status.BridgeStateErrorCode = "gm-unpaired"
|
||||
GMUnpairedGaia status.BridgeStateErrorCode = "gm-unpaired-gaia"
|
||||
GMUnpaired404 status.BridgeStateErrorCode = "gm-unpaired-entity-not-found"
|
||||
GMNotConnected status.BridgeStateErrorCode = "gm-not-connected"
|
||||
GMConnecting status.BridgeStateErrorCode = "gm-connecting"
|
||||
|
@ -49,12 +50,13 @@ func init() {
|
|||
GMFatalError: "Fatal error polling messages from Google Messages server, please re-link the bridge",
|
||||
GMUnpaired: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS",
|
||||
GMUnpaired404: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS",
|
||||
GMUnpairedGaia: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS",
|
||||
GMNotDefaultSMSApp: "Google Messages isn't set as the default SMS app. Please set the default SMS app on your Android phone to Google Messages to continue using SMS/RCS.",
|
||||
GMBrowserInactive: "Google Messages opened in another browser",
|
||||
GMBrowserInactiveTimeout: "Google Messages disconnected due to timeout",
|
||||
GMBrowserInactiveInactivity: "Google Messages disconnected due to inactivity",
|
||||
GMPhoneNotResponding: "Your phone is not responding, please check that it is connected to the internet. You may need to open the Messages app on your phone for it to reconnect.",
|
||||
GMSwitchedToGoogleLogin: "Please switch to the QR code pairing method in the Google Messages app to continue using SMS/RCS",
|
||||
GMSwitchedToGoogleLogin: "You switched to Google account pairing, please log in to continue using SMS/RCS",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
73
commands.go
73
commands.go
|
@ -17,6 +17,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -40,7 +41,8 @@ type WrappedCommandEvent struct {
|
|||
func (br *GMBridge) RegisterCommands() {
|
||||
proc := br.CommandProcessor.(*commands.Processor)
|
||||
proc.AddHandlers(
|
||||
cmdLogin,
|
||||
cmdLoginQR,
|
||||
cmdLoginGoogle,
|
||||
cmdDeleteSession,
|
||||
cmdLogout,
|
||||
cmdReconnect,
|
||||
|
@ -70,16 +72,17 @@ var (
|
|||
HelpSectionPortalManagement = commands.HelpSection{Name: "Portal management", Order: 20}
|
||||
)
|
||||
|
||||
var cmdLogin = &commands.FullHandler{
|
||||
Func: wrapCommand(fnLogin),
|
||||
Name: "login",
|
||||
var cmdLoginQR = &commands.FullHandler{
|
||||
Func: wrapCommand(fnLoginQR),
|
||||
Name: "login-qr",
|
||||
Aliases: []string{"login"},
|
||||
Help: commands.HelpMeta{
|
||||
Section: commands.HelpSectionAuth,
|
||||
Description: "Link the bridge to Google Messages on your Android phone as a web client.",
|
||||
Description: "Link the bridge to Google Messages on your Android phone by scanning a QR code.",
|
||||
},
|
||||
}
|
||||
|
||||
func fnLogin(ce *WrappedCommandEvent) {
|
||||
func fnLoginQR(ce *WrappedCommandEvent) {
|
||||
if ce.User.Session != nil {
|
||||
if ce.User.IsConnected() {
|
||||
ce.Reply("You're already logged in")
|
||||
|
@ -123,6 +126,64 @@ func fnLogin(ce *WrappedCommandEvent) {
|
|||
ce.ZLog.Trace().Msg("Login command finished")
|
||||
}
|
||||
|
||||
var cmdLoginGoogle = &commands.FullHandler{
|
||||
Func: wrapCommand(fnLoginGoogle),
|
||||
Name: "login-google",
|
||||
Aliases: []string{"login"},
|
||||
Help: commands.HelpMeta{
|
||||
Section: commands.HelpSectionAuth,
|
||||
Description: "Link the bridge to Google Messages on your Android phone by logging in with your Google account.",
|
||||
},
|
||||
}
|
||||
|
||||
func fnLoginGoogle(ce *WrappedCommandEvent) {
|
||||
if ce.User.Session != nil {
|
||||
if ce.User.IsConnected() {
|
||||
ce.Reply("You're already logged in")
|
||||
} else {
|
||||
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
|
||||
}
|
||||
return
|
||||
} else if ce.User.pairSuccessChan != nil {
|
||||
ce.Reply("You already have a login in progress")
|
||||
return
|
||||
}
|
||||
ce.User.CommandState = &commands.CommandState{
|
||||
Next: commands.MinimalHandlerFunc(wrapCommand(fnLoginGoogleCookies)),
|
||||
Action: "Login",
|
||||
}
|
||||
ce.Reply("Send your Google cookies here, formatted as a key-value JSON object (see <https://docs.mau.fi/bridges/go/gmessages/authentication.html> for details)")
|
||||
}
|
||||
|
||||
func fnLoginGoogleCookies(ce *WrappedCommandEvent) {
|
||||
ce.User.CommandState = nil
|
||||
if ce.User.Session != nil {
|
||||
if ce.User.IsConnected() {
|
||||
ce.Reply("You're already logged in")
|
||||
} else {
|
||||
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
|
||||
}
|
||||
return
|
||||
} else if ce.User.pairSuccessChan != nil {
|
||||
ce.Reply("You already have a login in progress")
|
||||
return
|
||||
}
|
||||
var cookies map[string]string
|
||||
err := json.Unmarshal([]byte(ce.RawArgs), &cookies)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to parse cookies: %v", err)
|
||||
return
|
||||
}
|
||||
err = ce.User.LoginGoogle(cookies, func(emoji string) {
|
||||
ce.Reply(emoji)
|
||||
})
|
||||
if err != nil {
|
||||
ce.Reply("Login failed: %v", err)
|
||||
} else {
|
||||
ce.Reply("Login successful")
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) sendQREdit(ce *WrappedCommandEvent, content *event.MessageEventContent, prevEvent id.EventID) id.EventID {
|
||||
if len(prevEvent) != 0 {
|
||||
content.SetEdit(prevEvent)
|
||||
|
|
16
go.mod
16
go.mod
|
@ -1,17 +1,17 @@
|
|||
module go.mau.fi/mautrix-gmessages
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.3
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
go.mau.fi/mautrix-gmessages/libgm v0.2.2
|
||||
go.mau.fi/util v0.2.1
|
||||
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611
|
||||
google.golang.org/protobuf v1.31.0
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
|
||||
google.golang.org/protobuf v1.32.0
|
||||
maunium.net/go/maulogger/v2 v2.4.1
|
||||
maunium.net/go/mautrix v0.16.2
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ require (
|
|||
require (
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
|
@ -32,9 +32,9 @@ require (
|
|||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/yuin/goldmark v1.6.0 // indirect
|
||||
go.mau.fi/zeroconfig v0.1.2 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
|
36
go.sum
36
go.sum
|
@ -1,17 +1,17 @@
|
|||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
|
@ -30,20 +30,22 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
|
@ -59,21 +61,19 @@ go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
|
|||
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
|
||||
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
|
||||
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4=
|
||||
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -34,6 +33,18 @@ type AuthData struct {
|
|||
TachyonTTL int64 `json:"tachyon_ttl,omitempty"`
|
||||
// Unknown encryption key, not used for anything
|
||||
WebEncryptionKey []byte `json:"web_encryption_key,omitempty"`
|
||||
|
||||
SessionID uuid.UUID `json:"session_id,omitempty"`
|
||||
DestRegID uuid.UUID `json:"dest_reg_id,omitempty"`
|
||||
PairingID uuid.UUID `json:"pairing_id,omitempty"`
|
||||
Cookies map[string]string `json:"cookies,omitempty"`
|
||||
}
|
||||
|
||||
func (ad *AuthData) AuthNetwork() string {
|
||||
if ad.Cookies != nil {
|
||||
return util.GoogleNetwork
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const RefreshTachyonBuffer = 1 * time.Hour
|
||||
|
@ -64,6 +75,7 @@ type Client struct {
|
|||
conversationsFetchedOnce bool
|
||||
|
||||
AuthData *AuthData
|
||||
cfg *gmproto.Config
|
||||
|
||||
proxy Proxy
|
||||
http *http.Client
|
||||
|
@ -89,9 +101,15 @@ func NewClient(authData *AuthData, logger zerolog.Logger) *Client {
|
|||
pingShortCircuit: make(chan struct{}),
|
||||
}
|
||||
sessionHandler.client = cli
|
||||
err := cli.FetchConfigVersion()
|
||||
var err error
|
||||
cli.cfg, err = cli.FetchConfig()
|
||||
if err != nil {
|
||||
cli.Logger.Warn().Err(err).Msg("Failed to fetch latest web version")
|
||||
cli.Logger.Err(err).Msg("Failed to fetch web config")
|
||||
} else if deviceID := cli.cfg.GetDeviceInfo().GetDeviceID(); deviceID != "" {
|
||||
authData.SessionID, err = uuid.Parse(deviceID)
|
||||
if err != nil {
|
||||
cli.Logger.Err(err).Str("device_id", deviceID).Msg("Failed to parse device ID")
|
||||
}
|
||||
}
|
||||
return cli
|
||||
}
|
||||
|
@ -130,11 +148,11 @@ func (c *Client) Connect() error {
|
|||
return fmt.Errorf("failed to refresh auth token: %w", err)
|
||||
}
|
||||
|
||||
webEncryptionKeyResponse, err := c.GetWebEncryptionKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get web encryption key: %w", err)
|
||||
}
|
||||
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
||||
//webEncryptionKeyResponse, err := c.GetWebEncryptionKey()
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("failed to get web encryption key: %w", err)
|
||||
//}
|
||||
//c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
||||
go c.doLongPoll(true)
|
||||
c.sessionHandler.startAckInterval()
|
||||
go c.postConnect()
|
||||
|
@ -150,6 +168,10 @@ func (c *Client) postConnect() {
|
|||
})
|
||||
return
|
||||
}
|
||||
if c.AuthData.Mobile.Network != util.QRNetwork {
|
||||
// Don't check bugle default unless using bugle
|
||||
return
|
||||
}
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
go func() {
|
||||
|
@ -166,7 +188,6 @@ func (c *Client) postConnect() {
|
|||
return
|
||||
}
|
||||
c.Logger.Debug().Bool("bugle_default", bugleRes.Success).Msg("Got is bugle default response on connect")
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) Disconnect() {
|
||||
|
@ -200,25 +221,29 @@ func (c *Client) triggerEvent(evt interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) FetchConfigVersion() error {
|
||||
func (c *Client) FetchConfig() (*gmproto.Config, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, util.ConfigURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare request: %w", err)
|
||||
return nil, fmt.Errorf("failed to prepare request: %w", err)
|
||||
}
|
||||
util.BuildRelayHeaders(req, "", "*/*")
|
||||
req.Header.Set("sec-fetch-site", "same-origin")
|
||||
req.Header.Del("x-user-agent")
|
||||
req.Header.Del("origin")
|
||||
c.AddCookieHeaders(req)
|
||||
|
||||
configRes, err := c.http.Do(req)
|
||||
resp, err := c.http.Do(req)
|
||||
if resp != nil {
|
||||
c.HandleCookieUpdates(resp)
|
||||
}
|
||||
config, err := typedHTTPResponse[*gmproto.Config](resp, err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseBody, err := io.ReadAll(configRes.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
version, parseErr := util.ParseConfigVersion(responseBody)
|
||||
version, parseErr := config.ParsedClientVersion()
|
||||
if parseErr != nil {
|
||||
return fmt.Errorf("failed to parse response body: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse client version: %w", err)
|
||||
}
|
||||
|
||||
currVersion := util.ConfigMessage
|
||||
|
@ -228,7 +253,8 @@ func (c *Client) FetchConfigVersion() error {
|
|||
} else {
|
||||
c.Logger.Debug().Any("version", currVersion).Msg("Using latest messages for web version")
|
||||
}
|
||||
return nil
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Client) diffVersionFormat(curr *gmproto.ConfigVersion, latest *gmproto.ConfigVersion) string {
|
||||
|
@ -240,17 +266,17 @@ func (c *Client) updateWebEncryptionKey(key []byte) {
|
|||
c.AuthData.WebEncryptionKey = key
|
||||
}
|
||||
|
||||
func (c *Client) updateTachyonAuthToken(t []byte, validFor int64) {
|
||||
c.AuthData.TachyonAuthToken = t
|
||||
validForDuration := time.Duration(validFor) * time.Microsecond
|
||||
func (c *Client) updateTachyonAuthToken(data *gmproto.TokenData) {
|
||||
c.AuthData.TachyonAuthToken = data.GetTachyonAuthToken()
|
||||
validForDuration := time.Duration(data.GetTTL()) * time.Microsecond
|
||||
if validForDuration == 0 {
|
||||
validForDuration = 24 * time.Hour
|
||||
}
|
||||
c.AuthData.TachyonExpiry = time.Now().UTC().Add(time.Microsecond * time.Duration(validFor))
|
||||
c.AuthData.TachyonExpiry = time.Now().UTC().Add(validForDuration)
|
||||
c.AuthData.TachyonTTL = validForDuration.Microseconds()
|
||||
c.Logger.Debug().
|
||||
Time("tachyon_expiry", c.AuthData.TachyonExpiry).
|
||||
Int64("valid_for", validFor).
|
||||
Int64("valid_for", data.GetTTL()).
|
||||
Msg("Updated tachyon token")
|
||||
}
|
||||
|
||||
|
@ -275,6 +301,7 @@ func (c *Client) refreshAuthToken() error {
|
|||
MessageAuth: &gmproto.AuthMessage{
|
||||
RequestID: requestID,
|
||||
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||
Network: c.AuthData.AuthNetwork(),
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
CurrBrowserDevice: c.AuthData.Browser,
|
||||
|
@ -291,14 +318,11 @@ func (c *Client) refreshAuthToken() error {
|
|||
return err
|
||||
}
|
||||
|
||||
token := resp.GetTokenData().GetTachyonAuthToken()
|
||||
if token == nil {
|
||||
if resp.GetTokenData().GetTachyonAuthToken() == nil {
|
||||
return fmt.Errorf("no tachyon auth token in refresh response")
|
||||
}
|
||||
|
||||
validFor, _ := strconv.ParseInt(resp.GetTokenData().GetValidFor(), 10, 64)
|
||||
|
||||
c.updateTachyonAuthToken(token, validFor)
|
||||
c.updateTachyonAuthToken(resp.GetTokenData())
|
||||
c.triggerEvent(&events.AuthTokenRefreshed{})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package libgm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
@ -18,6 +19,7 @@ type IncomingRPCMessage struct {
|
|||
IsOld bool
|
||||
|
||||
Pair *gmproto.RPCPairData
|
||||
Gaia *gmproto.RPCGaiaData
|
||||
|
||||
Message *gmproto.RPCMessageData
|
||||
DecryptedData []byte
|
||||
|
@ -57,6 +59,15 @@ func (c *Client) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*Inco
|
|||
Msg("Errored pair event content")
|
||||
return nil, fmt.Errorf("failed to decode pair event: %w", err)
|
||||
}
|
||||
case gmproto.BugleRoute_GaiaEvent:
|
||||
msg.Gaia = &gmproto.RPCGaiaData{}
|
||||
err := proto.Unmarshal(data.GetMessageData(), msg.Gaia)
|
||||
if err != nil {
|
||||
c.Logger.Trace().
|
||||
Str("data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
||||
Msg("Errored gaia event content")
|
||||
return nil, fmt.Errorf("failed to decode gaia event: %w", err)
|
||||
}
|
||||
case gmproto.BugleRoute_DataEvent:
|
||||
msg.Message = &gmproto.RPCMessageData{}
|
||||
err := proto.Unmarshal(data.GetMessageData(), msg.Message)
|
||||
|
@ -176,6 +187,8 @@ func (c *Client) HandleRPCMsg(rawMsg *gmproto.IncomingRPCMessage) {
|
|||
switch msg.BugleRoute {
|
||||
case gmproto.BugleRoute_PairEvent:
|
||||
c.handlePairingEvent(msg)
|
||||
case gmproto.BugleRoute_GaiaEvent:
|
||||
c.handleGaiaPairingEvent(msg)
|
||||
case gmproto.BugleRoute_DataEvent:
|
||||
if c.skipCount > 0 {
|
||||
c.skipCount--
|
||||
|
@ -191,9 +204,15 @@ type WrappedMessage struct {
|
|||
Data []byte
|
||||
}
|
||||
|
||||
var hackyLoggedOutBytes = []byte{0x72, 0x00}
|
||||
|
||||
func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) {
|
||||
switch msg.Message.Action {
|
||||
case gmproto.ActionType_GET_UPDATES:
|
||||
if msg.DecryptedData == nil && bytes.Equal(msg.Message.UnencryptedData, hackyLoggedOutBytes) {
|
||||
c.triggerEvent(&events.GaiaLoggedOut{})
|
||||
return
|
||||
}
|
||||
data, ok := msg.DecryptedMessage.(*gmproto.UpdateEvents)
|
||||
if !ok {
|
||||
c.Logger.Error().
|
||||
|
@ -256,11 +275,13 @@ func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) {
|
|||
|
||||
default:
|
||||
c.Logger.Warn().
|
||||
Str("evt_data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
|
||||
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
||||
Str("decrypted_data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
|
||||
Msg("Got unknown event type")
|
||||
}
|
||||
default:
|
||||
c.Logger.Debug().
|
||||
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
|
||||
Str("request_id", msg.Message.SessionID).
|
||||
Str("action_type", msg.Message.Action.String()).
|
||||
Bool("is_old", msg.IsOld).
|
||||
|
|
|
@ -15,6 +15,8 @@ type ClientReady struct {
|
|||
|
||||
type AuthTokenRefreshed struct{}
|
||||
|
||||
type GaiaLoggedOut struct{}
|
||||
|
||||
type AccountChange struct {
|
||||
*gmproto.AccountChangeOrSomethingEvent
|
||||
IsFake bool
|
||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -4,6 +4,7 @@ package authentication;
|
|||
option go_package = "../gmproto";
|
||||
|
||||
import "util.proto";
|
||||
import "pblite.proto";
|
||||
|
||||
enum BrowserType {
|
||||
UNKNOWN_BROWSER_TYPE = 0;
|
||||
|
@ -44,6 +45,95 @@ message ConfigVersion {
|
|||
int32 V2 = 9;
|
||||
}
|
||||
|
||||
message SignInGaiaRequest {
|
||||
message Inner {
|
||||
message DeviceID {
|
||||
int32 unknownInt1 = 1; // 3
|
||||
string deviceID = 2; // messages-web-{uuid without dashes}
|
||||
}
|
||||
message Data {
|
||||
bytes someData = 3; // maybe an encryption key?
|
||||
}
|
||||
DeviceID deviceID = 1;
|
||||
Data someData = 36 [(pblite.pblite_binary) = true];
|
||||
}
|
||||
AuthMessage authMessage = 1;
|
||||
Inner inner = 2;
|
||||
int32 unknownInt3 = 3;
|
||||
string network = 4;
|
||||
}
|
||||
|
||||
message SignInGaiaResponse {
|
||||
message Header {
|
||||
uint64 unknownInt2 = 2;
|
||||
int64 unknownTimestamp = 4;
|
||||
}
|
||||
message DeviceData {
|
||||
message DeviceWrapper {
|
||||
Device device = 1;
|
||||
}
|
||||
DeviceWrapper deviceWrapper = 1;
|
||||
repeated RPCGaiaData.UnknownContainer.Item2.Item1 unknownItems2 = 2;
|
||||
repeated RPCGaiaData.UnknownContainer.Item4 unknownItems3 = 3;
|
||||
// index 4 is some unknown field with no real data
|
||||
}
|
||||
Header header = 1;
|
||||
string maybeBrowserUUID = 2 [(pblite.pblite_binary) = true];
|
||||
DeviceData deviceData = 3;
|
||||
TokenData tokenData = 4;
|
||||
}
|
||||
|
||||
message GaiaPairingRequestContainer {
|
||||
string pairingAttemptID = 1;
|
||||
BrowserDetails browserDetails = 2;
|
||||
int64 startTimestamp = 3;
|
||||
bytes data = 4;
|
||||
}
|
||||
|
||||
message GaiaPairingResponseContainer {
|
||||
int32 finishErrorType = 1;
|
||||
int32 finishErrorCode = 2;
|
||||
int32 unknownInt3 = 3; // For init, 1
|
||||
string sessionUUID = 4;
|
||||
bytes data = 5;
|
||||
}
|
||||
|
||||
message RevokeGaiaPairingRequest {
|
||||
string pairingAttemptID = 1;
|
||||
}
|
||||
|
||||
message RPCGaiaData {
|
||||
message UnknownContainer {
|
||||
message Item2 {
|
||||
message Item1 {
|
||||
string destOrSourceUUID = 1 [(pblite.pblite_binary) = true];
|
||||
int32 unknownInt4 = 4; // 1 for destination device, 6 for local device?
|
||||
string languageCode = 5;
|
||||
uint64 unknownBigInt7 = 7;
|
||||
}
|
||||
repeated Item1 item1 = 1;
|
||||
}
|
||||
message Item4 {
|
||||
message Item8 {
|
||||
int32 unknownInt1 = 1; // present for destination device?
|
||||
int32 unknownTimestamp = 2; // present for destination device?
|
||||
bytes unknownBytes = 3; // present for local device?
|
||||
}
|
||||
string destOrSourceUUID = 1 [(pblite.pblite_binary) = true];
|
||||
int32 unknownInt3 = 3; // 1 for destination device, 6 for local device?
|
||||
int32 unknownInt4 = 4; // always 6?
|
||||
int64 unknownTimestampMicroseconds = 7; // maybe device creation ts?
|
||||
Item8 item8 = 8 [(pblite.pblite_binary) = true];
|
||||
}
|
||||
Item2 item2 = 2;
|
||||
int64 unknownTimestampMicroseconds = 3; // pairing timestamp?
|
||||
repeated Item4 item4 = 4;
|
||||
}
|
||||
|
||||
int32 command = 1; // 9
|
||||
UnknownContainer maybeServerData = 108;
|
||||
}
|
||||
|
||||
message AuthenticationContainer {
|
||||
AuthMessage authMessage = 1;
|
||||
BrowserDetails browserDetails = 3;
|
||||
|
@ -56,8 +146,8 @@ message AuthenticationContainer {
|
|||
|
||||
message AuthMessage {
|
||||
string requestID = 1;
|
||||
optional string network = 3;
|
||||
optional bytes tachyonAuthToken = 6;
|
||||
string network = 3;
|
||||
bytes tachyonAuthToken = 6;
|
||||
ConfigVersion configVersion = 7;
|
||||
}
|
||||
|
||||
|
@ -84,25 +174,15 @@ message RegisterRefreshRequest {
|
|||
}
|
||||
|
||||
message RegisterRefreshResponse {
|
||||
message AuthKeyData {
|
||||
bytes tachyonAuthToken = 1;
|
||||
string validFor = 2;
|
||||
}
|
||||
|
||||
AuthKeyData tokenData = 2;
|
||||
TokenData tokenData = 2;
|
||||
}
|
||||
|
||||
message RegisterPhoneRelayResponse {
|
||||
message AuthKeyData {
|
||||
bytes tachyonAuthToken = 1;
|
||||
int64 validFor = 2;
|
||||
}
|
||||
|
||||
CoordinateMessage coordinates = 1;
|
||||
Device browser = 2;
|
||||
bytes pairingKey = 3;
|
||||
int64 validFor = 4;
|
||||
AuthKeyData authKeyData = 5;
|
||||
TokenData authKeyData = 5;
|
||||
string responseID = 6;
|
||||
}
|
||||
|
||||
|
|
43
libgm/gmproto/config-extra.go
Normal file
43
libgm/gmproto/config-extra.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package gmproto
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (c *Config) ParsedClientVersion() (*ConfigVersion, error) {
|
||||
version := c.ClientVersion
|
||||
|
||||
v1 := version[0:4]
|
||||
v2 := version[4:6]
|
||||
v3 := version[6:8]
|
||||
|
||||
if v2[0] == 48 {
|
||||
v2 = string(v2[1])
|
||||
}
|
||||
if v3[0] == 48 {
|
||||
v3 = string(v3[1])
|
||||
}
|
||||
|
||||
first, e := strconv.Atoi(v1)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
second, e1 := strconv.Atoi(v2)
|
||||
if e1 != nil {
|
||||
return nil, e1
|
||||
}
|
||||
|
||||
third, e2 := strconv.Atoi(v3)
|
||||
if e2 != nil {
|
||||
return nil, e2
|
||||
}
|
||||
|
||||
return &ConfigVersion{
|
||||
Year: int32(first),
|
||||
Month: int32(second),
|
||||
Day: int32(third),
|
||||
V1: 4,
|
||||
V2: 6,
|
||||
}, nil
|
||||
}
|
587
libgm/gmproto/config.pb.go
Normal file
587
libgm/gmproto/config.pb.go
Normal file
|
@ -0,0 +1,587 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v3.21.12
|
||||
// source: config.proto
|
||||
|
||||
package gmproto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
import _ "embed"
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ClientVersion string `protobuf:"bytes,1,opt,name=clientVersion,proto3" json:"clientVersion,omitempty"`
|
||||
ServerVersion string `protobuf:"bytes,2,opt,name=serverVersion,proto3" json:"serverVersion,omitempty"`
|
||||
IntFlags []*Config_Flag `protobuf:"bytes,3,rep,name=intFlags,proto3" json:"intFlags,omitempty"`
|
||||
MoreFlags *Config_MoreFlags `protobuf:"bytes,4,opt,name=moreFlags,proto3" json:"moreFlags,omitempty"`
|
||||
DeviceInfo *Config_DeviceInfo `protobuf:"bytes,5,opt,name=deviceInfo,proto3" json:"deviceInfo,omitempty"`
|
||||
// item 6 seems like a list of device infos without device ID?
|
||||
CountryCode string `protobuf:"bytes,7,opt,name=countryCode,proto3" json:"countryCode,omitempty"`
|
||||
UnknownInts []uint32 `protobuf:"varint,8,rep,packed,name=unknownInts,proto3" json:"unknownInts,omitempty"`
|
||||
GeneratedAtMS int64 `protobuf:"varint,9,opt,name=generatedAtMS,proto3" json:"generatedAtMS,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Config) GetClientVersion() string {
|
||||
if x != nil {
|
||||
return x.ClientVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetServerVersion() string {
|
||||
if x != nil {
|
||||
return x.ServerVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetIntFlags() []*Config_Flag {
|
||||
if x != nil {
|
||||
return x.IntFlags
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetMoreFlags() *Config_MoreFlags {
|
||||
if x != nil {
|
||||
return x.MoreFlags
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetDeviceInfo() *Config_DeviceInfo {
|
||||
if x != nil {
|
||||
return x.DeviceInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetCountryCode() string {
|
||||
if x != nil {
|
||||
return x.CountryCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetUnknownInts() []uint32 {
|
||||
if x != nil {
|
||||
return x.UnknownInts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetGeneratedAtMS() int64 {
|
||||
if x != nil {
|
||||
return x.GeneratedAtMS
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Config_Flag struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
UnknownField4 *string `protobuf:"bytes,4,opt,name=unknownField4,proto3,oneof" json:"unknownField4,omitempty"`
|
||||
UnknownField5 *string `protobuf:"bytes,5,opt,name=unknownField5,proto3,oneof" json:"unknownField5,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config_Flag) Reset() {
|
||||
*x = Config_Flag{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config_Flag) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config_Flag) ProtoMessage() {}
|
||||
|
||||
func (x *Config_Flag) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config_Flag.ProtoReflect.Descriptor instead.
|
||||
func (*Config_Flag) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *Config_Flag) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config_Flag) GetValue() int32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config_Flag) GetUnknownField4() string {
|
||||
if x != nil && x.UnknownField4 != nil {
|
||||
return *x.UnknownField4
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config_Flag) GetUnknownField5() string {
|
||||
if x != nil && x.UnknownField5 != nil {
|
||||
return *x.UnknownField5
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Config_WrappedFlag struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value *Config_WrappedFlag_Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag) Reset() {
|
||||
*x = Config_WrappedFlag{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config_WrappedFlag) ProtoMessage() {}
|
||||
|
||||
func (x *Config_WrappedFlag) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config_WrappedFlag.ProtoReflect.Descriptor instead.
|
||||
func (*Config_WrappedFlag) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0, 1}
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag) GetValue() *Config_WrappedFlag_Value {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config_MoreFlags struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
WrappedFlags []*Config_WrappedFlag `protobuf:"bytes,1,rep,name=wrappedFlags,proto3" json:"wrappedFlags,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config_MoreFlags) Reset() {
|
||||
*x = Config_MoreFlags{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config_MoreFlags) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config_MoreFlags) ProtoMessage() {}
|
||||
|
||||
func (x *Config_MoreFlags) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config_MoreFlags.ProtoReflect.Descriptor instead.
|
||||
func (*Config_MoreFlags) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0, 2}
|
||||
}
|
||||
|
||||
func (x *Config_MoreFlags) GetWrappedFlags() []*Config_WrappedFlag {
|
||||
if x != nil {
|
||||
return x.WrappedFlags
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config_DeviceInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Zero string `protobuf:"bytes,3,opt,name=zero,proto3" json:"zero,omitempty"`
|
||||
DeviceID string `protobuf:"bytes,4,opt,name=deviceID,proto3" json:"deviceID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config_DeviceInfo) Reset() {
|
||||
*x = Config_DeviceInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config_DeviceInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config_DeviceInfo) ProtoMessage() {}
|
||||
|
||||
func (x *Config_DeviceInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config_DeviceInfo.ProtoReflect.Descriptor instead.
|
||||
func (*Config_DeviceInfo) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0, 3}
|
||||
}
|
||||
|
||||
func (x *Config_DeviceInfo) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config_DeviceInfo) GetZero() string {
|
||||
if x != nil {
|
||||
return x.Zero
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config_DeviceInfo) GetDeviceID() string {
|
||||
if x != nil {
|
||||
return x.DeviceID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Config_WrappedFlag_Value struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Value:
|
||||
//
|
||||
// *Config_WrappedFlag_Value_IntVal
|
||||
// *Config_WrappedFlag_Value_StrVal
|
||||
Value isConfig_WrappedFlag_Value_Value `protobuf_oneof:"value"`
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag_Value) Reset() {
|
||||
*x = Config_WrappedFlag_Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag_Value) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config_WrappedFlag_Value) ProtoMessage() {}
|
||||
|
||||
func (x *Config_WrappedFlag_Value) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config_WrappedFlag_Value.ProtoReflect.Descriptor instead.
|
||||
func (*Config_WrappedFlag_Value) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0, 1, 0}
|
||||
}
|
||||
|
||||
func (m *Config_WrappedFlag_Value) GetValue() isConfig_WrappedFlag_Value_Value {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag_Value) GetIntVal() int32 {
|
||||
if x, ok := x.GetValue().(*Config_WrappedFlag_Value_IntVal); ok {
|
||||
return x.IntVal
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config_WrappedFlag_Value) GetStrVal() string {
|
||||
if x, ok := x.GetValue().(*Config_WrappedFlag_Value_StrVal); ok {
|
||||
return x.StrVal
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type isConfig_WrappedFlag_Value_Value interface {
|
||||
isConfig_WrappedFlag_Value_Value()
|
||||
}
|
||||
|
||||
type Config_WrappedFlag_Value_IntVal struct {
|
||||
IntVal int32 `protobuf:"varint,2,opt,name=intVal,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Config_WrappedFlag_Value_StrVal struct {
|
||||
StrVal string `protobuf:"bytes,3,opt,name=strVal,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Config_WrappedFlag_Value_IntVal) isConfig_WrappedFlag_Value_Value() {}
|
||||
|
||||
func (*Config_WrappedFlag_Value_StrVal) isConfig_WrappedFlag_Value_Value() {}
|
||||
|
||||
var File_config_proto protoreflect.FileDescriptor
|
||||
|
||||
//go:embed config.pb.raw
|
||||
var file_config_proto_rawDesc []byte
|
||||
|
||||
var (
|
||||
file_config_proto_rawDescOnce sync.Once
|
||||
file_config_proto_rawDescData = file_config_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_config_proto_rawDescGZIP() []byte {
|
||||
file_config_proto_rawDescOnce.Do(func() {
|
||||
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
|
||||
})
|
||||
return file_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_config_proto_goTypes = []interface{}{
|
||||
(*Config)(nil), // 0: config.Config
|
||||
(*Config_Flag)(nil), // 1: config.Config.Flag
|
||||
(*Config_WrappedFlag)(nil), // 2: config.Config.WrappedFlag
|
||||
(*Config_MoreFlags)(nil), // 3: config.Config.MoreFlags
|
||||
(*Config_DeviceInfo)(nil), // 4: config.Config.DeviceInfo
|
||||
(*Config_WrappedFlag_Value)(nil), // 5: config.Config.WrappedFlag.Value
|
||||
}
|
||||
var file_config_proto_depIdxs = []int32{
|
||||
1, // 0: config.Config.intFlags:type_name -> config.Config.Flag
|
||||
3, // 1: config.Config.moreFlags:type_name -> config.Config.MoreFlags
|
||||
4, // 2: config.Config.deviceInfo:type_name -> config.Config.DeviceInfo
|
||||
5, // 3: config.Config.WrappedFlag.value:type_name -> config.Config.WrappedFlag.Value
|
||||
2, // 4: config.Config.MoreFlags.wrappedFlags:type_name -> config.Config.WrappedFlag
|
||||
5, // [5:5] is the sub-list for method output_type
|
||||
5, // [5:5] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
5, // [5:5] is the sub-list for extension extendee
|
||||
0, // [0:5] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_config_proto_init() }
|
||||
func file_config_proto_init() {
|
||||
if File_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config_Flag); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config_WrappedFlag); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config_MoreFlags); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config_DeviceInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config_WrappedFlag_Value); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[1].OneofWrappers = []interface{}{}
|
||||
file_config_proto_msgTypes[5].OneofWrappers = []interface{}{
|
||||
(*Config_WrappedFlag_Value_IntVal)(nil),
|
||||
(*Config_WrappedFlag_Value_StrVal)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_config_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_config_proto_goTypes,
|
||||
DependencyIndexes: file_config_proto_depIdxs,
|
||||
MessageInfos: file_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_config_proto = out.File
|
||||
file_config_proto_rawDesc = nil
|
||||
file_config_proto_goTypes = nil
|
||||
file_config_proto_depIdxs = nil
|
||||
}
|
BIN
libgm/gmproto/config.pb.raw
Normal file
BIN
libgm/gmproto/config.pb.raw
Normal file
Binary file not shown.
40
libgm/gmproto/config.proto
Normal file
40
libgm/gmproto/config.proto
Normal file
|
@ -0,0 +1,40 @@
|
|||
syntax = "proto3";
|
||||
package config;
|
||||
|
||||
option go_package = "../gmproto";
|
||||
|
||||
message Config {
|
||||
message Flag {
|
||||
string key = 1;
|
||||
int32 value = 2;
|
||||
optional string unknownField4 = 4;
|
||||
optional string unknownField5 = 5;
|
||||
}
|
||||
message WrappedFlag {
|
||||
message Value {
|
||||
oneof value {
|
||||
int32 intVal = 2;
|
||||
string strVal = 3;
|
||||
}
|
||||
}
|
||||
string key = 1;
|
||||
Value value = 2;
|
||||
}
|
||||
message MoreFlags {
|
||||
repeated WrappedFlag wrappedFlags = 1;
|
||||
}
|
||||
message DeviceInfo {
|
||||
string email = 2;
|
||||
string zero = 3;
|
||||
string deviceID = 4;
|
||||
}
|
||||
string clientVersion = 1;
|
||||
string serverVersion = 2;
|
||||
repeated Flag intFlags = 3;
|
||||
MoreFlags moreFlags = 4;
|
||||
DeviceInfo deviceInfo = 5;
|
||||
// item 6 seems like a list of device infos without device ID?
|
||||
string countryCode = 7;
|
||||
repeated uint32 unknownInts = 8;
|
||||
int64 generatedAtMS = 9;
|
||||
}
|
82
libgm/gmproto/pblite.pb.go
Normal file
82
libgm/gmproto/pblite.pb.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v3.21.12
|
||||
// source: pblite.proto
|
||||
|
||||
package gmproto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
import _ "embed"
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
var file_pblite_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*bool)(nil),
|
||||
Field: 50000,
|
||||
Name: "pblite.pblite_binary",
|
||||
Tag: "varint,50000,opt,name=pblite_binary",
|
||||
Filename: "pblite.proto",
|
||||
},
|
||||
}
|
||||
|
||||
// Extension fields to descriptorpb.FieldOptions.
|
||||
var (
|
||||
// optional bool pblite_binary = 50000;
|
||||
E_PbliteBinary = &file_pblite_proto_extTypes[0]
|
||||
)
|
||||
|
||||
var File_pblite_proto protoreflect.FileDescriptor
|
||||
|
||||
//go:embed pblite.pb.raw
|
||||
var file_pblite_proto_rawDesc []byte
|
||||
|
||||
var file_pblite_proto_goTypes = []interface{}{
|
||||
(*descriptorpb.FieldOptions)(nil), // 0: google.protobuf.FieldOptions
|
||||
}
|
||||
var file_pblite_proto_depIdxs = []int32{
|
||||
0, // 0: pblite.pblite_binary:extendee -> google.protobuf.FieldOptions
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
0, // [0:1] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_pblite_proto_init() }
|
||||
func file_pblite_proto_init() {
|
||||
if File_pblite_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_pblite_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 0,
|
||||
NumExtensions: 1,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_pblite_proto_goTypes,
|
||||
DependencyIndexes: file_pblite_proto_depIdxs,
|
||||
ExtensionInfos: file_pblite_proto_extTypes,
|
||||
}.Build()
|
||||
File_pblite_proto = out.File
|
||||
file_pblite_proto_rawDesc = nil
|
||||
file_pblite_proto_goTypes = nil
|
||||
file_pblite_proto_depIdxs = nil
|
||||
}
|
4
libgm/gmproto/pblite.pb.raw
Normal file
4
libgm/gmproto/pblite.pb.raw
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
pblite.protopblite google/protobuf/descriptor.proto:G
|
||||
pblite_binary.google.protobuf.FieldOptionsІ (RpbliteBinary<72>BZ
|
||||
../gmprotobproto3
|
10
libgm/gmproto/pblite.proto
Normal file
10
libgm/gmproto/pblite.proto
Normal file
|
@ -0,0 +1,10 @@
|
|||
syntax = "proto3";
|
||||
package pblite;
|
||||
|
||||
option go_package = "../gmproto";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional bool pblite_binary = 50000;
|
||||
}
|
|
@ -28,6 +28,7 @@ const (
|
|||
BugleRoute_Unknown BugleRoute = 0
|
||||
BugleRoute_DataEvent BugleRoute = 19
|
||||
BugleRoute_PairEvent BugleRoute = 14
|
||||
BugleRoute_GaiaEvent BugleRoute = 7
|
||||
)
|
||||
|
||||
// Enum value maps for BugleRoute.
|
||||
|
@ -36,11 +37,13 @@ var (
|
|||
0: "Unknown",
|
||||
19: "DataEvent",
|
||||
14: "PairEvent",
|
||||
7: "GaiaEvent",
|
||||
}
|
||||
BugleRoute_value = map[string]int32{
|
||||
"Unknown": 0,
|
||||
"DataEvent": 19,
|
||||
"PairEvent": 14,
|
||||
"GaiaEvent": 7,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -119,6 +122,7 @@ const (
|
|||
ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT ActionType = 44
|
||||
ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED ActionType = 45
|
||||
ActionType_UNPAIR_GAIA_PAIRING ActionType = 46
|
||||
ActionType_CANCEL_GAIA_PAIRING ActionType = 47
|
||||
)
|
||||
|
||||
// Enum value maps for ActionType.
|
||||
|
@ -169,6 +173,7 @@ var (
|
|||
44: "CREATE_GAIA_PAIRING_CLIENT_INIT",
|
||||
45: "CREATE_GAIA_PAIRING_CLIENT_FINISHED",
|
||||
46: "UNPAIR_GAIA_PAIRING",
|
||||
47: "CANCEL_GAIA_PAIRING",
|
||||
}
|
||||
ActionType_value = map[string]int32{
|
||||
"UNSPECIFIED": 0,
|
||||
|
@ -216,6 +221,7 @@ var (
|
|||
"CREATE_GAIA_PAIRING_CLIENT_INIT": 44,
|
||||
"CREATE_GAIA_PAIRING_CLIENT_FINISHED": 45,
|
||||
"UNPAIR_GAIA_PAIRING": 46,
|
||||
"CANCEL_GAIA_PAIRING": 47,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -251,7 +257,9 @@ type MessageType int32
|
|||
const (
|
||||
MessageType_UNKNOWN_MESSAGE_TYPE MessageType = 0
|
||||
MessageType_BUGLE_MESSAGE MessageType = 2
|
||||
MessageType_GAIA_1 MessageType = 3
|
||||
MessageType_BUGLE_ANNOTATION MessageType = 16
|
||||
MessageType_GAIA_2 MessageType = 20
|
||||
)
|
||||
|
||||
// Enum value maps for MessageType.
|
||||
|
@ -259,12 +267,16 @@ var (
|
|||
MessageType_name = map[int32]string{
|
||||
0: "UNKNOWN_MESSAGE_TYPE",
|
||||
2: "BUGLE_MESSAGE",
|
||||
3: "GAIA_1",
|
||||
16: "BUGLE_ANNOTATION",
|
||||
20: "GAIA_2",
|
||||
}
|
||||
MessageType_value = map[string]int32{
|
||||
"UNKNOWN_MESSAGE_TYPE": 0,
|
||||
"BUGLE_MESSAGE": 2,
|
||||
"GAIA_1": 3,
|
||||
"BUGLE_ANNOTATION": 16,
|
||||
"GAIA_2": 20,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -420,16 +432,18 @@ type IncomingRPCMessage struct {
|
|||
|
||||
ResponseID string `protobuf:"bytes,1,opt,name=responseID,proto3" json:"responseID,omitempty"`
|
||||
BugleRoute BugleRoute `protobuf:"varint,2,opt,name=bugleRoute,proto3,enum=rpc.BugleRoute" json:"bugleRoute,omitempty"`
|
||||
StartExecute string `protobuf:"bytes,3,opt,name=startExecute,proto3" json:"startExecute,omitempty"`
|
||||
StartExecute uint64 `protobuf:"varint,3,opt,name=startExecute,proto3" json:"startExecute,omitempty"`
|
||||
MessageType MessageType `protobuf:"varint,5,opt,name=messageType,proto3,enum=rpc.MessageType" json:"messageType,omitempty"`
|
||||
FinishExecute string `protobuf:"bytes,6,opt,name=finishExecute,proto3" json:"finishExecute,omitempty"`
|
||||
MillisecondsTaken string `protobuf:"bytes,7,opt,name=millisecondsTaken,proto3" json:"millisecondsTaken,omitempty"`
|
||||
FinishExecute uint64 `protobuf:"varint,6,opt,name=finishExecute,proto3" json:"finishExecute,omitempty"`
|
||||
MicrosecondsTaken uint64 `protobuf:"varint,7,opt,name=microsecondsTaken,proto3" json:"microsecondsTaken,omitempty"`
|
||||
Mobile *Device `protobuf:"bytes,8,opt,name=mobile,proto3" json:"mobile,omitempty"`
|
||||
Browser *Device `protobuf:"bytes,9,opt,name=browser,proto3" json:"browser,omitempty"`
|
||||
// Either a RPCMessageData or a RPCPairData encoded as bytes
|
||||
MessageData []byte `protobuf:"bytes,12,opt,name=messageData,proto3" json:"messageData,omitempty"`
|
||||
SignatureID string `protobuf:"bytes,17,opt,name=signatureID,proto3" json:"signatureID,omitempty"`
|
||||
Timestamp string `protobuf:"bytes,21,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
// Completely unsure about this, but it seems to be present for weird intermediate responses
|
||||
GdittoSource *IncomingRPCMessage_GDittoSource `protobuf:"bytes,23,opt,name=gdittoSource,proto3" json:"gdittoSource,omitempty"`
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) Reset() {
|
||||
|
@ -478,11 +492,11 @@ func (x *IncomingRPCMessage) GetBugleRoute() BugleRoute {
|
|||
return BugleRoute_Unknown
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) GetStartExecute() string {
|
||||
func (x *IncomingRPCMessage) GetStartExecute() uint64 {
|
||||
if x != nil {
|
||||
return x.StartExecute
|
||||
}
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) GetMessageType() MessageType {
|
||||
|
@ -492,18 +506,18 @@ func (x *IncomingRPCMessage) GetMessageType() MessageType {
|
|||
return MessageType_UNKNOWN_MESSAGE_TYPE
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) GetFinishExecute() string {
|
||||
func (x *IncomingRPCMessage) GetFinishExecute() uint64 {
|
||||
if x != nil {
|
||||
return x.FinishExecute
|
||||
}
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) GetMillisecondsTaken() string {
|
||||
func (x *IncomingRPCMessage) GetMicrosecondsTaken() uint64 {
|
||||
if x != nil {
|
||||
return x.MillisecondsTaken
|
||||
return x.MicrosecondsTaken
|
||||
}
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) GetMobile() *Device {
|
||||
|
@ -541,19 +555,27 @@ func (x *IncomingRPCMessage) GetTimestamp() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage) GetGdittoSource() *IncomingRPCMessage_GDittoSource {
|
||||
if x != nil {
|
||||
return x.GdittoSource
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RPCMessageData struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
|
||||
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
Action ActionType `protobuf:"varint,4,opt,name=action,proto3,enum=rpc.ActionType" json:"action,omitempty"`
|
||||
Bool1 bool `protobuf:"varint,6,opt,name=bool1,proto3" json:"bool1,omitempty"`
|
||||
Bool2 bool `protobuf:"varint,7,opt,name=bool2,proto3" json:"bool2,omitempty"`
|
||||
EncryptedData []byte `protobuf:"bytes,8,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"`
|
||||
Bool3 bool `protobuf:"varint,9,opt,name=bool3,proto3" json:"bool3,omitempty"`
|
||||
EncryptedData2 []byte `protobuf:"bytes,11,opt,name=encryptedData2,proto3" json:"encryptedData2,omitempty"`
|
||||
SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
|
||||
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
Action ActionType `protobuf:"varint,4,opt,name=action,proto3,enum=rpc.ActionType" json:"action,omitempty"`
|
||||
UnencryptedData []byte `protobuf:"bytes,5,opt,name=unencryptedData,proto3" json:"unencryptedData,omitempty"`
|
||||
Bool1 bool `protobuf:"varint,6,opt,name=bool1,proto3" json:"bool1,omitempty"`
|
||||
Bool2 bool `protobuf:"varint,7,opt,name=bool2,proto3" json:"bool2,omitempty"`
|
||||
EncryptedData []byte `protobuf:"bytes,8,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"`
|
||||
Bool3 bool `protobuf:"varint,9,opt,name=bool3,proto3" json:"bool3,omitempty"`
|
||||
EncryptedData2 []byte `protobuf:"bytes,11,opt,name=encryptedData2,proto3" json:"encryptedData2,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RPCMessageData) Reset() {
|
||||
|
@ -609,6 +631,13 @@ func (x *RPCMessageData) GetAction() ActionType {
|
|||
return ActionType_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *RPCMessageData) GetUnencryptedData() []byte {
|
||||
if x != nil {
|
||||
return x.UnencryptedData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RPCMessageData) GetBool1() bool {
|
||||
if x != nil {
|
||||
return x.Bool1
|
||||
|
@ -649,11 +678,11 @@ type OutgoingRPCMessage struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Mobile *Device `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"`
|
||||
Data *OutgoingRPCMessage_Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Auth *OutgoingRPCMessage_Auth `protobuf:"bytes,3,opt,name=auth,proto3" json:"auth,omitempty"`
|
||||
TTL int64 `protobuf:"varint,5,opt,name=TTL,proto3" json:"TTL,omitempty"`
|
||||
EmptyArr *EmptyArr `protobuf:"bytes,9,opt,name=emptyArr,proto3" json:"emptyArr,omitempty"`
|
||||
Mobile *Device `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"`
|
||||
Data *OutgoingRPCMessage_Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Auth *OutgoingRPCMessage_Auth `protobuf:"bytes,3,opt,name=auth,proto3" json:"auth,omitempty"`
|
||||
TTL int64 `protobuf:"varint,5,opt,name=TTL,proto3" json:"TTL,omitempty"`
|
||||
DestRegistrationIDs []string `protobuf:"bytes,9,rep,name=destRegistrationIDs,proto3" json:"destRegistrationIDs,omitempty"`
|
||||
}
|
||||
|
||||
func (x *OutgoingRPCMessage) Reset() {
|
||||
|
@ -716,9 +745,9 @@ func (x *OutgoingRPCMessage) GetTTL() int64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (x *OutgoingRPCMessage) GetEmptyArr() *EmptyArr {
|
||||
func (x *OutgoingRPCMessage) GetDestRegistrationIDs() []string {
|
||||
if x != nil {
|
||||
return x.EmptyArr
|
||||
return x.DestRegistrationIDs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -728,10 +757,11 @@ type OutgoingRPCData struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RequestID string `protobuf:"bytes,1,opt,name=requestID,proto3" json:"requestID,omitempty"`
|
||||
Action ActionType `protobuf:"varint,2,opt,name=action,proto3,enum=rpc.ActionType" json:"action,omitempty"`
|
||||
EncryptedProtoData []byte `protobuf:"bytes,5,opt,name=encryptedProtoData,proto3" json:"encryptedProtoData,omitempty"`
|
||||
SessionID string `protobuf:"bytes,6,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
|
||||
RequestID string `protobuf:"bytes,1,opt,name=requestID,proto3" json:"requestID,omitempty"`
|
||||
Action ActionType `protobuf:"varint,2,opt,name=action,proto3,enum=rpc.ActionType" json:"action,omitempty"`
|
||||
UnencryptedProtoData []byte `protobuf:"bytes,3,opt,name=unencryptedProtoData,proto3" json:"unencryptedProtoData,omitempty"`
|
||||
EncryptedProtoData []byte `protobuf:"bytes,5,opt,name=encryptedProtoData,proto3" json:"encryptedProtoData,omitempty"`
|
||||
SessionID string `protobuf:"bytes,6,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *OutgoingRPCData) Reset() {
|
||||
|
@ -780,6 +810,13 @@ func (x *OutgoingRPCData) GetAction() ActionType {
|
|||
return ActionType_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *OutgoingRPCData) GetUnencryptedProtoData() []byte {
|
||||
if x != nil {
|
||||
return x.UnencryptedProtoData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *OutgoingRPCData) GetEncryptedProtoData() []byte {
|
||||
if x != nil {
|
||||
return x.EncryptedProtoData
|
||||
|
@ -850,6 +887,53 @@ func (x *OutgoingRPCResponse) GetTimestamp() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
type IncomingRPCMessage_GDittoSource struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DeviceID int32 `protobuf:"varint,2,opt,name=deviceID,proto3" json:"deviceID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage_GDittoSource) Reset() {
|
||||
*x = IncomingRPCMessage_GDittoSource{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_rpc_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage_GDittoSource) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IncomingRPCMessage_GDittoSource) ProtoMessage() {}
|
||||
|
||||
func (x *IncomingRPCMessage_GDittoSource) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IncomingRPCMessage_GDittoSource.ProtoReflect.Descriptor instead.
|
||||
func (*IncomingRPCMessage_GDittoSource) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_proto_rawDescGZIP(), []int{2, 0}
|
||||
}
|
||||
|
||||
func (x *IncomingRPCMessage_GDittoSource) GetDeviceID() int32 {
|
||||
if x != nil {
|
||||
return x.DeviceID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type OutgoingRPCMessage_Auth struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -863,7 +947,7 @@ type OutgoingRPCMessage_Auth struct {
|
|||
func (x *OutgoingRPCMessage_Auth) Reset() {
|
||||
*x = OutgoingRPCMessage_Auth{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_rpc_proto_msgTypes[7]
|
||||
mi := &file_rpc_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -876,7 +960,7 @@ func (x *OutgoingRPCMessage_Auth) String() string {
|
|||
func (*OutgoingRPCMessage_Auth) ProtoMessage() {}
|
||||
|
||||
func (x *OutgoingRPCMessage_Auth) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[7]
|
||||
mi := &file_rpc_proto_msgTypes[8]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -928,7 +1012,7 @@ type OutgoingRPCMessage_Data struct {
|
|||
func (x *OutgoingRPCMessage_Data) Reset() {
|
||||
*x = OutgoingRPCMessage_Data{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_rpc_proto_msgTypes[8]
|
||||
mi := &file_rpc_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -941,7 +1025,7 @@ func (x *OutgoingRPCMessage_Data) String() string {
|
|||
func (*OutgoingRPCMessage_Data) ProtoMessage() {}
|
||||
|
||||
func (x *OutgoingRPCMessage_Data) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[8]
|
||||
mi := &file_rpc_proto_msgTypes[9]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -997,7 +1081,7 @@ type OutgoingRPCMessage_Data_Type struct {
|
|||
func (x *OutgoingRPCMessage_Data_Type) Reset() {
|
||||
*x = OutgoingRPCMessage_Data_Type{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_rpc_proto_msgTypes[9]
|
||||
mi := &file_rpc_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1010,7 +1094,7 @@ func (x *OutgoingRPCMessage_Data_Type) String() string {
|
|||
func (*OutgoingRPCMessage_Data_Type) ProtoMessage() {}
|
||||
|
||||
func (x *OutgoingRPCMessage_Data_Type) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[9]
|
||||
mi := &file_rpc_proto_msgTypes[10]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1052,7 +1136,7 @@ type OutgoingRPCResponse_SomeIdentifier struct {
|
|||
func (x *OutgoingRPCResponse_SomeIdentifier) Reset() {
|
||||
*x = OutgoingRPCResponse_SomeIdentifier{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_rpc_proto_msgTypes[10]
|
||||
mi := &file_rpc_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1065,7 +1149,7 @@ func (x *OutgoingRPCResponse_SomeIdentifier) String() string {
|
|||
func (*OutgoingRPCResponse_SomeIdentifier) ProtoMessage() {}
|
||||
|
||||
func (x *OutgoingRPCResponse_SomeIdentifier) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_proto_msgTypes[10]
|
||||
mi := &file_rpc_proto_msgTypes[11]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1106,7 +1190,7 @@ func file_rpc_proto_rawDescGZIP() []byte {
|
|||
}
|
||||
|
||||
var file_rpc_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_rpc_proto_goTypes = []interface{}{
|
||||
(BugleRoute)(0), // 0: rpc.BugleRoute
|
||||
(ActionType)(0), // 1: rpc.ActionType
|
||||
|
@ -1118,34 +1202,35 @@ var file_rpc_proto_goTypes = []interface{}{
|
|||
(*OutgoingRPCMessage)(nil), // 7: rpc.OutgoingRPCMessage
|
||||
(*OutgoingRPCData)(nil), // 8: rpc.OutgoingRPCData
|
||||
(*OutgoingRPCResponse)(nil), // 9: rpc.OutgoingRPCResponse
|
||||
(*OutgoingRPCMessage_Auth)(nil), // 10: rpc.OutgoingRPCMessage.Auth
|
||||
(*OutgoingRPCMessage_Data)(nil), // 11: rpc.OutgoingRPCMessage.Data
|
||||
(*OutgoingRPCMessage_Data_Type)(nil), // 12: rpc.OutgoingRPCMessage.Data.Type
|
||||
(*OutgoingRPCResponse_SomeIdentifier)(nil), // 13: rpc.OutgoingRPCResponse.SomeIdentifier
|
||||
(*EmptyArr)(nil), // 14: util.EmptyArr
|
||||
(*Device)(nil), // 15: authentication.Device
|
||||
(*ConfigVersion)(nil), // 16: authentication.ConfigVersion
|
||||
(*IncomingRPCMessage_GDittoSource)(nil), // 10: rpc.IncomingRPCMessage.GDittoSource
|
||||
(*OutgoingRPCMessage_Auth)(nil), // 11: rpc.OutgoingRPCMessage.Auth
|
||||
(*OutgoingRPCMessage_Data)(nil), // 12: rpc.OutgoingRPCMessage.Data
|
||||
(*OutgoingRPCMessage_Data_Type)(nil), // 13: rpc.OutgoingRPCMessage.Data.Type
|
||||
(*OutgoingRPCResponse_SomeIdentifier)(nil), // 14: rpc.OutgoingRPCResponse.SomeIdentifier
|
||||
(*EmptyArr)(nil), // 15: util.EmptyArr
|
||||
(*Device)(nil), // 16: authentication.Device
|
||||
(*ConfigVersion)(nil), // 17: authentication.ConfigVersion
|
||||
}
|
||||
var file_rpc_proto_depIdxs = []int32{
|
||||
5, // 0: rpc.LongPollingPayload.data:type_name -> rpc.IncomingRPCMessage
|
||||
14, // 1: rpc.LongPollingPayload.heartbeat:type_name -> util.EmptyArr
|
||||
15, // 1: rpc.LongPollingPayload.heartbeat:type_name -> util.EmptyArr
|
||||
3, // 2: rpc.LongPollingPayload.ack:type_name -> rpc.StartAckMessage
|
||||
14, // 3: rpc.LongPollingPayload.startRead:type_name -> util.EmptyArr
|
||||
15, // 3: rpc.LongPollingPayload.startRead:type_name -> util.EmptyArr
|
||||
0, // 4: rpc.IncomingRPCMessage.bugleRoute:type_name -> rpc.BugleRoute
|
||||
2, // 5: rpc.IncomingRPCMessage.messageType:type_name -> rpc.MessageType
|
||||
15, // 6: rpc.IncomingRPCMessage.mobile:type_name -> authentication.Device
|
||||
15, // 7: rpc.IncomingRPCMessage.browser:type_name -> authentication.Device
|
||||
1, // 8: rpc.RPCMessageData.action:type_name -> rpc.ActionType
|
||||
15, // 9: rpc.OutgoingRPCMessage.mobile:type_name -> authentication.Device
|
||||
11, // 10: rpc.OutgoingRPCMessage.data:type_name -> rpc.OutgoingRPCMessage.Data
|
||||
10, // 11: rpc.OutgoingRPCMessage.auth:type_name -> rpc.OutgoingRPCMessage.Auth
|
||||
14, // 12: rpc.OutgoingRPCMessage.emptyArr:type_name -> util.EmptyArr
|
||||
16, // 6: rpc.IncomingRPCMessage.mobile:type_name -> authentication.Device
|
||||
16, // 7: rpc.IncomingRPCMessage.browser:type_name -> authentication.Device
|
||||
10, // 8: rpc.IncomingRPCMessage.gdittoSource:type_name -> rpc.IncomingRPCMessage.GDittoSource
|
||||
1, // 9: rpc.RPCMessageData.action:type_name -> rpc.ActionType
|
||||
16, // 10: rpc.OutgoingRPCMessage.mobile:type_name -> authentication.Device
|
||||
12, // 11: rpc.OutgoingRPCMessage.data:type_name -> rpc.OutgoingRPCMessage.Data
|
||||
11, // 12: rpc.OutgoingRPCMessage.auth:type_name -> rpc.OutgoingRPCMessage.Auth
|
||||
1, // 13: rpc.OutgoingRPCData.action:type_name -> rpc.ActionType
|
||||
13, // 14: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier
|
||||
16, // 15: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
|
||||
14, // 14: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier
|
||||
17, // 15: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
|
||||
0, // 16: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute
|
||||
12, // 17: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
|
||||
14, // 18: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
|
||||
13, // 17: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
|
||||
15, // 18: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
|
||||
2, // 19: rpc.OutgoingRPCMessage.Data.Type.messageType:type_name -> rpc.MessageType
|
||||
20, // [20:20] is the sub-list for method output_type
|
||||
20, // [20:20] is the sub-list for method input_type
|
||||
|
@ -1161,6 +1246,7 @@ func file_rpc_proto_init() {
|
|||
}
|
||||
file_authentication_proto_init()
|
||||
file_util_proto_init()
|
||||
file_pblite_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_rpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StartAckMessage); i {
|
||||
|
@ -1247,7 +1333,7 @@ func file_rpc_proto_init() {
|
|||
}
|
||||
}
|
||||
file_rpc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OutgoingRPCMessage_Auth); i {
|
||||
switch v := v.(*IncomingRPCMessage_GDittoSource); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1259,7 +1345,7 @@ func file_rpc_proto_init() {
|
|||
}
|
||||
}
|
||||
file_rpc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OutgoingRPCMessage_Data); i {
|
||||
switch v := v.(*OutgoingRPCMessage_Auth); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1271,7 +1357,7 @@ func file_rpc_proto_init() {
|
|||
}
|
||||
}
|
||||
file_rpc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OutgoingRPCMessage_Data_Type); i {
|
||||
switch v := v.(*OutgoingRPCMessage_Data); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1283,6 +1369,18 @@ func file_rpc_proto_init() {
|
|||
}
|
||||
}
|
||||
file_rpc_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OutgoingRPCMessage_Data_Type); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_rpc_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OutgoingRPCResponse_SomeIdentifier); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1304,7 +1402,7 @@ func file_rpc_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_rpc_proto_rawDesc,
|
||||
NumEnums: 3,
|
||||
NumMessages: 11,
|
||||
NumMessages: 12,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
|
Binary file not shown.
|
@ -5,6 +5,7 @@ option go_package = "../gmproto";
|
|||
|
||||
import "authentication.proto";
|
||||
import "util.proto";
|
||||
import "pblite.proto";
|
||||
|
||||
message StartAckMessage {
|
||||
optional int32 count = 1;
|
||||
|
@ -20,11 +21,11 @@ message LongPollingPayload {
|
|||
message IncomingRPCMessage {
|
||||
string responseID = 1;
|
||||
BugleRoute bugleRoute = 2;
|
||||
string startExecute = 3;
|
||||
uint64 startExecute = 3;
|
||||
|
||||
MessageType messageType = 5;
|
||||
string finishExecute = 6;
|
||||
string millisecondsTaken = 7;
|
||||
uint64 finishExecute = 6;
|
||||
uint64 microsecondsTaken = 7;
|
||||
authentication.Device mobile = 8;
|
||||
authentication.Device browser = 9;
|
||||
|
||||
|
@ -34,12 +35,20 @@ message IncomingRPCMessage {
|
|||
string signatureID = 17;
|
||||
|
||||
string timestamp = 21;
|
||||
|
||||
message GDittoSource {
|
||||
int32 deviceID = 2;
|
||||
}
|
||||
|
||||
// Completely unsure about this, but it seems to be present for weird intermediate responses
|
||||
GDittoSource gdittoSource = 23;
|
||||
}
|
||||
|
||||
message RPCMessageData {
|
||||
string sessionID = 1;
|
||||
int64 timestamp = 3;
|
||||
ActionType action = 4;
|
||||
bytes unencryptedData = 5;
|
||||
bool bool1 = 6;
|
||||
bool bool2 = 7;
|
||||
bytes encryptedData = 8;
|
||||
|
@ -76,12 +85,13 @@ message OutgoingRPCMessage {
|
|||
|
||||
int64 TTL = 5;
|
||||
|
||||
util.EmptyArr emptyArr = 9;
|
||||
repeated string destRegistrationIDs = 9 [(pblite.pblite_binary) = true];
|
||||
}
|
||||
|
||||
message OutgoingRPCData {
|
||||
string requestID = 1;
|
||||
ActionType action = 2;
|
||||
bytes unencryptedProtoData = 3;
|
||||
bytes encryptedProtoData = 5;
|
||||
string sessionID = 6;
|
||||
}
|
||||
|
@ -101,6 +111,7 @@ enum BugleRoute {
|
|||
Unknown = 0;
|
||||
DataEvent = 19;
|
||||
PairEvent = 14;
|
||||
GaiaEvent = 7;
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
|
@ -149,10 +160,13 @@ enum ActionType {
|
|||
CREATE_GAIA_PAIRING_CLIENT_INIT = 44;
|
||||
CREATE_GAIA_PAIRING_CLIENT_FINISHED = 45;
|
||||
UNPAIR_GAIA_PAIRING = 46;
|
||||
CANCEL_GAIA_PAIRING = 47;
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
UNKNOWN_MESSAGE_TYPE = 0;
|
||||
BUGLE_MESSAGE = 2;
|
||||
GAIA_1 = 3;
|
||||
BUGLE_ANNOTATION = 16;
|
||||
GAIA_2 = 20;
|
||||
}
|
||||
|
|
1091
libgm/gmproto/ukey.pb.go
Normal file
1091
libgm/gmproto/ukey.pb.go
Normal file
File diff suppressed because it is too large
Load diff
BIN
libgm/gmproto/ukey.pb.raw
Normal file
BIN
libgm/gmproto/ukey.pb.raw
Normal file
Binary file not shown.
117
libgm/gmproto/ukey.proto
Normal file
117
libgm/gmproto/ukey.proto
Normal file
|
@ -0,0 +1,117 @@
|
|||
syntax = "proto3";
|
||||
package ukey;
|
||||
|
||||
option go_package = "../gmproto";
|
||||
|
||||
message Ukey2Message {
|
||||
enum Type {
|
||||
UNKNOWN_DO_NOT_USE = 0;
|
||||
ALERT = 1;
|
||||
CLIENT_INIT = 2;
|
||||
SERVER_INIT = 3;
|
||||
CLIENT_FINISH = 4;
|
||||
}
|
||||
|
||||
Type message_type = 1; // Identifies message type
|
||||
bytes message_data = 2; // Actual message, to be parsed according to message_type
|
||||
}
|
||||
|
||||
message Ukey2Alert {
|
||||
enum AlertType {
|
||||
UNKNOWN_ALERT_TYPE = 0;
|
||||
// Framing errors
|
||||
BAD_MESSAGE = 1; // The message could not be deserialized
|
||||
BAD_MESSAGE_TYPE = 2; // message_type has an undefined value
|
||||
INCORRECT_MESSAGE = 3; // message_type received does not correspond to expected type at this stage of the protocol
|
||||
BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per value in message_type
|
||||
|
||||
// ClientInit and ServerInit errors
|
||||
BAD_VERSION = 100; // version is invalid; server cannot find suitable version to speak with client.
|
||||
BAD_RANDOM = 101; // Random data is missing or of incorrect length
|
||||
BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found
|
||||
BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or unsupported
|
||||
BAD_PUBLIC_KEY = 104; // The public key could not be parsed
|
||||
|
||||
// Other errors
|
||||
INTERNAL_ERROR = 200; // An internal error has occurred. error_message may contain additional details for logging and debugging.
|
||||
}
|
||||
|
||||
AlertType type = 1;
|
||||
string error_message = 2;
|
||||
}
|
||||
|
||||
enum Ukey2HandshakeCipher {
|
||||
RESERVED = 0;
|
||||
P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for commitment
|
||||
CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for commitment
|
||||
}
|
||||
|
||||
message Ukey2ClientInit {
|
||||
int32 version = 1; // highest supported version for rollback protection
|
||||
bytes random = 2; // random bytes for replay/reuse protection
|
||||
|
||||
// One commitment (hash of ClientFinished containing public key) per supported cipher
|
||||
message CipherCommitment {
|
||||
Ukey2HandshakeCipher handshake_cipher = 1;
|
||||
bytes commitment = 2;
|
||||
}
|
||||
repeated CipherCommitment cipher_commitments = 3;
|
||||
|
||||
// Next protocol that the client wants to speak.
|
||||
string next_protocol = 4;
|
||||
}
|
||||
|
||||
message Ukey2ServerInit {
|
||||
int32 version = 1; // highest supported version for rollback protection
|
||||
bytes random = 2; // random bytes for replay/reuse protection
|
||||
|
||||
// Selected Cipher and corresponding public key
|
||||
Ukey2HandshakeCipher handshake_cipher = 3;
|
||||
GenericPublicKey public_key = 4;
|
||||
}
|
||||
|
||||
message Ukey2ClientFinished {
|
||||
GenericPublicKey public_key = 1; // public key matching selected handshake cipher
|
||||
}
|
||||
|
||||
// A list of supported public key types
|
||||
enum PublicKeyType {
|
||||
UNKNOWN_PUBLIC_KEY_TYPE = 0;
|
||||
EC_P256 = 1;
|
||||
RSA2048 = 2;
|
||||
// 2048-bit MODP group 14, from RFC 3526
|
||||
DH2048_MODP = 3;
|
||||
}
|
||||
|
||||
// A convenience proto for encoding NIST P-256 elliptic curve public keys
|
||||
message EcP256PublicKey {
|
||||
// x and y are encoded in big-endian two's complement (slightly wasteful)
|
||||
// Client MUST verify (x,y) is a valid point on NIST P256
|
||||
bytes x = 1;
|
||||
bytes y = 2;
|
||||
}
|
||||
|
||||
// A convenience proto for encoding RSA public keys with small exponents
|
||||
message SimpleRsaPublicKey {
|
||||
// Encoded in big-endian two's complement
|
||||
bytes n = 1;
|
||||
int32 e = 2;
|
||||
}
|
||||
|
||||
// A convenience proto for encoding Diffie-Hellman public keys,
|
||||
// for use only when Elliptic Curve based key exchanges are not possible.
|
||||
// (Note that the group parameters must be specified separately)
|
||||
message DhPublicKey {
|
||||
// Big-endian two's complement encoded group element
|
||||
bytes y = 1;
|
||||
}
|
||||
|
||||
message GenericPublicKey {
|
||||
PublicKeyType type = 1;
|
||||
oneof public_key {
|
||||
EcP256PublicKey ec_p256_public_key = 2;
|
||||
SimpleRsaPublicKey rsa2048_public_key = 3;
|
||||
// Use only as a last resort
|
||||
DhPublicKey dh2048_public_key = 4;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
module go.mau.fi/mautrix-gmessages/libgm/gmtest
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/rs/zerolog v1.32.0
|
||||
go.mau.fi/mautrix-gmessages/libgm v0.2.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
go.mau.fi/util v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
)
|
||||
|
||||
replace go.mau.fi/mautrix-gmessages/libgm => ../
|
||||
|
|
|
@ -1,38 +1,37 @@
|
|||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
|
||||
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -4,13 +4,13 @@ import (
|
|||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"go.mau.fi/mautrix-gmessages/libgm"
|
||||
|
@ -46,6 +46,8 @@ func main() {
|
|||
}
|
||||
sess = *libgm.NewAuthData()
|
||||
doLogin = true
|
||||
cookies := mustReturn(os.Open("cookies.json"))
|
||||
must(json.NewDecoder(cookies).Decode(&sess.Cookies))
|
||||
} else {
|
||||
must(json.NewDecoder(file).Decode(&sess))
|
||||
log.Info().Msg("Loaded session?")
|
||||
|
@ -54,19 +56,12 @@ func main() {
|
|||
cli = libgm.NewClient(&sess, log)
|
||||
cli.SetEventHandler(evtHandler)
|
||||
if doLogin {
|
||||
qr := mustReturn(cli.StartLogin())
|
||||
qrterminal.GenerateHalfBlock(qr, qrterminal.L, os.Stdout)
|
||||
go func() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if sess.Browser != nil {
|
||||
return
|
||||
}
|
||||
qr := mustReturn(cli.RefreshPhoneRelay())
|
||||
qrterminal.GenerateHalfBlock(qr, qrterminal.L, os.Stdout)
|
||||
}
|
||||
}()
|
||||
err = cli.DoGaiaPairing(func(emoji string) {
|
||||
fmt.Println(emoji)
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to pair")
|
||||
}
|
||||
} else {
|
||||
must(cli.Connect())
|
||||
}
|
||||
|
|
14
libgm/go.mod
14
libgm/go.mod
|
@ -1,13 +1,15 @@
|
|||
module go.mau.fi/mautrix-gmessages/libgm
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
|
||||
google.golang.org/protobuf v1.31.0
|
||||
go.mau.fi/util v0.2.1
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
|
||||
google.golang.org/protobuf v1.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -15,6 +17,6 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
29
libgm/go.sum
29
libgm/go.sum
|
@ -2,11 +2,10 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
|
@ -16,21 +15,23 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
|
||||
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
@ -3,11 +3,13 @@ package libgm
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
@ -41,13 +43,43 @@ func (c *Client) makeProtobufHTTPRequest(url string, data proto.Message, content
|
|||
return nil, err
|
||||
}
|
||||
util.BuildRelayHeaders(req, contentType, "*/*")
|
||||
c.AddCookieHeaders(req)
|
||||
res, reqErr := c.http.Do(req)
|
||||
if reqErr != nil {
|
||||
return res, reqErr
|
||||
}
|
||||
c.HandleCookieUpdates(res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) AddCookieHeaders(req *http.Request) {
|
||||
if c.AuthData == nil || c.AuthData.Cookies == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range c.AuthData.Cookies {
|
||||
req.AddCookie(&http.Cookie{Name: k, Value: v})
|
||||
}
|
||||
sapisid, ok := c.AuthData.Cookies["SAPISID"]
|
||||
if ok {
|
||||
req.Header.Set("Authorization", sapisidHash(util.MessagesBaseURL, sapisid))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) HandleCookieUpdates(resp *http.Response) {
|
||||
if c.AuthData.Cookies == nil {
|
||||
return
|
||||
}
|
||||
for _, cookie := range resp.Cookies() {
|
||||
c.AuthData.Cookies[cookie.Name] = cookie.Value
|
||||
}
|
||||
}
|
||||
|
||||
func sapisidHash(origin, sapisid string) string {
|
||||
ts := time.Now().Unix()
|
||||
hash := sha1.Sum([]byte(fmt.Sprintf("%d %s %s", ts, sapisid, origin)))
|
||||
return fmt.Sprintf("SAPISIDHASH %d_%x", ts, hash[:])
|
||||
}
|
||||
|
||||
func decodeProtoResp(body []byte, contentType string, into proto.Message) error {
|
||||
contentType, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
|
@ -56,7 +88,7 @@ func decodeProtoResp(body []byte, contentType string, into proto.Message) error
|
|||
switch contentType {
|
||||
case ContentTypeProtobuf:
|
||||
return proto.Unmarshal(body, into)
|
||||
case ContentTypePBLite:
|
||||
case ContentTypePBLite, "text/plain":
|
||||
return pblite.Unmarshal(body, into)
|
||||
default:
|
||||
return fmt.Errorf("unknown content type %s in response", contentType)
|
||||
|
|
|
@ -122,13 +122,19 @@ func (c *Client) doLongPoll(loggedIn bool) {
|
|||
Auth: &gmproto.AuthMessage{
|
||||
RequestID: listenReqID,
|
||||
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||
Network: c.AuthData.AuthNetwork(),
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
Unknown: &gmproto.ReceiveMessagesRequest_UnknownEmptyObject2{
|
||||
Unknown: &gmproto.ReceiveMessagesRequest_UnknownEmptyObject1{},
|
||||
},
|
||||
}
|
||||
resp, err := c.makeProtobufHTTPRequest(util.ReceiveMessagesURL, payload, ContentTypePBLite)
|
||||
url := util.ReceiveMessagesURL
|
||||
if c.AuthData.Cookies != nil {
|
||||
url = util.ReceiveMessagesURLGoogle
|
||||
payload.Auth.Network = util.GoogleNetwork
|
||||
}
|
||||
resp, err := c.makeProtobufHTTPRequest(url, payload, ContentTypePBLite)
|
||||
if err != nil {
|
||||
if loggedIn {
|
||||
c.triggerEvent(&events.ListenTemporaryError{Error: err})
|
||||
|
|
|
@ -120,9 +120,9 @@ func (c *Client) SetTyping(convID string) error {
|
|||
func (c *Client) SetActiveSession() error {
|
||||
c.sessionHandler.ResetSessionID()
|
||||
return c.sessionHandler.sendMessageNoResponse(SendMessageParams{
|
||||
Action: gmproto.ActionType_GET_UPDATES,
|
||||
OmitTTL: true,
|
||||
UseSessionID: true,
|
||||
Action: gmproto.ActionType_GET_UPDATES,
|
||||
OmitTTL: true,
|
||||
RequestID: c.sessionHandler.sessionID,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ func (c *Client) StartLogin() (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.AuthData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken
|
||||
c.updateTachyonAuthToken(registered.GetAuthKeyData())
|
||||
go c.doLongPoll(false)
|
||||
qr, err := c.GenerateQRCodeData(registered.GetPairingKey())
|
||||
if err != nil {
|
||||
|
@ -54,7 +54,7 @@ func (c *Client) handlePairingEvent(msg *IncomingRPCMessage) {
|
|||
}
|
||||
|
||||
func (c *Client) completePairing(data *gmproto.PairedData) {
|
||||
c.updateTachyonAuthToken(data.GetTokenData().GetTachyonAuthToken(), data.GetTokenData().GetTTL())
|
||||
c.updateTachyonAuthToken(data.GetTokenData())
|
||||
c.AuthData.Mobile = data.Mobile
|
||||
c.AuthData.Browser = data.Browser
|
||||
|
||||
|
@ -81,7 +81,7 @@ func (c *Client) RegisterPhoneRelay() (*gmproto.RegisterPhoneRelayResponse, erro
|
|||
payload := &gmproto.AuthenticationContainer{
|
||||
AuthMessage: &gmproto.AuthMessage{
|
||||
RequestID: uuid.NewString(),
|
||||
Network: &util.Network,
|
||||
Network: util.QRNetwork,
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
BrowserDetails: util.BrowserDetailsMessage,
|
||||
|
@ -103,7 +103,7 @@ func (c *Client) RefreshPhoneRelay() (string, error) {
|
|||
payload := &gmproto.AuthenticationContainer{
|
||||
AuthMessage: &gmproto.AuthMessage{
|
||||
RequestID: uuid.NewString(),
|
||||
Network: &util.Network,
|
||||
Network: util.QRNetwork,
|
||||
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
|
@ -134,7 +134,7 @@ func (c *Client) GetWebEncryptionKey() (*gmproto.WebEncryptionKeyResponse, error
|
|||
)
|
||||
}
|
||||
|
||||
func (c *Client) Unpair() (*gmproto.RevokeRelayPairingResponse, error) {
|
||||
func (c *Client) UnpairBugle() (*gmproto.RevokeRelayPairingResponse, error) {
|
||||
if c.AuthData.TachyonAuthToken == nil || c.AuthData.Browser == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -150,3 +150,12 @@ func (c *Client) Unpair() (*gmproto.RevokeRelayPairingResponse, error) {
|
|||
c.makeProtobufHTTPRequest(util.RevokeRelayPairingURL, payload, ContentTypeProtobuf),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) Unpair() (err error) {
|
||||
if c.AuthData.Cookies != nil {
|
||||
err = c.UnpairGaia()
|
||||
} else {
|
||||
_, err = c.UnpairBugle()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
349
libgm/pair_google.go
Normal file
349
libgm/pair_google.go
Normal file
|
@ -0,0 +1,349 @@
|
|||
// mautrix-gmessages - A Matrix-Google Messages puppeting bridge.
|
||||
// Copyright (C) 2024 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 libgm
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.mau.fi/util/random"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||
)
|
||||
|
||||
func (c *Client) handleGaiaPairingEvent(msg *IncomingRPCMessage) {
|
||||
c.Logger.Debug().Any("evt", msg.Gaia).Msg("Gaia event")
|
||||
}
|
||||
|
||||
func (c *Client) baseSignInGaiaPayload() *gmproto.SignInGaiaRequest {
|
||||
return &gmproto.SignInGaiaRequest{
|
||||
AuthMessage: &gmproto.AuthMessage{
|
||||
RequestID: uuid.NewString(),
|
||||
Network: util.GoogleNetwork,
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
Inner: &gmproto.SignInGaiaRequest_Inner{
|
||||
DeviceID: &gmproto.SignInGaiaRequest_Inner_DeviceID{
|
||||
UnknownInt1: 3,
|
||||
DeviceID: fmt.Sprintf("messages-web-%x", c.AuthData.SessionID[:]),
|
||||
},
|
||||
},
|
||||
Network: util.GoogleNetwork,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) signInGaiaInitial() (*gmproto.SignInGaiaResponse, error) {
|
||||
payload := c.baseSignInGaiaPayload()
|
||||
payload.UnknownInt3 = 1
|
||||
return typedHTTPResponse[*gmproto.SignInGaiaResponse](
|
||||
c.makeProtobufHTTPRequest(util.SignInGaiaURL, payload, ContentTypePBLite),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) signInGaiaGetToken() (*gmproto.SignInGaiaResponse, error) {
|
||||
key, err := x509.MarshalPKIXPublicKey(c.AuthData.RefreshKey.GetPublicKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := c.baseSignInGaiaPayload()
|
||||
payload.Inner.SomeData = &gmproto.SignInGaiaRequest_Inner_Data{
|
||||
SomeData: key,
|
||||
}
|
||||
resp, err := typedHTTPResponse[*gmproto.SignInGaiaResponse](
|
||||
c.makeProtobufHTTPRequest(util.SignInGaiaURL, payload, ContentTypePBLite),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.updateTachyonAuthToken(resp.GetTokenData())
|
||||
c.AuthData.Mobile = resp.GetDeviceData().GetDeviceWrapper().GetDevice()
|
||||
c.AuthData.Browser = resp.GetDeviceData().GetDeviceWrapper().GetDevice()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type PairingSession struct {
|
||||
UUID uuid.UUID
|
||||
Start time.Time
|
||||
PairingKeyDSA *ecdsa.PrivateKey
|
||||
InitPayload []byte
|
||||
NextKey []byte
|
||||
}
|
||||
|
||||
func NewPairingSession() PairingSession {
|
||||
ec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return PairingSession{
|
||||
UUID: uuid.New(),
|
||||
Start: time.Now(),
|
||||
PairingKeyDSA: ec,
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PairingSession) PreparePayloads() ([]byte, []byte, error) {
|
||||
pubKey := &gmproto.GenericPublicKey{
|
||||
Type: gmproto.PublicKeyType_EC_P256,
|
||||
PublicKey: &gmproto.GenericPublicKey_EcP256PublicKey{
|
||||
EcP256PublicKey: &gmproto.EcP256PublicKey{
|
||||
X: make([]byte, 33),
|
||||
Y: make([]byte, 33),
|
||||
},
|
||||
},
|
||||
}
|
||||
ps.PairingKeyDSA.X.FillBytes(pubKey.GetEcP256PublicKey().GetX()[1:])
|
||||
ps.PairingKeyDSA.Y.FillBytes(pubKey.GetEcP256PublicKey().GetY()[1:])
|
||||
|
||||
finishPayload, err := proto.Marshal(&gmproto.Ukey2ClientFinished{
|
||||
PublicKey: pubKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal finish payload: %w", err)
|
||||
}
|
||||
finish, err := proto.Marshal(&gmproto.Ukey2Message{
|
||||
MessageType: gmproto.Ukey2Message_CLIENT_FINISH,
|
||||
MessageData: finishPayload,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal finish message: %w", err)
|
||||
}
|
||||
|
||||
keyCommitment := sha512.Sum512(finish)
|
||||
initPayload, err := proto.Marshal(&gmproto.Ukey2ClientInit{
|
||||
Version: 1,
|
||||
Random: random.Bytes(32),
|
||||
CipherCommitments: []*gmproto.Ukey2ClientInit_CipherCommitment{{
|
||||
HandshakeCipher: gmproto.Ukey2HandshakeCipher_P256_SHA512,
|
||||
Commitment: keyCommitment[:],
|
||||
}},
|
||||
NextProtocol: "AES_256_CBC-HMAC_SHA256",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal init payload: %w", err)
|
||||
}
|
||||
init, err := proto.Marshal(&gmproto.Ukey2Message{
|
||||
MessageType: gmproto.Ukey2Message_CLIENT_INIT,
|
||||
MessageData: initPayload,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal init message: %w", err)
|
||||
}
|
||||
ps.InitPayload = init
|
||||
return init, finish, nil
|
||||
}
|
||||
|
||||
func doHKDF(key []byte, salt, info []byte) []byte {
|
||||
h := hkdf.New(sha256.New, key, salt, info)
|
||||
out := make([]byte, 32)
|
||||
_, err := io.ReadFull(h, out)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var encryptionKeyInfo = []byte{130, 170, 85, 160, 211, 151, 248, 131, 70, 202, 28, 238, 141, 57, 9, 185, 95, 19, 250, 125, 235, 29, 74, 179, 131, 118, 184, 37, 109, 168, 85, 16}
|
||||
var pairingEmojis = []string{"😁", "😅", "🤣", "🫠", "🥰", "😇", "🤩", "😘", "😜", "🤗", "🤔", "🤐", "😴", "🥶", "🤯", "🤠", "🥳", "🥸", "😎", "🤓", "🧐", "🥹", "😭", "😱", "😖", "🥱", "😮\u200d💨", "🤡", "💩", "👻", "👽", "🤖", "😻", "💌", "💘", "💕", "❤", "💢", "💥", "💫", "💬", "🗯", "💤", "👋", "🙌", "🙏", "✍", "🦶", "👂", "🧠", "🦴", "👀", "🧑", "🧚", "🧍", "👣", "🐵", "🐶", "🐺", "🦊", "🦁", "🐯", "🦓", "🦄", "🐑", "🐮", "🐷", "🐿", "🐰", "🦇", "🐻", "🐨", "🐼", "🦥", "🐾", "🐔", "🐥", "🐦", "🕊", "🦆", "🦉", "🪶", "🦩", "🐸", "🐢", "🦎", "🐍", "🐳", "🐬", "🦭", "🐠", "🐡", "🦈", "🪸", "🐌", "🦋", "🐛", "🐝", "🐞", "🪱", "💐", "🌸", "🌹", "🌻", "🌱", "🌲", "🌴", "🌵", "🌾", "☘", "🍁", "🍂", "🍄", "🪺", "🍇", "🍈", "🍉", "🍋", "🍌", "🍍", "🍎", "🍐", "🍒", "🍓", "🥝", "🥥", "🥑", "🥕", "🌽", "🌶", "🫑", "🥦", "🥜", "🍞", "🥐", "🥨", "🧀", "🍗", "🍔", "🍟", "🍕", "🌭", "🌮", "🥗", "🥣", "🍿", "🦀", "🦑", "🍦", "🍩", "🍪", "🍫", "🍰", "🍬", "🍭", "☕", "🫖", "🍹", "🥤", "🧊", "🥢", "🍽", "🥄", "🧭", "🏔", "🌋", "🏕", "🏖", "🪵", "🏗", "🏡", "🏰", "🛝", "🚂", "🛵", "🛴", "🛼", "🚥", "⚓", "🛟", "⛵", "✈", "🚀", "🛸", "🧳", "⏰", "🌙", "🌡", "🌞", "🪐", "🌠", "🌧", "🌀", "🌈", "☂", "⚡", "❄", "⛄", "🔥", "🎇", "🧨", "✨", "🎈", "🎉", "🎁", "🏆", "🏅", "⚽", "⚾", "🏀", "🏐", "🏈", "🎾", "🎳", "🏓", "🥊", "⛳", "⛸", "🎯", "🪁", "🔮", "🎮", "🧩", "🧸", "🪩", "🖼", "🎨", "🧵", "🧶", "🦺", "🧣", "🧤", "🧦", "🎒", "🩴", "👟", "👑", "👒", "🎩", "🧢", "💎", "🔔", "🎤", "📻", "🎷", "🪗", "🎸", "🎺", "🎻", "🥁", "📺", "🔋", "💻", "💿", "☎", "🕯", "💡", "📖", "📚", "📬", "✏", "✒", "🖌", "🖍", "📝", "💼", "📋", "📌", "📎", "🔑", "🔧", "🧲", "🪜", "🧬", "🔭", "🩹", "🩺", "🪞", "🛋", "🪑", "🛁", "🧹", "🧺", "🔱", "🏁", "🐪", "🐘", "🦃", "🍞", "🍜", "🍠", "🚘", "🤿", "🃏", "👕", "📸", "🏷", "✂", "🧪", "🚪", "🧴", "🧻", "🪣", "🧽", "🚸"}
|
||||
|
||||
func (ps *PairingSession) ProcessServerInit(msg *gmproto.GaiaPairingResponseContainer) (string, error) {
|
||||
var ukeyMessage gmproto.Ukey2Message
|
||||
err := proto.Unmarshal(msg.GetData(), &ukeyMessage)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal server init message: %w", err)
|
||||
} else if ukeyMessage.GetMessageType() != gmproto.Ukey2Message_SERVER_INIT {
|
||||
return "", fmt.Errorf("unexpected message type: %v", ukeyMessage.GetMessageType())
|
||||
}
|
||||
var serverInit gmproto.Ukey2ServerInit
|
||||
err = proto.Unmarshal(ukeyMessage.GetMessageData(), &serverInit)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal server init payload: %w", err)
|
||||
} else if serverInit.GetVersion() != 1 {
|
||||
return "", fmt.Errorf("unexpected server init version: %d", serverInit.GetVersion())
|
||||
} else if serverInit.GetHandshakeCipher() != gmproto.Ukey2HandshakeCipher_P256_SHA512 {
|
||||
return "", fmt.Errorf("unexpected handshake cipher: %v", serverInit.GetHandshakeCipher())
|
||||
} else if len(serverInit.GetRandom()) != 32 {
|
||||
return "", fmt.Errorf("unexpected random length %d", len(serverInit.GetRandom()))
|
||||
}
|
||||
serverKeyData := serverInit.GetPublicKey().GetEcP256PublicKey()
|
||||
x, y := serverKeyData.GetX(), serverKeyData.GetY()
|
||||
if len(x) == 33 {
|
||||
if x[0] != 0 {
|
||||
return "", fmt.Errorf("server key x coordinate has unexpected prefix: %d", x[0])
|
||||
}
|
||||
x = x[1:]
|
||||
}
|
||||
if len(y) == 33 {
|
||||
if y[0] != 0 {
|
||||
return "", fmt.Errorf("server key y coordinate has unexpected prefix: %d", y[0])
|
||||
}
|
||||
y = y[1:]
|
||||
}
|
||||
serverPairingKeyDSA := &ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
X: big.NewInt(0).SetBytes(x),
|
||||
Y: big.NewInt(0).SetBytes(y),
|
||||
}
|
||||
serverPairingKeyDH, err := serverPairingKeyDSA.ECDH()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid server key: %w", err)
|
||||
}
|
||||
ourPairingKeyDH, err := ps.PairingKeyDSA.ECDH()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid our key: %w", err)
|
||||
}
|
||||
diffieHellman, err := ourPairingKeyDH.ECDH(serverPairingKeyDH)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to calculate shared secret: %w", err)
|
||||
}
|
||||
sharedSecret := sha256.Sum256(diffieHellman)
|
||||
authInfo := append(ps.InitPayload, msg.GetData()...)
|
||||
ukeyV1Auth := doHKDF(sharedSecret[:], []byte("UKEY2 v1 auth"), authInfo)
|
||||
ps.NextKey = doHKDF(sharedSecret[:], []byte("UKEY2 v1 next"), authInfo)
|
||||
authNumber := binary.BigEndian.Uint32(ukeyV1Auth)
|
||||
pairingEmoji := pairingEmojis[int(authNumber)%len(pairingEmojis)]
|
||||
return pairingEmoji, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoCookies = errors.New("gaia pairing requires cookies")
|
||||
ErrNoDevicesFound = errors.New("no devices found for gaia pairing")
|
||||
ErrIncorrectEmoji = errors.New("user chose incorrect emoji on phone")
|
||||
ErrPairingCancelled = errors.New("user cancelled pairing on phone")
|
||||
ErrPairingTimeout = errors.New("pairing timed out")
|
||||
)
|
||||
|
||||
func (c *Client) DoGaiaPairing(emojiCallback func(string)) error {
|
||||
if len(c.AuthData.Cookies) == 0 {
|
||||
return ErrNoCookies
|
||||
}
|
||||
sigResp, err := c.signInGaiaGetToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare gaia pairing: %w", err)
|
||||
}
|
||||
// TODO multiple devices?
|
||||
var destRegID string
|
||||
for _, dev := range sigResp.GetDeviceData().GetUnknownItems2() {
|
||||
if dev.GetUnknownInt4() == 1 {
|
||||
destRegID = dev.GetDestOrSourceUUID()
|
||||
break
|
||||
}
|
||||
}
|
||||
if destRegID == "" {
|
||||
return ErrNoDevicesFound
|
||||
}
|
||||
destRegUUID, err := uuid.Parse(destRegID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse destination UUID: %w", err)
|
||||
}
|
||||
c.AuthData.DestRegID = destRegUUID
|
||||
go c.doLongPoll(false)
|
||||
ps := NewPairingSession()
|
||||
clientInit, clientFinish, err := ps.PreparePayloads()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare pairing payloads: %w", err)
|
||||
}
|
||||
serverInit, err := c.sendGaiaPairingMessage(ps, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT, clientInit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send client init: %w", err)
|
||||
}
|
||||
pairingEmoji, err := ps.ProcessServerInit(serverInit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing server init: %w", err)
|
||||
}
|
||||
emojiCallback(pairingEmoji)
|
||||
finishResp, err := c.sendGaiaPairingMessage(ps, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED, clientFinish)
|
||||
if finishResp.GetFinishErrorType() != 0 {
|
||||
switch finishResp.GetFinishErrorCode() {
|
||||
case 5:
|
||||
return ErrIncorrectEmoji
|
||||
case 7:
|
||||
return ErrPairingCancelled
|
||||
case 6, 2, 3:
|
||||
return fmt.Errorf("%w (code: %d/%d)", ErrPairingTimeout, finishResp.GetFinishErrorType(), finishResp.GetFinishErrorCode())
|
||||
default:
|
||||
return fmt.Errorf("unknown error pairing: %d/%d", finishResp.GetFinishErrorType(), finishResp.GetFinishErrorCode())
|
||||
}
|
||||
}
|
||||
c.AuthData.RequestCrypto.AESKey = doHKDF(ps.NextKey, encryptionKeyInfo, []byte("client"))
|
||||
c.AuthData.RequestCrypto.HMACKey = doHKDF(ps.NextKey, encryptionKeyInfo, []byte("server"))
|
||||
c.AuthData.PairingID = ps.UUID
|
||||
c.triggerEvent(&events.PairSuccessful{})
|
||||
|
||||
go func() {
|
||||
// Sleep for a bit to let the phone save the pair data. If we reconnect too quickly,
|
||||
// the phone won't recognize the session the bridge will get unpaired.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
err := c.Reconnect()
|
||||
if err != nil {
|
||||
c.triggerEvent(&events.ListenFatalError{Error: fmt.Errorf("failed to reconnect after pair success: %w", err)})
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) sendGaiaPairingMessage(sess PairingSession, action gmproto.ActionType, msg []byte) (*gmproto.GaiaPairingResponseContainer, error) {
|
||||
resp, err := c.sessionHandler.sendMessageWithParams(SendMessageParams{
|
||||
Action: action,
|
||||
Data: &gmproto.GaiaPairingRequestContainer{
|
||||
PairingAttemptID: sess.UUID.String(),
|
||||
BrowserDetails: util.BrowserDetailsMessage,
|
||||
StartTimestamp: sess.Start.UnixMilli(),
|
||||
Data: msg,
|
||||
},
|
||||
DontEncrypt: true,
|
||||
CustomTTL: (300 * time.Second).Microseconds(),
|
||||
MessageType: gmproto.MessageType_GAIA_2,
|
||||
|
||||
NoPingOnTimeout: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var respDat gmproto.GaiaPairingResponseContainer
|
||||
err = proto.Unmarshal(resp.Message.UnencryptedData, &respDat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &respDat, nil
|
||||
}
|
||||
|
||||
func (c *Client) UnpairGaia() error {
|
||||
return c.sessionHandler.sendMessageNoResponse(SendMessageParams{
|
||||
Action: gmproto.ActionType_UNPAIR_GAIA_PAIRING,
|
||||
Data: &gmproto.RevokeGaiaPairingRequest{
|
||||
PairingAttemptID: c.AuthData.PairingID.String(),
|
||||
},
|
||||
NoPingOnTimeout: true,
|
||||
})
|
||||
}
|
|
@ -4,9 +4,13 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
|
||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
||||
)
|
||||
|
||||
func Unmarshal(data []byte, m proto.Message) error {
|
||||
|
@ -21,6 +25,12 @@ func Unmarshal(data []byte, m proto.Message) error {
|
|||
return deserializeFromSlice(anyDataArr, m.ProtoReflect())
|
||||
}
|
||||
|
||||
func isPbliteBinary(descriptor protoreflect.FieldDescriptor) bool {
|
||||
opts := descriptor.Options().(*descriptorpb.FieldOptions)
|
||||
pbliteBinary, ok := proto.GetExtension(opts, gmproto.E_PbliteBinary).(bool)
|
||||
return ok && pbliteBinary
|
||||
}
|
||||
|
||||
func deserializeOne(val any, index int, ref protoreflect.Message, insideList protoreflect.List, fieldDescriptor protoreflect.FieldDescriptor) (protoreflect.Value, error) {
|
||||
var num float64
|
||||
var expectedKind, str string
|
||||
|
@ -45,18 +55,33 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
|
|||
switch fieldDescriptor.Kind() {
|
||||
case protoreflect.MessageKind:
|
||||
ok = true
|
||||
nestedData, ok := val.([]any)
|
||||
if !ok {
|
||||
return outputVal, fmt.Errorf("expected untyped array at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val)
|
||||
}
|
||||
var nestedMessage protoreflect.Message
|
||||
if insideList != nil {
|
||||
nestedMessage = insideList.NewElement().Message()
|
||||
} else {
|
||||
nestedMessage = ref.NewField(fieldDescriptor).Message()
|
||||
}
|
||||
if err := deserializeFromSlice(nestedData, nestedMessage); err != nil {
|
||||
return outputVal, err
|
||||
if isPbliteBinary(fieldDescriptor) {
|
||||
bytesBase64, ok := val.(string)
|
||||
if !ok {
|
||||
return outputVal, fmt.Errorf("expected string at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val)
|
||||
}
|
||||
bytes, err := base64.StdEncoding.DecodeString(bytesBase64)
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
err = proto.Unmarshal(bytes, nestedMessage.Interface())
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to unmarshal binary protobuf at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
} else {
|
||||
nestedData, ok := val.([]any)
|
||||
if !ok {
|
||||
return outputVal, fmt.Errorf("expected untyped array at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val)
|
||||
}
|
||||
if err := deserializeFromSlice(nestedData, nestedMessage); err != nil {
|
||||
return outputVal, err
|
||||
}
|
||||
}
|
||||
outputVal = protoreflect.ValueOfMessage(nestedMessage)
|
||||
case protoreflect.BytesKind:
|
||||
|
@ -76,21 +101,53 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
|
|||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfEnum(protoreflect.EnumNumber(int32(num)))
|
||||
case protoreflect.Int32Kind:
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfInt32(int32(num))
|
||||
if str, ok = val.(string); ok {
|
||||
parsedVal, err := strconv.ParseInt(str, 10, 32)
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to parse int32 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
outputVal = protoreflect.ValueOfInt32(int32(parsedVal))
|
||||
} else {
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfInt32(int32(num))
|
||||
}
|
||||
case protoreflect.Int64Kind:
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfInt64(int64(num))
|
||||
if str, ok = val.(string); ok {
|
||||
parsedVal, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to parse int64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
outputVal = protoreflect.ValueOfInt64(parsedVal)
|
||||
} else {
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfInt64(int64(num))
|
||||
}
|
||||
case protoreflect.Uint32Kind:
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfUint32(uint32(num))
|
||||
if str, ok = val.(string); ok {
|
||||
parsedVal, err := strconv.ParseUint(str, 10, 32)
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to parse uint32 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
outputVal = protoreflect.ValueOfUint32(uint32(parsedVal))
|
||||
} else {
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfUint32(uint32(num))
|
||||
}
|
||||
case protoreflect.Uint64Kind:
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfUint64(uint64(num))
|
||||
if str, ok = val.(string); ok {
|
||||
parsedVal, err := strconv.ParseUint(str, 10, 64)
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to parse uint64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
outputVal = protoreflect.ValueOfUint64(parsedVal)
|
||||
} else {
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
outputVal = protoreflect.ValueOfUint64(uint64(num))
|
||||
}
|
||||
case protoreflect.FloatKind:
|
||||
num, ok = val.(float64)
|
||||
expectedKind = "float64"
|
||||
|
@ -101,6 +158,13 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
|
|||
outputVal = protoreflect.ValueOfFloat64(num)
|
||||
case protoreflect.StringKind:
|
||||
str, ok = val.(string)
|
||||
if ok && isPbliteBinary(fieldDescriptor) {
|
||||
bytes, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
|
||||
}
|
||||
str = string(bytes)
|
||||
}
|
||||
expectedKind = "string"
|
||||
outputVal = protoreflect.ValueOfString(str)
|
||||
case protoreflect.BoolKind:
|
||||
|
|
|
@ -82,11 +82,19 @@ func serializeOneOrList(fieldDescriptor protoreflect.FieldDescriptor, fieldValue
|
|||
func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) {
|
||||
switch fieldDescriptor.Kind() {
|
||||
case protoreflect.MessageKind:
|
||||
serializedMsg, err := SerializeToSlice(fieldValue.Message().Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if isPbliteBinary(fieldDescriptor) {
|
||||
serializedMsg, err := proto.Marshal(fieldValue.Message().Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(serializedMsg), nil
|
||||
} else {
|
||||
serializedMsg, err := SerializeToSlice(fieldValue.Message().Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return serializedMsg, nil
|
||||
}
|
||||
return serializedMsg, nil
|
||||
case protoreflect.BytesKind:
|
||||
return base64.StdEncoding.EncodeToString(fieldValue.Bytes()), nil
|
||||
case protoreflect.Int32Kind, protoreflect.Int64Kind:
|
||||
|
@ -100,7 +108,11 @@ func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue proto
|
|||
case protoreflect.BoolKind:
|
||||
return fieldValue.Bool(), nil
|
||||
case protoreflect.StringKind:
|
||||
return fieldValue.String(), nil
|
||||
if isPbliteBinary(fieldDescriptor) {
|
||||
return base64.StdEncoding.EncodeToString([]byte(fieldValue.String())), nil
|
||||
} else {
|
||||
return fieldValue.String(), nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported field type %s in %s", fieldDescriptor.Kind(), fieldDescriptor.FullName())
|
||||
}
|
||||
|
|
54
libgm/pblitedecode/main.go
Normal file
54
libgm/pblitedecode/main.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/dynamicpb"
|
||||
|
||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
||||
)
|
||||
|
||||
func must[T any](t T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
files := []protoreflect.FileDescriptor{
|
||||
gmproto.File_authentication_proto,
|
||||
gmproto.File_config_proto,
|
||||
gmproto.File_client_proto,
|
||||
gmproto.File_conversations_proto,
|
||||
gmproto.File_events_proto,
|
||||
gmproto.File_rpc_proto,
|
||||
gmproto.File_settings_proto,
|
||||
gmproto.File_util_proto,
|
||||
gmproto.File_ukey_proto,
|
||||
}
|
||||
var msgDesc protoreflect.MessageDescriptor
|
||||
for _, file := range files {
|
||||
msgDesc = file.Messages().ByName(protoreflect.Name(os.Args[1]))
|
||||
if msgDesc != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msgDesc == nil {
|
||||
fmt.Println("Message not found")
|
||||
os.Exit(1)
|
||||
}
|
||||
msg := dynamicpb.NewMessage(msgDesc)
|
||||
|
||||
err := pblite.Unmarshal(must(io.ReadAll(os.Stdin)), msg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
fmt.Println(prototext.Format(msg))
|
||||
}
|
|
@ -37,8 +37,12 @@ func (s *SessionHandler) sendMessageNoResponse(params SendMessageParams) error {
|
|||
return err
|
||||
}
|
||||
|
||||
url := util.SendMessageURL
|
||||
if s.client.AuthData.Cookies != nil {
|
||||
url = util.SendMessageURLGoogle
|
||||
}
|
||||
_, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse](
|
||||
s.client.makeProtobufHTTPRequest(util.SendMessageURL, payload, ContentTypePBLite),
|
||||
s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
@ -50,8 +54,12 @@ func (s *SessionHandler) sendAsyncMessage(params SendMessageParams) (<-chan *Inc
|
|||
}
|
||||
|
||||
ch := s.waitResponse(requestID)
|
||||
url := util.SendMessageURL
|
||||
if s.client.AuthData.Cookies != nil {
|
||||
url = util.SendMessageURLGoogle
|
||||
}
|
||||
_, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse](
|
||||
s.client.makeProtobufHTTPRequest(util.SendMessageURL, payload, ContentTypePBLite),
|
||||
s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
|
||||
)
|
||||
if err != nil {
|
||||
s.cancelResponse(requestID, ch)
|
||||
|
@ -68,7 +76,7 @@ func typedResponse[T proto.Message](resp *IncomingRPCMessage, err error) (casted
|
|||
var ok bool
|
||||
casted, ok = resp.DecryptedMessage.(T)
|
||||
if !ok {
|
||||
retErr = fmt.Errorf("unexpected response type %T, expected %T", resp.DecryptedMessage, casted)
|
||||
retErr = fmt.Errorf("unexpected response type %T for %s, expected %T", resp.DecryptedMessage, resp.ResponseID, casted)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -92,6 +100,17 @@ func (s *SessionHandler) receiveResponse(msg *IncomingRPCMessage) bool {
|
|||
if msg.Message == nil {
|
||||
return false
|
||||
}
|
||||
if s.client.AuthData.Cookies != nil {
|
||||
switch msg.Message.Action {
|
||||
case gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED:
|
||||
default:
|
||||
// Very hacky way to ignore weird messages that come before real responses
|
||||
// TODO figure out how to properly handle these
|
||||
if msg.Message.UnencryptedData != nil && msg.Message.EncryptedData == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
requestID := msg.Message.SessionID
|
||||
s.responseWaitersLock.Lock()
|
||||
ch, ok := s.responseWaiters[requestID]
|
||||
|
@ -122,6 +141,10 @@ func (s *SessionHandler) sendMessageWithParams(params SendMessageParams) (*Incom
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if params.NoPingOnTimeout {
|
||||
return <-ch, nil
|
||||
}
|
||||
|
||||
select {
|
||||
case resp := <-ch:
|
||||
return resp, nil
|
||||
|
@ -147,19 +170,21 @@ type SendMessageParams struct {
|
|||
Action gmproto.ActionType
|
||||
Data proto.Message
|
||||
|
||||
UseSessionID bool
|
||||
OmitTTL bool
|
||||
MessageType gmproto.MessageType
|
||||
RequestID string
|
||||
OmitTTL bool
|
||||
CustomTTL int64
|
||||
DontEncrypt bool
|
||||
MessageType gmproto.MessageType
|
||||
|
||||
NoPingOnTimeout bool
|
||||
}
|
||||
|
||||
func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.Message, error) {
|
||||
var requestID string
|
||||
var err error
|
||||
sessionID := s.client.sessionHandler.sessionID
|
||||
|
||||
if params.UseSessionID {
|
||||
requestID = s.sessionID
|
||||
} else {
|
||||
requestID := params.RequestID
|
||||
if requestID == "" {
|
||||
requestID = uuid.NewString()
|
||||
}
|
||||
|
||||
|
@ -182,28 +207,38 @@ func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.M
|
|||
TachyonAuthToken: s.client.AuthData.TachyonAuthToken,
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
EmptyArr: &gmproto.EmptyArr{},
|
||||
DestRegistrationIDs: []string{},
|
||||
}
|
||||
if !params.OmitTTL {
|
||||
if s.client.AuthData != nil && s.client.AuthData.DestRegID != uuid.Nil {
|
||||
message.DestRegistrationIDs = append(message.DestRegistrationIDs, s.client.AuthData.DestRegID.String())
|
||||
}
|
||||
if params.CustomTTL != 0 {
|
||||
message.TTL = params.CustomTTL
|
||||
} else if !params.OmitTTL {
|
||||
message.TTL = s.client.AuthData.TachyonTTL
|
||||
}
|
||||
var encryptedData []byte
|
||||
var encryptedData, unencryptedData []byte
|
||||
if params.Data != nil {
|
||||
var serializedData []byte
|
||||
serializedData, err = proto.Marshal(params.Data)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
encryptedData, err = s.client.AuthData.RequestCrypto.Encrypt(serializedData)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
if params.DontEncrypt {
|
||||
unencryptedData = serializedData
|
||||
} else {
|
||||
encryptedData, err = s.client.AuthData.RequestCrypto.Encrypt(serializedData)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
message.Data.MessageData, err = proto.Marshal(&gmproto.OutgoingRPCData{
|
||||
RequestID: requestID,
|
||||
Action: params.Action,
|
||||
EncryptedProtoData: encryptedData,
|
||||
SessionID: sessionID,
|
||||
RequestID: requestID,
|
||||
Action: params.Action,
|
||||
UnencryptedProtoData: unencryptedData,
|
||||
EncryptedProtoData: encryptedData,
|
||||
SessionID: sessionID,
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
@ -255,13 +290,18 @@ func (s *SessionHandler) sendAckRequest() {
|
|||
AuthData: &gmproto.AuthMessage{
|
||||
RequestID: uuid.NewString(),
|
||||
TachyonAuthToken: s.client.AuthData.TachyonAuthToken,
|
||||
Network: s.client.AuthData.AuthNetwork(),
|
||||
ConfigVersion: util.ConfigMessage,
|
||||
},
|
||||
EmptyArr: &gmproto.EmptyArr{},
|
||||
Acks: ackMessages,
|
||||
}
|
||||
url := util.AckMessagesURL
|
||||
if s.client.AuthData.Cookies != nil {
|
||||
url = util.AckMessagesURLGoogle
|
||||
}
|
||||
_, err := typedHTTPResponse[*gmproto.OutgoingRPCResponse](
|
||||
s.client.makeProtobufHTTPRequest(util.AckMessagesURL, payload, ContentTypePBLite),
|
||||
s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
|
||||
)
|
||||
if err != nil {
|
||||
// TODO retry?
|
||||
|
|
|
@ -5,13 +5,16 @@ import (
|
|||
)
|
||||
|
||||
var ConfigMessage = &gmproto.ConfigVersion{
|
||||
Year: 2023,
|
||||
Month: 9,
|
||||
Day: 28,
|
||||
Year: 2024,
|
||||
Month: 2,
|
||||
Day: 20,
|
||||
V1: 4,
|
||||
V2: 6,
|
||||
}
|
||||
var Network = "Bugle"
|
||||
|
||||
const QRNetwork = "Bugle"
|
||||
const GoogleNetwork = "GDitto"
|
||||
|
||||
var BrowserDetailsMessage = &gmproto.BrowserDetails{
|
||||
UserAgent: UserAgent,
|
||||
BrowserType: gmproto.BrowserType_OTHER,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package util
|
||||
|
||||
const GoogleAPIKey = "AIzaSyCA4RsOZUFrm9whhtGosPlJLmVPnfSHKz8"
|
||||
const UserAgent = "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
|
||||
const UserAgent = "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
|
||||
const SecUA = `"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`
|
||||
const UAPlatform = "Android"
|
||||
const XUserAgent = "grpc-web-javascript/0.1"
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
||||
)
|
||||
|
||||
func GenerateTmpID() string {
|
||||
|
@ -19,119 +15,71 @@ func GenerateTmpID() string {
|
|||
}
|
||||
|
||||
func BuildRelayHeaders(req *http.Request, contentType string, accept string) {
|
||||
req.Header.Add("host", "instantmessaging-pa.googleapis.com")
|
||||
req.Header.Add("connection", "keep-alive")
|
||||
req.Header.Add("sec-ch-ua", SecUA)
|
||||
req.Header.Add("x-user-agent", XUserAgent)
|
||||
req.Header.Add("x-goog-api-key", GoogleAPIKey)
|
||||
//req.Header.Set("host", "instantmessaging-pa.googleapis.com")
|
||||
req.Header.Set("sec-ch-ua", SecUA)
|
||||
req.Header.Set("x-user-agent", XUserAgent)
|
||||
req.Header.Set("x-goog-api-key", GoogleAPIKey)
|
||||
if len(contentType) > 0 {
|
||||
req.Header.Add("content-type", contentType)
|
||||
req.Header.Set("content-type", contentType)
|
||||
}
|
||||
req.Header.Add("sec-ch-ua-mobile", SecUAMobile)
|
||||
req.Header.Add("user-agent", UserAgent)
|
||||
req.Header.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"")
|
||||
req.Header.Add("accept", accept)
|
||||
req.Header.Add("origin", "https://messages.google.com")
|
||||
req.Header.Add("sec-fetch-site", "cross-site")
|
||||
req.Header.Add("sec-fetch-mode", "cors")
|
||||
req.Header.Add("sec-fetch-dest", "empty")
|
||||
req.Header.Add("referer", "https://messages.google.com/")
|
||||
req.Header.Add("accept-language", "en-US,en;q=0.9")
|
||||
req.Header.Set("sec-ch-ua-mobile", SecUAMobile)
|
||||
req.Header.Set("user-agent", UserAgent)
|
||||
req.Header.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
|
||||
req.Header.Set("accept", accept)
|
||||
req.Header.Set("origin", "https://messages.google.com")
|
||||
req.Header.Set("sec-fetch-site", "cross-site")
|
||||
req.Header.Set("sec-fetch-mode", "cors")
|
||||
req.Header.Set("sec-fetch-dest", "empty")
|
||||
req.Header.Set("referer", "https://messages.google.com/")
|
||||
req.Header.Set("accept-language", "en-US,en;q=0.9")
|
||||
}
|
||||
|
||||
func BuildUploadHeaders(req *http.Request, metadata string) {
|
||||
req.Header.Add("host", "instantmessaging-pa.googleapis.com")
|
||||
req.Header.Add("connection", "keep-alive")
|
||||
req.Header.Add("x-goog-download-metadata", metadata)
|
||||
req.Header.Add("sec-ch-ua", SecUA)
|
||||
req.Header.Add("sec-ch-ua-mobile", SecUAMobile)
|
||||
req.Header.Add("user-agent", UserAgent)
|
||||
req.Header.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"")
|
||||
req.Header.Add("accept", "*/*")
|
||||
req.Header.Add("origin", "https://messages.google.com")
|
||||
req.Header.Add("sec-fetch-site", "cross-site")
|
||||
req.Header.Add("sec-fetch-mode", "cors")
|
||||
req.Header.Add("sec-fetch-dest", "empty")
|
||||
req.Header.Add("referer", "https://messages.google.com/")
|
||||
req.Header.Add("accept-encoding", "gzip, deflate, br")
|
||||
req.Header.Add("accept-language", "en-US,en;q=0.9")
|
||||
//req.Header.Set("host", "instantmessaging-pa.googleapis.com")
|
||||
req.Header.Set("x-goog-download-metadata", metadata)
|
||||
req.Header.Set("sec-ch-ua", SecUA)
|
||||
req.Header.Set("sec-ch-ua-mobile", SecUAMobile)
|
||||
req.Header.Set("user-agent", UserAgent)
|
||||
req.Header.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
|
||||
req.Header.Set("accept", "*/*")
|
||||
req.Header.Set("origin", "https://messages.google.com")
|
||||
req.Header.Set("sec-fetch-site", "cross-site")
|
||||
req.Header.Set("sec-fetch-mode", "cors")
|
||||
req.Header.Set("sec-fetch-dest", "empty")
|
||||
req.Header.Set("referer", "https://messages.google.com/")
|
||||
req.Header.Set("accept-encoding", "gzip, deflate, br")
|
||||
req.Header.Set("accept-language", "en-US,en;q=0.9")
|
||||
}
|
||||
|
||||
func NewMediaUploadHeaders(imageSize string, command string, uploadOffset string, imageContentType string, protocol string) *http.Header {
|
||||
headers := &http.Header{}
|
||||
|
||||
headers.Add("host", "instantmessaging-pa.googleapis.com")
|
||||
headers.Add("connection", "keep-alive")
|
||||
headers.Add("sec-ch-ua", SecUA)
|
||||
headers.Set("host", "instantmessaging-pa.googleapis.com")
|
||||
headers.Set("sec-ch-ua", SecUA)
|
||||
if protocol != "" {
|
||||
headers.Add("x-goog-upload-protocol", protocol)
|
||||
headers.Set("x-goog-upload-protocol", protocol)
|
||||
}
|
||||
headers.Add("x-goog-upload-header-content-length", imageSize)
|
||||
headers.Add("sec-ch-ua-mobile", SecUAMobile)
|
||||
headers.Add("user-agent", UserAgent)
|
||||
headers.Set("x-goog-upload-header-content-length", imageSize)
|
||||
headers.Set("sec-ch-ua-mobile", SecUAMobile)
|
||||
headers.Set("user-agent", UserAgent)
|
||||
if imageContentType != "" {
|
||||
headers.Add("x-goog-upload-header-content-type", imageContentType)
|
||||
headers.Set("x-goog-upload-header-content-type", imageContentType)
|
||||
}
|
||||
headers.Add("content-type", "application/x-www-form-urlencoded;charset=UTF-8")
|
||||
headers.Set("content-type", "application/x-www-form-urlencoded;charset=UTF-8")
|
||||
if command != "" {
|
||||
headers.Add("x-goog-upload-command", command)
|
||||
headers.Set("x-goog-upload-command", command)
|
||||
}
|
||||
if uploadOffset != "" {
|
||||
headers.Add("x-goog-upload-offset", uploadOffset)
|
||||
headers.Set("x-goog-upload-offset", uploadOffset)
|
||||
}
|
||||
headers.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"")
|
||||
headers.Add("accept", "*/*")
|
||||
headers.Add("origin", "https://messages.google.com")
|
||||
headers.Add("sec-fetch-site", "cross-site")
|
||||
headers.Add("sec-fetch-mode", "cors")
|
||||
headers.Add("sec-fetch-dest", "empty")
|
||||
headers.Add("referer", "https://messages.google.com/")
|
||||
headers.Add("accept-encoding", "gzip, deflate, br")
|
||||
headers.Add("accept-language", "en-US,en;q=0.9")
|
||||
headers.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
|
||||
headers.Set("accept", "*/*")
|
||||
headers.Set("origin", "https://messages.google.com")
|
||||
headers.Set("sec-fetch-site", "cross-site")
|
||||
headers.Set("sec-fetch-mode", "cors")
|
||||
headers.Set("sec-fetch-dest", "empty")
|
||||
headers.Set("referer", "https://messages.google.com/")
|
||||
headers.Set("accept-encoding", "gzip, deflate, br")
|
||||
headers.Set("accept-language", "en-US,en;q=0.9")
|
||||
return headers
|
||||
}
|
||||
|
||||
func ParseConfigVersion(res []byte) (*gmproto.ConfigVersion, error) {
|
||||
var data []interface{}
|
||||
|
||||
marshalErr := json.Unmarshal(res, &data)
|
||||
if marshalErr != nil {
|
||||
return nil, marshalErr
|
||||
}
|
||||
|
||||
version := data[0].(string)
|
||||
v1 := version[0:4]
|
||||
v2 := version[4:6]
|
||||
v3 := version[6:8]
|
||||
|
||||
if v2[0] == 48 {
|
||||
v2 = string(v2[1])
|
||||
}
|
||||
if v3[0] == 48 {
|
||||
v3 = string(v3[1])
|
||||
}
|
||||
|
||||
first, e := strconv.Atoi(v1)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
second, e1 := strconv.Atoi(v2)
|
||||
if e1 != nil {
|
||||
return nil, e1
|
||||
}
|
||||
|
||||
third, e2 := strconv.Atoi(v3)
|
||||
if e2 != nil {
|
||||
return nil, e2
|
||||
}
|
||||
|
||||
configMessage := &gmproto.ConfigVersion{
|
||||
Year: int32(first),
|
||||
Month: int32(second),
|
||||
Day: int32(third),
|
||||
V1: 4,
|
||||
V2: 6,
|
||||
}
|
||||
return configMessage, nil
|
||||
}
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
package util
|
||||
|
||||
const messagesBaseURL = "https://messages.google.com"
|
||||
const MessagesBaseURL = "https://messages.google.com"
|
||||
|
||||
const GoogleAuthenticationURL = messagesBaseURL + "/web/authentication"
|
||||
const GoogleTimesourceURL = messagesBaseURL + "/web/timesource"
|
||||
const GoogleAuthenticationURL = MessagesBaseURL + "/web/authentication"
|
||||
const GoogleTimesourceURL = MessagesBaseURL + "/web/timesource"
|
||||
|
||||
const instantMessangingBaseURL = "https://instantmessaging-pa.googleapis.com"
|
||||
const instantMessagingBaseURL = "https://instantmessaging-pa.googleapis.com"
|
||||
const instantMessagingBaseURLGoogle = "https://instantmessaging-pa.clients6.google.com"
|
||||
|
||||
const UploadMediaURL = instantMessangingBaseURL + "/upload"
|
||||
const UploadMediaURL = instantMessagingBaseURL + "/upload"
|
||||
|
||||
const pairingBaseURL = instantMessangingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Pairing"
|
||||
const pairingBaseURL = instantMessagingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Pairing"
|
||||
const RegisterPhoneRelayURL = pairingBaseURL + "/RegisterPhoneRelay"
|
||||
const RefreshPhoneRelayURL = pairingBaseURL + "/RefreshPhoneRelay"
|
||||
const GetWebEncryptionKeyURL = pairingBaseURL + "/GetWebEncryptionKey"
|
||||
const RevokeRelayPairingURL = pairingBaseURL + "/RevokeRelayPairing"
|
||||
|
||||
const messagingBaseURL = instantMessangingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Messaging"
|
||||
const messagingBaseURL = instantMessagingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Messaging"
|
||||
const messagingBaseURLGoogle = instantMessagingBaseURLGoogle + "/$rpc/google.internal.communications.instantmessaging.v1.Messaging"
|
||||
const ReceiveMessagesURL = messagingBaseURL + "/ReceiveMessages"
|
||||
const SendMessageURL = messagingBaseURL + "/SendMessage"
|
||||
const AckMessagesURL = messagingBaseURL + "/AckMessages"
|
||||
const ReceiveMessagesURLGoogle = messagingBaseURLGoogle + "/ReceiveMessages"
|
||||
const SendMessageURLGoogle = messagingBaseURLGoogle + "/SendMessage"
|
||||
const AckMessagesURLGoogle = messagingBaseURLGoogle + "/AckMessages"
|
||||
|
||||
const registrationBaseURL = instantMessangingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Registration"
|
||||
const registrationBaseURL = instantMessagingBaseURLGoogle + "/$rpc/google.internal.communications.instantmessaging.v1.Registration"
|
||||
const SignInGaiaURL = registrationBaseURL + "/SignInGaia"
|
||||
const RegisterRefreshURL = registrationBaseURL + "/RegisterRefresh"
|
||||
|
||||
const ConfigURL = "https://messages.google.com/web/config"
|
||||
|
|
|
@ -51,6 +51,8 @@ func (prov *ProvisioningAPI) Init() {
|
|||
r.Use(prov.AuthMiddleware)
|
||||
r.HandleFunc("/v1/ping", prov.Ping).Methods(http.MethodGet)
|
||||
r.HandleFunc("/v1/login", prov.Login).Methods(http.MethodPost)
|
||||
r.HandleFunc("/v1/google_login/emoji", prov.GoogleLoginStart).Methods(http.MethodPost)
|
||||
r.HandleFunc("/v1/google_login/wait", prov.GoogleLoginWait).Methods(http.MethodPost)
|
||||
r.HandleFunc("/v1/logout", prov.Logout).Methods(http.MethodPost)
|
||||
r.HandleFunc("/v1/delete_session", prov.DeleteSession).Methods(http.MethodPost)
|
||||
r.HandleFunc("/v1/disconnect", prov.Disconnect).Methods(http.MethodPost)
|
||||
|
@ -309,6 +311,68 @@ func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) {
|
|||
jsonResponse(w, http.StatusOK, Response{true, "Logged out successfully."})
|
||||
}
|
||||
|
||||
type ReqGoogleLoginStart struct {
|
||||
Cookies map[string]string
|
||||
}
|
||||
|
||||
type RespGoogleLoginStart struct {
|
||||
Status string `json:"status"`
|
||||
Emoji string `json:"emoji"`
|
||||
}
|
||||
|
||||
func (prov *ProvisioningAPI) GoogleLoginStart(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.URL.Query().Get("user_id")
|
||||
user := prov.bridge.GetUserByMXID(id.UserID(userID))
|
||||
|
||||
log := prov.zlog.With().Str("user_id", user.MXID.String()).Str("endpoint", "login").Logger()
|
||||
|
||||
if user.IsLoggedIn() {
|
||||
jsonResponse(w, http.StatusOK, LoginResponse{Status: "success", ErrCode: "already logged in"})
|
||||
return
|
||||
}
|
||||
var req ReqGoogleLoginStart
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, Error{
|
||||
Error: "Failed to parse request JSON",
|
||||
ErrCode: "bad json",
|
||||
})
|
||||
}
|
||||
emoji, err := user.AsyncLoginGoogleStart(req.Cookies)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to start login")
|
||||
// TODO proper error codes
|
||||
jsonResponse(w, http.StatusInternalServerError, Error{
|
||||
Error: "Failed to start login",
|
||||
ErrCode: "start login fail",
|
||||
})
|
||||
return
|
||||
}
|
||||
jsonResponse(w, http.StatusOK, &RespGoogleLoginStart{Status: "emoji", Emoji: emoji})
|
||||
}
|
||||
|
||||
func (prov *ProvisioningAPI) GoogleLoginWait(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.URL.Query().Get("user_id")
|
||||
user := prov.bridge.GetUserByMXID(id.UserID(userID))
|
||||
|
||||
log := prov.zlog.With().Str("user_id", user.MXID.String()).Str("endpoint", "login").Logger()
|
||||
|
||||
if user.IsLoggedIn() {
|
||||
jsonResponse(w, http.StatusOK, LoginResponse{Status: "success", ErrCode: "already logged in"})
|
||||
return
|
||||
}
|
||||
err := user.AsyncLoginGoogleWait()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to start login")
|
||||
// TODO proper error codes
|
||||
jsonResponse(w, http.StatusInternalServerError, Error{
|
||||
Error: "Failed to finish login",
|
||||
ErrCode: "finish login fail",
|
||||
})
|
||||
return
|
||||
}
|
||||
jsonResponse(w, http.StatusOK, LoginResponse{Status: "success"})
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Status string `json:"status"`
|
||||
Code string `json:"code,omitempty"`
|
||||
|
|
109
user.go
109
user.go
|
@ -35,6 +35,7 @@ import (
|
|||
"maunium.net/go/mautrix/appservice"
|
||||
"maunium.net/go/mautrix/bridge"
|
||||
"maunium.net/go/mautrix/bridge/bridgeconfig"
|
||||
"maunium.net/go/mautrix/bridge/commands"
|
||||
"maunium.net/go/mautrix/bridge/status"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/format"
|
||||
|
@ -64,7 +65,8 @@ type User struct {
|
|||
spaceCreateLock sync.Mutex
|
||||
connLock sync.Mutex
|
||||
|
||||
BridgeState *bridge.BridgeStateQueue
|
||||
BridgeState *bridge.BridgeStateQueue
|
||||
CommandState *commands.CommandState
|
||||
|
||||
spaceMembershipChecked bool
|
||||
|
||||
|
@ -80,16 +82,27 @@ type User struct {
|
|||
pollErrorAlertSent bool
|
||||
phoneNotRespondingAlertSent bool
|
||||
|
||||
loginInProgress atomic.Bool
|
||||
pairSuccessChan chan struct{}
|
||||
ongoingLoginChan <-chan qrChannelItem
|
||||
loginChanReadLock sync.Mutex
|
||||
lastQRCode string
|
||||
cancelLogin func()
|
||||
loginInProgress atomic.Bool
|
||||
pairSuccessChan chan struct{}
|
||||
ongoingLoginChan <-chan qrChannelItem
|
||||
lastQRCode string
|
||||
cancelLogin func()
|
||||
|
||||
googleAsyncPairErrChan atomic.Pointer[chan error]
|
||||
|
||||
DoublePuppetIntent *appservice.IntentAPI
|
||||
}
|
||||
|
||||
func (user *User) GetCommandState() *commands.CommandState {
|
||||
return user.CommandState
|
||||
}
|
||||
|
||||
func (user *User) SetCommandState(state *commands.CommandState) {
|
||||
user.CommandState = state
|
||||
}
|
||||
|
||||
var _ commands.CommandingUser = (*User)(nil)
|
||||
|
||||
func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User {
|
||||
_, isPuppet := br.ParsePuppetMXID(userID)
|
||||
if isPuppet || userID == br.Bot.UserID {
|
||||
|
@ -155,10 +168,6 @@ func (user *User) GetMXID() id.UserID {
|
|||
return user.MXID
|
||||
}
|
||||
|
||||
func (user *User) GetCommandState() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (br *GMBridge) GetUserByMXIDIfExists(userID id.UserID) *User {
|
||||
return br.getUserByMXID(userID, true)
|
||||
}
|
||||
|
@ -456,6 +465,71 @@ func (user *User) Login(maxAttempts int) (<-chan qrChannelItem, error) {
|
|||
return ch, nil
|
||||
}
|
||||
|
||||
func (user *User) AsyncLoginGoogleStart(cookies map[string]string) (outEmoji string, outErr error) {
|
||||
errChan := make(chan error, 1)
|
||||
if !user.googleAsyncPairErrChan.CompareAndSwap(nil, &errChan) {
|
||||
close(errChan)
|
||||
outErr = fmt.Errorf("login already in progress")
|
||||
return
|
||||
}
|
||||
var callbackDone bool
|
||||
var initialWait sync.WaitGroup
|
||||
initialWait.Add(1)
|
||||
callback := func(emoji string) {
|
||||
callbackDone = true
|
||||
outEmoji = emoji
|
||||
initialWait.Done()
|
||||
}
|
||||
go func() {
|
||||
err := user.LoginGoogle(cookies, callback)
|
||||
if !callbackDone {
|
||||
initialWait.Done()
|
||||
outErr = err
|
||||
close(errChan)
|
||||
user.googleAsyncPairErrChan.Store(nil)
|
||||
} else {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
initialWait.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
func (user *User) AsyncLoginGoogleWait() error {
|
||||
ch := user.googleAsyncPairErrChan.Swap(nil)
|
||||
if ch == nil {
|
||||
return fmt.Errorf("no login in progress")
|
||||
}
|
||||
return <-*ch
|
||||
}
|
||||
|
||||
func (user *User) LoginGoogle(cookies map[string]string, emojiCallback func(string)) error {
|
||||
user.connLock.Lock()
|
||||
defer user.connLock.Unlock()
|
||||
if user.Session != nil {
|
||||
return ErrAlreadyLoggedIn
|
||||
} else if !user.loginInProgress.CompareAndSwap(false, true) {
|
||||
return ErrLoginInProgress
|
||||
}
|
||||
if user.Client != nil {
|
||||
user.unlockedDeleteConnection()
|
||||
}
|
||||
pairSuccessChan := make(chan struct{})
|
||||
user.pairSuccessChan = pairSuccessChan
|
||||
authData := libgm.NewAuthData()
|
||||
authData.Cookies = cookies
|
||||
user.createClient(authData)
|
||||
Analytics.Track(user.MXID, "$login_start")
|
||||
err := user.Client.DoGaiaPairing(emojiCallback)
|
||||
if err != nil {
|
||||
user.unlockedDeleteConnection()
|
||||
user.pairSuccessChan = nil
|
||||
user.loginInProgress.Store(false)
|
||||
return fmt.Errorf("failed to connect to Google Messages: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) Connect() bool {
|
||||
user.connLock.Lock()
|
||||
defer user.connLock.Unlock()
|
||||
|
@ -637,6 +711,13 @@ func (user *User) syncHandleEvent(event any) {
|
|||
Error: GMUnpaired,
|
||||
}, false)
|
||||
go user.sendMarkdownBridgeAlert(true, "Unpaired from Google Messages. Log in again to continue using the bridge.")
|
||||
case *events.GaiaLoggedOut:
|
||||
user.zlog.Info().Msg("Got gaia logout event")
|
||||
go user.Logout(status.BridgeState{
|
||||
StateEvent: status.StateBadCredentials,
|
||||
Error: GMUnpaired,
|
||||
}, false)
|
||||
go user.sendMarkdownBridgeAlert(true, "Unpaired from Google Messages. Log in again to continue using the bridge.")
|
||||
case *events.AuthTokenRefreshed:
|
||||
go func() {
|
||||
err := user.Update(context.TODO())
|
||||
|
@ -731,9 +812,9 @@ func (user *User) handleAccountChange(v *events.AccountChange) {
|
|||
user.switchedToGoogleLogin = v.GetEnabled() || v.IsFake
|
||||
if !v.IsFake {
|
||||
if user.switchedToGoogleLogin {
|
||||
go user.sendMarkdownBridgeAlert(true, "The bridge will not work when the account-based pairing method is enabled in the Google Messages app. Unlink other devices and switch back to the QR code method to continue using the bridge.")
|
||||
go user.sendMarkdownBridgeAlert(true, "Switched to Google account pairing, please switch back or relogin with `login-google`.")
|
||||
} else {
|
||||
go user.sendMarkdownBridgeAlert(false, "Switched back to QR pairing, bridge should work now")
|
||||
go user.sendMarkdownBridgeAlert(false, "Switched back to QR pairing, bridge should be reconnected")
|
||||
// Assume connection is ready now even if it wasn't before
|
||||
user.ready = true
|
||||
}
|
||||
|
@ -874,7 +955,7 @@ func (user *User) FillBridgeState(state status.BridgeState) status.BridgeState {
|
|||
|
||||
func (user *User) Logout(state status.BridgeState, unpair bool) (logoutOK bool) {
|
||||
if user.Client != nil && unpair {
|
||||
_, err := user.Client.Unpair()
|
||||
err := user.Client.Unpair()
|
||||
if err != nil {
|
||||
user.zlog.Debug().Err(err).Msg("Error sending unpair request")
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue