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,429 @@
package memory
import (
"context"
"encoding/binary"
"hash/fnv"
"sort"
"sync"
"telesrv/internal/domain"
)
// ContactStore 是 store.ContactStore 的内存实现。
type ContactStore struct {
mu sync.RWMutex
m map[int64]domain.ContactList
blocks map[int64]map[int64]domain.BlockedContact
}
// NewContactStore 创建内存 ContactStore。
func NewContactStore() *ContactStore {
return &ContactStore{
m: make(map[int64]domain.ContactList),
blocks: make(map[int64]map[int64]domain.BlockedContact),
}
}
func (s *ContactStore) ListByUser(_ context.Context, userID int64) (domain.ContactList, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
list.Contacts = cloneContacts(list.Contacts)
list.Hash = contactListHash(list.Contacts)
return list, nil
}
func (s *ContactStore) Get(_ context.Context, userID, contactUserID int64) (domain.Contact, bool, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
for _, contact := range list.Contacts {
if contact.User.ID == contactUserID {
return cloneContact(contact), true, nil
}
}
return domain.Contact{}, false, nil
}
func (s *ContactStore) GetMany(_ context.Context, userID int64, contactUserIDs []int64) (map[int64]domain.Contact, error) {
out := make(map[int64]domain.Contact, len(contactUserIDs))
if userID == 0 || len(contactUserIDs) == 0 {
return out, nil
}
want := make(map[int64]struct{}, len(contactUserIDs))
for _, id := range contactUserIDs {
if id != 0 {
want[id] = struct{}{}
}
}
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
for _, contact := range list.Contacts {
if _, ok := want[contact.User.ID]; ok {
out[contact.User.ID] = cloneContact(contact)
}
}
return out, nil
}
func (s *ContactStore) GetReverseContacts(_ context.Context, userID int64, ownerUserIDs []int64) (map[int64]domain.Contact, error) {
out := make(map[int64]domain.Contact, len(ownerUserIDs))
if userID == 0 || len(ownerUserIDs) == 0 {
return out, nil
}
want := make(map[int64]struct{}, len(ownerUserIDs))
for _, id := range ownerUserIDs {
if id != 0 {
want[id] = struct{}{}
}
}
s.mu.RLock()
defer s.mu.RUnlock()
for ownerID := range want {
for _, contact := range s.m[ownerID].Contacts {
if contact.User.ID == userID {
out[ownerID] = cloneContact(contact)
break
}
}
}
return out, nil
}
func (s *ContactStore) Upsert(_ context.Context, userID int64, input domain.ContactInput) (domain.Contact, error) {
contact := domain.Contact{
User: domain.User{
ID: input.ContactUserID,
Phone: input.Phone,
FirstName: input.FirstName,
LastName: input.LastName,
Contact: true,
},
FirstName: input.FirstName,
LastName: input.LastName,
Phone: input.Phone,
Note: input.Note,
NoteEntities: append([]domain.MessageEntity(nil), input.NoteEntities...),
}
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
reverse := s.m[input.ContactUserID]
for i := range reverse.Contacts {
if reverse.Contacts[i].User.ID == userID {
reverse.Contacts[i].Mutual = true
reverse.Contacts[i].User.Mutual = true
contact.Mutual = true
contact.User.Mutual = true
s.m[input.ContactUserID] = reverse
break
}
}
for i, existing := range list.Contacts {
if existing.User.ID != input.ContactUserID {
continue
}
contact.User.AccessHash = existing.User.AccessHash
contact.User.Username = existing.User.Username
contact.User.CountryCode = existing.User.CountryCode
contact.User.Verified = existing.User.Verified
contact.User.Support = existing.User.Support
// premium/emoji/bot 列随快照保留(postgres 路径 JOIN users 始终带真实值;
// 双 store 行为对齐,防止重复 Upsert 抹掉已知状态)。
contact.User.Bot = existing.User.Bot
contact.User.BotInfoVersion = existing.User.BotInfoVersion
contact.User.PremiumUntil = existing.User.PremiumUntil
contact.User.EmojiStatusDocumentID = existing.User.EmojiStatusDocumentID
contact.User.EmojiStatusUntil = existing.User.EmojiStatusUntil
contact.CloseFriend = existing.CloseFriend
contact.User.CloseFriend = existing.CloseFriend || existing.User.CloseFriend
if contact.Phone == "" {
contact.User.Phone = existing.User.Phone
}
if contact.FirstName == "" {
contact.User.FirstName = existing.User.FirstName
}
if contact.LastName == "" {
contact.User.LastName = existing.User.LastName
}
list.Contacts[i] = contact
list.Hash = contactListHash(list.Contacts)
s.m[userID] = list
return cloneContact(contact), nil
}
list.Contacts = append(list.Contacts, contact)
list.Hash = contactListHash(list.Contacts)
s.m[userID] = list
return cloneContact(contact), nil
}
func (s *ContactStore) UpsertMany(ctx context.Context, userID int64, inputs []domain.ContactInput) ([]domain.Contact, error) {
if len(inputs) == 0 {
return nil, nil
}
out := make([]domain.Contact, 0, len(inputs))
for _, input := range inputs {
contact, err := s.Upsert(ctx, userID, input)
if err != nil {
return nil, err
}
out = append(out, contact)
}
return out, nil
}
func (s *ContactStore) UpdateNote(_ context.Context, userID, contactUserID int64, note string, entities []domain.MessageEntity) (domain.Contact, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i := range list.Contacts {
if list.Contacts[i].User.ID != contactUserID {
continue
}
list.Contacts[i].Note = note
list.Contacts[i].NoteEntities = append([]domain.MessageEntity(nil), entities...)
list.Hash = contactListHash(list.Contacts)
s.m[userID] = list
return cloneContact(list.Contacts[i]), true, nil
}
return domain.Contact{}, false, nil
}
func (s *ContactStore) SetCloseFriends(_ context.Context, userID int64, contactUserIDs []int64) (domain.CloseFriendsEditResult, error) {
want := make(map[int64]struct{}, len(contactUserIDs))
for _, id := range contactUserIDs {
if id > 0 && id != userID {
want[id] = struct{}{}
}
}
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
changed := false
var result domain.CloseFriendsEditResult
for i := range list.Contacts {
wasCloseFriend := list.Contacts[i].CloseFriend || list.Contacts[i].User.CloseFriend
_, closeFriend := want[list.Contacts[i].User.ID]
if list.Contacts[i].CloseFriend == closeFriend && list.Contacts[i].User.CloseFriend == closeFriend {
continue
}
list.Contacts[i].CloseFriend = closeFriend
list.Contacts[i].User.CloseFriend = closeFriend
switch {
case !wasCloseFriend && closeFriend:
result.AddedUserIDs = append(result.AddedUserIDs, list.Contacts[i].User.ID)
case wasCloseFriend && !closeFriend:
result.RemovedUserIDs = append(result.RemovedUserIDs, list.Contacts[i].User.ID)
}
changed = true
}
if changed {
list.Hash = contactListHash(list.Contacts)
s.m[userID] = list
}
sort.Slice(result.AddedUserIDs, func(i, j int) bool { return result.AddedUserIDs[i] < result.AddedUserIDs[j] })
sort.Slice(result.RemovedUserIDs, func(i, j int) bool { return result.RemovedUserIDs[i] < result.RemovedUserIDs[j] })
return result, nil
}
func (s *ContactStore) SetPersonalPhoto(_ context.Context, userID, contactUserID int64, photoID int64, date int) (domain.Contact, bool, error) {
_ = date
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i := range list.Contacts {
if list.Contacts[i].User.ID != contactUserID {
continue
}
list.Contacts[i].User.PhotoID = photoID
list.Contacts[i].User.PhotoPersonal = photoID != 0
list.Hash = contactListHash(list.Contacts)
s.m[userID] = list
return cloneContact(list.Contacts[i]), true, nil
}
return domain.Contact{}, false, nil
}
func (s *ContactStore) PersonalPhotos(_ context.Context, userID int64, contactUserIDs []int64) (map[int64]domain.ProfilePhotoRef, error) {
out := make(map[int64]domain.ProfilePhotoRef, len(contactUserIDs))
if userID == 0 || len(contactUserIDs) == 0 {
return out, nil
}
want := make(map[int64]struct{}, len(contactUserIDs))
for _, id := range contactUserIDs {
if id != 0 {
want[id] = struct{}{}
}
}
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
for _, contact := range list.Contacts {
if _, ok := want[contact.User.ID]; !ok || contact.User.PhotoID == 0 {
continue
}
out[contact.User.ID] = domain.ProfilePhotoRef{
PhotoID: contact.User.PhotoID,
DCID: contact.User.PhotoDCID,
Stripped: append([]byte(nil), contact.User.PhotoStripped...),
Personal: true,
HasVideo: contact.User.PhotoHasVideo,
}
}
return out, nil
}
func (s *ContactStore) Delete(_ context.Context, userID int64, contactUserIDs []int64) (int, error) {
remove := make(map[int64]struct{}, len(contactUserIDs))
for _, id := range contactUserIDs {
if id != 0 {
remove[id] = struct{}{}
}
}
if len(remove) == 0 {
return 0, nil
}
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
out := list.Contacts[:0]
deleted := 0
for _, contact := range list.Contacts {
if _, ok := remove[contact.User.ID]; ok {
deleted++
if reverse := s.m[contact.User.ID]; len(reverse.Contacts) > 0 {
for i := range reverse.Contacts {
if reverse.Contacts[i].User.ID == userID {
reverse.Contacts[i].Mutual = false
reverse.Contacts[i].User.Mutual = false
}
}
reverse.Hash = contactListHash(reverse.Contacts)
s.m[contact.User.ID] = reverse
}
continue
}
out = append(out, contact)
}
list.Contacts = out
list.Hash = contactListHash(list.Contacts)
s.m[userID] = list
return deleted, nil
}
func (s *ContactStore) Block(_ context.Context, userID, blockedUserID int64, date int) (bool, error) {
if userID == 0 || blockedUserID == 0 || userID == blockedUserID {
return false, nil
}
s.mu.Lock()
defer s.mu.Unlock()
if s.blocks[userID] == nil {
s.blocks[userID] = make(map[int64]domain.BlockedContact)
}
_, existed := s.blocks[userID][blockedUserID]
s.blocks[userID][blockedUserID] = domain.BlockedContact{
User: domain.User{ID: blockedUserID},
Date: date,
}
return !existed, nil
}
func (s *ContactStore) Unblock(_ context.Context, userID, blockedUserID int64) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.blocks[userID] == nil {
return false, nil
}
_, existed := s.blocks[userID][blockedUserID]
delete(s.blocks[userID], blockedUserID)
return existed, nil
}
func (s *ContactStore) IsBlocked(_ context.Context, userID, blockedUserID int64) (bool, error) {
s.mu.RLock()
defer s.mu.RUnlock()
_, blocked := s.blocks[userID][blockedUserID]
return blocked, nil
}
func (s *ContactStore) ListBlocked(_ context.Context, userID int64, offset, limit int) (domain.BlockedContactList, error) {
s.mu.RLock()
defer s.mu.RUnlock()
items := make([]domain.BlockedContact, 0, len(s.blocks[userID]))
for _, item := range s.blocks[userID] {
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool {
if items[i].Date == items[j].Date {
return items[i].User.ID > items[j].User.ID
}
return items[i].Date > items[j].Date
})
total := len(items)
if offset < 0 {
offset = 0
}
if offset >= len(items) {
return domain.BlockedContactList{Count: total}, nil
}
if limit <= 0 || limit > len(items)-offset {
limit = len(items) - offset
}
out := append([]domain.BlockedContact(nil), items[offset:offset+limit]...)
return domain.BlockedContactList{Blocked: out, Count: total}, nil
}
// SaveList 保存一份用户通讯录,供测试和本地替身使用。
func (s *ContactStore) SaveList(_ context.Context, userID int64, list domain.ContactList) error {
list.Contacts = cloneContacts(list.Contacts)
list.Hash = contactListHash(list.Contacts)
s.mu.Lock()
s.m[userID] = list
s.mu.Unlock()
return nil
}
func cloneContacts(contacts []domain.Contact) []domain.Contact {
out := append([]domain.Contact(nil), contacts...)
for i := range out {
out[i] = cloneContact(out[i])
}
return out
}
func cloneContact(contact domain.Contact) domain.Contact {
contact.NoteEntities = append([]domain.MessageEntity(nil), contact.NoteEntities...)
return contact
}
func contactListHash(contacts []domain.Contact) int64 {
if len(contacts) == 0 {
return 0
}
h := fnv.New64a()
var buf [16]byte
for _, contact := range contacts {
binary.LittleEndian.PutUint64(buf[:8], uint64(contact.User.ID))
if contact.Mutual {
buf[8] = 1
} else {
buf[8] = 0
}
if contact.CloseFriend || contact.User.CloseFriend {
buf[9] = 1
} else {
buf[9] = 0
}
_, _ = h.Write(buf[:10])
_, _ = h.Write([]byte(contact.FirstName))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(contact.LastName))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(contact.Phone))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(contact.Note))
_, _ = h.Write([]byte{0})
}
return int64(h.Sum64())
}