fix: sync monoforum suggested post flow

This commit is contained in:
A 2026-07-22 16:08:44 +08:00
parent 511f25efc2
commit 401a8f148e
35 changed files with 2415 additions and 144 deletions

View file

@ -505,6 +505,26 @@ type ChannelMember struct {
Guest bool
}
// CanManageDirectMessages reports whether this active parent-channel member may
// see and address every subscriber topic in the linked direct-messages
// monoforum. Telegram deliberately does not grant this capability to an
// ordinary channel administrator: the explicit manage_direct_messages right is
// required (creators have the capability implicitly).
func (m ChannelMember) CanManageDirectMessages() bool {
return m.Status == ChannelMemberActive &&
(m.Role == ChannelRoleCreator ||
(m.Role == ChannelRoleAdmin && m.AdminRights.ManageDirectMessages))
}
// CanPostChannelMessages reports whether this active member may publish a post
// to a broadcast channel. Suggested-post managers need this in addition to
// CanManageDirectMessages when approving a subscriber-authored suggestion.
func (m ChannelMember) CanPostChannelMessages() bool {
return m.Status == ChannelMemberActive &&
(m.Role == ChannelRoleCreator ||
(m.Role == ChannelRoleAdmin && m.AdminRights.PostMessages))
}
// ChannelDialog is the current user's owner-view dialog state for a channel.
type ChannelDialog struct {
UserID int64
@ -570,7 +590,10 @@ const (
ChannelActionSetChatWallpaper ChannelMessageActionType = "set_chat_wallpaper"
// ChannelActionChangeCommunity maps messageActionChangeCommunity. A non-zero
// CommunityID means linked; zero means unlinked.
ChannelActionChangeCommunity ChannelMessageActionType = "change_community"
ChannelActionChangeCommunity ChannelMessageActionType = "change_community"
ChannelActionSuggestedPostApproval ChannelMessageActionType = "suggested_post_approval"
ChannelActionSuggestedPostSuccess ChannelMessageActionType = "suggested_post_success"
ChannelActionSuggestedPostRefund ChannelMessageActionType = "suggested_post_refund"
)
// ChannelMessageAction describes a service action without depending on tg.*.
@ -609,6 +632,15 @@ type ChannelMessageAction struct {
Wallpaper *Wallpaper
// Photo 仅 chat_edit_photo 服务消息使用。
Photo *Photo
// Suggested-post lifecycle actions share the immutable price snapshot. The
// approval action additionally uses the reject/balance/schedule fields;
// refund uses PayerInitiated.
SuggestedPostRejected bool
SuggestedPostBalanceTooLow bool
SuggestedPostRejectComment string
SuggestedPostScheduleDate int
SuggestedPostPrice *SuggestedPostPrice
SuggestedPostPayerInitiated bool
}
// ChannelMessage is a single stored message in a channel/supergroup.
@ -1491,6 +1523,59 @@ type SendMonoforumMessageRequest struct {
Date int
}
// ToggleSuggestedPostApprovalRequest is the domain command behind
// messages.toggleSuggestedPostApproval. MessageID addresses the immutable
// suggestion in one monoforum subscriber sub-dialog.
type ToggleSuggestedPostApprovalRequest struct {
UserID int64
MonoforumID int64
MessageID int
Reject bool
RejectComment string
ScheduleDate int
Date int
}
// SuggestedPostLifecycleState is persisted so approval, scheduled publication,
// settlement and refund remain idempotent across restarts.
type SuggestedPostLifecycleState string
const (
SuggestedPostStateBalanceLow SuggestedPostLifecycleState = "balance_low"
SuggestedPostStateRejected SuggestedPostLifecycleState = "rejected"
SuggestedPostStateScheduled SuggestedPostLifecycleState = "scheduled"
SuggestedPostStatePublished SuggestedPostLifecycleState = "published"
SuggestedPostStateCompleted SuggestedPostLifecycleState = "completed"
SuggestedPostStateRefunded SuggestedPostLifecycleState = "refunded"
)
// ToggleSuggestedPostApprovalResult contains every durable update produced by
// one command or lifecycle transition. OriginalEvent is an edit in the
// monoforum; ServiceEvent is the approval/success/refund service message; an
// optional Published result is the broadcast post.
type ToggleSuggestedPostApprovalResult struct {
Monoforum Channel
Parent Channel
SavedPeer Peer
State SuggestedPostLifecycleState
OriginalMessage ChannelMessage
OriginalEvent ChannelUpdateEvent
ServiceMessage ChannelMessage
ServiceEvent ChannelUpdateEvent
Published *SendChannelMessageResult
Recipients []int64
PayerStarsBalance *StarsBalance
PayerTONBalance *int64
Duplicate bool
}
// SuggestedPostLifecycleRequest bounds one worker pass; stores must use an
// indexed seek and row locks rather than scanning every approval.
type SuggestedPostLifecycleRequest struct {
Now int
Limit int
}
// ChannelSendReplayRequest addresses either a regular channel send (SavedPeer is zero) or one
// monoforum sub-dialog send (SavedPeer is the subscriber scope). Lookup is read-only and must
// never re-run membership/permission checks or allocate pts/message ids.

View file

@ -6,38 +6,41 @@ import (
)
var (
ErrChannelInvalid = errors.New("channel invalid")
ErrChannelPrivate = errors.New("channel private")
ErrChannelTitleInvalid = errors.New("channel title invalid")
ErrChannelUserBanned = errors.New("user banned in channel")
ErrChannelWriteForbidden = errors.New("chat write forbidden")
ErrChannelAdminRequired = errors.New("chat admin required")
ErrChannelNotModified = errors.New("chat not modified")
ErrChannelForumMissing = errors.New("channel forum missing")
ErrChannelMonoforumUnsupported = errors.New("channel monoforum unsupported")
ErrLinkNotModified = errors.New("discussion link not modified")
ErrChatDiscussionUnallowed = errors.New("chat discussion unallowed")
ErrBroadcastIDInvalid = errors.New("broadcast id invalid")
ErrMegagroupIDInvalid = errors.New("megagroup id invalid")
ErrMegagroupPrehistoryHidden = errors.New("megagroup prehistory hidden")
ErrChatPublicRequired = errors.New("chat public required")
ErrChannelUserCreator = errors.New("channel user creator")
ErrChannelRightForbidden = errors.New("channel right forbidden")
ErrPersistentTimestamp = errors.New("persistent timestamp invalid")
ErrInviteHashEmpty = errors.New("invite hash empty")
ErrInviteHashInvalid = errors.New("invite hash invalid")
ErrInviteHashExpired = errors.New("invite hash expired")
ErrInvitePermanent = errors.New("chat invite permanent")
ErrInviteRevokedMissing = errors.New("invite revoked missing")
ErrInviteRequestSent = errors.New("invite request sent")
ErrHideRequesterMissing = errors.New("hide requester missing")
ErrUsersTooMuch = errors.New("users too much")
ErrUserAlreadyParticipant = errors.New("user already participant")
ErrUserKicked = errors.New("user kicked")
ErrUserNotParticipant = errors.New("user not participant")
ErrBotGroupsBlocked = errors.New("bot groups blocked")
ErrReactionInvalid = errors.New("reaction invalid")
ErrReactionsTooMany = errors.New("reactions too many")
ErrChannelInvalid = errors.New("channel invalid")
ErrChannelPrivate = errors.New("channel private")
ErrChannelTitleInvalid = errors.New("channel title invalid")
ErrChannelUserBanned = errors.New("user banned in channel")
ErrChannelWriteForbidden = errors.New("chat write forbidden")
ErrChannelAdminRequired = errors.New("chat admin required")
ErrChannelNotModified = errors.New("chat not modified")
ErrChannelForumMissing = errors.New("channel forum missing")
ErrChannelMonoforumUnsupported = errors.New("channel monoforum unsupported")
ErrLinkNotModified = errors.New("discussion link not modified")
ErrChatDiscussionUnallowed = errors.New("chat discussion unallowed")
ErrBroadcastIDInvalid = errors.New("broadcast id invalid")
ErrMegagroupIDInvalid = errors.New("megagroup id invalid")
ErrMegagroupPrehistoryHidden = errors.New("megagroup prehistory hidden")
ErrChatPublicRequired = errors.New("chat public required")
ErrChannelUserCreator = errors.New("channel user creator")
ErrChannelRightForbidden = errors.New("channel right forbidden")
ErrPersistentTimestamp = errors.New("persistent timestamp invalid")
ErrInviteHashEmpty = errors.New("invite hash empty")
ErrInviteHashInvalid = errors.New("invite hash invalid")
ErrInviteHashExpired = errors.New("invite hash expired")
ErrInvitePermanent = errors.New("chat invite permanent")
ErrInviteRevokedMissing = errors.New("invite revoked missing")
ErrInviteRequestSent = errors.New("invite request sent")
ErrHideRequesterMissing = errors.New("hide requester missing")
ErrUsersTooMuch = errors.New("users too much")
ErrUserAlreadyParticipant = errors.New("user already participant")
ErrUserKicked = errors.New("user kicked")
ErrUserNotParticipant = errors.New("user not participant")
ErrBotGroupsBlocked = errors.New("bot groups blocked")
ErrReactionInvalid = errors.New("reaction invalid")
ErrReactionsTooMany = errors.New("reactions too many")
ErrSuggestedPostInvalid = errors.New("suggested post invalid")
ErrSuggestedPostAlreadyHandled = errors.New("suggested post already handled")
ErrSuggestedPostApprovalForbidden = errors.New("suggested post approval forbidden")
)
// SlowModeWaitError carries the remaining wait seconds for a channel slow mode violation.

View file

@ -21,20 +21,21 @@ type StarsBalance struct {
type StarsTransactionReason string
const (
StarsReasonGrant StarsTransactionReason = "grant" // 起始余额自动授予
StarsReasonTopup StarsTransactionReason = "topup" // 充值(本地铸造)
StarsReasonReaction StarsTransactionReason = "reaction" // 付费 reaction 花费
StarsReasonGift StarsTransactionReason = "gift" // 星礼花费/收取
StarsReasonGiftUpgrade StarsTransactionReason = "gift_upgrade" // 普通礼物升级为唯一礼物
StarsReasonGiftTransfer StarsTransactionReason = "gift_transfer"
StarsReasonGiftResale StarsTransactionReason = "gift_resale"
StarsReasonGiftOffer StarsTransactionReason = "gift_offer"
StarsReasonGiftAuction StarsTransactionReason = "gift_auction"
StarsReasonGiftPrepaid StarsTransactionReason = "gift_prepaid_upgrade"
StarsReasonGiftDrop StarsTransactionReason = "gift_drop_original_details"
StarsReasonPaidMedia StarsTransactionReason = "paid_media" // 付费媒体解锁
StarsReasonPaidMessage StarsTransactionReason = "paid_message" // 频道 Direct Message 花费
StarsReasonAdjust StarsTransactionReason = "adjust" // 兜底/人工调整
StarsReasonGrant StarsTransactionReason = "grant" // 起始余额自动授予
StarsReasonTopup StarsTransactionReason = "topup" // 充值(本地铸造)
StarsReasonReaction StarsTransactionReason = "reaction" // 付费 reaction 花费
StarsReasonGift StarsTransactionReason = "gift" // 星礼花费/收取
StarsReasonGiftUpgrade StarsTransactionReason = "gift_upgrade" // 普通礼物升级为唯一礼物
StarsReasonGiftTransfer StarsTransactionReason = "gift_transfer"
StarsReasonGiftResale StarsTransactionReason = "gift_resale"
StarsReasonGiftOffer StarsTransactionReason = "gift_offer"
StarsReasonGiftAuction StarsTransactionReason = "gift_auction"
StarsReasonGiftPrepaid StarsTransactionReason = "gift_prepaid_upgrade"
StarsReasonGiftDrop StarsTransactionReason = "gift_drop_original_details"
StarsReasonPaidMedia StarsTransactionReason = "paid_media" // 付费媒体解锁
StarsReasonPaidMessage StarsTransactionReason = "paid_message" // 频道 Direct Message 花费
StarsReasonSuggestedPost StarsTransactionReason = "suggested_post"
StarsReasonAdjust StarsTransactionReason = "adjust" // 兜底/人工调整
)
// StarsTransaction 是一条账本流水。amount 带符号:贷记 > 0含 refund/收取),借记 < 0。