owpengram-server/internal/rpc/messages_reactions_catalog.go

276 lines
7.3 KiB
Go

package rpc
import (
"context"
"github.com/gotd/td/tg"
"hash/fnv"
"strconv"
"strings"
"telesrv/internal/compat/tdesktop"
"telesrv/internal/domain"
)
func (r *Router) onMessagesGetTopReactions(ctx context.Context, req *tg.MessagesGetTopReactionsRequest) (tg.MessagesReactionsClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if req.Limit < 0 || req.Limit > maxSearchResultsLimit {
return nil, limitInvalidErr()
}
limit := req.Limit
if limit == 0 {
limit = defaultTopReactionsLimit
}
reactions := []domain.MessageReaction{}
if r.deps.Channels != nil {
var err error
reactions, err = r.deps.Channels.TopReactions(ctx, userID, limit)
if err != nil {
return nil, channelInvalidErr(err)
}
}
return messagesReactionsFromDomain(r.reactionsWithCatalogFallback(ctx, reactions, limit), req.Hash), nil
}
func (r *Router) onMessagesGetRecentReactions(ctx context.Context, req *tg.MessagesGetRecentReactionsRequest) (tg.MessagesReactionsClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if req.Limit < 0 || req.Limit > maxSearchResultsLimit {
return nil, limitInvalidErr()
}
if r.deps.Channels == nil {
return messagesReactionsEmpty(req.Hash), nil
}
reactions, err := r.deps.Channels.RecentReactions(ctx, userID, req.Limit)
if err != nil {
return nil, channelInvalidErr(err)
}
return messagesReactionsFromDomain(reactions, req.Hash), nil
}
func (r *Router) onMessagesClearRecentReactions(ctx context.Context) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if r.deps.Channels != nil {
if err := r.deps.Channels.ClearRecentReactions(ctx, userID); err != nil {
return false, channelInvalidErr(err)
}
}
return true, nil
}
func (r *Router) onMessagesGetSavedReactionTags(ctx context.Context, req *tg.MessagesGetSavedReactionTagsRequest) (tg.MessagesSavedReactionTagsClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if peer, ok := req.GetPeer(); ok && peer != nil {
if _, err := r.checkedDomainPeerFromInputPeer(ctx, userID, peer); err != nil {
return nil, err
}
return savedReactionTagsEmpty(req.Hash), nil
}
if r.deps.Channels == nil {
return savedReactionTagsEmpty(req.Hash), nil
}
tags, err := r.deps.Channels.SavedReactionTags(ctx, userID, domain.MaxSavedReactionTags)
if err != nil {
return nil, channelInvalidErr(err)
}
return savedReactionTagsFromDomain(tags, req.Hash), nil
}
func (r *Router) onMessagesGetDefaultTagReactions(ctx context.Context, hash int64) (tg.MessagesReactionsClass, error) {
if _, _, err := r.currentUserID(ctx); err != nil {
return nil, internalErr()
}
return messagesReactionsEmpty(hash), nil
}
func messagesReactionsEmpty(hash int64) tg.MessagesReactionsClass {
if hash != 0 {
return &tg.MessagesReactionsNotModified{}
}
return &tg.MessagesReactions{
Hash: 0,
Reactions: []tg.ReactionClass{},
}
}
func messagesReactionsFromDomain(reactions []domain.MessageReaction, requestHash int64) tg.MessagesReactionsClass {
hash := messageReactionListHash(reactions)
if hash != 0 && requestHash == hash {
return &tg.MessagesReactionsNotModified{}
}
out := make([]tg.ReactionClass, 0, len(reactions))
for _, reaction := range reactions {
tgReaction := tgMessageReaction(reaction)
if tgReaction != nil {
out = append(out, tgReaction)
}
}
return &tg.MessagesReactions{
Hash: hash,
Reactions: out,
}
}
func savedReactionTagsEmpty(_ int64) tg.MessagesSavedReactionTagsClass {
return &tg.MessagesSavedReactionTags{
Tags: []tg.SavedReactionTag{},
Hash: 0,
}
}
func savedReactionTagsFromDomain(tags []domain.SavedReactionTag, requestHash int64) tg.MessagesSavedReactionTagsClass {
hash := savedReactionTagListHash(tags)
if hash != 0 && requestHash == hash {
return &tg.MessagesSavedReactionTagsNotModified{}
}
out := make([]tg.SavedReactionTag, 0, len(tags))
for _, tag := range tags {
reaction := tgMessageReaction(tag.Reaction)
if reaction == nil {
continue
}
item := tg.SavedReactionTag{
Reaction: reaction,
Count: tag.Count,
}
if tag.Title != "" {
item.SetTitle(tag.Title)
}
out = append(out, item)
}
if len(out) == 0 {
return savedReactionTagsEmpty(requestHash)
}
return &tg.MessagesSavedReactionTags{
Tags: out,
Hash: hash,
}
}
func (r *Router) reactionsWithCatalogFallback(ctx context.Context, reactions []domain.MessageReaction, limit int) []domain.MessageReaction {
return mergeReactionCatalogFallback(reactions, r.availableReactionCatalog(ctx, limit), limit)
}
func (r *Router) availableReactionCatalog(ctx context.Context, limit int) []domain.MessageReaction {
if limit <= 0 {
return nil
}
if r.deps.Files != nil {
catalog, err := r.deps.Files.ListAvailableReactions(ctx)
if err == nil {
out := make([]domain.MessageReaction, 0, min(limit, len(catalog)))
for _, item := range catalog {
emoticon := strings.TrimSpace(item.Reaction)
if item.Inactive || emoticon == "" {
continue
}
out = append(out, domain.MessageReaction{Type: domain.MessageReactionEmoji, Emoticon: emoticon})
if len(out) >= limit {
return out
}
}
if len(out) > 0 {
return out
}
}
}
return staticReactionCatalog()
}
func staticReactionCatalog() []domain.MessageReaction {
emoticons := tdesktop.DefaultReactionEmoticons()
out := make([]domain.MessageReaction, 0, len(emoticons))
for _, emoticon := range emoticons {
out = append(out, domain.MessageReaction{Type: domain.MessageReactionEmoji, Emoticon: emoticon})
}
return out
}
func mergeReactionCatalogFallback(reactions, fallback []domain.MessageReaction, limit int) []domain.MessageReaction {
if limit <= 0 {
return []domain.MessageReaction{}
}
if limit > domain.MaxTopMessageReactions {
limit = domain.MaxTopMessageReactions
}
out := make([]domain.MessageReaction, 0, limit)
seen := make(map[string]struct{}, limit)
for _, reaction := range reactions {
if !reaction.Valid() {
continue
}
key := reaction.Key()
if _, ok := seen[key]; ok {
continue
}
out = append(out, reaction)
seen[key] = struct{}{}
if len(out) >= limit {
return out
}
}
for _, reaction := range fallback {
if !reaction.Valid() {
continue
}
key := reaction.Key()
if _, ok := seen[key]; ok {
continue
}
out = append(out, reaction)
seen[key] = struct{}{}
if len(out) >= limit {
return out
}
}
return out
}
func messageReactionListHash(reactions []domain.MessageReaction) int64 {
if len(reactions) == 0 {
return 0
}
h := fnv.New64a()
for _, reaction := range reactions {
_, _ = h.Write([]byte(reaction.Type))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(reaction.Value()))
_, _ = h.Write([]byte{0xff})
}
sum := int64(h.Sum64() & 0x7fffffffffffffff)
if sum == 0 {
return 1
}
return sum
}
func savedReactionTagListHash(tags []domain.SavedReactionTag) int64 {
if len(tags) == 0 {
return 0
}
h := fnv.New64a()
for _, tag := range tags {
_, _ = h.Write([]byte(tag.Reaction.Type))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(tag.Reaction.Value()))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(tag.Title))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(strconv.Itoa(tag.Count)))
_, _ = h.Write([]byte{0xff})
}
sum := int64(h.Sum64() & 0x7fffffffffffffff)
if sum == 0 {
return 1
}
return sum
}