Skip to content

Commit

Permalink
add registration for push notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
John Newman committed Jun 28, 2024
1 parent 147c31a commit 94bba61
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 2 deletions.
25 changes: 25 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (br *MetaBridge) RegisterCommands() {
cmdDeleteAllPortals,
cmdDeleteThread,
cmdSearch,
cmdRegisterPushNotifications,
)
}

Expand Down Expand Up @@ -282,6 +283,30 @@ func fnSyncSpace(ce *WrappedCommandEvent) {
ce.Reply("Added %d room%s to space", count, plural)
}

var cmdRegisterPushNotifications = &commands.FullHandler{
Func: wrapCommand(fnRegisterForPushNotifications),
Name: "register-push-notifications",
Help: commands.HelpMeta{
Section: commands.HelpSectionGeneral,
Description: "Register for push notifications",
Args: "<push_endpoint>",
},
}

func fnRegisterForPushNotifications(ce *WrappedCommandEvent) {
endpoint := ce.Args[0]
var err error
if ce.User.Cookies.Platform.IsMessenger() {
err = ce.User.Client.Facebook.RegisterPushNotifications(endpoint)
} else {
err = ce.User.Client.Instagram.RegisterPushNotifications(endpoint)
}
if err != nil {
ce.Reply("failed to register for push notifications")
}
ce.Reply("successfully registered for push notifications")
}

