chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
278
internal/rpc/messages_edit.go
Normal file
278
internal/rpc/messages_edit.go
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/gotd/td/tg"
|
||||
"telesrv/internal/domain"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func (r *Router) onMessagesEditMessage(ctx context.Context, req *tg.MessagesEditMessageRequest) (tg.UpdatesClass, error) {
|
||||
scheduleDate, hasScheduleDate := req.GetScheduleDate()
|
||||
scheduleRepeatPeriod, hasScheduleRepeatPeriod := req.GetScheduleRepeatPeriod()
|
||||
if hasScheduleRepeatPeriod && scheduleRepeatPeriod != 0 {
|
||||
return nil, scheduleDateInvalidErr()
|
||||
}
|
||||
if hasScheduleRepeatPeriod && !hasScheduleDate {
|
||||
return nil, scheduleDateInvalidErr()
|
||||
}
|
||||
if _, ok := req.GetQuickReplyShortcutID(); ok {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
message, hasMessage := req.GetMessage()
|
||||
if hasMessage && utf8.RuneCountInString(message) > maxSendMessageTextLength {
|
||||
return nil, messageTooLongErr()
|
||||
}
|
||||
entities, _ := req.GetEntities()
|
||||
if hasMessage {
|
||||
if len(entities) > maxMessageEntityCount {
|
||||
return nil, entitiesTooLongErr()
|
||||
}
|
||||
// 编辑后的文本同样补服务端自动实体(url/@mention/#hashtag/bot command),与发送一致;
|
||||
// 覆盖频道/私聊编辑与各自的定时编辑分支(editScheduledMessage 仅由本处调用)。
|
||||
entities = augmentAutoEntities(message, entities)
|
||||
} else {
|
||||
entities = nil
|
||||
}
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, peerIDInvalidErr()
|
||||
}
|
||||
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasScheduleDate {
|
||||
if media, ok := req.GetMedia(); ok && !editMessageMediaCanDegradeToText(media) {
|
||||
return nil, mediaInvalidErr()
|
||||
}
|
||||
return r.editScheduledMessage(ctx, userID, peer, req.ID, message, hasMessage, entities, scheduleDate)
|
||||
}
|
||||
if media, ok := req.GetMedia(); ok {
|
||||
// 关闭 poll 走 editMessage + InputMediaPoll(closed)(TDesktop "Stop poll" 路径)。
|
||||
if pollMedia, isPoll := media.(*tg.InputMediaPoll); isPoll {
|
||||
return r.onEditMessageClosePoll(ctx, req, pollMedia)
|
||||
}
|
||||
// live location 续报/停止走 editMessage + InputMediaGeoLive(DrKLO Android 路径)。
|
||||
if liveMedia, isLive := media.(*tg.InputMediaGeoLive); isLive {
|
||||
return r.onEditMessageLiveLocation(ctx, req, liveMedia)
|
||||
}
|
||||
if !editMessageMediaCanDegradeToText(media) {
|
||||
return nil, mediaInvalidErr()
|
||||
}
|
||||
}
|
||||
if !hasMessage {
|
||||
return nil, messageEmptyErr()
|
||||
}
|
||||
if message == "" {
|
||||
// 编辑媒体消息时 message="" 是合法的清空 caption;当前文本-only
|
||||
// 编辑模型由 store 层校验目标消息(无媒体的纯文本消息清空仍会
|
||||
// 落 MESSAGE_EMPTY),RPC 层不再一刀切拒绝。
|
||||
if _, hasMedia := req.GetMedia(); !hasMedia {
|
||||
return nil, messageEmptyErr()
|
||||
}
|
||||
}
|
||||
// reply_markup(bot inline keyboard):仅 bot 编辑自己消息时替换/清空(非 bot 丢弃,
|
||||
// 不报错)。P3 仅私聊;markup-only 编辑(无文本)暂不支持——须随文本一起编辑。
|
||||
// 携带 inline markup(即便空 rows)即视为「设置」:空 inline 表示清空键盘,
|
||||
// 否则替换;非 inline(reply keyboard 等)或非 bot 一律不设置(保留原 markup)。
|
||||
var replyMarkup *domain.MessageReplyMarkup
|
||||
var setReplyMarkup bool
|
||||
if req.ReplyMarkup != nil {
|
||||
isBot := r.userIsBot(ctx, userID)
|
||||
replyMarkup, err = domainReplyMarkupForSender(req.ReplyMarkup, isBot)
|
||||
if err != nil {
|
||||
return nil, replyMarkupErr(err)
|
||||
}
|
||||
if _, isInline := req.ReplyMarkup.(*tg.ReplyInlineMarkup); isInline && isBot {
|
||||
setReplyMarkup = true
|
||||
}
|
||||
}
|
||||
if peer.Type == domain.PeerTypeChannel {
|
||||
if r.deps.Channels == nil {
|
||||
return nil, peerIDInvalidErr()
|
||||
}
|
||||
mentionUserIDs, err := r.mentionedUserIDsFromMessage(ctx, userID, message, entities)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
res, err := r.deps.Channels.EditMessage(ctx, userID, domain.EditChannelMessageRequest{
|
||||
UserID: userID,
|
||||
ChannelID: peer.ID,
|
||||
ID: req.ID,
|
||||
Message: message,
|
||||
Entities: domainMessageEntitiesForViewer(userID, entities),
|
||||
MentionUserIDs: mentionUserIDs,
|
||||
EditDate: int(r.clock.Now().Unix()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelEditErr(err)
|
||||
}
|
||||
updates := r.channelEditMessageUpdates(ctx, userID, res)
|
||||
// 编辑 fan-out 异步化 + 跨 viewer 投影预热(设计 Phase 0/Phase 1;P1-x bot 高频 editMessage
|
||||
// 是稳态放大源)。同步 echo 仍走上面单 viewer 的 channelEditMessageUpdates。
|
||||
r.enqueueChannelEditMessageFanout(ctx, userID, res)
|
||||
return updates, nil
|
||||
}
|
||||
if peer.Type != domain.PeerTypeUser || r.deps.Messages == nil {
|
||||
return nil, peerIDInvalidErr()
|
||||
}
|
||||
blocked, err := r.peerBlocksUser(ctx, userID, peer.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blocked {
|
||||
return nil, messageEditForbiddenErr()
|
||||
}
|
||||
sessionID, _ := SessionIDFrom(ctx)
|
||||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||||
res, err := r.deps.Messages.EditMessage(ctx, userID, domain.EditMessageRequest{
|
||||
OwnerUserID: userID,
|
||||
Peer: peer,
|
||||
ID: req.ID,
|
||||
Message: message,
|
||||
Entities: domainMessageEntitiesForViewer(userID, entities),
|
||||
EditDate: int(r.clock.Now().Unix()),
|
||||
OriginAuthKeyID: authKeyID,
|
||||
OriginSessionID: sessionID,
|
||||
SetReplyMarkup: setReplyMarkup,
|
||||
ReplyMarkup: replyMarkup,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, messageEditErr(err)
|
||||
}
|
||||
self := res.Self()
|
||||
if self.Event.Pts == 0 || self.Message.ID == 0 {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
users := r.usersForMessageUpdate(ctx, userID, self.Message)
|
||||
chats := r.chatsForMessageUpdate(ctx, userID, self.Message)
|
||||
return tgEditMessageUpdates(self.Event, self.Message, users, chats), nil
|
||||
}
|
||||
|
||||
func editMessageMediaCanDegradeToText(media tg.InputMediaClass) bool {
|
||||
switch media.(type) {
|
||||
case *tg.InputMediaEmpty, *tg.InputMediaWebPage:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesGetMessageEditData(ctx context.Context, req *tg.MessagesGetMessageEditDataRequest) (*tg.MessagesMessageEditData, error) {
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if req.ID <= 0 || req.ID > domain.MaxMessageBoxID {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch peer.Type {
|
||||
case domain.PeerTypeChannel:
|
||||
if r.deps.Channels == nil {
|
||||
return nil, peerIDInvalidErr()
|
||||
}
|
||||
history, err := r.deps.Channels.GetHistory(ctx, userID, domain.ChannelHistoryFilter{
|
||||
ChannelID: peer.ID,
|
||||
Limit: 1,
|
||||
MaxID: req.ID,
|
||||
MinID: req.ID - 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelInvalidErr(err)
|
||||
}
|
||||
if len(history.Messages) != 1 || history.Messages[0].ID != req.ID {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
msg := history.Messages[0]
|
||||
if msg.Deleted || msg.Action != nil {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
view, err := r.deps.Channels.GetChannel(ctx, userID, peer.ID)
|
||||
if err != nil {
|
||||
return nil, channelInvalidErr(err)
|
||||
}
|
||||
if msg.SenderUserID != userID && !canEditChannelMessageForRPC(view.Self) {
|
||||
return nil, messageAuthorRequiredErr()
|
||||
}
|
||||
case domain.PeerTypeUser:
|
||||
if r.deps.Messages == nil {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
msg, ok, err := r.lookupOwnerMessage(ctx, userID, req.ID)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if !ok || msg.Peer != peer {
|
||||
return nil, messageIDInvalidErr()
|
||||
}
|
||||
if !msg.Out || msg.From.ID != userID {
|
||||
return nil, messageAuthorRequiredErr()
|
||||
}
|
||||
default:
|
||||
return nil, peerIDInvalidErr()
|
||||
}
|
||||
return &tg.MessagesMessageEditData{}, nil
|
||||
}
|
||||
|
||||
func canEditChannelMessageForRPC(member domain.ChannelMember) bool {
|
||||
return member.Role == domain.ChannelRoleCreator ||
|
||||
(member.Role == domain.ChannelRoleAdmin && member.AdminRights.EditMessages)
|
||||
}
|
||||
|
||||
func messageEditErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrMessageIDInvalid):
|
||||
return messageIDInvalidErr()
|
||||
case errors.Is(err, domain.ErrMessageEmpty):
|
||||
return messageEmptyErr()
|
||||
case errors.Is(err, domain.ErrMessageAuthorRequired):
|
||||
return messageAuthorRequiredErr()
|
||||
case errors.Is(err, domain.ErrMessageNotModified):
|
||||
return messageNotModifiedErr()
|
||||
default:
|
||||
return internalErr()
|
||||
}
|
||||
}
|
||||
|
||||
func channelEditErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrMessageIDInvalid):
|
||||
return messageIDInvalidErr()
|
||||
case errors.Is(err, domain.ErrMessageEmpty):
|
||||
return messageEmptyErr()
|
||||
case errors.Is(err, domain.ErrMessageAuthorRequired):
|
||||
return messageAuthorRequiredErr()
|
||||
case errors.Is(err, domain.ErrMessageNotModified):
|
||||
return messageNotModifiedErr()
|
||||
default:
|
||||
return channelInvalidErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func tgEditMessageUpdates(event domain.UpdateEvent, msg domain.Message, users []tg.UserClass, chats []tg.ChatClass) *tg.Updates {
|
||||
update := tgOtherUpdateFromEvent(domain.UpdateEvent{
|
||||
Type: domain.UpdateEventEditMessage,
|
||||
Pts: event.Pts,
|
||||
PtsCount: event.PtsCount,
|
||||
Message: msg,
|
||||
})
|
||||
if update == nil {
|
||||
return &tg.Updates{Date: event.Date}
|
||||
}
|
||||
return &tg.Updates{
|
||||
Updates: []tg.UpdateClass{update},
|
||||
Users: users,
|
||||
Chats: chats,
|
||||
Date: event.Date,
|
||||
Seq: 0,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue