Files
mautrix-telegram/pkg/connector/push.go
T
2025-07-30 04:22:23 +08:00

438 lines
21 KiB
Go

// mautrix-telegram - A Matrix-Telegram puppeting bridge.
// Copyright (C) 2025 Sumner Evans
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package connector
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/tidwall/gjson"
"go.mau.fi/util/exslices"
"go.mau.fi/util/random"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
)
var (
_ bridgev2.PushableNetworkAPI = (*TelegramClient)(nil)
_ bridgev2.PushParsingNetwork = (*TelegramConnector)(nil)
)
var PushAppSandbox = false
type PushCustomData struct {
MessageID int `json:"msg_id,string"`
ChannelID int64 `json:"channel_id,string"`
ChatID int64 `json:"chat_id,string"`
FromID int64 `json:"from_id,string"`
ChatFromBroadcastID int64 `json:"chat_from_broadcast_id,string"`
ChatFromGroupID int64 `json:"chat_from_group_id,string"`
ChatFromID int64 `json:"chat_from_id,string"`
}
type Aps struct {
Alert Alert `json:"alert"`
ThreadID string `json:"thread-id"`
}
type Alert struct {
Title string `json:"title"`
Body string `json:"body"`
}
type PushNotificationData struct {
Aps *Aps `json:"aps"` // Only present for APNs
LocKey string `json:"loc_key"`
LocArgs []string `json:"loc_args"`
Custom PushCustomData `json:"custom"`
Sound string `json:"sound"`
UserID int `json:"user_id,string"`
}
var PushMessageFormats = map[string]string{
"AUTH_REGION": "New login from unrecognized device %[1]v, location: %[2]v",
"AUTH_UNKNOWN": "New login from unrecognized device %[1]v",
"CHANNEL_MESSAGES": "%[1]v posted an album",
"CHANNEL_MESSAGE_AUDIO": "%[1]v posted a voice message",
"CHANNEL_MESSAGE_CONTACT": "%[1]v posted a contact %[2]v",
"CHANNEL_MESSAGE_DOC": "%[1]v posted a file",
"CHANNEL_MESSAGE_DOCS": "%[1]v posted %[2]v files",
"CHANNEL_MESSAGE_FWDS": "%[1]v posted %[2]v forwarded messages",
"CHANNEL_MESSAGE_GAME": "%[1]v invited you to play %[2]v",
"CHANNEL_MESSAGE_GAME_SCORE": "%[1]v scored %[3]v in game %[2]v",
"CHANNEL_MESSAGE_GEO": "%[1]v posted a location",
"CHANNEL_MESSAGE_GEOLIVE": "%[1]v posted a live location",
"CHANNEL_MESSAGE_GIF": "%[1]v posted a GIF",
"CHANNEL_MESSAGE_GIVEAWAY": "%[1]v posted a giveaway of %[2]vx %[3]vm Premium subscriptions",
"CHANNEL_MESSAGE_GIVEAWAY_STARS": "%[1]v posted a giveaway of %[3]v stars %[2]v",
"CHANNEL_MESSAGE_NOTEXT": "%[1]v posted a message",
"CHANNEL_MESSAGE_PAID_MEDIA": "%[1]v posted a paid post for %[2]v star",
"CHANNEL_MESSAGE_PHOTO": "%[1]v posted a photo",
"CHANNEL_MESSAGE_PHOTOS": "%[1]v posted %[2]v photos",
"CHANNEL_MESSAGE_PLAYLIST": "%[1]v posted %[2]v music files",
"CHANNEL_MESSAGE_POLL": "%[1]v posted a poll %[2]v",
"CHANNEL_MESSAGE_QUIZ": "%[1]v posted a quiz %[2]v",
"CHANNEL_MESSAGE_ROUND": "%[1]v posted a video message",
"CHANNEL_MESSAGE_STICKER": "%[1]v posted a %[2]v sticker",
"CHANNEL_MESSAGE_STORY": "%[1]v shared a story",
"CHANNEL_MESSAGE_TEXT": "%[1]v: %[2]v",
"CHANNEL_MESSAGE_VIDEO": "%[1]v posted a video",
"CHANNEL_MESSAGE_VIDEOS": "%[1]v posted %[2]v videos",
"CHAT_ADD_MEMBER": "%[1]v invited %[3]v to the group %[2]v",
"CHAT_ADD_YOU": "%[1]v invited you to the group %[2]v",
"CHAT_CREATED": "%[1]v invited you to the group %[2]v",
"CHAT_DELETE_MEMBER": "%[1]v removed %[3]v from the group %[2]v",
"CHAT_DELETE_YOU": "%[1]v removed you from the group %[2]v",
"CHAT_JOINED": "%[1]v joined the group %[2]v",
"CHAT_LEFT": "%[1]v left the group %[2]v",
"CHAT_MESSAGES": "%[1]v sent an album to the group %[2]v",
"CHAT_MESSAGE_AUDIO": "%[1]v sent a voice message to the group %[2]v",
"CHAT_MESSAGE_CONTACT": "%[1]v shared a contact %[3]v in the group %[2]v",
"CHAT_MESSAGE_DOC": "%[1]v sent a file to the group %[2]v",
"CHAT_MESSAGE_DOCS": "%[1]v sent %[3]v files to the group %[2]v",
"CHAT_MESSAGE_FWDS": "%[1]v forwarded %[3]v messages to the group %[2]v",
"CHAT_MESSAGE_GAME": "%[1]v invited the group %[2]v to play %[3]v",
"CHAT_MESSAGE_GAME_SCORE": "%[1]v scored %[4]v in game %[3]v in the group %[2]v",
"CHAT_MESSAGE_GEO": "%[1]v sent a location to the group %[2]v",
"CHAT_MESSAGE_GEOLIVE": "%[1]v shared a live location with the group %[2]v",
"CHAT_MESSAGE_GIF": "%[1]v sent a GIF to the group %[2]v",
"CHAT_MESSAGE_GIVEAWAY": "%[1]v sent a giveaway of %[3]vx %[4]vm Premium subscriptions to the group %[2]v",
"CHAT_MESSAGE_GIVEAWAY_STARS": "%[1]v sent a giveaway of %[4]v stars %[3]v to the group %[2]v",
"CHAT_MESSAGE_INVOICE": "%[1]v sent an invoice to the group %[2]v for %[3]v",
"CHAT_MESSAGE_NOTEXT": "%[1]v sent a message to the group %[2]v",
"CHAT_MESSAGE_PAID_MEDIA": "%[1]v posted a paid post in %[2]v group for %[3]v star",
"CHAT_MESSAGE_PHOTO": "%[1]v sent a photo to the group %[2]v",
"CHAT_MESSAGE_PHOTOS": "%[1]v sent %[3]v photos to the group %[2]v",
"CHAT_MESSAGE_PLAYLIST": "%[1]v sent %[3]v music files to the group %[2]v",
"CHAT_MESSAGE_POLL": "%[1]v sent a poll %[3]v to the group %[2]v",
"CHAT_MESSAGE_QUIZ": "%[1]v sent a quiz %[3]v to the group %[2]v",
"CHAT_MESSAGE_ROUND": "%[1]v sent a video message to the group %[2]v",
"CHAT_MESSAGE_STICKER": "%[1]v sent a %[3]v sticker to the group %[2]v",
"CHAT_MESSAGE_STORY": "%[1]v shared a story to the group",
"CHAT_MESSAGE_TEXT": "%[1]v @ %[2]v: %[3]v",
"CHAT_MESSAGE_VIDEO": "%[1]v sent a video to the group %[2]v",
"CHAT_MESSAGE_VIDEOS": "%[1]v sent %[3]v videos to the group %[2]v",
"CHAT_PHOTO_EDITED": "%[1]v changed the group photo for %[2]v",
"CHAT_REACT_AUDIO": "%[1]v: %[3]v to your voice message in %[2]v",
"CHAT_REACT_CONTACT": "%[1]v: %[3]v to your contact %[4]v in %[2]v",
"CHAT_REACT_DOC": "%[1]v: %[3]v to your file in %[2]v",
"CHAT_REACT_GAME": "%[1]v: %[3]v to your game in %[2]v",
"CHAT_REACT_GEO": "%[1]v: %[3]v to your map in %[2]v",
"CHAT_REACT_GEOLIVE": "%[1]v: %[3]v to your live location in %[2]v",
"CHAT_REACT_GIF": "%[1]v: %[3]v to your GIF in %[2]v",
"CHAT_REACT_GIVEAWAY": "%[1]v reacted %[3]v in group %[2]v to your giveaway",
"CHAT_REACT_INVOICE": "%[1]v: %[3]v to your invoice in %[2]v",
"CHAT_REACT_NOTEXT": "%[1]v: %[3]v to your message in %[2]v",
"CHAT_REACT_PAID_MEDIA": "%[1]v reacted %[3]v in group %[2]v to your paid post for %[4]v star",
"CHAT_REACT_PHOTO": "%[1]v: %[3]v to your photo in %[2]v",
"CHAT_REACT_POLL": "%[1]v: %[3]v to your poll %[4]v in %[2]v",
"CHAT_REACT_QUIZ": "%[1]v: %[3]v to your quiz %[4]v in %[2]v",
"CHAT_REACT_ROUND": "%[1]v: %[3]v to your video message in %[2]v",
"CHAT_REACT_STICKER": "%[1]v: %[3]v to your %[4]v sticker in %[2]v",
"CHAT_REACT_TEXT": "%[1]v: %[3]v in %[2]v to your \"%[4]v\"",
"CHAT_REACT_VIDEO": "%[1]v: %[3]v to your video in %[2]v",
"CHAT_REQ_JOINED": "%[2]v|%[1]v was accepted into the group",
"CHAT_RETURNED": "%[1]v returned to the group %[2]v",
"CHAT_TITLE_EDITED": "%[1]v renamed the group %[2]v",
"CHAT_VOICECHAT_END": "%[1]v ended a voice chat in the group %[2]v",
"CHAT_VOICECHAT_INVITE": "%[1]v invited %[3]v to a voice chat in the group %[2]v",
"CHAT_VOICECHAT_INVITE_YOU": "%[1]v invited you to a voice chat in the group %[2]v",
"CHAT_VOICECHAT_START": "%[1]v started a voice chat in the group %[2]v",
"CONTACT_JOINED": "%[1]v joined Telegram!",
"ENCRYPTED_MESSAGE": "You have a new message",
"ENCRYPTION_ACCEPT": "You have a new message",
"ENCRYPTION_REQUEST": "You have a new message",
"LOCKED_MESSAGE": "You have a new message",
"MESSAGES": "%[1]v sent you an album",
"MESSAGE_AUDIO": "%[1]v sent you a voice message",
"MESSAGE_CONTACT": "%[1]v shared a contact %[2]v with you",
"MESSAGE_DOC": "%[1]v sent you a file",
"MESSAGE_DOCS": "%[1]v sent you %[2]v files",
"MESSAGE_FWDS": "%[1]v forwarded you %[2]v messages",
"MESSAGE_GAME": "%[1]v invited you to play %[2]v",
"MESSAGE_GAME_SCORE": "%[1]v scored %[3]v in game %[2]v",
"MESSAGE_GEO": "%[1]v sent you a location",
"MESSAGE_GEOLIVE": "%[1]v sent you a live location",
"MESSAGE_GIF": "%[1]v sent you a GIF",
"MESSAGE_GIFTCODE": "%[1]v sent you a Gift Code for %[2]v of Telegram Premium",
"MESSAGE_GIVEAWAY": "%[1]v sent you a giveaway of %[2]vx %[3]vm Premium subscriptions",
"MESSAGE_GIVEAWAY_STARS": "%[1]v sent you a giveaway of %[3]v stars %[2]v",
"MESSAGE_INVOICE": "%[1]v sent you an invoice for %[2]v",
"MESSAGE_NOTEXT": "%[1]v sent you a message",
"MESSAGE_PAID_MEDIA": "%[1]v sent you a paid post for %[2]v star",
"MESSAGE_PHOTO": "%[1]v sent you a photo",
"MESSAGE_PHOTOS": "%[1]v sent you %[2]v photos",
"MESSAGE_PHOTO_SECRET": "%[1]v sent you a self-destructing photo",
"MESSAGE_PLAYLIST": "%[1]v sent you %[2]v music files",
"MESSAGE_POLL": "%[1]v sent you a poll %[2]v",
"MESSAGE_QUIZ": "%[1]v sent you a quiz %[2]v",
"MESSAGE_RECURRING_PAY": "You were charged %[2]v by %[1]v",
"MESSAGE_ROUND": "%[1]v sent you a video message",
"MESSAGE_SAME_WALLPAPER": "%[1]v set a same wallpaper for this chat",
"MESSAGE_SCREENSHOT": "%[1]v took a screenshot",
"MESSAGE_STARGIFT": "%[1]v sent you a Gift worth %[2]v Stars",
"MESSAGE_STICKER": "%[1]v sent you a %[2]v sticker",
"MESSAGE_STORY": "%[1]v shared a story with you",
"MESSAGE_STORY_MENTION": "%[1]v mentioned you in a story",
"MESSAGE_TEXT": "%[1]v: %[2]v",
"MESSAGE_VIDEO": "%[1]v sent you a video",
"MESSAGE_VIDEOS": "%[1]v sent you %[2]v videos",
"MESSAGE_VIDEO_SECRET": "%[1]v sent you a self-destructing video",
"MESSAGE_WALLPAPER": "%[1]v set a new wallpaper for this chat",
"PHONE_CALL_MISSED": "You missed a call from %[1]v",
"PHONE_CALL_REQUEST": "%[1]v is calling you!",
"PINNED_AUDIO": "%[1]v pinned a voice message in the group %[2]v",
"PINNED_CONTACT": "%[1]v pinned a contact %[3]v in the group %[2]v",
"PINNED_DOC": "%[1]v pinned a file in the group %[2]v",
"PINNED_GAME": "%[1]v pinned a game in the group %[2]v",
"PINNED_GAME_SCORE": "%[1]v pinned a game score in the group %[2]v",
"PINNED_GEO": "%[1]v pinned a map in the group %[2]v",
"PINNED_GEOLIVE": "%[1]v pinned a live location in the group %[2]v",
"PINNED_GIF": "%[1]v pinned a GIF in the group %[2]v",
"PINNED_GIVEAWAY": "%[1]v pinned a giveaway in the group %[2]v",
"PINNED_INVOICE": "%[1]v pinned an invoice in the group %[2]v",
"PINNED_NOTEXT": "%[1]v pinned a message in the group %[2]v",
"PINNED_PAID_MEDIA": "%[1]v pinned a paid post for %[3]v star in the group %[2]v",
"PINNED_PHOTO": "%[1]v pinned a photo in the group %[2]v",
"PINNED_POLL": "%[1]v pinned a poll %[3]v in the group %[2]v",
"PINNED_QUIZ": "%[1]v pinned a quiz %[3]v in the group %[2]v",
"PINNED_ROUND": "%[1]v pinned a video message in the group %[2]v",
"PINNED_STICKER": "%[1]v pinned a %[3]v sticker in the group %[2]v",
"PINNED_TEXT": "%[1]v pinned \"%[3]v\" in the group %[2]v",
"PINNED_VIDEO": "%[1]v pinned a video in the group %[2]v",
"REACT_AUDIO": "%[1]v: %[2]v to your voice message",
"REACT_CONTACT": "%[1]v: %[2]v to your contact %[3]v",
"REACT_DOC": "%[1]v: %[2]v to your file",
"REACT_GAME": "%[1]v: %[2]v to your game",
"REACT_GEO": "%[1]v: %[2]v to your map",
"REACT_GEOLIVE": "%[1]v: %[2]v to your live location",
"REACT_GIF": "%[1]v: %[2]v to your GIF",
"REACT_GIVEAWAY": "%[1]v reacted %[2]v to your giveaway",
"REACT_HIDDEN": "New reaction to your message",
"REACT_INVOICE": "%[1]v: %[2]v to your invoice",
"REACT_NOTEXT": "%[1]v: %[2]v to your message",
"REACT_PHOTO": "%[1]v: %[2]v to your photo",
"REACT_POLL": "%[1]v: %[2]v to your poll %[3]v",
"REACT_QUIZ": "%[1]v: %[2]v to your quiz %[3]v",
"REACT_ROUND": "%[1]v: %[2]v to your video message",
"REACT_STICKER": "%[1]v: %[2]v to your %[3]v sticker",
"REACT_STORY": "%[1]v: %[2]v to your story",
"REACT_STORY_HIDDEN": "New reaction to your story",
"REACT_TEXT": "%[1]v: %[2]v to your \"%[3]v\"",
"REACT_VIDEO": "%[1]v: %[2]v to your video",
"STORY_HIDDEN_AUTHOR": "A new story was posted",
"STORY_NOTEXT": "%[1]v posted a story",
}
var FullSyncOnConnectBackground = false
func (t *TelegramClient) ConnectBackground(ctx context.Context, params *bridgev2.ConnectBackgroundParams) error {
data, _ := params.ExtraData.(*PushNotificationData)
var relatedPortal *bridgev2.Portal
var sender *bridgev2.Ghost
var messageID networkid.MessageID
var messageText, notificationText, notificationTitle string
if data != nil {
if data.Aps != nil {
notificationTitle = data.Aps.Alert.Title
notificationText = data.Aps.Alert.Body
if data.LocKey == "" {
messageText = data.Aps.Alert.Body
}
}
tpl, ok := PushMessageFormats[data.LocKey]
if ok && (len(data.LocArgs) > 0 || !strings.Contains(tpl, "%[1]")) {
notificationText = fmt.Sprintf(tpl, exslices.CastToAny(data.LocArgs)...)
}
switch data.LocKey {
case "MESSAGE_TEXT", "CHANNEL_MESSAGE_TEXT":
messageText = data.LocArgs[1]
case "CHAT_MESSAGE_TEXT":
messageText = data.LocArgs[2]
}
var err error
if data.Custom.ChannelID != 0 {
relatedPortal, err = t.main.Bridge.GetPortalByKey(ctx, t.makePortalKeyFromID(ids.PeerTypeChannel, data.Custom.ChannelID))
} else if data.Custom.ChatID != 0 {
relatedPortal, err = t.main.Bridge.GetPortalByKey(ctx, t.makePortalKeyFromID(ids.PeerTypeChat, data.Custom.ChatID))
} else if data.Custom.FromID != 0 {
relatedPortal, err = t.main.Bridge.GetPortalByKey(ctx, t.makePortalKeyFromID(ids.PeerTypeUser, data.Custom.FromID))
}
if err != nil {
return fmt.Errorf("failed to get related portal: %w", err)
}
if data.Custom.ChatFromBroadcastID != 0 {
sender, err = t.main.Bridge.GetGhostByID(ctx, ids.MakeChannelUserID(data.Custom.FromID))
} else if data.Custom.ChatFromGroupID != 0 {
sender, err = t.main.Bridge.GetGhostByID(ctx, ids.MakeChannelUserID(data.Custom.ChatFromGroupID))
} else if data.Custom.ChatFromID != 0 {
sender, err = t.main.Bridge.GetGhostByID(ctx, ids.MakeUserID(data.Custom.ChatFromID))
} else if data.Custom.FromID != 0 {
sender, err = t.main.Bridge.GetGhostByID(ctx, ids.MakeUserID(data.Custom.FromID))
}
if err != nil {
return fmt.Errorf("failed to get sender: %w", err)
}
if relatedPortal != nil && data.Custom.MessageID != 0 {
messageID = ids.MakeMessageID(relatedPortal.PortalKey, data.Custom.MessageID)
}
}
notifs, ok := t.main.Bridge.Matrix.(bridgev2.MatrixConnectorWithNotifications)
if ok {
notifs.DisplayNotification(ctx, &bridgev2.DirectNotificationData{
Portal: relatedPortal,
Sender: sender,
Message: messageText,
MessageID: messageID,
FormattedNotification: notificationText,
FormattedTitle: notificationTitle,
})
}
if FullSyncOnConnectBackground {
t.Connect(ctx)
defer t.Disconnect()
// TODO is it possible to safely only sync one chat?
select {
case <-time.After(20 * time.Second):
case <-ctx.Done():
}
}
return nil
}
func (tg *TelegramConnector) ParsePushNotification(ctx context.Context, data json.RawMessage) (networkid.UserLoginID, any, error) {
val := gjson.GetBytes(data, "p")
if val.Type != gjson.String {
return "", nil, fmt.Errorf("missing or invalid p field")
}
valBytes, err := base64.RawURLEncoding.DecodeString(val.Str)
if err != nil {
return "", nil, fmt.Errorf("failed to base64 decode p field: %w", err)
}
var em crypto.EncryptedMessage
err = em.DecodeWithoutCopy(&bin.Buffer{Buf: valBytes})
if err != nil {
return "", nil, fmt.Errorf("failed to decode auth key and message ID: %w", err)
}
userIDs, err := tg.Bridge.DB.UserLogin.GetAllUserIDsWithLogins(ctx)
if err != nil {
return "", nil, fmt.Errorf("failed to get users with logins: %w", err)
}
var matchingAuthKey *crypto.AuthKey
var userLoginID networkid.UserLoginID
UserLoop:
for _, userID := range userIDs {
user, err := tg.Bridge.GetExistingUserByMXID(ctx, userID)
if err != nil {
return "", nil, fmt.Errorf("failed to get user %s: %w", userID, err)
}
for _, login := range user.GetUserLogins() {
key := login.Metadata.(*UserLoginMetadata).PushEncryptionKey
if len(key) != 256 {
continue
}
authKey := crypto.Key(key).WithID()
if authKey.ID == em.AuthKeyID {
matchingAuthKey = &authKey
userLoginID = login.ID
break UserLoop
}
}
}
if matchingAuthKey == nil {
return "", nil, fmt.Errorf("no matching auth key found")
}
c := crypto.NewClientCipher(rand.Reader)
plaintext, err := c.DecryptRaw(*matchingAuthKey, &em)
if err != nil {
return userLoginID, nil, fmt.Errorf("failed to decrypt payload: %w", err)
} else if len(plaintext) < 4 {
return userLoginID, nil, fmt.Errorf("decrypted payload too short (expected >4, got %d)", len(plaintext))
}
jsonLength := binary.LittleEndian.Uint32(plaintext[0:4])
if len(plaintext) < int(jsonLength)+4 {
return userLoginID, nil, fmt.Errorf("decrypted payload too short (expected 4+%d, got %d)", jsonLength, len(plaintext))
}
jsonData := plaintext[4 : jsonLength+4]
var pmd PushNotificationData
err = json.Unmarshal(jsonData, &pmd)
if err != nil {
zerolog.Ctx(ctx).Debug().Str("raw_data", base64.StdEncoding.EncodeToString(plaintext)).Msg("Decrypted non-JSON push data")
return userLoginID, nil, fmt.Errorf("failed to unmarshal decrypted payload: %w", err)
}
if pmd.Aps != nil {
// APNs notifications don't have custom data nested in a separate key, they have it at the top level
err = json.Unmarshal(jsonData, &pmd.Custom)
if err != nil {
return userLoginID, nil, fmt.Errorf("failed to unmarshal APNs data into custom field: %w", err)
}
}
return userLoginID, &pmd, nil
}
func (t *TelegramClient) RegisterPushNotifications(ctx context.Context, pushType bridgev2.PushType, token string) error {
meta := t.userLogin.Metadata.(*UserLoginMetadata)
if meta.PushEncryptionKey == nil {
meta.PushEncryptionKey = random.Bytes(256)
err := t.userLogin.Save(ctx)
if err != nil {
return fmt.Errorf("failed to save push encryption key: %w", err)
}
}
var tokenType int
switch pushType {
case bridgev2.PushTypeWeb:
tokenType = 10
case bridgev2.PushTypeFCM:
tokenType = 2
case bridgev2.PushTypeAPNs:
tokenType = 1
default:
return fmt.Errorf("unsupported push type %s", pushType)
}
_, err := t.client.API().AccountRegisterDevice(ctx, &tg.AccountRegisterDeviceRequest{
NoMuted: true,
TokenType: tokenType,
Token: token,
AppSandbox: PushAppSandbox,
Secret: meta.PushEncryptionKey,
OtherUIDs: nil, // TODO set properly
})
return err
}
func (t *TelegramClient) GetPushConfigs() *bridgev2.PushConfig {
return &bridgev2.PushConfig{Native: true}
}