Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
67def72
ig: add native FCM registration
iFixRobots Sep 18, 2026
5231080
ig: request the initial web verification code
iFixRobots Sep 18, 2026
b87ca57
ig: enroll local logins for native push
iFixRobots Sep 18, 2026
8ba20ac
ig: recognize native push wakeups
iFixRobots Sep 18, 2026
7b794c6
ig: complete native push enrollment
iFixRobots Sep 18, 2026
ff5533f
ig: propagate background sync failures
iFixRobots Sep 18, 2026
e4b7f70
ig: avoid fetching mailbox during push enrollment
iFixRobots Sep 18, 2026
31332a2
ig: use one native session for local messaging and push
iFixRobots Sep 18, 2026
3b8446f
ig: handle optional login arguments and remove unused helpers
iFixRobots Sep 18, 2026
b6e0133
ig: preserve native login continuation
iFixRobots Sep 18, 2026
8e42549
ig: reuse req for native HTTP/3
iFixRobots Sep 18, 2026
71c2a45
ig: reuse the message query for native subscriptions
iFixRobots Sep 18, 2026
d784562
build: remove obsolete req checksums
iFixRobots Sep 18, 2026
300cde5
ig: expose native login independently of bridge host
iFixRobots Sep 19, 2026
5db6e32
ig: match native TLS fingerprint with existing HTTP transport
iFixRobots Sep 19, 2026
9072c8f
ig: preserve req HTTP/2 compatibility in TLS state
iFixRobots Sep 19, 2026
275a803
ig: correct native TLS scope and gateway defaults
iFixRobots Sep 19, 2026
3a2cdea
ig: accept empty native response envelopes
iFixRobots Sep 19, 2026
6eae289
ig: omit cookies from native login requests
iFixRobots Sep 19, 2026
fdf73a7
ig: use generated FlatBuffers for native envelopes
iFixRobots Sep 19, 2026
9197c23
ig: name the generated Lightspeed import
iFixRobots Sep 19, 2026
92122e2
ig: match native HTTP/2 settings
iFixRobots Sep 19, 2026
7a7131f
ig: register saved device keys for new native logins
iFixRobots Sep 20, 2026
616e48b
ig: synchronize native push session updates
iFixRobots Sep 20, 2026
1a181a2
ig: simplify native login and response handling
iFixRobots Sep 21, 2026
9594314
Rename new login flow to `android`
tulir Sep 21, 2026
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
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/beeper/poly1305 v0.0.0-20250815183548-d4eede7bbf3c
github.com/coder/websocket v1.8.15
github.com/gabriel-vasile/mimetype v1.4.15
github.com/google/flatbuffers v25.12.19+incompatible
github.com/google/go-querystring v1.2.0
github.com/google/uuid v1.6.0
github.com/imroc/req/v3 v3.56.0
Expand Down Expand Up @@ -62,4 +63,4 @@ require (
maunium.net/go/mauflag v1.0.0 // indirect
)