var cmdLogin = &commands.FullHandler{
Func: wrapCommand(fnLogin),
Name: "login",
Expand Down
12 changes: 12 additions & 0 deletions messagix/cookies/cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Cookies struct {
values map[MetaCookieName]string
lock sync.RWMutex

PushKeys *PushKeys
IGWWWClaim string
}

Expand Down Expand Up @@ -81,6 +82,17 @@ func (c *Cookies) GetViewports() (width, height string) {
return pxs[0], pxs[1]
}

func (c *Cookies) GeneratePushKeys() error {
c.lock.RLock()
defer c.lock.RUnlock()
pushKeys, err := generatePushKeys()
if err != nil {
return err
}
c.PushKeys = pushKeys
return nil
}

func (c *Cookies) GetMissingCookieNames() []MetaCookieName {
c.lock.RLock()
defer c.lock.RUnlock()
Expand Down
47 changes: 47 additions & 0 deletions messagix/cookies/pushkeys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cookies

import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
)

type PushKeysPublic struct {
P256dh string `json:"p256dh"`
Auth string `json:"auth"`
}

type PushKeys struct {
Public PushKeysPublic
Private string
}

func generatePushKeys() (*PushKeys, error) {
curve := ecdh.P256()
privateKey, err := curve.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}

publicKey := privateKey.Public().(*ecdh.PublicKey)
encodedPublicKey := base64.URLEncoding.EncodeToString(publicKey.Bytes())

privateKeyBytes := privateKey.Bytes()
encodedPrivateKey := base64.URLEncoding.EncodeToString(privateKeyBytes)

auth := make([]byte, 16)
_, err = rand.Read(auth)
if err != nil {
return nil, err
}
encodedAuth := base64.URLEncoding.EncodeToString(auth)

pushKeys := &PushKeys{
Public: PushKeysPublic{
P256dh: encodedPublicKey,
Auth: encodedAuth,
},
Private: encodedPrivateKey,
}
return pushKeys, nil
}
1 change: 1 addition & 0 deletions messagix/data/endpoints/facebook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func makeFacebookEndpoints(host string) map[string]string {
"cookie_consent": baseURL + "/cookie/consent/",
"graphql": baseURL + "/api/graphql/",
"media_upload": baseURL + "/ajax/mercury/upload.php?",
"web_push": baseURL + "/push/register/service_worker/",

"icdc_fetch": "https://reg-e2ee.facebook.com/v2/fb_icdc_fetch",
"icdc_register": "https://reg-e2ee.facebook.com/v2/fb_register_v2",
Expand Down
1 change: 1 addition & 0 deletions messagix/data/endpoints/instagram.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ var InstagramEndpoints = map[string]string{
"web_profile_info": instaApiV1Url + "/users/web_profile_info/?",
"reels_media": instaApiV1Url + "/feed/reels_media/?",
"media_info": instaApiV1Url + "/media/%s/info/",
"web_push": instaApiV1Url + "/api/v1/web/push/register/",
}
67 changes: 67 additions & 0 deletions messagix/facebook.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package messagix

import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"strings"

"github.com/google/go-querystring/query"

Expand Down Expand Up @@ -72,3 +75,67 @@ func (fb *FacebookMethods) Login(identifier, password string) (*cookies.Cookies,

return fb.client.cookies, nil
}

func (fb *FacebookMethods) RegisterPushNotifications(endpoint string) error {
c := fb.client
jsonKeys, err := json.Marshal(c.cookies.PushKeys.Public)
if err != nil {
c.Logger.Err(err).Msg("failed to encode push keys to json")
return err
}

payload := c.NewHttpQuery()
payload.AppID = "1443096165982425"
payload.PushEndpoint = endpoint
payload.SubscriptionKeys = string(jsonKeys)

form, err := query.Values(payload)
if err != nil {
return err
}

payloadBytes := []byte(form.Encode())

headers := c.buildHeaders(true)
headers.Set("Referer", c.getEndpoint("host"))
headers.Set("Sec-fetch-site", "same-origin")

url := c.getEndpoint("web_push")

resp, body, err := c.MakeRequest(url, "POST", headers, payloadBytes, types.FORM)
if err != nil {
return err
}

if resp.StatusCode >= 300 || resp.StatusCode < 200 {
return fmt.Errorf("bad status code: %d", resp.StatusCode)
}

bodyStr := string(body)
jsonStr := strings.TrimPrefix(bodyStr, "for (;;);")
jsonBytes := []byte(jsonStr)

var r pushNotificationsResponse
err = json.Unmarshal(jsonBytes, &r)
if err != nil {
c.Logger.Err(err).Str("body", bodyStr).Msg("failed to unmarshal response")
return err
}

if !r.Payload.Success {
return errors.New("failed to register for push notifications")
}

return nil
}

type pushNotificationsResponse struct {
Ar int `json:"__ar"`
Payload payload `json:"payload"`
DtsgToken string `json:"dtsgToken"`
Lid string `json:"lid"`
}

type payload struct {
Success bool `json:"success"`
}
8 changes: 7 additions & 1 deletion messagix/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ type HttpQuery struct {
Variables string `url:"variables,omitempty"`
ServerTimestamps string `url:"server_timestamps,omitempty"` // "true" or "false"
DocID string `url:"doc_id,omitempty"`
D string `url:"__d,omitempty"` // for insta
D string `url:"__d,omitempty"` // for insta
AppID string `url:"app_id,omitempty"` // not required
PushEndpoint string `url:"push_endpoint,omitempty"` // not required
SubscriptionKeys string `url:"subscription_keys,omitempty"` // not required
DeviceToken string `url:"device_token,omitempty"` // not required
DeviceType string `url:"device_type,omitempty"` // not required
Mid string `url:"mid,omitempty"` // not required
}

func (c *Client) NewHttpQuery() *HttpQuery {
Expand Down
52 changes: 52 additions & 0 deletions messagix/instagram.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package messagix

import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"

"github.com/google/go-querystring/query"
"github.com/google/uuid"

"go.mau.fi/mautrix-meta/messagix/cookies"
"go.mau.fi/mautrix-meta/messagix/crypto"
Expand Down Expand Up @@ -175,3 +177,53 @@ func (ig *InstagramMethods) FetchReel(reelIds []string, mediaID string) (*respon
func (ig *InstagramMethods) FetchHighlights(highlightIds []string) (*responses.ReelInfoResponse, error) {
return ig.FetchReel(highlightIds, "")
}

func (ig *InstagramMethods) RegisterPushNotifications(endpoint string) error {
c := ig.client

jsonKeys, err := json.Marshal(c.cookies.PushKeys.Public)
if err != nil {
c.Logger.Err(err).Msg("failed to encode push keys to json")
return err
}

u := uuid.New()
payload := c.NewHttpQuery()
payload.Mid = u.String()
payload.DeviceType = "web_vapid"
payload.DeviceToken = endpoint
payload.SubscriptionKeys = string(jsonKeys)

form, err := query.Values(payload)
if err != nil {
return err
}

payloadBytes := []byte(form.Encode())

headers := c.buildHeaders(true)
headers.Set("x-requested-with", "XMLHttpRequest")
headers.Set("Referer", c.getEndpoint("host"))
headers.Set("Referrer-Policy", "strict-origin-when-cross-origin")

url := c.getEndpoint("web_push")
resp, body, err := c.MakeRequest(url, "POST", headers, payloadBytes, types.FORM)
if err != nil {
return err
}

if resp.StatusCode >= 300 || resp.StatusCode < 200 {
return fmt.Errorf("bad status code: %d", resp.StatusCode)
}

resBody := &struct {
Status string `json:"status"`
}{}

err = json.Unmarshal(body, resBody)
if err != nil {
return errors.New("failed to decode response payload, not subscribed to push notifications")
}

return nil
}
8 changes: 7 additions & 1 deletion user.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,13 @@ func (user *User) unlockedConnect() {
func (user *User) Login(ctx context.Context, cookies *cookies.Cookies) error {
user.Lock()
defer user.Unlock()
err := user.unlockedConnectWithCookies(cookies)

err := cookies.GeneratePushKeys()
if err != nil {
return err
}

err = user.unlockedConnectWithCookies(cookies)
if err != nil {
return err
}
Expand Down

0 comments on commit 94bba61

Please sign in to comment.