teldrive/pkg/services/user.go

374 lines
9.5 KiB
Go

package services
import (
"bytes"
"context"
"errors"
"fmt"
"sort"
"strings"
"sync"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/message/peer"
"github.com/gotd/td/telegram/query"
"github.com/gotd/td/tg"
"github.com/gotd/td/tgerr"
"github.com/tgdrive/teldrive/internal/api"
"github.com/tgdrive/teldrive/internal/auth"
"github.com/tgdrive/teldrive/internal/tgc"
"github.com/tgdrive/teldrive/pkg/models"
"github.com/tgdrive/teldrive/pkg/types"
"golang.org/x/sync/errgroup"
tgbbolt "github.com/gotd/contrib/bbolt"
"github.com/gotd/contrib/storage"
"gorm.io/gorm/clause"
)
func (a *apiService) UsersAddBots(ctx context.Context, req *api.AddBots) error {
userId, session := auth.GetUser(ctx)
client, _ := tgc.AuthClient(ctx, &a.cnf.TG, session, a.middlewares...)
if len(req.Bots) > 0 {
channelId, err := getDefaultChannel(a.db, a.cache, userId)
if err != nil {
return &apiError{err: err}
}
err = a.addBots(ctx, client, userId, channelId, req.Bots)
if err != nil {
return &apiError{err: err}
}
}
return nil
}
func (a *apiService) UsersListChannels(ctx context.Context) ([]api.Channel, error) {
userID, _ := auth.GetUser(ctx)
channels := make(map[int64]*api.Channel)
peerStorage := tgbbolt.NewPeerStorage(a.boltdb, []byte(fmt.Sprintf("peers:%d", userID)))
iter, err := peerStorage.Iterate(ctx)
if err != nil {
return []api.Channel{}, nil
}
for iter.Next(ctx) {
peer := iter.Value()
if peer.Channel != nil && peer.Channel.AdminRights.AddAdmins {
_, exists := channels[peer.Channel.ID]
if !exists {
channels[peer.Channel.ID] = &api.Channel{ChannelId: peer.Channel.ID, ChannelName: peer.Channel.Title}
}
}
}
res := []api.Channel{}
for _, channel := range channels {
res = append(res, *channel)
}
sort.Slice(res, func(i, j int) bool {
return res[i].ChannelName < res[j].ChannelName
})
return res, nil
}
func (a *apiService) UsersSyncChannels(ctx context.Context) error {
userId, session := auth.GetUser(ctx)
peerStorage := tgbbolt.NewPeerStorage(a.boltdb, []byte(fmt.Sprintf("peers:%d", userId)))
collector := storage.CollectPeers(peerStorage)
client, err := tgc.AuthClient(ctx, &a.cnf.TG, session, a.middlewares...)
if err != nil {
return &apiError{err: err}
}
err = client.Run(ctx, func(ctx context.Context) error {
return collector.Dialogs(ctx, query.GetDialogs(client.API()).Iter())
})
if err != nil {
return &apiError{err: err}
}
return nil
}
func (a *apiService) UsersListSessions(ctx context.Context) ([]api.UserSession, error) {
userId, userSession := auth.GetUser(ctx)
client, _ := tgc.AuthClient(ctx, &a.cnf.TG, userSession, a.middlewares...)
var (
auth *tg.AccountAuthorizations
err error
)
err = client.Run(ctx, func(ctx context.Context) error {
auth, err = client.API().AccountGetAuthorizations(ctx)
if err != nil {
return err
}
return nil
})
if err != nil && !tgerr.Is(err, "AUTH_KEY_UNREGISTERED") {
return nil, err
}
dbSessions := []models.Session{}
if err = a.db.Where("user_id = ?", userId).Order("created_at DESC").Find(&dbSessions).Error; err != nil {
return nil, err
}
sessionsOut := []api.UserSession{}
for _, session := range dbSessions {
s := api.UserSession{Hash: session.Hash,
CreatedAt: session.CreatedAt.UTC(),
Current: session.Session == userSession}
if auth != nil {
for _, a := range auth.Authorizations {
if session.SessionDate == a.DateCreated {
s.AppName = api.NewOptString(strings.Trim(strings.Replace(a.AppName, "Telegram", "", -1), " "))
s.Location = api.NewOptString(a.Country)
s.OfficialApp = api.NewOptBool(a.OfficialApp)
s.Valid = true
break
}
}
}
sessionsOut = append(sessionsOut, s)
}
return sessionsOut, nil
}
func (a *apiService) UsersProfileImage(ctx context.Context, params api.UsersProfileImageParams) (*api.UsersProfileImageOKHeaders, error) {
_, session := auth.GetUser(ctx)
client, err := tgc.AuthClient(ctx, &a.cnf.TG, session, a.middlewares...)
if err != nil {
return nil, &apiError{err: err}
}
res := &api.UsersProfileImageOKHeaders{}
err = tgc.RunWithAuth(ctx, client, "", func(ctx context.Context) error {
self, err := client.Self(ctx)
if err != nil {
return err
}
peer := self.AsInputPeer()
if self.Photo == nil {
return nil
}
photo, ok := self.Photo.AsNotEmpty()
if !ok {
return errors.New("profile not found")
}
photo.GetPersonal()
location := &tg.InputPeerPhotoFileLocation{Big: false, Peer: peer, PhotoID: photo.PhotoID}
buff, err := tgc.GetMediaContent(ctx, client.API(), location)
if err != nil {
return err
}
content := buff.Bytes()
res.SetCacheControl("public, max-age=86400, must-revalidate")
res.SetContentLength(int64(len(content)))
res.SetEtag(fmt.Sprintf("\"%v\"", photo.PhotoID))
res.SetContentDisposition(fmt.Sprintf("inline; filename=\"%s\"", "profile.jpeg"))
res.Response = api.UsersProfileImageOK{Data: bytes.NewReader(content)}
return nil
})
if err != nil {
return nil, &apiError{err: err}
}
return res, nil
}
func (a *apiService) UsersRemoveBots(ctx context.Context) error {
userID, _ := auth.GetUser(ctx)
channelId, err := getDefaultChannel(a.db, a.cache, userID)
if err != nil {
return &apiError{err: err}
}
if err := a.db.Where("user_id = ?", userID).Where("channel_id = ?", channelId).
Delete(&models.Bot{}).Error; err != nil {
return &apiError{err: err}
}
a.cache.Delete(fmt.Sprintf("users:bots:%d:%d", userID, channelId))
return nil
}
func (a *apiService) UsersRemoveSession(ctx context.Context, params api.UsersRemoveSessionParams) error {
userId, _ := auth.GetUser(ctx)
session := &models.Session{}
if err := a.db.Where("user_id = ?", userId).Where("hash = ?", params.ID).First(session).Error; err != nil {
return &apiError{err: err}
}
client, _ := tgc.AuthClient(ctx, &a.cnf.TG, session.Session, a.middlewares...)
client.Run(ctx, func(ctx context.Context) error {
_, err := client.API().AuthLogOut(ctx)
if err != nil {
return err
}
return nil
})
a.db.Where("user_id = ?", userId).Where("hash = ?", session.Hash).Delete(&models.Session{})
return nil
}
func (a *apiService) UsersStats(ctx context.Context) (*api.UserConfig, error) {
userID, _ := auth.GetUser(ctx)
var (
channelId int64
err error
)
channelId, _ = getDefaultChannel(a.db, a.cache, userID)
tokens, err := getBotsToken(a.db, a.cache, userID, channelId)
if err != nil {
return nil, &apiError{err: err}
}
return &api.UserConfig{Bots: tokens, ChannelId: channelId}, nil
}
func (a *apiService) UsersUpdateChannel(ctx context.Context, req *api.ChannelUpdate) error {
userId, _ := auth.GetUser(ctx)
channel := &models.Channel{UserID: userId, Selected: true}
if req.ChannelId.Value != 0 {
channel.ChannelID = req.ChannelId.Value
}
if req.ChannelName.Value != "" {
channel.ChannelName = req.ChannelName.Value
}
if err := a.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "channel_id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"selected": true}),
}).Create(channel).Error; err != nil {
return &apiError{err: errors.New("failed to update channel")}
}
a.db.Model(&models.Channel{}).Where("channel_id != ?", channel.ChannelID).
Where("user_id = ?", userId).Update("selected", false)
key := fmt.Sprintf("users:channel:%d", userId)
a.cache.Set(key, channel.ChannelID, 0)
return nil
}
func (a *apiService) addBots(c context.Context, client *telegram.Client, userId int64, channelId int64, botsTokens []string) error {
botInfoMap := make(map[string]*types.BotInfo)
err := tgc.RunWithAuth(c, client, "", func(ctx context.Context) error {
channel, err := tgc.GetChannelById(ctx, client.API(), channelId)
if err != nil {
return err
}
g, _ := errgroup.WithContext(ctx)
g.SetLimit(8)
mapMu := sync.Mutex{}
for _, token := range botsTokens {
g.Go(func() error {
info, err := tgc.GetBotInfo(c, a.boltdb, &a.cnf.TG, token)
if err != nil {
return err
}
botPeerClass, err := peer.DefaultResolver(client.API()).ResolveDomain(ctx, info.UserName)
if err != nil {
return err
}
botPeer := botPeerClass.(*tg.InputPeerUser)
info.AccessHash = botPeer.AccessHash
mapMu.Lock()
botInfoMap[token] = info
mapMu.Unlock()
return nil
})
}
if err = g.Wait(); err != nil {
return err
}
if len(botsTokens) == len(botInfoMap) {
users := []tg.InputUser{}
for _, info := range botInfoMap {
users = append(users, tg.InputUser{UserID: info.Id, AccessHash: info.AccessHash})
}
for _, user := range users {
payload := &tg.ChannelsEditAdminRequest{
Channel: channel,
UserID: tg.InputUserClass(&user),
AdminRights: tg.ChatAdminRights{
ChangeInfo: true,
PostMessages: true,
EditMessages: true,
DeleteMessages: true,
BanUsers: true,
InviteUsers: true,
PinMessages: true,
ManageCall: true,
Other: true,
ManageTopics: true,
},
Rank: "bot",
}
_, err := client.API().ChannelsEditAdmin(ctx, payload)
if err != nil {
return err
}
}
} else {
return errors.New("failed to fetch bots")
}
return nil
})
if err != nil {
return err
}
payload := []models.Bot{}
for _, info := range botInfoMap {
payload = append(payload, models.Bot{UserID: userId, Token: info.Token, BotID: info.Id,
BotUserName: info.UserName, ChannelID: channelId,
})
}
a.cache.Delete(fmt.Sprintf("users:bots:%d:%d", userId, channelId))
if err := a.db.Clauses(clause.OnConflict{DoNothing: true}).Create(&payload).Error; err != nil {
return err
}
return nil
}