chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
167
internal/app/files/effects_seed.go
Normal file
167
internal/app/files/effects_seed.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// telegram_effects_export/effects.json 的解析结构。effects 引用文档 id,文档全量元数据
|
||||
// 在 documents[] 里(与 messages.availableEffects 同构),blob 在 documents/<docid>.<ext>。
|
||||
type seedEffectJSON struct {
|
||||
ID int64 `json:"id"`
|
||||
Emoticon string `json:"emoticon"`
|
||||
StaticIconID int64 `json:"static_icon_id"`
|
||||
EffectStickerID int64 `json:"effect_sticker_id"`
|
||||
EffectAnimationID int64 `json:"effect_animation_id"`
|
||||
PremiumRequired bool `json:"premium_required"`
|
||||
}
|
||||
|
||||
type seedEffectsFileJSON struct {
|
||||
Result struct {
|
||||
Effects []seedEffectJSON `json:"effects"`
|
||||
Documents []seedDocumentJSON `json:"documents"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// seedEffects 从 telegram_effects_export 导入消息特效。特效元数据每次启动都从 JSON
|
||||
// 重建进内存;document/blob 只有 catalog hash 变化或持久化资源不完整时才重导。
|
||||
func (s *Service) seedEffects(ctx context.Context, root string, stats *SeedStats) error {
|
||||
dir := filepath.Join(root, "telegram_effects_export")
|
||||
raw, err := os.ReadFile(filepath.Join(dir, "effects.json"))
|
||||
if err != nil {
|
||||
return nil // 无 effects 资源 → 跳过
|
||||
}
|
||||
var parsed seedEffectsFileJSON
|
||||
if err := json.Unmarshal(raw, &parsed); err != nil {
|
||||
return fmt.Errorf("parse effects.json: %w", err)
|
||||
}
|
||||
docsDir := filepath.Join(dir, "documents")
|
||||
index, err := scanSeedDir(docsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
effects, requiredDocs := seedEffectsCatalog(parsed)
|
||||
s.effects = effects
|
||||
s.effectsHash = effectsCatalogHash(effects)
|
||||
stats.Effects = len(effects)
|
||||
|
||||
stateHash, err := s.seedEffectsStateHash(raw, docsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ready, err := s.seedDocumentJSONsReady(ctx, requiredDocs, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matched, err := s.seedStateMatches(ctx, seedEffectsStateKey, stateHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if matched && ready {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 多个 effect 常共享同一文档(static icon 尤甚):每个唯一源文档只导一次。
|
||||
for _, dj := range requiredDocs {
|
||||
if _, err := s.importDocument(ctx, dj, docsDir, index, stats); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s.putSeedState(ctx, seedEffectsStateKey, stateHash)
|
||||
}
|
||||
|
||||
func seedEffectsCatalog(parsed seedEffectsFileJSON) ([]domain.AvailableEffect, []seedDocumentJSON) {
|
||||
docByID := make(map[int64]seedDocumentJSON, len(parsed.Result.Documents))
|
||||
for _, d := range parsed.Result.Documents {
|
||||
docByID[d.ID] = d
|
||||
}
|
||||
required := make(map[int64]struct{}, len(docByID))
|
||||
storageID := func(sourceID int64) int64 {
|
||||
if sourceID == 0 {
|
||||
return 0
|
||||
}
|
||||
if _, ok := docByID[sourceID]; !ok {
|
||||
return 0
|
||||
}
|
||||
required[sourceID] = struct{}{}
|
||||
return seedDocumentStorageID(sourceID)
|
||||
}
|
||||
effects := make([]domain.AvailableEffect, 0, len(parsed.Result.Effects))
|
||||
for i, ej := range parsed.Result.Effects {
|
||||
if ej.ID == 0 || ej.EffectStickerID == 0 {
|
||||
continue
|
||||
}
|
||||
staticID := storageID(ej.StaticIconID)
|
||||
stickerID := storageID(ej.EffectStickerID)
|
||||
if stickerID == 0 {
|
||||
continue
|
||||
}
|
||||
animID := storageID(ej.EffectAnimationID)
|
||||
effects = append(effects, domain.AvailableEffect{
|
||||
ID: ej.ID,
|
||||
Emoticon: ej.Emoticon,
|
||||
StaticIconID: staticID,
|
||||
EffectStickerID: stickerID,
|
||||
EffectAnimationID: animID,
|
||||
PremiumRequired: ej.PremiumRequired,
|
||||
Order: i,
|
||||
})
|
||||
}
|
||||
docs := make([]seedDocumentJSON, 0, len(required))
|
||||
for _, d := range parsed.Result.Documents {
|
||||
if _, ok := required[d.ID]; ok {
|
||||
docs = append(docs, d)
|
||||
}
|
||||
}
|
||||
return effects, docs
|
||||
}
|
||||
|
||||
func (s *Service) seedEffectsStateHash(raw []byte, docsDir string) (string, error) {
|
||||
return seedStateHash(func(h hash.Hash) error {
|
||||
writeSeedStateHeader(h, seedEffectsStateVersion, s.dc)
|
||||
_, _ = h.Write(raw)
|
||||
_, _ = h.Write([]byte{'\n'})
|
||||
return writeSeedDirFingerprint(h, docsDir)
|
||||
})
|
||||
}
|
||||
|
||||
// AvailableEffects 返回 seed 进内存的消息特效目录与其内容 hash(全局静态;hash 在 seed 时
|
||||
// 算一次,handler 直接比对返回 NotModified,无需每次 RPC 重算)。
|
||||
func (s *Service) AvailableEffects(ctx context.Context) ([]domain.AvailableEffect, int, error) {
|
||||
return s.effects, s.effectsHash, nil
|
||||
}
|
||||
|
||||
// effectsCatalogHash 由 effect 字段算稳定正整数 hash(FNV-1a)。内容变则 hash 变,
|
||||
// 客户端发旧 hash 即不命中→重取。
|
||||
func effectsCatalogHash(effects []domain.AvailableEffect) int {
|
||||
if len(effects) == 0 {
|
||||
return 0
|
||||
}
|
||||
h := fnv.New64a()
|
||||
var buf [8]byte
|
||||
put := func(v int64) {
|
||||
binary.LittleEndian.PutUint64(buf[:], uint64(v))
|
||||
_, _ = h.Write(buf[:])
|
||||
}
|
||||
for _, e := range effects {
|
||||
put(e.ID)
|
||||
_, _ = h.Write([]byte(e.Emoticon))
|
||||
put(e.StaticIconID)
|
||||
put(e.EffectStickerID)
|
||||
put(e.EffectAnimationID)
|
||||
if e.PremiumRequired {
|
||||
put(1)
|
||||
} else {
|
||||
put(0)
|
||||
}
|
||||
}
|
||||
return int(h.Sum64() & 0x7fffffff)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue