feat(platform): Phase 115 bridges — CSV mesh, archive health, webhook URL (v0.8.79)
- exportHookFailureDigest / getHookFailureDigestCsvExport with signatureValid - GUILD_SYNC slices: hookFailureDigestCsvExport, archiveHealthPrefs, modDigestGroupDmMeta - refreshArchivePeerHealth + archive health prefs on guild focus/join/open - dryRunDigestWebhookRelay + digest relay webhook URL plugin sync - syncModDigestGroupDmFromMesh for mod digest group DM participant sync Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -292,6 +292,7 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
this._devicePairSwarm = null
|
this._devicePairSwarm = null
|
||||||
this._discoveryWired = false
|
this._discoveryWired = false
|
||||||
this._archivePeerHealthRefreshedAt = null
|
this._archivePeerHealthRefreshedAt = null
|
||||||
|
this._archiveHealthRefreshTimer = null
|
||||||
this.mode = 'home'
|
this.mode = 'home'
|
||||||
this.activeChannelId = null
|
this.activeChannelId = null
|
||||||
this.onboarded = false
|
this.onboarded = false
|
||||||
@@ -1199,6 +1200,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
let auditExportArchives = []
|
let auditExportArchives = []
|
||||||
let archivePeerSeen = []
|
let archivePeerSeen = []
|
||||||
let hookFailureDigest = null
|
let hookFailureDigest = null
|
||||||
|
let hookFailureDigestCsvExport = null
|
||||||
|
let archiveHealthPrefs = null
|
||||||
await this._initDeliveryReceipts(guild.id)
|
await this._initDeliveryReceipts(guild.id)
|
||||||
if (this.deliveryReceipts) {
|
if (this.deliveryReceipts) {
|
||||||
auditExportSchedule = await this.deliveryReceipts.exportAuditExportScheduleSlice()
|
auditExportSchedule = await this.deliveryReceipts.exportAuditExportScheduleSlice()
|
||||||
@@ -1209,6 +1212,9 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
await this.deliveryReceipts.exportAutomationDigestExportSnapshotsSlice(2)
|
await this.deliveryReceipts.exportAutomationDigestExportSnapshotsSlice(2)
|
||||||
auditExportArchives = await this.deliveryReceipts.exportAuditExportArchivesSlice(3)
|
auditExportArchives = await this.deliveryReceipts.exportAuditExportArchivesSlice(3)
|
||||||
archivePeerSeen = await this.deliveryReceipts.exportArchivePeerSeenSlice(16)
|
archivePeerSeen = await this.deliveryReceipts.exportArchivePeerSeenSlice(16)
|
||||||
|
archiveHealthPrefs = await this.deliveryReceipts.exportArchiveHealthPrefsSlice()
|
||||||
|
hookFailureDigestCsvExport =
|
||||||
|
await this.deliveryReceipts.exportHookFailureDigestCsvSlice()
|
||||||
digestRelayHandoff = await this.deliveryReceipts.exportDigestRelayHandoffSlice()
|
digestRelayHandoff = await this.deliveryReceipts.exportDigestRelayHandoffSlice()
|
||||||
const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot()
|
const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot()
|
||||||
if (digestSnap) {
|
if (digestSnap) {
|
||||||
@@ -1293,6 +1299,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
auditExportArchives,
|
auditExportArchives,
|
||||||
archivePeerSeen,
|
archivePeerSeen,
|
||||||
hookFailureDigest,
|
hookFailureDigest,
|
||||||
|
hookFailureDigestCsvExport,
|
||||||
|
archiveHealthPrefs,
|
||||||
modDigestGroupDmMeta,
|
modDigestGroupDmMeta,
|
||||||
workerReleaseHint,
|
workerReleaseHint,
|
||||||
automodConfig: automodConfig
|
automodConfig: automodConfig
|
||||||
@@ -1427,6 +1435,20 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
payload.digestRelayHandoff
|
payload.digestRelayHandoff
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (payload.hookFailureDigestCsvExport) {
|
||||||
|
await this._initDeliveryReceipts(payload.guildId)
|
||||||
|
await this.deliveryReceipts.ingestHookFailureDigestCsvSlice(
|
||||||
|
payload.guildId,
|
||||||
|
payload.hookFailureDigestCsvExport
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (payload.archiveHealthPrefs) {
|
||||||
|
await this._initDeliveryReceipts(payload.guildId)
|
||||||
|
await this.deliveryReceipts.ingestArchiveHealthPrefsSlice(
|
||||||
|
payload.guildId,
|
||||||
|
payload.archiveHealthPrefs
|
||||||
|
)
|
||||||
|
}
|
||||||
if (payload.auditExportSchedule) {
|
if (payload.auditExportSchedule) {
|
||||||
await this._initDeliveryReceipts(payload.guildId)
|
await this._initDeliveryReceipts(payload.guildId)
|
||||||
await this.deliveryReceipts.ingestAuditExportScheduleSlice(
|
await this.deliveryReceipts.ingestAuditExportScheduleSlice(
|
||||||
@@ -2725,7 +2747,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
guildMute: false,
|
guildMute: false,
|
||||||
muteNotifications: false,
|
muteNotifications: false,
|
||||||
digestInboxChannelId: null,
|
digestInboxChannelId: null,
|
||||||
digestRelayEnabled: false
|
digestRelayEnabled: false,
|
||||||
|
digestRelayWebhookUrl: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this._initDeliveryReceipts(this.guild.guild.id)
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
@@ -2741,10 +2764,34 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
muteNotifications: !!guild.muteNotifications || userMute,
|
muteNotifications: !!guild.muteNotifications || userMute,
|
||||||
digestInboxChannelId: guild.digestInboxChannelId || null,
|
digestInboxChannelId: guild.digestInboxChannelId || null,
|
||||||
digestRelayEnabled: !!guild.digestRelayEnabled,
|
digestRelayEnabled: !!guild.digestRelayEnabled,
|
||||||
activeRelayPluginId: guild.activeRelayPluginId || null
|
activeRelayPluginId: guild.activeRelayPluginId || null,
|
||||||
|
digestRelayWebhookUrl: guild.digestRelayWebhookUrl || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getArchiveHealthPrefs () {
|
||||||
|
if (!this.guild?.guild) {
|
||||||
|
return { autoRefreshOnGuildFocus: true, updatedAt: 0 }
|
||||||
|
}
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
return this.deliveryReceipts.getArchiveHealthPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
async setArchiveHealthPrefs (partial = {}) {
|
||||||
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
|
const roles = await this._memberRoles()
|
||||||
|
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
|
||||||
|
throw new Error('no permission to manage archive health prefs')
|
||||||
|
}
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
const prefs = await this.deliveryReceipts.setArchiveHealthPrefs({
|
||||||
|
autoRefreshOnGuildFocus: partial.autoRefreshOnGuildFocus
|
||||||
|
})
|
||||||
|
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
|
||||||
|
this._restartArchiveHealthRefreshTimer()
|
||||||
|
return prefs
|
||||||
|
}
|
||||||
|
|
||||||
async setAutomationDigestNotifyPrefs (partial = {}) {
|
async setAutomationDigestNotifyPrefs (partial = {}) {
|
||||||
if (!this.guild?.guild) throw new Error('no guild')
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
const userId = this.identity.user?.id
|
const userId = this.identity.user?.id
|
||||||
@@ -2770,7 +2817,13 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
if (partial.activeRelayPluginId !== undefined) {
|
if (partial.activeRelayPluginId !== undefined) {
|
||||||
patch.activeRelayPluginId = partial.activeRelayPluginId || null
|
patch.activeRelayPluginId = partial.activeRelayPluginId || null
|
||||||
}
|
}
|
||||||
|
if (partial.digestRelayWebhookUrl !== undefined) {
|
||||||
|
patch.digestRelayWebhookUrl = partial.digestRelayWebhookUrl || null
|
||||||
|
}
|
||||||
await this.deliveryReceipts.setAutomationDigestNotifyPrefs(patch)
|
await this.deliveryReceipts.setAutomationDigestNotifyPrefs(patch)
|
||||||
|
if (patch.digestRelayWebhookUrl !== undefined) {
|
||||||
|
await this._syncDigestRelayWebhookUrlToPlugin(patch.digestRelayWebhookUrl).catch(() => {})
|
||||||
|
}
|
||||||
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
|
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
|
||||||
} else {
|
} else {
|
||||||
if (partial.userMute != null || partial.muteNotifications != null) {
|
if (partial.userMute != null || partial.muteNotifications != null) {
|
||||||
@@ -2867,7 +2920,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
const plugin = await this.deliveryReceipts.getActiveDigestRelayPlugin()
|
const plugin = await this.deliveryReceipts.getActiveDigestRelayPlugin()
|
||||||
if (plugin) {
|
if (plugin) {
|
||||||
handoff = applyRelayPluginToHandoff(handoff, plugin, {
|
handoff = applyRelayPluginToHandoff(handoff, plugin, {
|
||||||
relaySecret: this.guild.guild.id
|
relaySecret: this.guild.guild.id,
|
||||||
|
webhookUrl: prefs.digestRelayWebhookUrl || plugin.config?.webhookUrl || null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await this.deliveryReceipts.appendDigestRelayHandoff(handoff)
|
await this.deliveryReceipts.appendDigestRelayHandoff(handoff)
|
||||||
@@ -2978,6 +3032,15 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
})
|
})
|
||||||
signatureValid = verified.ok
|
signatureValid = verified.ok
|
||||||
body = body.csv
|
body = body.csv
|
||||||
|
await this.deliveryReceipts.recordHookFailureDigestCsvExport({
|
||||||
|
csv: body,
|
||||||
|
signature,
|
||||||
|
signatureAlg,
|
||||||
|
exportedAt: Date.now()
|
||||||
|
})
|
||||||
|
if (opts.recordMesh !== false) {
|
||||||
|
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
format,
|
format,
|
||||||
@@ -2991,6 +3054,18 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getHookFailureDigestCsvExport () {
|
||||||
|
if (!this.guild?.guild) return null
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
const row = await this.deliveryReceipts.getHookFailureDigestCsvExport()
|
||||||
|
if (!row?.signature) return row
|
||||||
|
const verified = verifyHookFailureDigestCsv(row.csvBody, row.signature, {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
relaySecret: this.guild.guild.id
|
||||||
|
})
|
||||||
|
return { ...row, signatureValid: verified.ok }
|
||||||
|
}
|
||||||
|
|
||||||
async setGuildIconImage ({ data, filename, mimeType }) {
|
async setGuildIconImage ({ data, filename, mimeType }) {
|
||||||
const user = this.identity.user
|
const user = this.identity.user
|
||||||
if (!user || !this.guild?.guild) throw new Error('no guild')
|
if (!user || !this.guild?.guild) throw new Error('no guild')
|
||||||
@@ -3892,11 +3967,17 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
guildName: this.guild.guild.name,
|
guildName: this.guild.guild.name,
|
||||||
exported: { exportedAt: Date.now(), body: '', format: 'text' }
|
exported: { exportedAt: Date.now(), body: '', format: 'text' }
|
||||||
})
|
})
|
||||||
|
const digPrefs = await this.deliveryReceipts.getAutomationDigestNotifyPrefs()
|
||||||
const stamped = plugin
|
const stamped = plugin
|
||||||
? applyRelayPluginToHandoff(handoff, plugin, { relaySecret: this.guild.guild.id })
|
? applyRelayPluginToHandoff(handoff, plugin, {
|
||||||
|
relaySecret: this.guild.guild.id,
|
||||||
|
webhookUrl: digPrefs.digestRelayWebhookUrl || null
|
||||||
|
})
|
||||||
: handoff
|
: handoff
|
||||||
if (plugin?.transport === 'webhook-bridge') {
|
if (plugin?.transport === 'webhook-bridge') {
|
||||||
result.webhookRelayPreview = dryRunWebhookRelayPost(stamped, {})
|
result.webhookRelayPreview = dryRunWebhookRelayPost(stamped, {
|
||||||
|
webhookUrl: digPrefs.digestRelayWebhookUrl || null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { sample, result }
|
return { sample, result }
|
||||||
@@ -4029,6 +4110,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
await this.discovery?.touchGuild(guildRecord.id).catch(() => {})
|
await this.discovery?.touchGuild(guildRecord.id).catch(() => {})
|
||||||
await this._ensureGuildModules()
|
await this._ensureGuildModules()
|
||||||
this._startAuditExportScheduleTimer()
|
this._startAuditExportScheduleTimer()
|
||||||
|
await this._maybeArchiveHealthRefreshOnFocus()
|
||||||
|
this._restartArchiveHealthRefreshTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
async createGuild ({ name }) {
|
async createGuild ({ name }) {
|
||||||
@@ -4077,10 +4160,43 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
await this._ensureGuildModules()
|
await this._ensureGuildModules()
|
||||||
this._startAuditExportScheduleTimer()
|
this._startAuditExportScheduleTimer()
|
||||||
|
await this._maybeArchiveHealthRefreshOnFocus()
|
||||||
|
this._restartArchiveHealthRefreshTimer()
|
||||||
this.emit('guild', result)
|
this.emit('guild', result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_stopArchiveHealthRefreshTimer () {
|
||||||
|
if (this._archiveHealthRefreshTimer) {
|
||||||
|
clearInterval(this._archiveHealthRefreshTimer)
|
||||||
|
this._archiveHealthRefreshTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_restartArchiveHealthRefreshTimer () {
|
||||||
|
this._stopArchiveHealthRefreshTimer()
|
||||||
|
if (this.mode !== 'guild' || !this.guild?.guild) return
|
||||||
|
this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
.then(() => this.deliveryReceipts.getArchiveHealthPrefs())
|
||||||
|
.then((prefs) => {
|
||||||
|
if (!prefs.autoRefreshOnGuildFocus) return
|
||||||
|
this._archiveHealthRefreshTimer = setInterval(() => {
|
||||||
|
this.refreshArchivePeerHealth().catch(() => {})
|
||||||
|
}, 30000)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async _maybeArchiveHealthRefreshOnFocus () {
|
||||||
|
if (!this.guild?.guild) return null
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
const prefs = await this.deliveryReceipts.getArchiveHealthPrefs()
|
||||||
|
if (!prefs.autoRefreshOnGuildFocus) return null
|
||||||
|
const last = this._archivePeerHealthRefreshedAt || 0
|
||||||
|
if (Date.now() - last < 5000) return null
|
||||||
|
return this.refreshArchivePeerHealth().catch(() => null)
|
||||||
|
}
|
||||||
|
|
||||||
async listGuilds () {
|
async listGuilds () {
|
||||||
return this.db.find(COLLECTIONS.GUILDS, {})
|
return this.db.find(COLLECTIONS.GUILDS, {})
|
||||||
}
|
}
|
||||||
@@ -4114,6 +4230,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
await this.discovery?.recordGuild(guild).catch(() => {})
|
await this.discovery?.recordGuild(guild).catch(() => {})
|
||||||
await this._ensureGuildModules()
|
await this._ensureGuildModules()
|
||||||
this._startAuditExportScheduleTimer()
|
this._startAuditExportScheduleTimer()
|
||||||
|
await this._maybeArchiveHealthRefreshOnFocus()
|
||||||
|
this._restartArchiveHealthRefreshTimer()
|
||||||
return guild
|
return guild
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5857,12 +5975,24 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const prefs = await this.deliveryReceipts.getAutomationDigestNotifyPrefs()
|
||||||
const preview = dryRunWebhookRelayPost(handoff, {
|
const preview = dryRunWebhookRelayPost(handoff, {
|
||||||
webhookUrl: opts.webhookUrl || null
|
webhookUrl: opts.webhookUrl || prefs.digestRelayWebhookUrl || handoff.webhookUrl || null
|
||||||
})
|
})
|
||||||
return { handoff, preview }
|
return { handoff, preview }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _syncDigestRelayWebhookUrlToPlugin (url) {
|
||||||
|
if (!this.guild?.guild) return null
|
||||||
|
const plugins = await this.deliveryReceipts.listDigestRelayPlugins()
|
||||||
|
const webhook = plugins.find((p) => p.pluginId === 'pearcord/digest-webhook-bridge')
|
||||||
|
if (!webhook) return null
|
||||||
|
return this.deliveryReceipts.upsertDigestRelayPlugin({
|
||||||
|
...webhook,
|
||||||
|
config: { ...(webhook.config || {}), webhookUrl: url || null }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async syncModDigestGroupDmFromMesh (slice = {}) {
|
async syncModDigestGroupDmFromMesh (slice = {}) {
|
||||||
if (!this.guild?.guild || !slice?.channelId) return null
|
if (!this.guild?.guild || !slice?.channelId) return null
|
||||||
const roles = await this._memberRoles()
|
const roles = await this._memberRoles()
|
||||||
@@ -7902,6 +8032,12 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
digestRelayHandoff,
|
digestRelayHandoff,
|
||||||
auditArchiveFetchPeers,
|
auditArchiveFetchPeers,
|
||||||
archivePeerHealthRefresh: this.getArchivePeerHealthRefreshStatus(),
|
archivePeerHealthRefresh: this.getArchivePeerHealthRefreshStatus(),
|
||||||
|
archiveHealthPrefs: this.guild?.guild
|
||||||
|
? await this.getArchiveHealthPrefs().catch(() => ({
|
||||||
|
autoRefreshOnGuildFocus: true,
|
||||||
|
updatedAt: 0
|
||||||
|
}))
|
||||||
|
: { autoRefreshOnGuildFocus: true, updatedAt: 0 },
|
||||||
workerReleaseHint,
|
workerReleaseHint,
|
||||||
workerInstallPlan,
|
workerInstallPlan,
|
||||||
auditLogFilterOptions,
|
auditLogFilterOptions,
|
||||||
|
|||||||
Reference in New Issue
Block a user