Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.24.0
toolchain go1.25.1

require (
github.com/beeper/poly1305 v0.0.0-20250815183548-d4eede7bbf3c
github.com/gabriel-vasile/mimetype v1.4.10
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs=
github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4=
github.com/beeper/poly1305 v0.0.0-20250815183548-d4eede7bbf3c h1:Axyrzr6L42CLNJIQq0S87UhgK0Hq+4hCed4rc6sDdao=
github.com/beeper/poly1305 v0.0.0-20250815183548-d4eede7bbf3c/go.mod h1:mo9x0FBxrMo75mAnk1ALdXrHfe8NG3C25CFDf4RSRpc=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
Expand Down
4 changes: 4 additions & 0 deletions pkg/connector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync/atomic"
"time"

"github.com/google/uuid"
"github.com/rs/zerolog"
"go.mau.fi/util/exsync"
"go.mau.fi/whatsmeow"
Expand Down Expand Up @@ -427,6 +428,9 @@ func (m *MetaClient) connectE2EE() error {
isNew = true
m.WADevice = m.Main.DeviceStore.NewDevice()
}
if m.Client.DeviceID != uuid.Nil {
m.WADevice.FacebookUUID = m.Client.DeviceID
}
m.Client.SetDevice(m.WADevice)

if isNew {
Expand Down
132 changes: 131 additions & 1 deletion pkg/connector/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ const (
FlowIDFacebookCookies = "facebook"
FlowIDMessengerCookies = "messenger"
FlowIDInstagramCookies = "instagram"
FlowIDMessengerLite = "messenger-lite"

LoginStepIDCookies = "fi.mau.meta.cookies"
LoginStepIDComplete = "fi.mau.meta.complete"

LoginStepIDCredentials = "fi.mau.meta.credentials"
)

func (m *MetaConnector) CreateLogin(ctx context.Context, user *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) {
Expand All @@ -41,6 +44,13 @@ func (m *MetaConnector) CreateLogin(ctx context.Context, user *bridgev2.User, fl
plat = types.Messenger
case FlowIDInstagramCookies:
plat = types.Instagram
case FlowIDMessengerLite:
plat = types.MessengerLite
return &MetaNativeLogin{
Mode: plat,
User: user,
Main: m,
}, nil
default:
return nil, bridgev2.ErrInvalidLoginFlowID
}
Expand Down Expand Up @@ -95,12 +105,17 @@ var (
Description: "Login using cookies from instagram.com",
ID: FlowIDInstagramCookies,
}
loginFlowMessengerLite = bridgev2.LoginFlow{
Name: "Messenger iOS",
Description: "Login in using Messenger mobile API",
ID: FlowIDMessengerLite,
}
)

func (m *MetaConnector) GetLoginFlows() []bridgev2.LoginFlow {
switch m.Config.Mode {
case types.Unset:
return []bridgev2.LoginFlow{loginFlowFacebook, loginFlowMessenger, loginFlowInstagram}
return []bridgev2.LoginFlow{loginFlowFacebook, loginFlowMessenger, loginFlowInstagram, loginFlowMessengerLite}
case types.Facebook:
if m.Config.AllowMessengerComOnFB {
return []bridgev2.LoginFlow{loginFlowMessenger, loginFlowFacebook}
Expand Down Expand Up @@ -269,3 +284,118 @@ func (m *MetaCookieLogin) SubmitCookies(ctx context.Context, strCookies map[stri
},
}, nil
}

type MetaNativeLogin struct {
Mode types.Platform
User *bridgev2.User
Main *MetaConnector
}

func (m *MetaNativeLogin) Cancel() {
panic("unimplemented")
}

func (m *MetaNativeLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
step := &bridgev2.LoginStep{
Type: bridgev2.LoginStepTypeUserInput,
StepID: LoginStepIDCredentials,
Instructions: "Enter your Messenger credentials",
UserInputParams: &bridgev2.LoginUserInputParams{
Fields: []bridgev2.LoginInputDataField{
{ID: "username", Name: "Username", Type: bridgev2.LoginInputFieldTypeEmail},
{ID: "password", Name: "Password", Type: bridgev2.LoginInputFieldTypePassword},
},
},
}
return step, nil
}

func (m *MetaNativeLogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
log := m.User.Log.With().Str("component", "messagix").Logger()
fakeCookies := &cookies.Cookies{
Platform: m.Mode,
}
client := messagix.NewClient(fakeCookies, log, m.Main.getMessagixConfig())
if m.Main.Config.GetProxyFrom != "" || m.Main.Config.Proxy != "" {
client.GetNewProxy = m.Main.getProxy
if !client.UpdateProxy("login") {
return nil, fmt.Errorf("failed to update proxy")
}
}

cookies, err := client.MessengerLite.Login(ctx, input["username"], input["password"])
if err != nil {
return nil, fmt.Errorf("failed to login: %w", err)
}

newCookies, err := cookies.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("failed to marshal cookies: %w", err)
}
client.GetCookies().UnmarshalJSON(newCookies)

log.Debug().Any("cookies", cookies).Msg("Logged in with Messenger Lite")

user, tbl, err := client.LoadMessagesPage(ctx)
if err != nil {
log.Err(err).Msg("Failed to load messages page for login")
if errors.Is(err, messagix.ErrChallengeRequired) {
return nil, ErrLoginChallenge
} else if errors.Is(err, messagix.ErrCheckpointRequired) {
return nil, ErrLoginCheckpoint
} else if errors.Is(err, messagix.ErrConsentRequired) {
return nil, ErrLoginConsent
} else if errors.Is(err, messagix.ErrTokenInvalidated) {
return nil, ErrLoginTokenInvalidated
} else {
return nil, fmt.Errorf("%w: %w", ErrLoginUnknown, err)
}
}

log.Debug().Any("user", user).Any("tbl", tbl).Msg("Loaded user after Messenger Lite login")
id := user.GetFBID()

loginID := metaid.MakeUserLoginID(id)
var loginUA string
if req, ok := ctx.Value("fi.mau.provision.request").(*http.Request); ok {
loginUA = req.Header.Get("User-Agent")
}

ul, err := m.User.NewLogin(ctx, &database.UserLogin{
ID: loginID,
RemoteName: user.GetName(),
RemoteProfile: status.RemoteProfile{
Name: user.GetName(),
},
Metadata: &metaid.UserLoginMetadata{
Platform: client.GetCookies().Platform,
Cookies: client.GetCookies(),
LoginUA: loginUA,
},
}, nil)
if err != nil {
return nil, fmt.Errorf("failed to save new login: %w", err)
}

metaClient := ul.Client.(*MetaClient)
// Override the client because LoadMessagesPage saves some state and we don't want to call it again
client.Logger = metaClient.Client.Logger
client.SetEventHandler(metaClient.handleMetaEvent)
metaClient.lastFullReconnect = time.Time{}
metaClient.Client = client

backgroundCtx := ul.Log.WithContext(m.Main.Bridge.BackgroundCtx)
ul.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting})
go metaClient.connectWithTable(backgroundCtx, tbl, user)
return &bridgev2.LoginStep{
Type: bridgev2.LoginStepTypeComplete,
StepID: LoginStepIDComplete,
Instructions: fmt.Sprintf("Logged in as %s (%d)", user.GetName(), id),
CompleteParams: &bridgev2.LoginCompleteParams{
UserLoginID: ul.ID,
UserLogin: ul,
},
}, nil
}

var _ bridgev2.LoginProcessUserInput = (*MetaNativeLogin)(nil)
77 changes: 77 additions & 0 deletions pkg/messagix/bloks/bloks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package bloks

import (
"encoding/json"
)

const BloksVersion = "3988ff4cdf5ca5de647ba84aa74b5bd2fcd4ffd768e0faec8adc3e53492f3f87"

// TODO: Fix analytics
type NetworkTags struct {
Product string `json:"product"`
Purpose string `json:"purpose,omitempty"`
RequestCategory string `json:"request_category,omitempty"`
RetryAttempt string `json:"retry_attempt"`
}

type RequestAnalytics struct {
NetworkTags NetworkTags `json:"network_tags"`
}

// WrappedBloks:

type wrappedBloksParams struct {
ServerParams map[string]any `json:"server_params"`
ClientInputParams map[string]any `json:"client_input_params"`
}

type wrappedBloksBkContext struct {
PixelRatio float64 `json:"pixel_ratio"`
BloksVersion string `json:"bloks_version"`
}

type wrappedBloksOuterParams struct {
BloksVersioningId string `json:"bloks_versioning_id"`
AppID string `json:"app_id"`
Params string `json:"params"`
}
type WrappedBloksRequest struct {
BkContext *wrappedBloksBkContext `json:"bk_context,omitempty"`
Params *wrappedBloksOuterParams `json:"params,omitempty"`
}

func makeWrappedBloksRequest(pixelRatio float64, bloksVersion string, appID string, params wrappedBloksParams) (*WrappedBloksRequest, error) {
innerInnerParamsJson, err := json.Marshal(params)
if err != nil {
return nil, err
}

innerParamsJson, err := json.Marshal(map[string]any{
"params": string(innerInnerParamsJson),
})
if err != nil {
return nil, err
}

wrappedRequest := &WrappedBloksRequest{
BkContext: &wrappedBloksBkContext{
PixelRatio: pixelRatio,
BloksVersion: bloksVersion,
},
Params: &wrappedBloksOuterParams{
BloksVersioningId: bloksVersion,
AppID: appID,
Params: string(innerParamsJson),
},
}

return wrappedRequest, nil
}

// WrappedBloks is very cursed
func MakeWrappedBloksRequest(appID string, serverParams map[string]any, clientParams map[string]any) (*WrappedBloksRequest, error) {
return makeWrappedBloksRequest(3, BloksVersion, appID, wrappedBloksParams{
ServerParams: serverParams,
ClientInputParams: clientParams,
})
}
21 changes: 21 additions & 0 deletions pkg/messagix/bloks/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package bloks


type BloksDoc struct {
ClientDocId string
AppID string // used in inner layer
FriendlyName string // usually MSGBloksActionRootQuery-{AppId}
}

var BloksDocs = map[string]BloksDoc{
"CAA_LOGIN_HOME_PAGE": {
ClientDocId: "28114594638751287741908354449",
AppID: "com.bloks.www.caa.login.login_homepage",
FriendlyName: "MSGBloksActionRootQuery-com.bloks.www.caa.login.login_homepage",
},
"CAA_LOGIN_ASYNC_SEND_LOGIN_REQUEST": { // this is a made up name
ClientDocId: "155775708812630868437451274928",
AppID: "com.bloks.www.bloks.caa.login.async.send_login_request",
FriendlyName: "MSGBloksActionRootQuery-com.bloks.www.bloks.caa.login.async.send_login_request",
},
}
17 changes: 12 additions & 5 deletions pkg/messagix/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"time"

"github.com/google/go-querystring/query"
"github.com/google/uuid"
"github.com/rs/zerolog"
"go.mau.fi/util/exsync"
"go.mau.fi/whatsmeow"
Expand All @@ -40,10 +41,11 @@ type Config struct {
}

type Client struct {
Instagram *InstagramMethods
Facebook *FacebookMethods
Logger zerolog.Logger
Platform types.Platform
Instagram *InstagramMethods
Facebook *FacebookMethods
MessengerLite *MessengerLiteMethods
Logger zerolog.Logger
Platform types.Platform

http *http.Client
socket *Socket
Expand All @@ -58,7 +60,9 @@ type Client struct {
GetNewProxy func(reason string) (string, error)
mayConnectToDGW bool

device *store.Device
device *store.Device
DeviceID uuid.UUID // aka store.Device.FacebookUUID
machineId string

lsRequests int
graphQLRequests int
Expand Down Expand Up @@ -218,6 +222,9 @@ func (c *Client) configurePlatformClient() {
case types.Messenger:
selectedEndpoints = endpoints.MessengerEndpoints
c.Facebook = &FacebookMethods{client: c}
case types.MessengerLite:
selectedEndpoints = endpoints.MessengerLiteEndpoints
c.MessengerLite = &MessengerLiteMethods{client: c}
case types.Instagram:
selectedEndpoints = endpoints.InstagramEndpoints
c.Instagram = &InstagramMethods{client: c}
Expand Down
Loading
Loading