replace github.com/imroc/req/v3 => github.com/beeper/req/v3 v3.0.0-20260808092153-100cef0a2fbd
replace github.com/imroc/req/v3 => github.com/beeper/req/v3 v3.0.0-20260919222256-fca57bce366e
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,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/beeper/req/v3 v3.0.0-20260808092153-100cef0a2fbd h1:IJfLD+LIFeAuBZk0ZHVG4I4dFhFuaQBxo6nsp3vJ2tI=
github.com/beeper/req/v3 v3.0.0-20260808092153-100cef0a2fbd/go.mod h1:iY9GG/Oj7JPTmyK4y2im3tHKhegHe6IzCj6mmQOjCQs=
github.com/beeper/req/v3 v3.0.0-20260919222256-fca57bce366e h1:+Re9t7bZ3p6sSUJ2VaeTtbmbb+76BlkkKEeWRnuLhV4=
github.com/beeper/req/v3 v3.0.0-20260919222256-fca57bce366e/go.mod h1:iY9GG/Oj7JPTmyK4y2im3tHKhegHe6IzCj6mmQOjCQs=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
Expand All @@ -25,6 +25,8 @@ github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
Expand Down
5 changes: 5 additions & 0 deletions pkg/igconnector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type IGClient struct {
stopConnectAttempt atomic.Pointer[context.CancelFunc]
stopChatBackfill atomic.Pointer[context.CancelFunc]
chatBackfillLock sync.Mutex
pushRegistrationLock sync.Mutex
mailboxProcessed atomic.Bool
waitMailboxProcessed chan struct{}
permanentErrored atomic.Bool
Expand All @@ -64,6 +65,9 @@ type IGClient struct {

func (ic *IGConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error {
loginMetadata := login.Metadata.(*metaid.UserLoginMetadata)
if login.Client != nil {
login.Client.Disconnect()
}
c := &IGClient{
Main: ic,
LoginMeta: loginMetadata,
Expand Down Expand Up @@ -127,6 +131,7 @@ func (ic *IGClient) ensureIGClient() {
Settings: ic.Main.Bridge.GetHTTPClientSettings(),
EventHandler: ic.handleIGEvent,
DisableTyping: ic.Main.Config.DisableTyping,
NativeSession: ic.LoginMeta.InstagramNativeSession,

LogRedactedLoginResponses: ic.Main.Config.LogRedactedLoginResponses,
})
Expand Down
20 changes: 13 additions & 7 deletions pkg/igconnector/handlemeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"strconv"
"time"

"github.com/coder/websocket"
"github.com/rs/zerolog"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
Expand Down Expand Up @@ -86,7 +85,7 @@ func (ic *IGClient) doWaitMailboxProcessed(ctx context.Context) error {
return ctx.Err()
}
}
return nil
return ctx.Err()
}

func (ic *IGClient) handleIGEvent(ctx context.Context, rawEvt slidetypes.ClientEvent) error {
Expand All @@ -97,8 +96,9 @@ func (ic *IGClient) handleIGEvent(ctx context.Context, rawEvt slidetypes.ClientE
if evt.SubscribedSeqID >= evt.LatestSeqID {
ic.catchingUpTo = 0
go func() {
_ = ic.doWaitMailboxProcessed(ctx)
ic.caughtUp.Set()
if ic.doWaitMailboxProcessed(ctx) == nil {
ic.caughtUp.Set()
}
}()
} else {
ic.catchingUpTo = evt.LatestSeqID
Expand All @@ -111,7 +111,7 @@ func (ic *IGClient) handleIGEvent(ctx context.Context, rawEvt slidetypes.ClientE
stateEvt := status.StateTransientDisconnect
errCode := DGWConnectionError
var retErr error
if websocket.CloseStatus(evt.Error) == dgw.CloseStatusUnauthorized {
if dgw.IsUnauthorized(evt.Error) {
// TODO do full reconnect instead of this?
stateEvt = status.StateBadCredentials
errCode = DGWConnectionUnauthorized
Expand Down Expand Up @@ -142,7 +142,9 @@ func (ic *IGClient) handleIGEvent(ctx context.Context, rawEvt slidetypes.ClientE
}
return nil
case *slidetypes.SeqIDUpdate:
_ = ic.doWaitMailboxProcessed(ctx)
if err := ic.doWaitMailboxProcessed(ctx); err != nil {
return err
}
err := ic.Main.DB.PutIGSeqID(ctx, ic.UserLogin.ID, evt.SeqID, evt.Timestamp)
if err != nil {
return err
Expand Down Expand Up @@ -231,14 +233,15 @@ func (ic *IGClient) ensurePortal(ctx context.Context, threadIGID string, allowCr
return key, true, err
}

func (ic *IGClient) handleDelta(ctx context.Context, d *slidetypes.Delta) error {
func (ic *IGClient) handleDelta(ctx context.Context, d *slidetypes.Delta) (retErr error) {
defer func() {
v := recover()
if v != nil {
err, ok := v.(error)
if !ok {
err = fmt.Errorf("%v", v)
}
retErr = err
stack := debug.Stack()
zerolog.Ctx(ctx).Err(err).
Bytes(zerolog.ErrorStackFieldName, stack).
Expand Down Expand Up @@ -342,6 +345,9 @@ func (ic *IGClient) handleDelta(ctx context.Context, d *slidetypes.Delta) error
return fmt.Errorf("unrecognized event type: %T", d.Data)
}
if !res.Success {
if res.Error == nil {
return errors.New("failed to handle delta")
}
return res.Error
}
return nil
Expand Down
40 changes: 29 additions & 11 deletions pkg/igconnector/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

const (
FlowIDInstagramCookies = "instagram"
FlowIDCookies = "instagram"

LoginStepIDCookies = "fi.mau.meta.cookies"
LoginStepIDComplete = "fi.mau.meta.complete"
Expand All @@ -34,12 +34,14 @@ const (

func (ic *IGConnector) CreateLogin(ctx context.Context, user *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) {
switch flowID {
case FlowIDInstagramPassword:
case FlowIDAndroidNative:
return &MetaNativeLogin{User: user, Main: ic, nativeLogin: true}, nil
case FlowIDWebNative:
return &MetaNativeLogin{
User: user,
Main: ic,
}, nil
case FlowIDInstagramCookies:
case FlowIDCookies:
default:
return nil, bridgev2.ErrInvalidLoginFlowID
}
Expand All @@ -52,14 +54,14 @@ func (ic *IGConnector) CreateLogin(ctx context.Context, user *bridgev2.User, flo

var (
loginFlowInstagram = bridgev2.LoginFlow{
Name: "instagram.com",
Name: "Cookies",
Description: "Login using cookies from instagram.com",
ID: FlowIDInstagramCookies,
ID: FlowIDCookies,
}
)

func (ic *IGConnector) GetLoginFlows() []bridgev2.LoginFlow {
return []bridgev2.LoginFlow{loginFlowInstagram, loginFlowInstagramPassword}
return []bridgev2.LoginFlow{loginFlowInstagramNative, loginFlowInstagram, loginFlowInstagramPassword}
}

type MetaCookieLogin struct {
Expand Down Expand Up @@ -130,11 +132,19 @@ func loginWithCookies(
bridgeUser *bridgev2.User,
conn *IGConnector,
c *cookies.Cookies,
nativeSession *types.InstagramNativeSession,
requireNative bool,
beforeClientStart func(),
) (*bridgev2.LoginStep, error) {
log.Debug().
Strs("cookie_names", exslices.CastToString[string](slices.Collect(maps.Keys(c.GetAll())))).
Msg("Logging in with cookies")
if requireNative {
if nativeSession == nil || nativeSession.Authorization == "" || nativeSession.UserID != c.Get(cookies.IGCookieDSUserID) {
return nil, errInstagramCAAFlowFailed
}
client.SetInstagramNativeSession(nativeSession)
}
user, mailbox, err := client.LoadIndex(ctx)
if err != nil {
log.Err(err).Msg("Failed to load messages page for login")
Expand All @@ -159,6 +169,13 @@ func loginWithCookies(
if ownFBID == 0 {
return nil, fmt.Errorf("own fbid not found")
}
if nativeSession != nil && (nativeSession.UserID != user.ID || nativeSession.UserID != c.Get(cookies.IGCookieDSUserID)) {
nativeSession = nil
}
if requireNative && (nativeSession == nil || nativeSession.Authorization == "") {
return nil, errInstagramCAAFlowFailed
}
client.SetInstagramNativeSession(nativeSession)
loginID := metaid.MakeUserLoginID(ownFBID)
var loginUA string
if req, ok := ctx.Value("fi.mau.provision.request").(*http.Request); ok {
Expand All @@ -172,10 +189,11 @@ func loginWithCookies(
Name: user.GetName(),
},
Metadata: &metaid.UserLoginMetadata{
Platform: c.Platform,
Cookies: c,
LoginUA: loginUA,
IGID: user.ID,
Platform: c.Platform,
Cookies: c,
LoginUA: loginUA,
IGID: user.ID,
InstagramNativeSession: nativeSession,
},
}, nil)
if err != nil {
Expand Down Expand Up @@ -235,7 +253,7 @@ func submitInstagramCookies(ctx context.Context, conn *IGConnector, user *bridge
if err != nil {
return nil, err
}
return loginWithCookies(ctx, log, client, user, conn, c, nil)
return loginWithCookies(ctx, log, client, user, conn, c, nil, false, nil)
}

func (m *MetaCookieLogin) SubmitCookies(ctx context.Context, strCookies map[string]string) (*bridgev2.LoginStep, error) {
Expand Down
Loading
Loading