fix: restore sticker placeholders and media history

(cherry picked from commit 488e409a1898e9c739cc0bd24cb9791636dfd6b3)
This commit is contained in:
A 2026-06-07 20:28:37 +08:00
parent 27970adf46
commit 23a2b2aff7
11 changed files with 225 additions and 165 deletions

View file

@ -338,8 +338,13 @@ func (s *Service) importDocument(ctx context.Context, dj seedDocumentJSON, binDi
stats.Blobs++
}
// 缩略图PhotoPathSize 内联;小的 PhotoSize 静态图同时写 blob 并作为
// PhotoCachedSize 返回,让 TDesktop 处理 document 元数据时即可填本地 image cache。
// 缩略图保留两类并存(与官方 sticker 一致):
// - PhotoPathSize 矢量轮廓:随 document 元数据内联下发,是 TDesktop 对 animated
// sticker 在完整 .tgs 下载完成前唯一可即时渲染的占位history_view_sticker 显式
// 禁用了 stripped 内联占位cached 字节又经 RPC 出口转成 downloadable丢掉它会让
// 打开会话时 sticker 先空白、并多触发一次缩略图 getFile。
// - 小 PhotoSize 静态图:写 blob 并暂存为 PhotoCachedSizeRPC 出口再转 downloadable
// photoSize m供 sticker 面板等需要小缩略图的场景下载。
thumbs := make([]domain.PhotoSize, 0, len(dj.Thumbs))
for _, tj := range dj.Thumbs {
ps, downloadable := seedPhotoSize(tj)
@ -375,7 +380,7 @@ func (s *Service) importDocument(ctx context.Context, dj seedDocumentJSON, binDi
}
thumbs = append(thumbs, ps)
}
doc.Thumbs = seedPreferRasterDocumentThumbs(thumbs)
doc.Thumbs = thumbs
if err := s.media.PutDocument(ctx, doc); err != nil {
return domain.Document{}, err
@ -607,20 +612,6 @@ func seedInlineCachedDocumentThumb(ps domain.PhotoSize, data []byte) domain.Phot
return ps
}
func seedPreferRasterDocumentThumbs(sizes []domain.PhotoSize) []domain.PhotoSize {
if !documentThumbsHaveRaster(sizes) {
return sizes
}
out := sizes[:0]
for _, size := range sizes {
if size.Kind == domain.PhotoSizeKindPath {
continue
}
out = append(out, size)
}
return out
}
func seedThumbMimeType(data []byte) string {
switch {
case len(data) >= 12 && data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F' &&
@ -677,9 +668,6 @@ func (s *Service) documentsNeedInlineCachedThumbs(ctx context.Context, ids []int
return false, err
}
for _, doc := range docs {
if documentThumbsHaveRaster(doc.Thumbs) && documentThumbsHavePath(doc.Thumbs) {
return true, nil
}
for _, thumb := range doc.Thumbs {
if thumb.Kind == domain.PhotoSizeKindDefault && thumb.Size > 0 && thumb.Size <= seedInlineCachedDocumentThumbMaxBytes {
return true, nil
@ -701,35 +689,6 @@ func (s *Service) documentsNeedInlineCachedThumbs(ctx context.Context, ids []int
return false, nil
}
func documentThumbsHaveRaster(sizes []domain.PhotoSize) bool {
for _, size := range sizes {
switch size.Kind {
case domain.PhotoSizeKindCached:
if len(size.Bytes) > 0 {
return true
}
case domain.PhotoSizeKindDefault:
if size.Type != "" && size.Size > 0 {
return true
}
case domain.PhotoSizeKindProgressive:
if size.Type != "" && len(size.Sizes) > 0 {
return true
}
}
}
return false
}
func documentThumbsHavePath(sizes []domain.PhotoSize) bool {
for _, size := range sizes {
if size.Kind == domain.PhotoSizeKindPath && len(size.Bytes) > 0 {
return true
}
}
return false
}
func seedStickerPacks(setPacks, resultPacks []seedStickerPackJSON, docIDBySource map[int64]int64) []domain.StickerPack {
packs := setPacks
if len(packs) == 0 {

View file

@ -334,8 +334,8 @@ func TestSeedMediaFromRealExport(t *testing.T) {
if want := seedThumbMimeType(thumb.Bytes); blob.MimeType != want {
t.Fatalf("sample sticker thumb mime = %q, want %q", blob.MimeType, want)
}
if hasPathThumb(doc.Thumbs) {
t.Fatalf("sample sticker document still exposes path thumb together with raster: %+v", doc.Thumbs)
if !hasPathThumb(doc.Thumbs) {
t.Fatalf("sample sticker document dropped its PhotoPathSize placeholder: %+v", doc.Thumbs)
}
}
}
@ -397,25 +397,6 @@ func TestSeedThumbMimeType(t *testing.T) {
}
}
func TestSeedPreferRasterDocumentThumbsDropsPathWhenRasterExists(t *testing.T) {
sizes := []domain.PhotoSize{
{Kind: domain.PhotoSizeKindPath, Type: "j", Bytes: []byte("path")},
{Kind: domain.PhotoSizeKindCached, Type: "m", Bytes: []byte("webp")},
}
got := seedPreferRasterDocumentThumbs(sizes)
if hasPathThumb(got) {
t.Fatalf("path thumb should be dropped when raster exists: %+v", got)
}
if !hasCachedThumb(got) {
t.Fatalf("cached thumb should be kept: %+v", got)
}
onlyPath := []domain.PhotoSize{{Kind: domain.PhotoSizeKindPath, Type: "j", Bytes: []byte("path")}}
if got := seedPreferRasterDocumentThumbs(onlyPath); !hasPathThumb(got) {
t.Fatalf("path-only thumbs should be kept: %+v", got)
}
}
func TestDocumentsNeedInlineCachedThumbsDetectsStaleMime(t *testing.T) {
ctx := context.Background()
media := newFakeMediaStore()
@ -453,34 +434,6 @@ func TestDocumentsNeedInlineCachedThumbsDetectsStaleMime(t *testing.T) {
}
}
func TestDocumentsNeedInlineCachedThumbsDetectsPathWithRaster(t *testing.T) {
ctx := context.Background()
media := newFakeMediaStore()
doc := domain.Document{
ID: 100,
Thumbs: []domain.PhotoSize{
{Kind: domain.PhotoSizeKindPath, Type: "j", Bytes: []byte("path")},
{Kind: domain.PhotoSizeKindCached, Type: "m", Bytes: []byte("webp")},
},
}
if err := media.PutDocument(ctx, doc); err != nil {
t.Fatalf("put doc: %v", err)
}
svc := NewService(media, nil, 2)
stale, err := svc.documentsNeedInlineCachedThumbs(ctx, []int64{doc.ID})
if err != nil {
t.Fatalf("documentsNeedInlineCachedThumbs: %v", err)
}
if !stale {
t.Fatal("path thumb with raster should require repair")
}
}
func hasCachedThumb(sizes []domain.PhotoSize) bool {
_, ok := findCachedThumb(sizes)
return ok
}
func findCachedThumb(sizes []domain.PhotoSize) (domain.PhotoSize, bool) {
for _, size := range sizes {
if size.Kind == domain.PhotoSizeKindCached && len(size.Bytes) > 0 {

View file

@ -4348,6 +4348,7 @@ func (r *Router) onMessagesForwardMessages(ctx context.Context, req *tg.Messages
RandomID: req.RandomID[i],
Message: source.body,
Entities: source.entities,
Media: source.media,
Silent: req.Silent,
NoForwards: req.Noforwards,
ReplyTo: replyTo,
@ -4395,6 +4396,7 @@ func (r *Router) onMessagesForwardMessages(ctx context.Context, req *tg.Messages
RandomID: req.RandomID[i],
Message: source.body,
Entities: source.entities,
Media: source.media,
Silent: req.Silent,
NoForwards: req.Noforwards,
ReplyTo: replyTo,
@ -4472,6 +4474,7 @@ func mergeForwardTopMsgID(toPeer domain.Peer, replyTo *domain.MessageReply, topM
type forwardSource struct {
body string
entities []domain.MessageEntity
media *domain.MessageMedia
forward *domain.MessageForward
from domain.Peer
date int
@ -4496,17 +4499,14 @@ func (r *Router) forwardSources(ctx context.Context, userID int64, fromPeer doma
if r.deps.Messages == nil {
return nil, domain.ErrMessageIDInvalid
}
list, err := r.deps.Messages.GetHistory(ctx, userID, domain.MessageFilter{
HasPeer: true,
Peer: fromPeer,
Limit: 1,
MaxID: id,
MinID: id - 1,
})
list, err := r.deps.Messages.GetMessages(ctx, userID, []int{id})
if err != nil || len(list.Messages) != 1 || list.Messages[0].ID != id {
return nil, domain.ErrMessageIDInvalid
}
msg := list.Messages[0]
if msg.Peer != fromPeer {
return nil, domain.ErrMessageIDInvalid
}
if msg.NoForwards {
return nil, domain.ErrChatForwardsRestricted
}
@ -4518,6 +4518,7 @@ func (r *Router) forwardSources(ctx context.Context, userID int64, fromPeer doma
body: msg.Body,
entities: append([]domain.MessageEntity(nil),
msg.Entities...),
media: msg.Media,
forward: forward,
from: msg.From,
date: msg.Date,
@ -4526,12 +4527,7 @@ func (r *Router) forwardSources(ctx context.Context, userID int64, fromPeer doma
if r.deps.Channels == nil {
return nil, domain.ErrMessageIDInvalid
}
history, err := r.deps.Channels.GetHistory(ctx, userID, domain.ChannelHistoryFilter{
ChannelID: fromPeer.ID,
Limit: 1,
MaxID: id,
MinID: id - 1,
})
history, err := r.deps.Channels.GetMessages(ctx, userID, fromPeer.ID, []int{id})
if err != nil || len(history.Messages) != 1 || history.Messages[0].ID != id {
return nil, domain.ErrMessageIDInvalid
}
@ -4539,7 +4535,7 @@ func (r *Router) forwardSources(ctx context.Context, userID int64, fromPeer doma
if msg.NoForwards || history.Channel.NoForwards {
return nil, domain.ErrChatForwardsRestricted
}
if msg.Body == "" || msg.Action != nil {
if msg.Action != nil || (msg.Body == "" && msg.Media.IsZero()) {
return nil, domain.ErrMessageIDInvalid
}
forward := cloneDomainMessageForward(msg.Forward)
@ -4560,6 +4556,7 @@ func (r *Router) forwardSources(ctx context.Context, userID int64, fromPeer doma
body: msg.Body,
entities: append([]domain.MessageEntity(nil),
msg.Entities...),
media: msg.Media,
forward: forward,
from: from,
date: msg.Date,

View file

@ -3,10 +3,12 @@ package rpc
import (
"context"
"errors"
"fmt"
"strconv"
"unicode/utf8"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"telesrv/internal/domain"
)
@ -361,6 +363,7 @@ func (r *Router) resolveInputMedia(ctx context.Context, userID int64, input tg.I
case *tg.InputMediaDocument:
docIDs, ok := inputDocumentCandidateIDs(in.ID)
if !ok {
r.log.Warn("sendMedia InputMediaDocument unresolvable id", zap.String("id_type", fmt.Sprintf("%T", in.ID)))
return nil, mediaInvalidErr()
}
var doc domain.Document
@ -376,6 +379,7 @@ func (r *Router) resolveInputMedia(ctx context.Context, userID int64, input tg.I
}
}
if !found {
r.log.Warn("sendMedia references unknown document", zap.Int64s("doc_ids", docIDs), zap.Int64("user_id", userID))
return nil, mediaInvalidErr()
}
return messageMediaFromDocument(doc, in.Spoiler, in.TTLSeconds), nil

View file

@ -4438,71 +4438,158 @@ func (s *ChannelStore) ListChannelHistory(ctx context.Context, viewerUserID int6
if limit <= 0 || limit > 100 {
limit = 100
}
args := []any{filter.ChannelID}
where := "channel_id = $1 AND NOT deleted"
// 公共过滤条件(不含 offset 锚点的方向条件,供 add_offset 各模式复用)
baseArgs := []any{filter.ChannelID}
base := "channel_id = $1 AND NOT deleted"
if member.AvailableMinID > 0 {
args = append(args, member.AvailableMinID)
where += fmt.Sprintf(" AND id > $%d", len(args))
baseArgs = append(baseArgs, member.AvailableMinID)
base += fmt.Sprintf(" AND id > $%d", len(baseArgs))
}
if filter.Query != "" {
args = append(args, filter.Query)
where += fmt.Sprintf(" AND body ILIKE '%%' || $%d || '%%'", len(args))
baseArgs = append(baseArgs, filter.Query)
base += fmt.Sprintf(" AND body ILIKE '%%' || $%d || '%%'", len(baseArgs))
}
if filter.SenderUserID != 0 {
args = append(args, filter.SenderUserID)
where += fmt.Sprintf(" AND sender_user_id = $%d", len(args))
baseArgs = append(baseArgs, filter.SenderUserID)
base += fmt.Sprintf(" AND sender_user_id = $%d", len(baseArgs))
}
if filter.MinDate > 0 {
args = append(args, filter.MinDate)
where += fmt.Sprintf(" AND message_date > $%d", len(args))
baseArgs = append(baseArgs, filter.MinDate)
base += fmt.Sprintf(" AND message_date > $%d", len(baseArgs))
}
if filter.MaxDate > 0 {
args = append(args, filter.MaxDate)
where += fmt.Sprintf(" AND message_date < $%d", len(args))
}
if filter.OffsetID > 0 {
args = append(args, filter.OffsetID)
where += fmt.Sprintf(" AND id < $%d", len(args))
} else if filter.OffsetDate > 0 {
args = append(args, filter.OffsetDate)
where += fmt.Sprintf(" AND message_date < $%d", len(args))
baseArgs = append(baseArgs, filter.MaxDate)
base += fmt.Sprintf(" AND message_date < $%d", len(baseArgs))
}
if filter.MaxID > 0 {
args = append(args, filter.MaxID)
where += fmt.Sprintf(" AND id <= $%d", len(args))
baseArgs = append(baseArgs, filter.MaxID)
base += fmt.Sprintf(" AND id <= $%d", len(baseArgs))
}
if filter.MinID > 0 {
args = append(args, filter.MinID)
where += fmt.Sprintf(" AND id > $%d", len(args))
baseArgs = append(baseArgs, filter.MinID)
base += fmt.Sprintf(" AND id > $%d", len(baseArgs))
}
queryLimit := limit + 1
args = append(args, queryLimit)
rows, err := s.db.Query(ctx, `
SELECT `+channelMessageColumns+`
FROM channel_messages
WHERE `+where+`
ORDER BY id DESC
LIMIT $`+fmt.Sprint(len(args)), args...)
if err != nil {
return domain.ChannelHistory{}, fmt.Errorf("list channel history: %w", err)
scanList := func(sql string, queryArgs []any) ([]domain.ChannelMessage, error) {
rows, err := s.db.Query(ctx, sql, queryArgs...)
if err != nil {
return nil, fmt.Errorf("list channel history: %w", err)
}
defer rows.Close()
var list []domain.ChannelMessage
for rows.Next() {
msg, scanErr := scanChannelMessage(rows)
if scanErr != nil {
return nil, scanErr
}
list = append(list, msg)
}
return list, rows.Err()
}
defer rows.Close()
// add_offset 决定加载方向(对齐私聊 ListMessagesByUser
// >= 0 backward锚点更旧方向先跳过 add_offset 条
// < 0 且 +limit>0 around以锚点为中心向更新取 -add_offset 条 + 向更旧取 limit+add_offset 条
// 否则 forward仅锚点更新方向拉未读消息
addOffset := filter.AddOffset
out := domain.ChannelHistory{Channel: channel, Self: member}
for rows.Next() {
msg, err := scanChannelMessage(rows)
hasMoreOlder := false
// 锚点条件offset_date 优先按日期、否则按消息 id对齐私聊/orange
// 二者皆空时向更新方向退化为空、向更旧方向退化为全部(取最新)。
forwardCond := func(args *[]any) string {
if filter.OffsetDate > 0 {
*args = append(*args, filter.OffsetDate)
return fmt.Sprintf("message_date >= $%d", len(*args))
}
if filter.OffsetID > 0 {
*args = append(*args, filter.OffsetID)
return fmt.Sprintf("id > $%d", len(*args))
}
return "false"
}
aroundOlderCond := func(args *[]any) string {
if filter.OffsetDate > 0 {
*args = append(*args, filter.OffsetDate)
return fmt.Sprintf("message_date < $%d", len(*args))
}
if filter.OffsetID > 0 {
*args = append(*args, filter.OffsetID)
return fmt.Sprintf("id <= $%d", len(*args))
}
return "true"
}
switch {
case addOffset < 0 && addOffset+limit > 0:
// around以锚点为中心向更新取 -add_offset 条 + 向更旧(含锚点)取 limit+add_offset 条
fwdLimit := minInt(-addOffset, limit)
bwdLimit := maxInt(limit+addOffset, 0)
fwdArgs := append([]any{}, baseArgs...)
fwdWhere := forwardCond(&fwdArgs)
fwdArgs = append(fwdArgs, fwdLimit)
newer, err := scanList(fmt.Sprintf("SELECT "+channelMessageColumns+" FROM channel_messages WHERE %s AND %s ORDER BY id ASC LIMIT $%d", base, fwdWhere, len(fwdArgs)), fwdArgs)
if err != nil {
return domain.ChannelHistory{}, err
}
out.Messages = append(out.Messages, msg)
bwdArgs := append([]any{}, baseArgs...)
bwdWhere := aroundOlderCond(&bwdArgs)
bwdArgs = append(bwdArgs, bwdLimit+1)
older, err := scanList(fmt.Sprintf("SELECT "+channelMessageColumns+" FROM channel_messages WHERE %s AND %s ORDER BY id DESC LIMIT $%d", base, bwdWhere, len(bwdArgs)), bwdArgs)
if err != nil {
return domain.ChannelHistory{}, err
}
if len(older) > bwdLimit {
older = older[:bwdLimit]
hasMoreOlder = true
}
for i := len(newer) - 1; i >= 0; i-- {
out.Messages = append(out.Messages, newer[i])
}
out.Messages = append(out.Messages, older...)
case addOffset < 0:
// forward仅锚点更新方向拉未读/更新消息)
fwdArgs := append([]any{}, baseArgs...)
fwdWhere := forwardCond(&fwdArgs)
fwdArgs = append(fwdArgs, limit+1)
newer, err := scanList(fmt.Sprintf("SELECT "+channelMessageColumns+" FROM channel_messages WHERE %s AND %s ORDER BY id ASC LIMIT $%d", base, fwdWhere, len(fwdArgs)), fwdArgs)
if err != nil {
return domain.ChannelHistory{}, err
}
if len(newer) > limit {
newer = newer[:limit]
}
for i := len(newer) - 1; i >= 0; i-- {
out.Messages = append(out.Messages, newer[i])
}
default:
// backward锚点更旧方向不含锚点先跳过 add_offset 条
where := base
args := append([]any{}, baseArgs...)
if filter.OffsetDate > 0 {
args = append(args, filter.OffsetDate)
where += fmt.Sprintf(" AND message_date < $%d", len(args))
} else if filter.OffsetID > 0 {
args = append(args, filter.OffsetID)
where += fmt.Sprintf(" AND id < $%d", len(args))
}
args = append(args, limit+1)
limIdx := len(args)
sql := "SELECT " + channelMessageColumns + " FROM channel_messages WHERE " + where + " ORDER BY id DESC"
if addOffset > 0 {
args = append(args, addOffset)
sql += fmt.Sprintf(" OFFSET $%d", len(args))
}
sql += fmt.Sprintf(" LIMIT $%d", limIdx)
older, err := scanList(sql, args)
if err != nil {
return domain.ChannelHistory{}, err
}
if len(older) > limit {
older = older[:limit]
hasMoreOlder = true
}
out.Messages = older
}
if err := rows.Err(); err != nil {
return domain.ChannelHistory{}, err
}
if len(out.Messages) > limit {
out.Messages = out.Messages[:limit]
out.Count = limit + 1
} else {
out.Count = len(out.Messages)
out.Count = len(out.Messages)
if hasMoreOlder {
out.Count = len(out.Messages) + 1
}
if err := s.populateChannelMessageReplies(ctx, s.db, viewerUserID, channel, out.Messages); err != nil {
return domain.ChannelHistory{}, err

View file

@ -155,6 +155,10 @@ func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domai
if err != nil {
return domain.DialogList{}, fmt.Errorf("decode message entities: %w", err)
}
media, err := decodeMessageMedia(row.MessageMediaJson)
if err != nil {
return domain.DialogList{}, fmt.Errorf("decode message media: %w", err)
}
out.Messages = append(out.Messages, domain.Message{
ID: int(row.MessageID),
OwnerUserID: row.UserID,
@ -164,6 +168,7 @@ func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domai
Out: row.MessageOutgoing,
Body: row.MessageBody,
Entities: entities,
Media: media,
})
}
}
@ -241,6 +246,10 @@ func (s *DialogStore) ListByPeers(ctx context.Context, userID int64, peers []dom
if err != nil {
return domain.DialogList{}, fmt.Errorf("decode message entities: %w", err)
}
media, err := decodeMessageMedia(row.MessageMediaJson)
if err != nil {
return domain.DialogList{}, fmt.Errorf("decode message media: %w", err)
}
out.Messages = append(out.Messages, domain.Message{
ID: int(row.MessageID),
OwnerUserID: row.UserID,
@ -250,6 +259,7 @@ func (s *DialogStore) ListByPeers(ctx context.Context, userID int64, peers []dom
Out: row.MessageOutgoing,
Body: row.MessageBody,
Entities: entities,
Media: media,
})
}
}

View file

@ -33,7 +33,8 @@ WITH base AS (
COALESCE(m.message_date, 0)::int AS message_date,
COALESCE(m.outgoing, false)::boolean AS message_outgoing,
COALESCE(m.body, '')::text AS message_body,
COALESCE(m.entities::text, '[]')::text AS message_entities_json
COALESCE(m.entities::text, '[]')::text AS message_entities_json,
COALESCE(m.media::text, '{}')::text AS message_media_json
FROM dialogs d
LEFT JOIN users u ON d.peer_type = 'user' AND u.id = d.peer_id
LEFT JOIN contacts c ON d.peer_type = 'user' AND c.user_id = d.user_id AND c.contact_user_id = d.peer_id
@ -155,7 +156,8 @@ SELECT
message_date,
message_outgoing,
message_body,
message_entities_json
message_entities_json,
message_media_json
FROM paged
ORDER BY
pinned DESC,
@ -288,6 +290,7 @@ base AS (
COALESCE(m.outgoing, false)::boolean AS message_outgoing,
COALESCE(m.body, '')::text AS message_body,
COALESCE(m.entities::text, '[]')::text AS message_entities_json,
COALESCE(m.media::text, '{}')::text AS message_media_json,
r.ord
FROM deduped r
LEFT JOIN dialogs d
@ -331,7 +334,8 @@ SELECT
message_date,
message_outgoing,
message_body,
message_entities_json
message_entities_json,
message_media_json
FROM base
ORDER BY ord;

View file

@ -602,6 +602,7 @@ base AS (
COALESCE(m.outgoing, false)::boolean AS message_outgoing,
COALESCE(m.body, '')::text AS message_body,
COALESCE(m.entities::text, '[]')::text AS message_entities_json,
COALESCE(m.media::text, '{}')::text AS message_media_json,
r.ord
FROM deduped r
LEFT JOIN dialogs d
@ -645,7 +646,8 @@ SELECT
message_date,
message_outgoing,
message_body,
message_entities_json
message_entities_json,
message_media_json
FROM base
ORDER BY ord
`
@ -690,6 +692,7 @@ type ListDialogsByPeersRow struct {
MessageOutgoing bool
MessageBody string
MessageEntitiesJson string
MessageMediaJson string
}
func (q *Queries) ListDialogsByPeers(ctx context.Context, arg ListDialogsByPeersParams) ([]ListDialogsByPeersRow, error) {
@ -735,6 +738,7 @@ func (q *Queries) ListDialogsByPeers(ctx context.Context, arg ListDialogsByPeers
&i.MessageOutgoing,
&i.MessageBody,
&i.MessageEntitiesJson,
&i.MessageMediaJson,
); err != nil {
return nil, err
}
@ -781,7 +785,8 @@ WITH base AS (
COALESCE(m.message_date, 0)::int AS message_date,
COALESCE(m.outgoing, false)::boolean AS message_outgoing,
COALESCE(m.body, '')::text AS message_body,
COALESCE(m.entities::text, '[]')::text AS message_entities_json
COALESCE(m.entities::text, '[]')::text AS message_entities_json,
COALESCE(m.media::text, '{}')::text AS message_media_json
FROM dialogs d
LEFT JOIN users u ON d.peer_type = 'user' AND u.id = d.peer_id
LEFT JOIN contacts c ON d.peer_type = 'user' AND c.user_id = d.user_id AND c.contact_user_id = d.peer_id
@ -834,7 +839,7 @@ WITH base AS (
AND (NOT $16::boolean OR NOT d.pinned)
),
paged AS (
SELECT user_id, peer_type, peer_id, folder_id, top_message_id, top_message_date, read_inbox_max_id, read_outbox_max_id, unread_count, unread_mentions_count, unread_reactions_count, pinned, pinned_order, unread_mark, hidden_peer_settings_bar, peer_user_id, peer_access_hash, peer_phone, peer_first_name, peer_last_name, peer_username, peer_country_code, peer_verified, peer_support, peer_last_seen_at, peer_contact, peer_mutual, message_id, message_from_user_id, message_date, message_outgoing, message_body, message_entities_json
SELECT user_id, peer_type, peer_id, folder_id, top_message_id, top_message_date, read_inbox_max_id, read_outbox_max_id, unread_count, unread_mentions_count, unread_reactions_count, pinned, pinned_order, unread_mark, hidden_peer_settings_bar, peer_user_id, peer_access_hash, peer_phone, peer_first_name, peer_last_name, peer_username, peer_country_code, peer_verified, peer_support, peer_last_seen_at, peer_contact, peer_mutual, message_id, message_from_user_id, message_date, message_outgoing, message_body, message_entities_json, message_media_json
FROM base
WHERE (
($17::int <= 0 AND $18::int <= 0)
@ -903,7 +908,8 @@ SELECT
message_date,
message_outgoing,
message_body,
message_entities_json
message_entities_json,
message_media_json
FROM paged
ORDER BY
pinned DESC,
@ -971,6 +977,7 @@ type ListDialogsByUserRow struct {
MessageOutgoing bool
MessageBody string
MessageEntitiesJson string
MessageMediaJson string
}
func (q *Queries) ListDialogsByUser(ctx context.Context, arg ListDialogsByUserParams) ([]ListDialogsByUserRow, error) {
@ -1037,6 +1044,7 @@ func (q *Queries) ListDialogsByUser(ctx context.Context, arg ListDialogsByUserPa
&i.MessageOutgoing,
&i.MessageBody,
&i.MessageEntitiesJson,
&i.MessageMediaJson,
); err != nil {
return nil, err
}

View file

@ -20,6 +20,20 @@ type AccountPassword struct {
UpdatedAt pgtype.Timestamptz
}
type AccountReactionSetting struct {
UserID int64
MessagesNotifyFrom string
StoriesNotifyFrom string
PollVotesNotifyFrom string
ShowPreviews bool
DefaultReactionType string
DefaultReactionValue string
PaidPrivacyKind string
PaidPrivacyPeerType *string
PaidPrivacyPeerID *int64
UpdatedAt pgtype.Timestamptz
}
type AppConfig struct {
Client string
Hash int32
@ -539,6 +553,19 @@ type PrivateMessage struct {
Media []byte
}
type PrivateMessageReaction struct {
MessageSenderID int64
PrivateMessageID int64
UserID int64
ReactionType string
ReactionValue string
Big bool
ReactionDate int32
ChosenOrder int32
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
}
type ProfilePhoto struct {
OwnerPeerType string
OwnerPeerID int64
@ -622,6 +649,16 @@ type User struct {
LastSeenAt int64
}
type UserChannelMemberIndex struct {
UserID int64
ChannelID int64
Status string
Megagroup bool
Broadcast bool
Deleted bool
UpdatedAt pgtype.Timestamptz
}
type UserRecentReaction struct {
UserID int64
ReactionType string