package rpc import ( "context" "errors" "unicode/utf8" "github.com/iamxvbaba/td/tg" "github.com/iamxvbaba/td/tgerr" "telesrv/internal/domain" ) func (r *Router) onEphemeralSendWelcomeMessage(ctx context.Context, request *tg.EphemeralSendMessageRequest) (tg.UpdatesClass, error) { if request == nil { return nil, inputRequestInvalidErr() } _, hasQueryID := request.GetQueryID() _, hasReplyTo := request.GetReplyTo() if !request.Welcome || r.deps.WelcomeMessages == nil || request.Peer == nil || request.Anchor || hasReplyTo || hasQueryID || request.RandomID == 0 || !welcomeReceiverEmpty(request.ReceiverID) { return nil, inputRequestInvalidErr() } userID, peer, err := r.welcomeActorAndPeer(ctx, request.Peer) if err != nil { return nil, err } if err := r.deps.WelcomeMessages.Authorize(ctx, userID, peer); err != nil { return nil, welcomeMessageRPCError(err) } content, err := r.domainWelcomeSendContent(ctx, userID, request) if err != nil { return nil, err } message, _, err := r.deps.WelcomeMessages.Create(ctx, userID, peer, request.RandomID, content) if err != nil { return nil, welcomeMessageRPCError(err) } return r.welcomeMessageUpdates(ctx, userID, message, false) } func (r *Router) onEphemeralEditWelcomeMessage(ctx context.Context, request *tg.EphemeralEditMessageRequest) (tg.UpdatesClass, error) { if request == nil || !request.Welcome || r.deps.WelcomeMessages == nil || request.Peer == nil || request.ID <= 0 || request.ID > domain.MaxMessageBoxID || !welcomeReceiverEmpty(request.ReceiverID) { return nil, inputRequestInvalidErr() } userID, peer, err := r.welcomeActorAndPeer(ctx, request.Peer) if err != nil { return nil, err } if err := r.deps.WelcomeMessages.Authorize(ctx, userID, peer); err != nil { return nil, welcomeMessageRPCError(err) } fields, err := r.domainWelcomeEditFields(ctx, userID, request) if err != nil { return nil, err } message, err := r.deps.WelcomeMessages.Edit(ctx, userID, peer, request.ID, fields) if err != nil { return nil, welcomeMessageRPCError(err) } return r.welcomeMessageUpdates(ctx, userID, message, true) } func (r *Router) onEphemeralDeleteWelcomeMessage(ctx context.Context, request *tg.EphemeralDeleteWelcomeMessageRequest) (bool, error) { if request == nil || r.deps.WelcomeMessages == nil || request.Peer == nil || request.ID <= 0 || request.ID > domain.MaxMessageBoxID { return false, messageIDInvalidErr() } userID, peer, err := r.welcomeActorAndPeer(ctx, request.Peer) if err != nil { return false, err } ok, err := r.deps.WelcomeMessages.Delete(ctx, userID, peer, request.ID) if err != nil { return false, welcomeMessageRPCError(err) } return ok, nil } func (r *Router) onEphemeralDeleteAllWelcomeMessages(ctx context.Context, request *tg.EphemeralDeleteAllWelcomeMessagesRequest) (bool, error) { if request == nil || r.deps.WelcomeMessages == nil || request.Peer == nil { return false, inputRequestInvalidErr() } userID, peer, err := r.welcomeActorAndPeer(ctx, request.Peer) if err != nil { return false, err } ok, err := r.deps.WelcomeMessages.DeleteAll(ctx, userID, peer) if err != nil { return false, welcomeMessageRPCError(err) } return ok, nil } func (r *Router) onEphemeralGetWelcomeMessages(ctx context.Context, request *tg.EphemeralGetWelcomeMessagesRequest) (tg.EphemeralWelcomeMessagesClass, error) { if request == nil || r.deps.WelcomeMessages == nil || request.Peer == nil || request.Hash < 0 { return nil, inputRequestInvalidErr() } userID, peer, err := r.welcomeActorAndPeer(ctx, request.Peer) if err != nil { return nil, err } result, err := r.deps.WelcomeMessages.List(ctx, userID, peer, request.Hash) if err != nil { return nil, welcomeMessageRPCError(err) } if result.NotModified { return &tg.EphemeralWelcomeMessagesNotModified{}, nil } messages := make([]tg.EphemeralMessage, 0, len(result.Messages)) for _, message := range result.Messages { wire, err := tgWelcomeMessage(message) if err != nil { return nil, internalErr() } messages = append(messages, wire) } return &tg.EphemeralWelcomeMessages{Hash: result.Hash, Messages: messages}, nil } func (r *Router) welcomeActorAndPeer(ctx context.Context, input tg.InputPeerClass) (int64, domain.Peer, error) { userID, _, err := r.currentUserID(ctx) if err != nil || userID <= 0 { return 0, domain.Peer{}, internalErr() } peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, input) if err != nil { return 0, domain.Peer{}, err } if peer.Type != domain.PeerTypeChannel || peer.ID <= 0 { return 0, domain.Peer{}, peerIDInvalidErr() } return userID, peer, nil } func welcomeReceiverEmpty(receiver tg.InputUserClass) bool { _, ok := receiver.(*tg.InputUserEmpty) return ok } func (r *Router) domainWelcomeSendContent(ctx context.Context, userID int64, request *tg.EphemeralSendMessageRequest) (domain.WelcomeMessageContent, error) { entities, err := welcomeEntities(userID, request.Message, request.Entities) if err != nil { return domain.WelcomeMessageContent{}, err } media, err := r.welcomeInputMedia(ctx, userID, request.Media) if err != nil { return domain.WelcomeMessageContent{}, err } markup, err := welcomeReplyMarkup(request.ReplyMarkup) if err != nil { return domain.WelcomeMessageContent{}, err } rich, err := r.domainRichMessageFromInput(ctx, request.RichMessage) if err != nil { return domain.WelcomeMessageContent{}, err } content := domain.WelcomeMessageContent{ Message: request.Message, Entities: entities, Media: media, ReplyMarkup: markup, RichMessage: rich, InvertMedia: request.InvertMedia, NoForwards: request.Noforwards, } if err := content.Validate(); err != nil { if request.Message == "" && media == nil && rich.IsZero() { return domain.WelcomeMessageContent{}, messageEmptyErr() } return domain.WelcomeMessageContent{}, inputRequestInvalidErr() } return content, nil } func (r *Router) domainWelcomeEditFields(ctx context.Context, userID int64, request *tg.EphemeralEditMessageRequest) (domain.WelcomeMessageEditFields, error) { var fields domain.WelcomeMessageEditFields if message, ok := request.GetMessage(); ok { if !utf8.ValidString(message) || utf8.RuneCountInString(message) > domain.MaxMessageTextLength { return fields, messageTooLongErr() } fields.SetMessage = true fields.Message = message // TDesktop omits f_entities for an empty vector; a text edit therefore // replaces, rather than accidentally retains, the old entity vector. fields.SetEntities = true fields.Entities = nil } if entities, ok := request.GetEntities(); ok { text := request.Message if !fields.SetMessage { text = "" } converted := domainMessageEntitiesForViewer(userID, entities) if len(converted) != len(entities) || (fields.SetMessage && !validEphemeralEntityBounds(text, converted)) { return fields, entityBoundsInvalidErr() } fields.SetEntities = true fields.Entities = converted } if media, ok := request.GetMedia(); ok { resolved, err := r.welcomeInputMedia(ctx, userID, media) if err != nil { return fields, err } fields.SetMedia = true fields.Media = resolved fields.SetInvertMedia = true fields.InvertMedia = request.InvertMedia } else if request.InvertMedia { fields.SetInvertMedia = true fields.InvertMedia = true } if markup, ok := request.GetReplyMarkup(); ok { converted, err := welcomeReplyMarkup(markup) if err != nil { return fields, err } fields.SetReplyMarkup = true fields.ReplyMarkup = converted } if rich, ok := request.GetRichMessage(); ok { converted, err := r.domainRichMessageFromInput(ctx, rich) if err != nil { return fields, err } fields.SetRichMessage = true fields.RichMessage = converted } if fields.Empty() { return fields, inputRequestInvalidErr() } return fields, nil } func welcomeEntities(userID int64, text string, input []tg.MessageEntityClass) ([]domain.MessageEntity, error) { if !utf8.ValidString(text) || utf8.RuneCountInString(text) > domain.MaxMessageTextLength || len(input) > domain.MaxMessageEntityCount { return nil, messageTooLongErr() } entities := domainMessageEntitiesForViewer(userID, input) if len(entities) != len(input) || !validEphemeralEntityBounds(text, entities) { return nil, entityBoundsInvalidErr() } return entities, nil } func (r *Router) welcomeInputMedia(ctx context.Context, userID int64, input tg.InputMediaClass) (*domain.MessageMedia, error) { if input == nil { return nil, nil } media, err := r.resolveInputMedia(ctx, userID, input) if err != nil { return nil, err } if media != nil && !ephemeralMediaAllowed(media) { return nil, mediaTypeInvalidErr() } return media, nil } func welcomeReplyMarkup(input tg.ReplyMarkupClass) (*domain.MessageReplyMarkup, error) { if input == nil { return nil, nil } markup, err := domainReplyMarkupForSender(input, true) if err != nil { return nil, replyMarkupErr(err) } return markup, nil } func (r *Router) welcomeMessageUpdates(ctx context.Context, viewerUserID int64, message domain.WelcomeMessage, edited bool) (*tg.Updates, error) { if r.deps.Users == nil || r.deps.Channels == nil { return nil, internalErr() } users, err := r.deps.Users.ByIDs(ctx, viewerUserID, []int64{message.CreatorUserID}) if err != nil { return nil, internalErr() } view, err := r.deps.Channels.ResolveChannel(ctx, viewerUserID, message.Peer.ID) if err != nil { return nil, channelInvalidErr(err) } wire, err := tgWelcomeMessage(message) if err != nil { return nil, internalErr() } var update tg.UpdateClass = &tg.UpdateNewEphemeralMessage{Message: wire} if edited { update = &tg.UpdateEditEphemeralMessage{Message: wire} } date := message.Date if edited && message.EditDate > 0 { date = message.EditDate } return &tg.Updates{ Updates: []tg.UpdateClass{update}, Users: tgUsersForViewer(viewerUserID, users), Chats: []tg.ChatClass{tgChannelChatForView(viewerUserID, view)}, Date: date, Seq: 0, }, nil } func tgWelcomeMessage(message domain.WelcomeMessage) (tg.EphemeralMessage, error) { out := tg.EphemeralMessage{ Out: true, WelcomeTemplate: true, InvertMedia: message.Content.InvertMedia, Noforwards: message.Content.NoForwards, ID: message.ID, FromID: &tg.PeerUser{UserID: message.CreatorUserID}, PeerID: tgPeer(message.Peer), ReceiverID: 0, Date: message.Date, Message: message.Content.Message, } if len(message.Content.Entities) != 0 { out.SetEntities(tgMessageEntities(message.Content.Entities)) } if message.Content.Media != nil && !message.Content.Media.IsZero() { out.SetMedia(tgMessageMedia(message.Content.Media)) } if message.Content.ReplyMarkup != nil && !message.Content.ReplyMarkup.IsZero() { out.SetReplyMarkup(tgReplyMarkup(message.Content.ReplyMarkup)) } rich, err := tgRichMessage(message.Content.RichMessage) if err != nil { return tg.EphemeralMessage{}, err } if rich != nil { out.SetRichMessage(*rich) } return out, nil } func welcomeMessageRPCError(err error) error { switch { case errors.Is(err, domain.ErrWelcomeMessageForbidden): return tgerr.New(400, "CHAT_ADMIN_REQUIRED") case errors.Is(err, domain.ErrWelcomeMessagePeerInvalid): return peerIDInvalidErr() case errors.Is(err, domain.ErrWelcomeMessageNotFound): return messageIDInvalidErr() case errors.Is(err, domain.ErrWelcomeMessageNotModified): return messageNotModifiedErr() case errors.Is(err, domain.ErrWelcomeMessageLimit): return limitInvalidErr() case errors.Is(err, domain.ErrWelcomeMessageInvalid), errors.Is(err, domain.ErrWelcomeMessageRandomIDConflict): return inputRequestInvalidErr() case errors.Is(err, domain.ErrUserFrozen), errors.Is(err, domain.ErrChannelInvalid), errors.Is(err, domain.ErrChannelPrivate), errors.Is(err, domain.ErrChannelUserBanned), errors.Is(err, domain.ErrChannelAdminRequired), errors.Is(err, domain.ErrChannelMonoforumUnsupported): return channelInvalidErr(err) default: return internalErr() } }