owpengram-server/internal/rpc/channels_legacy_chat.go

640 lines
20 KiB
Go

package rpc
import (
"context"
"errors"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"telesrv/internal/compat/tdesktop"
"telesrv/internal/domain"
"unicode/utf8"
)
func (r *Router) onMessagesCreateChat(ctx context.Context, req *tg.MessagesCreateChatRequest) (*tg.MessagesInvitedUsers, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if !validChannelTitle(req.Title) || len(req.Users) > domain.MaxChannelInviteUsers {
return nil, channelInvalidErr(domain.ErrChannelTitleInvalid)
}
if req.TTLPeriod < 0 {
return nil, ttlPeriodInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
memberIDs, err := r.userIDsFromInputUsers(ctx, userID, req.Users)
if err != nil {
return nil, err
}
memberIDs = createChatInviteMemberIDs(memberIDs, userID)
date := int(r.clock.Now().Unix())
r.log.Debug("messages.createChat resolved users",
zap.Int("input_users", len(req.Users)),
zap.Int("member_ids", len(memberIDs)),
zap.Int64s("member_user_ids", memberIDs),
)
if len(memberIDs) == 0 {
return nil, usersTooFewErr()
}
createRes, err := r.deps.Channels.CreateMegagroupFromCreateChat(ctx, userID, domain.CreateChannelRequest{
CreatorUserID: userID,
Title: req.Title,
TTLPeriod: req.TTLPeriod,
Date: date,
})
if err != nil {
return nil, channelInvalidErr(err)
}
r.addOnlineChannelMemberships(createRes.Channel.ID, channelMemberUserIDs(createRes.Members)...)
responseRes := createRes
var inviteRes domain.CreateChannelResult
if len(memberIDs) > 0 {
inviteRes, err = r.deps.Channels.InviteToChannel(ctx, userID, createRes.Channel.ID, memberIDs, date)
if err != nil {
return nil, channelInviteErr(err)
}
r.addOnlineChannelMemberships(inviteRes.Channel.ID, channelMemberUserIDs(inviteRes.Members)...)
responseRes.Channel = inviteRes.Channel
responseRes.Members = mergeChannelMembers(createRes.Members, inviteRes.Members)
responseRes.Recipients = uniqueRecipientIDs(append(append([]int64{}, createRes.Recipients...), inviteRes.Recipients...))
}
cache := newViewerPeerCache(r)
updates := r.channelOperationUpdatesWithPeerCache(ctx, userID, responseRes, cache)
if createChatNeedsLegacyChat(ctx) {
updates = r.tdesktopCreateChatUpdatesWithPeerCache(ctx, userID, responseRes, cache)
}
if inviteRes.Event.Pts != 0 {
inviteUpdates := r.channelOperationUpdatesWithPeerCache(ctx, userID, inviteRes, cache)
if inviteUpdates != nil {
updates.Updates = append(updates.Updates, inviteUpdates.Updates...)
}
}
if inviteRes.Event.Pts != 0 {
r.pushChannelExplicitUpdates(ctx, userID, inviteRes.Channel.ID, memberIDs, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdatesWithPeerCache(ctx, viewerUserID, inviteRes, cache)
})
}
return &tg.MessagesInvitedUsers{Updates: updates, MissingInvitees: []tg.MissingInvitee{}}, nil
}
func (r *Router) onMessagesMigrateChat(ctx context.Context, chatID int64) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if chatID <= 0 {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
userID, view, err := r.channelChangeInfoView(ctx, &tg.InputChannel{ChannelID: chatID})
if err != nil {
return nil, err
}
if !view.Channel.Megagroup {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
return r.channelStateUpdates(userID, view.Channel), nil
}
func (r *Router) onMessagesGetChats(ctx context.Context, ids []int64) (tg.MessagesChatsClass, error) {
if len(ids) > maxGetMessagesIDs {
return nil, limitInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
chats := make([]tg.ChatClass, 0, len(ids))
if r.deps.Channels != nil {
unique := make([]int64, 0, len(ids))
seen := make(map[int64]struct{}, len(ids))
for _, id := range ids {
if id <= 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
unique = append(unique, id)
}
views, err := r.deps.Channels.GetChannels(ctx, userID, unique)
if err != nil {
return nil, channelInvalidErr(err)
}
byID := make(map[int64]domain.ChannelView, len(views))
for _, view := range views {
if view.Channel.ID != 0 {
byID[view.Channel.ID] = view
}
}
for _, id := range ids {
if id <= 0 {
continue
}
if view, ok := byID[id]; ok {
chats = append(chats, tgChannelChatForView(userID, view))
}
}
}
return &tg.MessagesChats{Chats: chats}, nil
}
func (r *Router) onMessagesGetFullChat(ctx context.Context, chatID int64) (*tg.MessagesChatFull, error) {
if chatID <= 0 {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
return r.onChannelsGetFullChannel(ctx, &tg.InputChannel{ChannelID: chatID})
}
func (r *Router) onMessagesAddChatUser(ctx context.Context, req *tg.MessagesAddChatUserRequest) (*tg.MessagesInvitedUsers, error) {
if req.ChatID <= 0 || req.UserID == nil {
return nil, peerIDInvalidErr()
}
return r.onChannelsInviteToChannel(ctx, &tg.ChannelsInviteToChannelRequest{
Channel: &tg.InputChannel{ChannelID: req.ChatID},
Users: []tg.InputUserClass{req.UserID},
})
}
func (r *Router) onMessagesDeleteChatUser(ctx context.Context, req *tg.MessagesDeleteChatUserRequest) (tg.UpdatesClass, error) {
if req.ChatID <= 0 || req.UserID == nil {
return nil, peerIDInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
target, found, err := r.userFromInput(ctx, userID, req.UserID)
if err != nil {
return nil, internalErr()
}
if !found || target.ID == 0 {
return nil, peerIDInvalidErr()
}
if target.ID == userID {
return r.onChannelsLeaveChannel(ctx, &tg.InputChannel{ChannelID: req.ChatID})
}
return r.onChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{
Channel: &tg.InputChannel{ChannelID: req.ChatID},
Participant: &tg.InputPeerUser{UserID: target.ID, AccessHash: target.AccessHash},
BannedRights: tg.ChatBannedRights{
ViewMessages: true,
UntilDate: 0,
},
})
}
func (r *Router) onMessagesEditChatTitle(ctx context.Context, req *tg.MessagesEditChatTitleRequest) (tg.UpdatesClass, error) {
if req.ChatID <= 0 {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
return r.onChannelsEditTitle(ctx, &tg.ChannelsEditTitleRequest{
Channel: &tg.InputChannel{ChannelID: req.ChatID},
Title: req.Title,
})
}
func (r *Router) onMessagesEditChatPhoto(ctx context.Context, req *tg.MessagesEditChatPhotoRequest) (tg.UpdatesClass, error) {
if req.ChatID <= 0 || req.Photo == nil {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
return r.onChannelsEditPhoto(ctx, &tg.ChannelsEditPhotoRequest{
Channel: &tg.InputChannel{ChannelID: req.ChatID},
Photo: req.Photo,
})
}
func (r *Router) onMessagesEditChatAdmin(ctx context.Context, req *tg.MessagesEditChatAdminRequest) (bool, error) {
if req.ChatID <= 0 || req.UserID == nil {
return false, peerIDInvalidErr()
}
rights := tg.ChatAdminRights{}
if req.IsAdmin {
rights = legacyBasicGroupAdminRights()
}
_, err := r.onChannelsEditAdmin(ctx, &tg.ChannelsEditAdminRequest{
Channel: &tg.InputChannel{ChannelID: req.ChatID},
UserID: req.UserID,
AdminRights: rights,
})
if err != nil {
return false, err
}
return true, nil
}
func (r *Router) onMessagesEditChatAbout(ctx context.Context, req *tg.MessagesEditChatAboutRequest) (bool, error) {
if r.deps.Channels == nil {
return false, notImplementedErr()
}
if utf8.RuneCountInString(req.About) > maxChannelAboutLength {
return false, channelInvalidErr(domain.ErrChannelInvalid)
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
channelID, err := r.channelIDFromLegacyInputPeerChecked(ctx, userID, req.Peer)
if err != nil {
return false, err
}
channel, err := r.deps.Channels.EditAbout(ctx, userID, domain.EditChannelAboutRequest{
UserID: userID,
ChannelID: channelID,
About: req.About,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return false, channelAdminErr(err)
}
r.pushChannelStateToMembers(ctx, userID, channel)
return true, nil
}
func (r *Router) onMessagesEditChatDefaultBannedRights(ctx context.Context, req *tg.MessagesEditChatDefaultBannedRightsRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
if peer.Type != domain.PeerTypeChannel || peer.ID == 0 {
return nil, peerIDInvalidErr()
}
channel, err := r.deps.Channels.EditDefaultBannedRights(ctx, userID, domain.EditChannelDefaultBannedRightsRequest{
UserID: userID,
ChannelID: peer.ID,
BannedRights: domainChannelBannedRights(req.BannedRights),
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelAdminErr(err)
}
return r.channelStateMutationUpdates(ctx, userID, channel), nil
}
func (r *Router) onMessagesEditChatCreator(ctx context.Context, req *tg.MessagesEditChatCreatorRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if req.UserID == nil {
return nil, peerIDInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromLegacyInputPeerChecked(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
if req.Password == nil {
return nil, passwordHashInvalidErr()
}
if _, ok := req.UserID.(*tg.InputUserEmpty); ok {
return nil, passwordHashInvalidErr()
}
if _, ok := req.Password.(*tg.InputCheckPasswordEmpty); ok {
return nil, passwordHashInvalidErr()
}
target, found, err := r.userFromInput(ctx, userID, req.UserID)
if err != nil {
return nil, internalErr()
}
if !found || target.ID == 0 {
return nil, peerIDInvalidErr()
}
if target.Bot {
return nil, userIDInvalidErr()
}
if r.deps.Account == nil {
return nil, passwordHashInvalidErr()
}
if err := r.deps.Account.CheckPassword(ctx, userID, domainPasswordCheck(req.Password)); err != nil {
return nil, passwordErr(err)
}
res, err := r.deps.Channels.TransferOwnership(ctx, userID, domain.TransferChannelOwnershipRequest{
UserID: userID,
ChannelID: channelID,
NewOwnerID: target.ID,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelTransferErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
r.addOnlineChannelMemberships(res.Channel.ID, res.OldOwner.UserID, res.NewOwner.UserID)
cache := newViewerPeerCache(r)
updates := r.channelOwnershipTransferUpdatesWithPeerCache(ctx, userID, userID, res, cache)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOwnershipTransferUpdatesWithPeerCache(ctx, viewerUserID, userID, res, cache)
})
return updates, nil
}
func (r *Router) onMessagesGetFutureChatCreatorAfterLeave(ctx context.Context, peer tg.InputPeerClass) (tg.UserClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
resolved, err := r.checkedDomainPeerFromInputPeer(ctx, userID, peer)
if err != nil {
return nil, err
}
if resolved.Type != domain.PeerTypeChannel || resolved.ID == 0 {
return nil, peerIDInvalidErr()
}
member, err := r.deps.Channels.FutureCreatorAfterLeave(ctx, userID, resolved.ID)
if err != nil {
return nil, channelAdminErr(err)
}
if r.deps.Users == nil {
return nil, userIDInvalidErr()
}
user, found, err := r.deps.Users.ByID(ctx, userID, member.UserID)
if err != nil {
return nil, internalErr()
}
if !found || user.Bot {
return nil, tgerr400("USER_NOT_PARTICIPANT")
}
users := r.tgUsersForViewer(userID, []domain.User{user})
if len(users) == 0 {
return nil, userIDInvalidErr()
}
return users[0], nil
}
func (r *Router) onMessagesEditChatParticipantRank(ctx context.Context, req *tg.MessagesEditChatParticipantRankRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if len(req.Rank) > domain.MaxChannelAdminRankLength {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromLegacyInputPeerChecked(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
participant, ok := r.domainPeerFromInputPeer(userID, req.Participant)
if !ok || participant.Type != domain.PeerTypeUser || participant.ID == 0 {
return nil, peerIDInvalidErr()
}
res, err := r.deps.Channels.EditMemberRank(ctx, userID, domain.EditChannelMemberRankRequest{
UserID: userID,
ChannelID: channelID,
MemberID: participant.ID,
Rank: req.Rank,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelAdminErr(err)
}
cache := newViewerPeerCache(r)
updates := r.channelParticipantUpdatesWithPeerCache(ctx, userID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelParticipantUpdatesWithPeerCache(ctx, viewerUserID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
})
return updates, nil
}
func (r *Router) onMessagesSetChatTheme(ctx context.Context, req *tg.MessagesSetChatThemeRequest) (tg.UpdatesClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if channelID, err := r.channelIDFromLegacyInputPeerChecked(ctx, userID, req.Peer); err == nil {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
view, err := r.deps.Channels.GetChannel(ctx, userID, channelID)
if err != nil {
return nil, channelInvalidErr(err)
}
return r.channelStateUpdates(userID, view.Channel), nil
} else if _, ok := channelIDFromLegacyInputPeer(userID, req.Peer); ok {
return nil, err
}
peer, ok := r.domainPeerFromInputPeer(userID, req.Peer)
if !ok || peer.Type != domain.PeerTypeUser || peer.ID == 0 {
return nil, peerIDInvalidErr()
}
emoticon, err := chatThemeEmoticonFromInput(req.Theme)
if err != nil {
return nil, err
}
if r.deps.Messages == nil {
return tgEmptyUpdates(int(r.clock.Now().Unix())), nil
}
recipientBlocked, err := r.peerBlocksUser(ctx, userID, peer.ID)
if err != nil {
return nil, err
}
authKeyID, _ := AuthKeyIDFrom(ctx)
sessionID, _ := SessionIDFrom(ctx)
res, err := r.deps.Messages.SetChatTheme(ctx, userID, domain.SetPrivateChatThemeRequest{
OwnerUserID: userID,
Peer: peer,
Emoticon: emoticon,
Date: int(r.clock.Now().Unix()),
OriginAuthKeyID: authKeyID,
OriginSessionID: sessionID,
RecipientBlocked: recipientBlocked,
})
if err != nil {
if errors.Is(err, domain.ErrMessageIDInvalid) {
return nil, peerIDInvalidErr()
}
return nil, internalErr()
}
if res.Changed {
r.invalidateRPCProjectionForPeer(userID, peer)
r.invalidateRPCProjectionForPeer(peer.ID, domain.Peer{Type: domain.PeerTypeUser, ID: userID})
}
if !res.Changed || res.Send.SenderMessage.ID == 0 {
return tgEmptyUpdates(int(r.clock.Now().Unix())), nil
}
return tgPrivateMessageUpdates(
res.Send.SenderEvent,
res.Send.SenderMessage,
0,
false,
r.usersForMessageUpdate(ctx, userID, res.Send.SenderMessage),
[]tg.ChatClass{},
), nil
}
func chatThemeEmoticonFromInput(theme tg.InputChatThemeClass) (string, error) {
switch value := theme.(type) {
case *tg.InputChatThemeEmpty:
return "", nil
case *tg.InputChatTheme:
if value.Emoticon == "" {
return "", nil
}
if !tdesktop.IsChatThemeEmoticon(value.Emoticon) {
return "", themeInvalidErr()
}
return value.Emoticon, nil
case *tg.InputChatThemeUniqueGift:
return "", themeInvalidErr()
default:
return "", inputConstructorInvalidErr()
}
}
func (r *Router) onMessagesSetChatWallPaper(ctx context.Context, req *tg.MessagesSetChatWallPaperRequest) (tg.UpdatesClass, error) {
if req == nil {
return nil, inputConstructorInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
wallpaper, err := domainWallpaperFromSetChatWallPaper(req)
if err != nil {
return nil, err
}
switch peer.Type {
case domain.PeerTypeUser:
if peer.ID == 0 {
return nil, peerIDInvalidErr()
}
return tgEmptyUpdates(int(r.clock.Now().Unix())), nil
case domain.PeerTypeChannel:
if r.deps.Channels == nil || peer.ID == 0 {
return nil, peerIDInvalidErr()
}
res, err := r.deps.Channels.SetWallpaper(ctx, userID, domain.SetChannelWallpaperRequest{
UserID: userID,
ChannelID: peer.ID,
Wallpaper: wallpaper,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelAdminErr(err)
}
r.invalidateRPCProjectionForChannel(res.Channel.ID)
if res.Changed && res.Event.Pts != 0 {
r.enqueueChannelWallpaperFanout(ctx, userID, res)
} else if res.Changed {
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelWallpaperUpdatesWithPeerCache(ctx, viewerUserID, res, nil)
})
}
return r.channelWallpaperUpdatesWithPeerCache(ctx, userID, res, nil), nil
default:
return nil, peerIDInvalidErr()
}
}
func (r *Router) channelWallpaperUpdatesWithPeerCache(ctx context.Context, viewerUserID int64, res domain.SetChannelWallpaperResult, cache *viewerPeerCache) *tg.Updates {
update := tgUpdatePeerWallpaper(domain.Peer{Type: domain.PeerTypeChannel, ID: res.Channel.ID}, res.Channel.Wallpaper)
if res.Event.Pts != 0 {
sendRes := domain.SendChannelMessageResult{
Channel: res.Channel,
Message: res.Message,
Event: res.Event,
Recipients: res.Recipients,
}
updates := r.channelMessageUpdatesWithPeerCache(ctx, viewerUserID, sendRes, 0, cache)
updates.Updates = append(updates.Updates, update)
return updates
}
return &tg.Updates{
Updates: []tg.UpdateClass{update},
Chats: []tg.ChatClass{tgChannelChatMin(viewerUserID, res.Channel)},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
}
func (r *Router) enqueueChannelWallpaperFanout(ctx context.Context, originUserID int64, res domain.SetChannelWallpaperResult) {
sendRes := domain.SendChannelMessageResult{
Channel: res.Channel,
Message: res.Message,
Event: res.Event,
Recipients: res.Recipients,
}
fanoutCache := newViewerPeerCache(r)
ownerIDs := channelMessageFanoutOwnerIDs(sendRes, nil)
r.enqueueChannelFanoutWithPrefetch(ctx, channelFanoutMembers, originUserID, res.Channel.ID, res.Event.Pts, res.Recipients,
func(bgCtx context.Context, viewers []int64) {
r.prefetchChannelFanoutUsers(bgCtx, fanoutCache, viewers, ownerIDs)
},
func(bgCtx context.Context, viewerUserID int64) *tg.Updates {
return r.channelWallpaperUpdatesWithPeerCache(bgCtx, viewerUserID, res, fanoutCache)
})
}
func (r *Router) onMessagesToggleNoForwards(ctx context.Context, req *tg.MessagesToggleNoForwardsRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromLegacyInputPeerChecked(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
channel, err := r.deps.Channels.SetNoForwards(ctx, userID, channelID, req.Enabled)
if err != nil {
return nil, channelAdminErr(err)
}
return r.channelStateMutationUpdates(ctx, userID, channel), nil
}
func (r *Router) onMessagesSetChatAvailableReactions(ctx context.Context, req *tg.MessagesSetChatAvailableReactionsRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromLegacyInputPeerChecked(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
policy, err := domainChannelReactionPolicy(req)
if err != nil {
return nil, err
}
channel, err := r.deps.Channels.SetAvailableReactions(ctx, userID, channelID, policy)
if err != nil {
return nil, channelAdminErr(err)
}
return r.channelStateMutationUpdates(ctx, userID, channel), nil
}
func legacyBasicGroupAdminRights() tg.ChatAdminRights {
return tg.ChatAdminRights{
ChangeInfo: true,
DeleteMessages: true,
BanUsers: true,
InviteUsers: true,
PinMessages: true,
Other: true,
}
}