owpengram-server/internal/rpc/help.go
2026-09-01 12:06:31 +03:00

226 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"time"
"github.com/iamxvbaba/td/tg"
"github.com/iamxvbaba/td/tlprofile"
"telesrv/internal/branding"
androidcompat "telesrv/internal/compat/android"
"telesrv/internal/compat/tdesktop"
)
// registerHelp 注册 help.* RPC handlerDC 配置、最近 DC
func (r *Router) registerHelp(d *tlprofile.Dispatcher) {
registerRPC[*tg.HelpGetConfigRequest](d, tlprofile.SemanticMethodHelpGetConfig, func(ctx context.Context, layerRequest *tg.HelpGetConfigRequest) (any, error) {
return r.onHelpGetConfig(ctx)
})
registerRPC[*tg.HelpGetNearestDCRequest](d, tlprofile.SemanticMethodHelpGetNearestDC, func(ctx context.Context, layerRequest *tg.HelpGetNearestDCRequest) (any, error) {
return &tg.NearestDC{
Country: r.cfg.DefaultCountryCode,
ThisDC: r.cfg.DC,
NearestDC: r.cfg.DC,
}, nil
})
registerRPC[*tg.HelpGetInviteTextRequest](d, tlprofile.SemanticMethodHelpGetInviteText, func(ctx context.Context, layerRequest *tg.HelpGetInviteTextRequest) (any, error) {
return &tg.HelpInviteText{Message: "Join me on " + branding.ProductName + "."}, nil
})
registerRPC[*tg.HelpSaveAppLogRequest](d, tlprofile.SemanticMethodHelpSaveAppLog, func(ctx context.Context, _ *tg.HelpSaveAppLogRequest) (any, error) {
return r.onHelpSaveAppLog(ctx)
})
registerRPC[*tg.HelpGetAppUpdateRequest](d, tlprofile.SemanticMethodHelpGetAppUpdate, func(ctx context.Context, layerRequest *tg.HelpGetAppUpdateRequest) (any, error) {
return r.onHelpGetAppUpdate(ctx, layerRequest.Source)
})
registerRPC[*tg.HelpGetAppConfigRequest](d, tlprofile.SemanticMethodHelpGetAppConfig, func(ctx context.Context, layerRequest *tg.HelpGetAppConfigRequest) (any, error) {
hash := layerRequest.
Hash
_ = hash
if r.deps.Help == nil {
return tdesktop.AppConfig(hash), nil
}
userID, _ := UserIDFrom(ctx)
cfg, notModified, err := r.deps.Help.GetAppConfig(ctx, userID, hash)
if err != nil {
return nil, internalErr()
}
if notModified {
return &tg.HelpAppConfigNotModified{}, nil
}
return &tg.HelpAppConfig{Hash: cfg.Hash, Config: tgJSONValue(cfg.JSON)}, nil
})
registerRPC[*tg.HelpGetCountriesListRequest](d, tlprofile.SemanticMethodHelpGetCountriesList, func(ctx context.Context, req *tg.HelpGetCountriesListRequest) (any, error) {
if r.deps.Help == nil {
return tdesktop.CountriesList(req.Hash), nil
}
list, notModified, err := r.deps.Help.GetCountries(ctx, req.LangCode, req.Hash)
if err != nil {
return nil, internalErr()
}
if notModified {
return &tg.HelpCountriesListNotModified{}, nil
}
return tgCountriesList(list), nil
})
registerRPC[*tg.HelpGetTimezonesListRequest](d, tlprofile.SemanticMethodHelpGetTimezonesList, func(ctx context.Context, layerRequest *tg.HelpGetTimezonesListRequest) (any, error) {
hash := layerRequest.
Hash
_ = hash
return tdesktop.TimezonesList(hash), nil
})
registerRPC[*tg.HelpGetPeerColorsRequest](d, tlprofile.SemanticMethodHelpGetPeerColors, func(ctx context.Context, layerRequest *tg.HelpGetPeerColorsRequest) (any, error) {
hash := layerRequest.
Hash
_ = hash
return tdesktop.PeerColors(hash), nil
})
registerRPC[*tg.HelpGetPeerProfileColorsRequest](d, tlprofile.SemanticMethodHelpGetPeerProfileColors, func(ctx context.Context, layerRequest *tg.HelpGetPeerProfileColorsRequest) (any, error) {
hash := layerRequest.
Hash
_ = hash
return tdesktop.PeerProfileColors(hash), nil
})
registerRPC[*tg.HelpGetPromoDataRequest](d, tlprofile.SemanticMethodHelpGetPromoData, func(ctx context.Context, layerRequest *tg.HelpGetPromoDataRequest) (any, error) {
return tdesktop.PromoData(r.clock.Now()), nil
})
registerRPC[*tg.HelpGetTermsOfServiceUpdateRequest](d, tlprofile.SemanticMethodHelpGetTermsOfServiceUpdate, func(ctx context.Context, layerRequest *tg.HelpGetTermsOfServiceUpdateRequest) (any, error) {
return tdesktop.TermsOfServiceUpdate(r.clock.Now()), nil
})
registerRPC[
// 客户端遇到无法识别的 tg:// 深链时会查询 help.getDeepLinkInfo。telesrv 不维护
// “需更新 App”的特殊深链提示库对所有 path 返回 deepLinkInfoEmpty——这是规范的
// “无特殊信息”应答DrKLO 仅在收到非空 deepLinkInfo 时才弹“请更新 App”弹窗
// LaunchActivity.java:5175收到 Empty 则静默放行按普通链接处理。此前未注册
// handler 会落 fallback 返回 500 NOT_IMPLEMENTED污染日志且非正确协议行为
*tg.HelpGetDeepLinkInfoRequest](d, tlprofile.SemanticMethodHelpGetDeepLinkInfo, func(ctx context.Context, layerRequest *tg.HelpGetDeepLinkInfoRequest) (any, error) {
path := layerRequest.
Path
_ = path
return &tg.HelpDeepLinkInfoEmpty{}, nil
})
registerRPC[*tg.HelpDismissSuggestionRequest](d, tlprofile.SemanticMethodHelpDismissSuggestion, func(ctx context.Context, layerRequest *tg.HelpDismissSuggestionRequest) (any, error) {
return r.onHelpDismissSuggestion(ctx, layerRequest)
})
registerRPC[*tg.HelpGetPremiumPromoRequest](d, tlprofile.SemanticMethodHelpGetPremiumPromo, func(ctx context.Context, layerRequest *tg.HelpGetPremiumPromoRequest) (any, error) {
return r.onHelpGetPremiumPromo(ctx)
})
}
// onHelpSaveAppLog 为官方客户端的 fire-and-forget 应用遥测提供有界兼容应答。
// telesrv 当前不运营遥测产品,因此不读取、记录或持久化事件内容;请求已在 exact
// Layer admission 处受 wire/vector/aggregate/depth 限制。该方法按 TL 访问约束允许
// 未授权连接调用,但已登录 bot 必须拒绝。
func (r *Router) onHelpSaveAppLog(ctx context.Context) (bool, error) {
userID, authorized, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if authorized && r.userIsBot(ctx, userID) {
return false, botMethodInvalidErr()
}
return true, nil
}
func (r *Router) onHelpGetConfig(ctx context.Context) (*tg.Config, error) {
config := tdesktop.BuildConfig(r.cfg.DC, r.cfg.IP, r.cfg.Port, r.clock.Now(), r.cfg.PublicBaseURL, r.cfg.UpdatePublicURL)
userID, authorized, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if !authorized || userID == 0 {
return config, nil
}
if svc, ok := r.deps.Account.(accountReactionSettingsReader); ok {
settings, err := svc.GetReactionSettings(ctx, userID)
if err != nil {
return nil, internalErr()
}
reaction := tgMessageReaction(settings.DefaultReaction)
if reaction == nil {
return nil, internalErr()
}
config.SetReactionsDefault(reaction)
}
return config, nil
}
// onHelpDismissSuggestion 为 DrKLO 改号成功后的 suggestion 清理提供有界兼容。
// Android 会先把 suggestion 从本地状态删除,再发送该 RPC且 generic 500 会被
// 连接层持续重试。当前 server 不发布 pending suggestions故非空 dismissal
// 无需持久化,幂等 BoolTrue 即为完整的当前边界语义。
func (r *Router) onHelpDismissSuggestion(ctx context.Context, req *tg.HelpDismissSuggestionRequest) (bool, error) {
userID, found, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if !found {
return false, authKeyUnregisteredErr()
}
if r.userIsBot(ctx, userID) {
return false, botMethodInvalidErr()
}
if req == nil {
return false, nil
}
return androidcompat.DismissSuggestion(req.Suggestion), nil
}
// onHelpGetPremiumPromo returns the viewer-specific Premium status plus the
// immutable, startup-seeded video catalog. Period options intentionally remain
// empty: telesrv has no subscription purchase backend and must not advertise
// dead payment URLs. All six TL fields are mandatory.
func (r *Router) onHelpGetPremiumPromo(ctx context.Context) (*tg.HelpPremiumPromo, error) {
promo := &tg.HelpPremiumPromo{
StatusText: branding.PremiumName + " is not active on this account.",
StatusEntities: []tg.MessageEntityClass{},
VideoSections: []string{},
Videos: []tg.DocumentClass{},
PeriodOptions: []tg.PremiumSubscriptionOption{},
Users: []tg.UserClass{},
}
userID, authorized, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if !authorized || userID == 0 {
return nil, authKeyUnregisteredErr()
}
if r.deps.Users == nil {
return nil, notImplementedErr()
}
u, err := r.deps.Users.Self(ctx, userID)
if err != nil {
return nil, internalErr()
}
if u.ID != userID {
return nil, internalErr()
}
if u.Bot {
return nil, botMethodInvalidErr()
}
if u.PremiumActiveAt(r.clock.Now().Unix()) {
until := time.Unix(int64(u.PremiumUntil), 0)
promo.StatusText = branding.PremiumName + " is active until " + until.Format("2006-01-02") + "."
}
if r.deps.PremiumPromo != nil {
catalog, found, err := r.deps.PremiumPromo.PremiumPromo(ctx)
if err != nil {
return nil, internalErr()
}
if found {
if len(catalog.VideoSections) != len(catalog.Videos) {
return nil, internalErr()
}
promo.VideoSections = append([]string(nil), catalog.VideoSections...)
promo.Videos = tgDocuments(catalog.Videos)
}
}
return promo, nil
}