feat(mesh): guild soundboard GUILD_SYNC slice and gossip sync-ready (Phase 793)
Export and ingest guildSounds in full sync bundles, emit guild-sync-ready on sound gossip (including tombstone deletes), and sanitize sound rows for wire. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Application facade: one `PearcordPlatform` class that wires identity, database, guild mesh, DMs, invites, voice, discovery, bots, and dozens of feature modules for the Pearcord desktop sidecar and companion. **Mixin sources:** [`mixins/README.md`](mixins/README.md) — `domain/`, `json-export/`, `runtime/{core,mesh,guild-sync,search-discovery,partition-heal,view,voice-dm,features}/`. Architecture: [PLATFORM_MIXINS.md](../../docs/PLATFORM_MIXINS.md). Regenerate after manifest edits: `cd apps/pearcord && npm run regenerate:platform-mixins`. OTA prestage recursively verifies symbols under `mixins/` — [RELEASE.md](../../docs/RELEASE.md#prestage-sync-file-deps).
|
Application facade: one `PearcordPlatform` class that wires identity, database, guild mesh, DMs, invites, voice, discovery, bots, and dozens of feature modules for the Pearcord desktop sidecar and companion. **Mixin sources:** [`mixins/README.md`](mixins/README.md) — `domain/`, `json-export/`, `runtime/{core,mesh,guild-sync,search-discovery,partition-heal,view,voice-dm,features}/`. Architecture: [PLATFORM_MIXINS.md](../../docs/PLATFORM_MIXINS.md). Regenerate after manifest edits: `cd apps/pearcord && npm run regenerate:platform-mixins`. OTA prestage recursively verifies symbols under `mixins/` — [RELEASE.md](../../docs/RELEASE.md#prestage-sync-file-deps).
|
||||||
|
|
||||||
|
**Phase 793 (v0.8.760):** Sound gossip + GUILD_SYNC `guildSounds` slice → `guild-sync-ready`. Bundle: `npm run test:ci-phase793`.
|
||||||
|
|
||||||
**Phase 792 (v0.8.759):** Pin gossip + GUILD_SYNC `pins` slice → `guild-sync-ready`. Bundle: `npm run test:ci-phase792`.
|
**Phase 792 (v0.8.759):** Pin gossip + GUILD_SYNC `pins` slice → `guild-sync-ready`. Bundle: `npm run test:ci-phase792`.
|
||||||
|
|
||||||
**Phase 791 (v0.8.758):** Sticker gossip + GUILD_SYNC `guildStickers` slice → `guild-sync-ready`. Bundle: `npm run test:ci-phase791`.
|
**Phase 791 (v0.8.758):** Sticker gossip + GUILD_SYNC `guildStickers` slice → `guild-sync-ready`. Bundle: `npm run test:ci-phase791`.
|
||||||
|
|||||||
@@ -582,6 +582,63 @@ async _ingestGuildStickersSyncBundle (rows = []) {
|
|||||||
return changed
|
return changed
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async _exportGuildSoundsForSyncBundle (guildId) {
|
||||||
|
if (!guildId) return []
|
||||||
|
await this._initSoundboardRegistry(guildId)
|
||||||
|
if (!this.soundboardRegistry) return []
|
||||||
|
const rows = await this.soundboardRegistry.list().catch(() => [])
|
||||||
|
return rows
|
||||||
|
.filter((s) => !s.deleted)
|
||||||
|
.slice(0, 64)
|
||||||
|
.map((s) => ({
|
||||||
|
guildId: s.guildId || guildId,
|
||||||
|
name: s.name,
|
||||||
|
description: s.description || '',
|
||||||
|
authorId: s.authorId || null,
|
||||||
|
attachmentId: s.attachmentId || null,
|
||||||
|
driveKey: s.driveKey || null,
|
||||||
|
drivePath: s.drivePath || null,
|
||||||
|
mimeType: s.mimeType || null,
|
||||||
|
filename: s.filename || null,
|
||||||
|
durationMs: s.durationMs || 0,
|
||||||
|
volume: s.volume != null ? s.volume : 1,
|
||||||
|
updatedAt: hyperdbScope.coerceSyncUint(s.updatedAt, 0),
|
||||||
|
tombstone: false
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
async _ingestGuildSoundsSyncBundle (rows = []) {
|
||||||
|
const span = this.log.time('guild.sync.sounds', {
|
||||||
|
spanKind: 'guild.sync.sounds',
|
||||||
|
guildId: this.guild?.guild?.id,
|
||||||
|
count: rows?.length || 0
|
||||||
|
})
|
||||||
|
let changed = 0
|
||||||
|
const guildId = this.guild?.guild?.id
|
||||||
|
if (!guildId) {
|
||||||
|
span.end({ changed: 0, rows: 0 })
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
await this._initSoundboardRegistry(guildId)
|
||||||
|
if (!this.soundboardRegistry) {
|
||||||
|
span.end({ changed: 0, rows: 0 })
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for (const r of rows) {
|
||||||
|
if (!r?.name) continue
|
||||||
|
if (r.tombstone || r.removed || r.deleted) {
|
||||||
|
const removed = await this.soundboardRegistry.remove(r.name).catch(() => null)
|
||||||
|
if (removed) changed += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const out = await this.soundboardRegistry.ingestGossip(r)
|
||||||
|
if (out) changed += 1
|
||||||
|
}
|
||||||
|
span.end({ changed, rows: rows?.length || 0 })
|
||||||
|
if (changed > 0) this.emit('guild-sound', { batch: true })
|
||||||
|
return changed
|
||||||
|
},
|
||||||
|
|
||||||
async _ingestPresenceVectorBundle (guildId, rows = []) {
|
async _ingestPresenceVectorBundle (guildId, rows = []) {
|
||||||
const span = this.log.time('guild.sync.presence', {
|
const span = this.log.time('guild.sync.presence', {
|
||||||
spanKind: 'guild.sync.presence',
|
spanKind: 'guild.sync.presence',
|
||||||
@@ -952,6 +1009,7 @@ async _buildGuildSyncBundle (limitPerChannel, opts = {}) {
|
|||||||
)
|
)
|
||||||
const guildEmojis = await this._exportGuildEmojisForSyncBundle(guild.id)
|
const guildEmojis = await this._exportGuildEmojisForSyncBundle(guild.id)
|
||||||
const guildStickers = await this._exportGuildStickersForSyncBundle(guild.id)
|
const guildStickers = await this._exportGuildStickersForSyncBundle(guild.id)
|
||||||
|
const guildSounds = await this._exportGuildSoundsForSyncBundle(guild.id)
|
||||||
const pins = await this._exportPinsForSyncBundle(
|
const pins = await this._exportPinsForSyncBundle(
|
||||||
guild.id,
|
guild.id,
|
||||||
syncChannels.map((c) => c.id),
|
syncChannels.map((c) => c.id),
|
||||||
@@ -1066,6 +1124,7 @@ async _buildGuildSyncBundle (limitPerChannel, opts = {}) {
|
|||||||
reactions,
|
reactions,
|
||||||
guildEmojis,
|
guildEmojis,
|
||||||
guildStickers,
|
guildStickers,
|
||||||
|
guildSounds,
|
||||||
presenceVector: this._exportPresenceVectorForSync(guild.id),
|
presenceVector: this._exportPresenceVectorForSync(guild.id),
|
||||||
forumIndex,
|
forumIndex,
|
||||||
voiceOccupancy,
|
voiceOccupancy,
|
||||||
|
|||||||
@@ -601,6 +601,7 @@ async _ingestGuildSync (payload) {
|
|||||||
const reactionChanges = await this._ingestReactionsSyncBundle(payload.reactions || [])
|
const reactionChanges = await this._ingestReactionsSyncBundle(payload.reactions || [])
|
||||||
const emojiChanges = await this._ingestGuildEmojisSyncBundle(payload.guildEmojis || [])
|
const emojiChanges = await this._ingestGuildEmojisSyncBundle(payload.guildEmojis || [])
|
||||||
const stickerChanges = await this._ingestGuildStickersSyncBundle(payload.guildStickers || [])
|
const stickerChanges = await this._ingestGuildStickersSyncBundle(payload.guildStickers || [])
|
||||||
|
const soundChanges = await this._ingestGuildSoundsSyncBundle(payload.guildSounds || [])
|
||||||
let sparse = { mode: 'gossip', added: 0 }
|
let sparse = { mode: 'gossip', added: 0 }
|
||||||
if (payload.channelCoreKeys?.length) {
|
if (payload.channelCoreKeys?.length) {
|
||||||
this._lastGuildSyncChannelCoreKeys = payload.channelCoreKeys.slice(0, 96)
|
this._lastGuildSyncChannelCoreKeys = payload.channelCoreKeys.slice(0, 96)
|
||||||
@@ -634,6 +635,7 @@ async _ingestGuildSync (payload) {
|
|||||||
reactions: reactionChanges,
|
reactions: reactionChanges,
|
||||||
emojis: emojiChanges,
|
emojis: emojiChanges,
|
||||||
stickers: stickerChanges,
|
stickers: stickerChanges,
|
||||||
|
sounds: soundChanges,
|
||||||
pins: pinsApplied,
|
pins: pinsApplied,
|
||||||
sparse,
|
sparse,
|
||||||
attachPull,
|
attachPull,
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ const platformGuildSyncWireSparseMixin = {
|
|||||||
updatedAt: hyperdbScope.coerceSyncUint(s.updatedAt, 0)
|
updatedAt: hyperdbScope.coerceSyncUint(s.updatedAt, 0)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(next.guildSounds)) {
|
||||||
|
next.guildSounds = next.guildSounds.map((s) => ({
|
||||||
|
...s,
|
||||||
|
updatedAt: hyperdbScope.coerceSyncUint(s.updatedAt, 0)
|
||||||
|
}))
|
||||||
|
}
|
||||||
if (Array.isArray(next.pins)) {
|
if (Array.isArray(next.pins)) {
|
||||||
next.pins = next.pins.map((p) => ({
|
next.pins = next.pins.map((p) => ({
|
||||||
...p,
|
...p,
|
||||||
|
|||||||
@@ -265,6 +265,7 @@ _wireGuild (guildInstance) {
|
|||||||
const payloadReactions = payload?.reactions?.length || 0
|
const payloadReactions = payload?.reactions?.length || 0
|
||||||
const payloadEmojis = payload?.guildEmojis?.length || 0
|
const payloadEmojis = payload?.guildEmojis?.length || 0
|
||||||
const payloadStickers = payload?.guildStickers?.length || 0
|
const payloadStickers = payload?.guildStickers?.length || 0
|
||||||
|
const payloadSounds = payload?.guildSounds?.length || 0
|
||||||
const payloadPins = payload?.pins?.length || 0
|
const payloadPins = payload?.pins?.length || 0
|
||||||
const hasSlice =
|
const hasSlice =
|
||||||
payloadMsgs > 0 ||
|
payloadMsgs > 0 ||
|
||||||
@@ -276,6 +277,7 @@ _wireGuild (guildInstance) {
|
|||||||
payloadReactions > 0 ||
|
payloadReactions > 0 ||
|
||||||
payloadEmojis > 0 ||
|
payloadEmojis > 0 ||
|
||||||
payloadStickers > 0 ||
|
payloadStickers > 0 ||
|
||||||
|
payloadSounds > 0 ||
|
||||||
payloadPins > 0
|
payloadPins > 0
|
||||||
if (!result && !hasSlice) return
|
if (!result && !hasSlice) return
|
||||||
this._lastGuildSyncIngestAt = Date.now()
|
this._lastGuildSyncIngestAt = Date.now()
|
||||||
@@ -315,6 +317,10 @@ _wireGuild (guildInstance) {
|
|||||||
typeof result === 'object'
|
typeof result === 'object'
|
||||||
? Math.max(result.stickers || 0, payloadStickers)
|
? Math.max(result.stickers || 0, payloadStickers)
|
||||||
: payloadStickers
|
: payloadStickers
|
||||||
|
const soundsCount =
|
||||||
|
typeof result === 'object'
|
||||||
|
? Math.max(result.sounds || 0, payloadSounds)
|
||||||
|
: payloadSounds
|
||||||
const pinsCount =
|
const pinsCount =
|
||||||
typeof result === 'object'
|
typeof result === 'object'
|
||||||
? Math.max(result.pins || 0, payloadPins)
|
? Math.max(result.pins || 0, payloadPins)
|
||||||
@@ -329,6 +335,7 @@ _wireGuild (guildInstance) {
|
|||||||
reactionsCount > 0 ||
|
reactionsCount > 0 ||
|
||||||
emojisCount > 0 ||
|
emojisCount > 0 ||
|
||||||
stickersCount > 0 ||
|
stickersCount > 0 ||
|
||||||
|
soundsCount > 0 ||
|
||||||
pinsCount > 0 ||
|
pinsCount > 0 ||
|
||||||
hasSlice
|
hasSlice
|
||||||
) {
|
) {
|
||||||
@@ -343,6 +350,7 @@ _wireGuild (guildInstance) {
|
|||||||
reactions: reactionsCount,
|
reactions: reactionsCount,
|
||||||
emojis: emojisCount,
|
emojis: emojisCount,
|
||||||
stickers: stickersCount,
|
stickers: stickersCount,
|
||||||
|
sounds: soundsCount,
|
||||||
pins: pinsCount
|
pins: pinsCount
|
||||||
})
|
})
|
||||||
if (payload?.guildId === this.guild?.guild?.id) {
|
if (payload?.guildId === this.guild?.guild?.id) {
|
||||||
@@ -608,8 +616,14 @@ _wireGuild (guildInstance) {
|
|||||||
this.emit('guild-sticker-pack-delete', payload)
|
this.emit('guild-sticker-pack-delete', payload)
|
||||||
})
|
})
|
||||||
guildInstance.on('guild-sound', (row) => {
|
guildInstance.on('guild-sound', (row) => {
|
||||||
|
if (row?.deleted) {
|
||||||
|
this.soundboardRegistry?.remove(row.name).catch(() => {})
|
||||||
|
} else {
|
||||||
this.soundboardRegistry?.ingestGossip(row).catch(() => {})
|
this.soundboardRegistry?.ingestGossip(row).catch(() => {})
|
||||||
|
}
|
||||||
this.emit('guild-sound', row)
|
this.emit('guild-sound', row)
|
||||||
|
const gid = this.guild?.guild?.id
|
||||||
|
if (gid) this.emit('guild-sync-ready', { guildId: gid, sounds: 1 })
|
||||||
})
|
})
|
||||||
guildInstance.on('guild-sound-play', (play) => {
|
guildInstance.on('guild-sound-play', (play) => {
|
||||||
this._onRemoteSoundPlay(play).catch(() => {})
|
this._onRemoteSoundPlay(play).catch(() => {})
|
||||||
|
|||||||
Reference in New Issue
Block a user