chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,140 @@
package rpc
import (
"context"
"errors"
"time"
"go.uber.org/zap"
"telesrv/internal/domain"
)
// 链接预览异步回填P3发送时挂的 pending 占位(见 webpage_url_extract.go由这里带外
// 解析为已解析卡片,并经 messages.EditMessage 的 WebPageResolve 模式就地替换——只换 media、
// 生成 updateWebPage按 webPage id 与占位关联,不标记「已编辑」、不翻倍扇出)。投递与
// difference 复用既有 dispatch_outbox + 通用消息事件路径。
const (
webPageResolveConcurrency = 16
webPageResolveTimeout = 30 * time.Second
)
type webPageResolveJob struct {
senderID int64
peer domain.Peer
msgID int
expectedID int64
url string
invertMedia bool
forceLarge bool
forceSmall bool
}
// maybeEnqueueWebPageResolve 若刚发出的消息(私聊或频道)携带 pending 链接预览占位,
// 异步排入解析。msgID 为发送方视角的消息 idmedia 为该消息媒体。
func (r *Router) maybeEnqueueWebPageResolve(senderID int64, peer domain.Peer, msgID int, media *domain.MessageMedia) {
if msgID == 0 || (peer.Type != domain.PeerTypeUser && peer.Type != domain.PeerTypeChannel) {
return
}
if media == nil || media.WebPage == nil || media.WebPage.State != domain.MessageWebPageStatePending {
return
}
r.enqueueWebPageResolve(webPageResolveJob{
senderID: senderID,
peer: peer,
msgID: msgID,
expectedID: media.WebPage.ID,
url: media.WebPage.URL,
invertMedia: media.InvertMedia,
forceLarge: media.WebPage.ForceLargeMedia,
forceSmall: media.WebPage.ForceSmallMedia,
})
}
// enqueueWebPageResolve 在并发上限内异步处理一个解析任务;满则丢弃(消息留 pending
// 兜底靠客户端重开会话 getHistory 读到当时仍 pending 的占位——WIP 可接受P4 加清扫)。
func (r *Router) enqueueWebPageResolve(job webPageResolveJob) {
if r.webPageResolveSem == nil {
return
}
select {
case r.webPageResolveSem <- struct{}{}:
default:
r.log.Debug("web page resolve dropped: at capacity", zap.Int64("expected_id", job.expectedID))
return
}
go func() {
defer func() { <-r.webPageResolveSem }()
ctx, cancel := context.WithTimeout(context.Background(), webPageResolveTimeout)
defer cancel()
if err := r.resolvePendingWebPage(ctx, job); err != nil {
r.log.Debug("web page resolve failed",
zap.Int64("sender_id", job.senderID),
zap.Int("msg_id", job.msgID),
zap.Error(err))
}
}()
}
// resolvePendingWebPage 解析链接并就地把消息的 pending 占位替换为已解析卡片(同步,可测)。
// 瞬时抓取失败返回 error消息留 pending不毒化热门链接幂等冲突消息已删/已改/已被
// 解析)经 ErrMessageNotModified 静默成功。done 与 empty 两态都替换empty 让客户端停止转圈)。
func (r *Router) resolvePendingWebPage(ctx context.Context, job webPageResolveJob) error {
if r.deps.Files == nil {
return nil
}
resolved, err := r.deps.Files.ResolveWebPage(ctx, job.url)
if err != nil {
return err
}
// 保留发送时占位上的 wrapper 偏好force large/small它们不在抓取结果里。
resolved.ForceLargeMedia = job.forceLarge
resolved.ForceSmallMedia = job.forceSmall
media := &domain.MessageMedia{
Kind: domain.MessageMediaKindWebPage,
InvertMedia: job.invertMedia,
WebPage: &resolved,
}
switch job.peer.Type {
case domain.PeerTypeChannel:
if r.deps.Channels == nil {
return nil
}
res, err := r.deps.Channels.EditMessage(ctx, job.senderID, domain.EditChannelMessageRequest{
UserID: job.senderID,
ChannelID: job.peer.ID,
ID: job.msgID,
Media: media,
WebPageResolve: true,
ExpectedWebPageID: job.expectedID,
})
if errors.Is(err, domain.ErrMessageNotModified) {
return nil
}
if err != nil {
return err
}
// 复用频道编辑扇出(投影 updateChannelWebPage触达成员difference 走通用回放。
r.enqueueChannelEditMessageFanout(ctx, job.senderID, res)
return nil
case domain.PeerTypeUser:
if r.deps.Messages == nil {
return nil
}
_, err = r.deps.Messages.EditMessage(ctx, job.senderID, domain.EditMessageRequest{
OwnerUserID: job.senderID,
Peer: job.peer,
ID: job.msgID,
Media: media,
WebPageResolve: true,
ExpectedWebPageID: job.expectedID,
})
if errors.Is(err, domain.ErrMessageNotModified) {
return nil
}
return err
default:
return nil
}
}