feat(platform): sticker spanKind and stage/delete spans for Phase 542
Add sticker.stage and sticker.delete spans with spanKind and activeChannelId; extend sticker.create and sticker.send span ends. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -45,6 +45,8 @@ Expose a single `EventEmitter` API the UI drives over IPC (`apps/pearcord/index.
|
|||||||
|
|
||||||
**v0.8.452 (Phase 489):** `slash.register` `customCount`; `slash.delete` `deleted`; `slash.invoke` `replyLen`/`commandFound`. UI slash compositor + hints. Bundle: `npm run test:phase489-slash`. See [SLASH_COMMANDS.md](../../docs/SLASH_COMMANDS.md).
|
**v0.8.452 (Phase 489):** `slash.register` `customCount`; `slash.delete` `deleted`; `slash.invoke` `replyLen`/`commandFound`. UI slash compositor + hints. Bundle: `npm run test:phase489-slash`. See [SLASH_COMMANDS.md](../../docs/SLASH_COMMANDS.md).
|
||||||
|
|
||||||
|
**v0.8.505 (Phase 542):** Sticker spans extend — `sticker.create`/`sticker.delete`/`sticker.stage`/`sticker.send` with `spanKind` + `activeChannelId`. UI split sticker compositor panels, `lastStickerTileName`, dedicated **sticker-errors** dev-log filter. Bundle: `npm run test:phase542-stickers`. See [STICKERS.md](../../docs/STICKERS.md).
|
||||||
|
|
||||||
**v0.8.504 (Phase 541):** Emoji spans extend — `emoji.upsert` ends with `spanKind: 'emoji.create'`; new `emoji.delete` + `emoji.stage` spans with `activeChannelId`. UI split emoji compositor panels, picker tile `lastEmojiTileName`, clipboard emoji busy flags. Bundle: `npm run test:phase541-emoji`. See [EMOJI.md](../../docs/EMOJI.md).
|
**v0.8.504 (Phase 541):** Emoji spans extend — `emoji.upsert` ends with `spanKind: 'emoji.create'`; new `emoji.delete` + `emoji.stage` spans with `activeChannelId`. UI split emoji compositor panels, picker tile `lastEmojiTileName`, clipboard emoji busy flags. Bundle: `npm run test:phase541-emoji`. See [EMOJI.md](../../docs/EMOJI.md).
|
||||||
|
|
||||||
**v0.8.503 (Phase 540):** Automation spans extend — `automation.hook`/`automation.manifest`/`automation.hook.dryRun`/`automation.dispatch` end metadata (`spanKind`, `activeChannelId`). UI automation panel compositor wrappers, hook row `lastAutomationHookRowId` + roving `tabindex`, clipboard automation busy flags. Bundle: `npm run test:phase540-automation`. See [AUTOMATION_EXPORT.md](../../docs/AUTOMATION_EXPORT.md).
|
**v0.8.503 (Phase 540):** Automation spans extend — `automation.hook`/`automation.manifest`/`automation.hook.dryRun`/`automation.dispatch` end metadata (`spanKind`, `activeChannelId`). UI automation panel compositor wrappers, hook row `lastAutomationHookRowId` + roving `tabindex`, clipboard automation busy flags. Bundle: `npm run test:phase540-automation`. See [AUTOMATION_EXPORT.md](../../docs/AUTOMATION_EXPORT.md).
|
||||||
|
|||||||
@@ -8491,7 +8491,11 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
if (!names.length) {
|
if (!names.length) {
|
||||||
span.end({ resolved: 0 })
|
span.end({
|
||||||
|
resolved: 0,
|
||||||
|
spanKind: 'sticker.send',
|
||||||
|
activeChannelId: this.activeChannelId
|
||||||
|
})
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
if (this.mode !== 'guild' || !this.guild?.guild) {
|
if (this.mode !== 'guild' || !this.guild?.guild) {
|
||||||
@@ -8505,12 +8509,17 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
if (!row) throw new Error(`unknown sticker: ${name}`)
|
if (!row) throw new Error(`unknown sticker: ${name}`)
|
||||||
out.push(row.name)
|
out.push(row.name)
|
||||||
}
|
}
|
||||||
span.end({ resolved: out.length, names: out })
|
span.end({
|
||||||
|
resolved: out.length,
|
||||||
|
names: out,
|
||||||
|
spanKind: 'sticker.send',
|
||||||
|
activeChannelId: this.activeChannelId
|
||||||
|
})
|
||||||
return out
|
return out
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('sticker.send error', {
|
this.log.error('sticker.send error', {
|
||||||
channelId: this.activeChannelId || null,
|
channelId: this.activeChannelId || null,
|
||||||
err: err?.message || String(err)
|
error: err?.message || String(err)
|
||||||
})
|
})
|
||||||
span.fail(err)
|
span.fail(err)
|
||||||
throw err
|
throw err
|
||||||
@@ -9113,34 +9122,52 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async stageGuildStickerImage ({ data, filename, mimeType }) {
|
async stageGuildStickerImage ({ data, filename, mimeType }) {
|
||||||
if (!this.attachments) throw new Error('attachments not ready')
|
const guildId = this.guild?.guild?.id || null
|
||||||
const user = this.identity.user
|
const span = this.log.time('sticker.stage', { guildId, filename: filename || null })
|
||||||
if (!user) throw new Error('register first')
|
try {
|
||||||
const guildId = this.guild?.guild?.id
|
if (!this.attachments) throw new Error('attachments not ready')
|
||||||
if (!guildId) throw new Error('no guild')
|
const user = this.identity.user
|
||||||
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data || [])
|
if (!user) throw new Error('register first')
|
||||||
let mt = String(mimeType || '').toLowerCase()
|
if (!guildId) throw new Error('no guild')
|
||||||
const { isAnimatedMime, MAX_STICKER_BYTES } = require('pearcord-stickers')
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data || [])
|
||||||
const { LOTTIE_MIME, validateLottieSticker, stickerFormatFromMime } = require('pearcord-lottie')
|
let mt = String(mimeType || '').toLowerCase()
|
||||||
const fmt = stickerFormatFromMime(mt, buf)
|
const { isAnimatedMime, MAX_STICKER_BYTES } = require('pearcord-stickers')
|
||||||
if (fmt === 'lottie') {
|
const { LOTTIE_MIME, validateLottieSticker, stickerFormatFromMime } = require('pearcord-lottie')
|
||||||
const check = validateLottieSticker(buf)
|
const fmt = stickerFormatFromMime(mt, buf)
|
||||||
if (!check.valid) throw new Error(check.error || 'invalid lottie')
|
if (fmt === 'lottie') {
|
||||||
mt = LOTTIE_MIME
|
const check = validateLottieSticker(buf)
|
||||||
} else if (!isAnimatedMime(mt)) {
|
if (!check.valid) throw new Error(check.error || 'invalid lottie')
|
||||||
throw new Error('sticker must be GIF, WebP, PNG, APNG, or Lottie JSON')
|
mt = LOTTIE_MIME
|
||||||
|
} else if (!isAnimatedMime(mt)) {
|
||||||
|
throw new Error('sticker must be GIF, WebP, PNG, APNG, or Lottie JSON')
|
||||||
|
}
|
||||||
|
if (buf.length > MAX_STICKER_BYTES) {
|
||||||
|
throw new Error('sticker file too large (max 512 KB)')
|
||||||
|
}
|
||||||
|
const att = await this.attachments.stage({
|
||||||
|
data: buf,
|
||||||
|
filename: filename || (fmt === 'lottie' ? 'sticker.json' : 'sticker.gif'),
|
||||||
|
mimeType: mt,
|
||||||
|
guildId,
|
||||||
|
channelId: '__sticker__',
|
||||||
|
authorId: user.id
|
||||||
|
})
|
||||||
|
span.end({
|
||||||
|
guildId,
|
||||||
|
attachmentId: att?.id || null,
|
||||||
|
spanKind: 'sticker.stage',
|
||||||
|
activeChannelId: this.activeChannelId
|
||||||
|
})
|
||||||
|
return att
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('sticker.stage error', {
|
||||||
|
guildId,
|
||||||
|
filename: filename || null,
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
if (buf.length > MAX_STICKER_BYTES) {
|
|
||||||
throw new Error('sticker file too large (max 512 KB)')
|
|
||||||
}
|
|
||||||
return this.attachments.stage({
|
|
||||||
data: buf,
|
|
||||||
filename: filename || (fmt === 'lottie' ? 'sticker.json' : 'sticker.gif'),
|
|
||||||
mimeType: mt,
|
|
||||||
guildId,
|
|
||||||
channelId: '__sticker__',
|
|
||||||
authorId: user.id
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertGuildSticker ({ name, description, attachmentId, image, packName }) {
|
async upsertGuildSticker ({ name, description, attachmentId, image, packName }) {
|
||||||
@@ -9186,7 +9213,12 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
meta: { name: row.name }
|
meta: { name: row.name }
|
||||||
})
|
})
|
||||||
this.emit('guild-sticker', row)
|
this.emit('guild-sticker', row)
|
||||||
span.end({ name: row.name, guildId })
|
span.end({
|
||||||
|
name: row.name,
|
||||||
|
guildId,
|
||||||
|
spanKind: 'sticker.create',
|
||||||
|
activeChannelId: this.activeChannelId
|
||||||
|
})
|
||||||
return row
|
return row
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('sticker.create error', {
|
this.log.error('sticker.create error', {
|
||||||
@@ -9200,28 +9232,46 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeGuildSticker (name) {
|
async removeGuildSticker (name) {
|
||||||
if (!this.guild?.guild) throw new Error('no guild')
|
const guildId = this.guild?.guild?.id || null
|
||||||
const user = this.identity.user
|
const span = this.log.time('sticker.delete', { name: name || null, guildId })
|
||||||
if (!user) throw new Error('register first')
|
try {
|
||||||
const roles = await this._memberRoles()
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
|
const user = this.identity.user
|
||||||
throw new Error('no permission to manage stickers')
|
if (!user) throw new Error('register first')
|
||||||
|
const roles = await this._memberRoles()
|
||||||
|
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
|
||||||
|
throw new Error('no permission to manage stickers')
|
||||||
|
}
|
||||||
|
if (!this.stickerRegistry) await this._initStickerRegistry(this.guild.guild.id)
|
||||||
|
const removed = await this.stickerRegistry.remove(name)
|
||||||
|
const payload = {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
name: removed.name,
|
||||||
|
deletedAt: Date.now()
|
||||||
|
}
|
||||||
|
this.guild.gossipStickerDelete(payload)
|
||||||
|
await this._audit('sticker.delete', {
|
||||||
|
guildId: payload.guildId,
|
||||||
|
targetId: removed.name,
|
||||||
|
meta: { name: removed.name }
|
||||||
|
})
|
||||||
|
this.emit('guild-sticker-delete', payload)
|
||||||
|
span.end({
|
||||||
|
name: removed.name,
|
||||||
|
guildId,
|
||||||
|
spanKind: 'sticker.delete',
|
||||||
|
activeChannelId: this.activeChannelId
|
||||||
|
})
|
||||||
|
return removed
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('sticker.delete error', {
|
||||||
|
name: name || null,
|
||||||
|
guildId,
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
if (!this.stickerRegistry) await this._initStickerRegistry(this.guild.guild.id)
|
|
||||||
const removed = await this.stickerRegistry.remove(name)
|
|
||||||
const payload = {
|
|
||||||
guildId: this.guild.guild.id,
|
|
||||||
name: removed.name,
|
|
||||||
deletedAt: Date.now()
|
|
||||||
}
|
|
||||||
this.guild.gossipStickerDelete(payload)
|
|
||||||
await this._audit('sticker.delete', {
|
|
||||||
guildId: payload.guildId,
|
|
||||||
targetId: removed.name,
|
|
||||||
meta: { name: removed.name }
|
|
||||||
})
|
|
||||||
this.emit('guild-sticker-delete', payload)
|
|
||||||
return removed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertStickerPack ({
|
async upsertStickerPack ({
|
||||||
|
|||||||
Reference in New Issue
Block a user