Merge remote-tracking branch 'upstream/main' into merge-gramsrv-9106877

This commit is contained in:
onysd 2026-08-03 23:29:20 +03:00
commit ac6a50c5ff
697 changed files with 100880 additions and 8052 deletions

View file

@ -47,6 +47,14 @@ type AuthService interface {
ResetAuthorizations(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error)
}
type AuthDeliveryReportService interface {
ReportMissingCode(ctx context.Context, req domain.AuthMissingCodeReportRequest) (domain.AuthDeliveryReport, bool, error)
}
type ClientTelemetryService interface {
Record(ctx context.Context, userID int64, kind domain.ClientTelemetryKind, peer domain.Peer, subjectIDs []int64, payload any, createdAt time.Time) (domain.ClientTelemetryEvent, bool, error)
}
// SessionBinder 抽象登录后 session 与 user 的在线绑定。
//
// MTProto session 的完整身份是 raw auth_key_id + session_id。所有定位单个 session
@ -218,6 +226,16 @@ type OnlineUserProvider interface {
OnlineChannelMemberUserIDs(channelID int64, limit int) []int64
}
// ChannelSubscriptionProvider is the bounded process-local implementation of
// Telegram's public-channel short-poll subscription. A successful
// updates.getChannelDifference refresh from one session enables passive channel
// updates for the whole user account until the subscription expires.
type ChannelSubscriptionProvider interface {
RefreshChannelSubscription(rawAuthKeyID [8]byte, sessionID, userID, channelID int64, ttl time.Duration)
OnlineChannelSubscriberUserIDs(channelID int64, limit int) []int64
OnlineChannelSubscriberUserIDsExcluding(channelID int64, exclude map[int64]struct{}, limit int) []int64
}
// ChannelNudgeProvider 暴露「频道在线成员中排除已投递集合后的剩余 user id」用于 >cap
// 在线成员的 UpdateChannelTooLong nudgeP0-8。SessionManager 实现;测试/未装配 fake 可不实现
// type-assert 失败时跳过 nudge不影响完整 payload 投递)。
@ -321,6 +339,27 @@ type BotsService interface {
PutWebViewCustomMethodQuery(ctx context.Context, botUserID, userID int64, method, paramsJSON string) (domain.BotWebViewCustomMethodQuery, error)
}
// ServiceBotCallbacks answers inline-button clicks for the built-in bots that run
// inside this process (@verifybot and friends); app/bots implements it.
//
// It exists because the ordinary callback path cannot serve them: an internal bot
// has no MTProto session to receive updateBotCallbackQuery and no Bot API consumer
// to drain the update queue, so pushing the query at it and waiting could only
// ever end in BOT_RESPONSE_TIMEOUT after the full 25-second window. A bot claimed
// here is answered synchronously instead, by the responder that owns it.
//
// OnCallbackQuery reports handled=false when the bot is not one of the responder's
// own, which the edge treats as invalid callback data. The answer is final:
// nothing is registered in the shared callback registry for it, so no external
// setBotCallbackAnswer can overwrite or spoof it.
//
// A nil Deps.ServiceBotCallbacks keeps the edge behaviour exactly as it was:
// every callback is pushed to the bot's session and waited on.
type ServiceBotCallbacks interface {
HandlesBot(botUserID int64) bool
OnCallbackQuery(ctx context.Context, query domain.BotCallbackQuery) (domain.BotCallbackAnswer, bool, error)
}
// UserIdentityService 是 UsersService 的资料扩展能力,用于 username/phone 解析。
type UserIdentityService interface {
CheckUsername(ctx context.Context, userID int64, username string) (bool, error)
@ -474,7 +513,6 @@ type UpdatesService interface {
RecordDialogFilterOrder(ctx context.Context, stateAuthKeyID [8]byte, userID int64, order []int, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
RecordDialogFiltersReload(ctx context.Context, stateAuthKeyID [8]byte, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
RecordFolderPeers(ctx context.Context, stateAuthKeyID [8]byte, userID int64, peers []domain.FolderPeerUpdate, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
RecordChannelAvailableMessages(ctx context.Context, stateAuthKeyID [8]byte, userID, channelID int64, availableMinID int, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
RecordChannelViewForumAsMessages(ctx context.Context, stateAuthKeyID [8]byte, userID, channelID int64, enabled bool, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
RecordChannelDiscussionInbox(ctx context.Context, stateAuthKeyID [8]byte, userID, channelID int64, topicID, maxID int, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
RecordDraftMessage(ctx context.Context, stateAuthKeyID [8]byte, userID int64, peer domain.Peer, topMsgID int, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.UpdateEvent, domain.UpdateState, error)
@ -563,6 +601,8 @@ type MessagesService interface {
GetOutboxReadDate(ctx context.Context, userID int64, req domain.OutboxReadDateRequest) (int, error)
SetMessageReactions(ctx context.Context, userID int64, req domain.SetPrivateMessageReactionsRequest) (domain.PrivateMessageReactionsResult, error)
GetMessageReactions(ctx context.Context, userID int64, req domain.PrivateMessageReactionsRequest) (domain.PrivateMessageReactionsResult, error)
SavedReactionTags(ctx context.Context, userID int64, savedPeer domain.Peer, limit int) ([]domain.SavedReactionTag, error)
UpdateSavedReactionTag(ctx context.Context, userID int64, tag domain.SavedReactionTag) error
VoteMessagePoll(ctx context.Context, userID int64, req domain.VotePrivateMessagePollRequest) (domain.PrivateMessagePollResult, error)
CloseMessagePoll(ctx context.Context, userID int64, req domain.ClosePrivateMessagePollRequest) (domain.PrivateMessagePollResult, error)
ListUnreadReactionMessages(ctx context.Context, userID int64, peer domain.Peer, limit int) ([]domain.Message, error)
@ -580,6 +620,13 @@ type MessagesService interface {
DeleteSavedHistory(ctx context.Context, userID int64, req domain.DeleteSavedHistoryRequest) (domain.DeleteSavedHistoryResult, error)
}
// PrivateNoForwardsService is an optional messages capability used by the
// private-user branch of messages.toggleNoForwards and userFull projection.
type PrivateNoForwardsService interface {
GetPrivateNoForwards(ctx context.Context, userID, peerUserID int64) (domain.PrivateNoForwardsState, error)
TogglePrivateNoForwards(ctx context.Context, userID int64, req domain.TogglePrivateNoForwardsRequest) (domain.TogglePrivateNoForwardsResult, error)
}
// TranslationService owns read-only translation and the durable per-account
// peer preference. It only exposes domain values to the RPC edge.
type TranslationService interface {
@ -686,11 +733,10 @@ type ChannelsService interface {
VoteMessagePoll(ctx context.Context, userID int64, req domain.VoteChannelMessagePollRequest) (domain.ChannelMessagePollResult, error)
CloseMessagePoll(ctx context.Context, userID int64, req domain.CloseChannelMessagePollRequest) (domain.ChannelMessagePollResult, error)
ListMessageReactions(ctx context.Context, userID int64, req domain.ChannelMessageReactionsListRequest) (domain.ChannelMessageReactionsList, error)
FindMessageReaction(ctx context.Context, userID int64, req domain.ChannelMessageReactionLookupRequest) (domain.ChannelMessageReactionLookup, bool, error)
TopReactions(ctx context.Context, userID int64, limit int) ([]domain.MessageReaction, error)
RecentReactions(ctx context.Context, userID int64, limit int) ([]domain.MessageReaction, error)
ClearRecentReactions(ctx context.Context, userID int64) error
SavedReactionTags(ctx context.Context, userID int64, limit int) ([]domain.SavedReactionTag, error)
UpdateSavedReactionTag(ctx context.Context, userID int64, tag domain.SavedReactionTag) error
GetPremiumBoostStatus(ctx context.Context, userID, channelID int64, now int) (domain.PremiumBoostStatus, error)
ListPremiumBoosts(ctx context.Context, userID, channelID int64, gifts bool, offset string, limit, now int) (domain.PremiumBoostList, error)
GetPremiumMyBoosts(ctx context.Context, userID int64, now, premiumUntil int) (domain.PremiumMyBoosts, error)
@ -769,6 +815,19 @@ type ChannelsService interface {
FilterActiveMemberIDs(ctx context.Context, channelID int64, userIDs []int64) ([]int64, error)
}
// ChannelMessageAudienceService is the optional production authorization
// boundary for public short-poll subscribers. Lightweight test/domain adapters
// that only model joined members may omit it and retain member-only behavior.
type ChannelMessageAudienceService interface {
FilterMessageAudienceIDs(ctx context.Context, channelID int64, userIDs []int64) ([]int64, error)
}
// ChannelAuthoritativeProjectionService bypasses long-lived channel read
// models for a durable channel_state refresh emitted by an admin mutation.
type ChannelAuthoritativeProjectionService interface {
GetChannelsAuthoritative(ctx context.Context, userID int64, channelIDs []int64) ([]domain.ChannelView, error)
}
// CommunitiesService abstracts the Layer 228 Community aggregation domain.
// Community containers never expose tg types and never own message/read/pts state.
type CommunitiesService interface {
@ -892,9 +951,106 @@ type EphemeralService interface {
ReportTarget(ctx context.Context, userID int64, device domain.EphemeralDevice, peer domain.Peer, id int) (domain.EphemeralMessage, error)
}
// ModerationService accepts only final report choices. Implementations must
// validate and snapshot referenced evidence, then durably commit the immutable
// submission before returning success.
type ModerationService interface {
ReportPeer(ctx context.Context, reporterUserID int64, source domain.ModerationReportSource, target domain.Peer, reason domain.ModerationReason, option, comment string, createdAt time.Time) (domain.ModerationReport, bool, error)
ReportMessages(ctx context.Context, req domain.ModerationMessageReportRequest) (domain.ModerationReport, bool, error)
ReportProfilePhoto(ctx context.Context, req domain.ModerationProfilePhotoReportRequest) (domain.ModerationReport, bool, error)
ReportChannelSpam(ctx context.Context, req domain.ModerationChannelSpamReportRequest) (domain.ModerationReport, bool, error)
ReportReaction(ctx context.Context, req domain.ModerationReactionReportRequest) (domain.ModerationReport, bool, error)
ReportEncryptedSpam(ctx context.Context, reporterUserID int64, chat domain.SecretChat, createdAt time.Time) (domain.ModerationReport, bool, error)
ReportStories(ctx context.Context, req domain.ModerationStoryReportRequest) (domain.ModerationReport, bool, error)
ReportEphemeral(ctx context.Context, reporterUserID int64, target domain.EphemeralMessage, reason domain.ModerationReason, option, comment string, createdAt time.Time) (domain.ModerationReport, bool, error)
SponsoredImpression(ctx context.Context, userID int64, randomID []byte, now time.Time) (domain.SponsoredMessageImpression, error)
ReportSponsored(ctx context.Context, userID int64, randomID []byte, reason domain.ModerationReason, option string, now time.Time) (domain.ModerationReport, bool, error)
ReportAntiSpamFalsePositive(ctx context.Context, reporterUserID, channelID int64, messageID int, now time.Time) (domain.ModerationReport, bool, error)
}
// PremiumPromoService exposes the immutable promo media catalog through a
// domain-only boundary. File bytes remain served by upload.getFile through the
// ordinary Files service.
type PremiumPromoService interface {
PremiumPromo(ctx context.Context) (domain.PremiumPromoCatalog, bool, error)
}
// UsernameRegistryService is the collectible (Fragment-style) username registry
// boundary. It owns the full per-peer username list -- the editable slot the
// client owns through account/channels.updateUsername plus every collectible
// asset attached to the peer -- and the purchase record behind a collectible.
//
// The registry is deliberately optional. Every RPC surface that consults it must
// degrade to the legacy single-editable-username behaviour when the field is nil
// or a call fails, because the scalar users.username / channels.username column
// remains the editable-name persistence slot.
type UsernameRegistryService interface {
// PeerUsernames returns one peer's full username list. Order is irrelevant:
// callers project through domain.SortUsernames.
PeerUsernames(ctx context.Context, peer domain.Peer) ([]domain.Username, error)
// UsernamesBatch is the N+1-free variant used by list projections. Peers with
// no registry row may be omitted from the result map.
UsernamesBatch(ctx context.Context, peers []domain.Peer) (map[domain.Peer][]domain.Username, error)
// ToggleUsername activates/deactivates one collectible username. The bool
// reports whether anything changed; false maps to USERNAME_NOT_MODIFIED.
ToggleUsername(ctx context.Context, peer domain.Peer, username string, active bool) (bool, error)
// ReorderUsernames rewrites the active username display order, including the
// editable slot when present (domain.ValidateUsernameReorder).
ReorderUsernames(ctx context.Context, peer domain.Peer, order []string) (bool, error)
// DeactivateAllUsernames deactivates every collectible username of the peer.
DeactivateAllUsernames(ctx context.Context, peer domain.Peer) (bool, error)
// Collectible returns the asset and owner needed by the RPC edge to enforce
// fragment visibility before projecting fragment.collectibleInfo.
Collectible(ctx context.Context, username string) (domain.CollectibleUsername, error)
}
// AccountRatingService exposes the stored gramsrv composite rating used by the
// userFull rating projection.
//
// It is deliberately read-only at the RPC boundary: ratings are computed by the
// bounded background worker, while profile reads only fetch the latest stored
// projection. A nil service or a read failure leaves every rating flag unset.
type AccountRatingService interface {
Rating(ctx context.Context, userID int64) (domain.AccountRating, error)
}
// BotVerificationService is the third-party bot verification boundary
// (core.telegram.org/api/bots/verification): a verifier bot marking peers with its
// own icon and description, which official clients render as a badge distinct from
// the operator-granted checkmark.
//
// It is the single source for every surface that projects the feature --
// user.bot_verification_icon, channel.bot_verification_icon,
// userFull.bot_verification, channelFull.bot_verification,
// chatInvite.bot_verification and botInfo.verifier_settings -- so no two responses
// can disagree about which mark a peer carries.
//
// Optional like UsernameRegistryService: a nil field, or
// any read error, must leave every flag unset and bots.setCustomVerification
// answering BOT_VERIFIER_FORBIDDEN, which is exactly the pre-feature wire shape.
type BotVerificationService interface {
// PeerVerification returns the peer's single mark, or
// domain.ErrCustomVerificationNotFound.
PeerVerification(ctx context.Context, peer domain.Peer) (domain.CustomVerification, error)
// PeerVerificationBatch is the N+1-free variant used by the response-boundary
// overlay. Peers without a mark may be omitted from the result map.
PeerVerificationBatch(ctx context.Context, peers []domain.Peer) (map[domain.Peer]domain.CustomVerification, error)
// VerifierSettings reads one bot's verifier status, or
// domain.ErrVerifierNotFound.
VerifierSettings(ctx context.Context, botID int64) (domain.BotVerifierSettings, error)
// VerifierSettingsBatch resolves several bots at once for the botInfo
// projection; bots without verifier status may be omitted.
VerifierSettingsBatch(ctx context.Context, botIDs []int64) (map[int64]domain.BotVerifierSettings, error)
// SetCustomVerification applies bots.setCustomVerification. changed controls
// update fan-out only; the RPC returns Bool true for an idempotent success.
SetCustomVerification(ctx context.Context, req domain.SetCustomVerificationRequest) (changed bool, err error)
}
// Deps 按业务域注入服务接口。各域的 handler 注册见对应文件auth.go / users.go / updates.go
type Deps struct {
Auth AuthService
Auth AuthService
AuthDeliveryReports AuthDeliveryReportService
ClientTelemetry ClientTelemetryService
// AuthKeySessionLayers is the protocol-only durable ordering boundary for
// explicit invokeWithLayer evidence. Production must wire the same auth-key
// store used by the MTProto edge; nil is reserved for isolated router tests.
@ -906,8 +1062,11 @@ type Deps struct {
AICompose AIComposeService
Ephemeral EphemeralService
EphemeralPush store.EphemeralPushBroker
EphemeralReports store.EphemeralReportStore
Moderation ModerationService
Users UsersService
Usernames UsernameRegistryService
AccountRatings AccountRatingService
BotVerifications BotVerificationService
TelegramLogin TelegramLoginService
Updates UpdatesService
BootstrapUpdates store.BootstrapUpdateJobStore
@ -922,7 +1081,9 @@ type Deps struct {
Channels ChannelsService
Communities CommunitiesService
Files FilesService
PremiumPromo PremiumPromoService
Bots BotsService
ServiceBotCallbacks ServiceBotCallbacks
Polls PollsService
Phone PhoneService
GroupCalls GroupCallsService
@ -974,6 +1135,7 @@ type GiftsService interface {
GiftByID(ctx context.Context, id int64) (domain.StarGift, bool, error)
GiftRevisionByID(ctx context.Context, revisionID int64) (domain.StarGift, bool, error)
CollectiblePreview(ctx context.Context, giftID int64) (domain.StarGiftUpgradePreview, bool, error)
CollectiblePreviewSample(ctx context.Context, giftID int64) (domain.StarGiftUpgradePreview, bool, error)
CollectibleAvailability(ctx context.Context, giftIDs []int64) (map[int64]domain.StarGiftCollectibleAvailability, error)
UniqueBySlug(ctx context.Context, slug string) (domain.UniqueStarGift, bool, error)
UniqueByID(ctx context.Context, uniqueGiftID int64) (domain.UniqueStarGift, bool, error)
@ -1014,7 +1176,7 @@ type GiftsService interface {
SetNotifications(ctx context.Context, userID, channelID int64, enabled bool) error
Withdraw(ctx context.Context, req domain.StarGiftWithdrawalRequest) (domain.StarGiftWithdrawal, error)
TonBalance(ctx context.Context, userID int64) (int64, error)
TonTransactions(ctx context.Context, userID int64, offset string, limit int) (domain.TonTransactionPage, error)
TonTransactions(ctx context.Context, userID int64, query domain.StarsTransactionQuery) (domain.TonTransactionPage, error)
IssuePurchaseForm(ctx context.Context, form domain.StarGiftPurchaseForm) (domain.StarGiftPurchaseForm, error)
ValidatePurchaseForm(ctx context.Context, req domain.StarGiftPurchaseRequest) error
Purchase(ctx context.Context, req domain.StarGiftPurchaseRequest) (domain.StarGiftPurchaseResult, error)
@ -1027,7 +1189,7 @@ type StarsService interface {
GetBalance(ctx context.Context, userID int64) (domain.StarsBalance, error)
Credit(ctx context.Context, userID, amount int64, reason domain.StarsTransactionReason, peer domain.Peer, title, desc string) (domain.StarsBalance, error)
Debit(ctx context.Context, userID, amount int64, reason domain.StarsTransactionReason, peer domain.Peer, title, desc string) (domain.StarsBalance, error)
ListTransactions(ctx context.Context, userID int64, offset string, limit int) (domain.StarsTransactionPage, error)
ListTransactions(ctx context.Context, userID int64, query domain.StarsTransactionQuery) (domain.StarsTransactionPage, error)
}
// SecretChatService 抽象私聊端对端加密Secret Chat握手状态机app/secretchat