diff --git a/README.md b/README.md index ee50197..caa2656 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,8 @@ Primary consumer: `apps/pearcord/index.js` sidecar reading pear-pipe JSON. **v0.8.400:** `createChannelWebhook`/`deleteChannelWebhook`/`executeChannelWebhook` log `webhook.*` spans and errors; `registerSlashCommands`/`deleteSlashCommand`/`executeSlashCommand` log `slash.*` spans and errors. +**v0.8.401:** `createGuildCustomRole`/`updateGuildCustomRole`/`deleteGuildCustomRole`/`reorderGuildCustomRoles`/`setMemberCustomRoles` log `role.*` spans and errors; `upsertChannelPermissionOverwrite`/`deleteChannelPermissionOverwrite` log `permission.overwrite*` spans and errors. + **v0.8.252:** Guild search mesh skipped when `guildOpenNoViewableChannels`; emoji batch prefetch skips negative miss TTL; sticker gossip prefetch gated on `attachments`; `session.startup` logs INFO when initial guild mesh flush was bounded. Smoke: `test:guild-search-no-viewable-skip`, `test:sticker-prefetch-attachments-ready`. **v0.8.248:** Guild open filters unread/last picks with `_canViewChannel`; sets `guildOpenNoViewableChannels` when none accessible; defers `_ensureGuildModules` via `setTimeout(0)` after channel pick; emoji mesh prefetch clears miss cache on success. Smokes: `test:guild-viewable-channel-pick`, `test:guild-open-no-viewable`, `test:guild-plain-beats-dual-combo`. diff --git a/index.js b/index.js index 91e4b36..871bba5 100644 --- a/index.js +++ b/index.js @@ -4896,87 +4896,142 @@ class PearcordPlatform extends EventEmitter { } async createGuildCustomRole ({ name, color, permissions } = {}) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { - throw new Error('no permission to manage roles') + const span = this.log.time('role.create', { name: String(name || '').slice(0, 32) }) + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { + throw new Error('no permission to manage roles') + } + await this._initGuildRoles(this.guild.guild.id) + const row = await this.guildRoles.createRole(this.guild.guild.id, { + name, + color, + permissions + }) + this.guild.gossipRoleUpsert(row) + await this._audit('role.create', { + guildId: this.guild.guild.id, + targetId: row.id + }) + this.emit('guild-role', row) + span.end({ roleId: row.id }) + return row + } catch (err) { + this.log.error('role.create error', { + name: String(name || '').slice(0, 32), + error: err?.message || String(err) + }) + span.fail(err) + throw err } - await this._initGuildRoles(this.guild.guild.id) - const row = await this.guildRoles.createRole(this.guild.guild.id, { - name, - color, - permissions - }) - this.guild.gossipRoleUpsert(row) - await this._audit('role.create', { - guildId: this.guild.guild.id, - targetId: row.id - }) - this.emit('guild-role', row) - return row } async updateGuildCustomRole (roleId, patch = {}) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { - throw new Error('no permission to manage roles') + const span = this.log.time('role.update', { roleId }) + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { + throw new Error('no permission to manage roles') + } + await this._initGuildRoles(this.guild.guild.id) + const row = await this.guildRoles.updateRole(this.guild.guild.id, roleId, patch) + this.guild.gossipRoleUpsert(row) + this.emit('guild-role', row) + span.end({ roleId: row?.id || roleId }) + return row + } catch (err) { + this.log.error('role.update error', { + roleId, + error: err?.message || String(err) + }) + span.fail(err) + throw err } - await this._initGuildRoles(this.guild.guild.id) - const row = await this.guildRoles.updateRole(this.guild.guild.id, roleId, patch) - this.guild.gossipRoleUpsert(row) - this.emit('guild-role', row) - return row } async deleteGuildCustomRole (roleId) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { - throw new Error('no permission to manage roles') + const span = this.log.time('role.delete', { roleId }) + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { + throw new Error('no permission to manage roles') + } + await this._initGuildRoles(this.guild.guild.id) + const prev = await this.guildRoles.deleteRole(this.guild.guild.id, roleId) + if (prev) { + this.guild.gossipRoleDelete({ guildId: this.guild.guild.id, id: roleId }) + this.emit('guild-role-delete', { guildId: this.guild.guild.id, id: roleId }) + } + span.end({ roleId }) + return prev + } catch (err) { + this.log.error('role.delete error', { + roleId, + error: err?.message || String(err) + }) + span.fail(err) + throw err } - await this._initGuildRoles(this.guild.guild.id) - const prev = await this.guildRoles.deleteRole(this.guild.guild.id, roleId) - if (prev) { - this.guild.gossipRoleDelete({ guildId: this.guild.guild.id, id: roleId }) - this.emit('guild-role-delete', { guildId: this.guild.guild.id, id: roleId }) - } - return prev } async reorderGuildCustomRoles (roleIds = []) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { - throw new Error('no permission to manage roles') + const span = this.log.time('role.reorder', { count: (roleIds || []).length }) + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { + throw new Error('no permission to manage roles') + } + await this._initGuildRoles(this.guild.guild.id) + const rows = await this.guildRoles.reorderRoles(this.guild.guild.id, roleIds) + for (const row of rows) { + if (row.updatedAt) this.guild.gossipRoleUpsert(row) + } + this.emit('guild-role-reorder', { guildId: this.guild.guild.id, roleIds }) + span.end({ count: rows.length }) + return rows + } catch (err) { + this.log.error('role.reorder error', { + count: (roleIds || []).length, + error: err?.message || String(err) + }) + span.fail(err) + throw err } - await this._initGuildRoles(this.guild.guild.id) - const rows = await this.guildRoles.reorderRoles(this.guild.guild.id, roleIds) - for (const row of rows) { - if (row.updatedAt) this.guild.gossipRoleUpsert(row) - } - this.emit('guild-role-reorder', { guildId: this.guild.guild.id, roleIds }) - return rows } async setMemberCustomRoles (userId, roleIds = []) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { - throw new Error('no permission to manage roles') + const span = this.log.time('role.member', { userId }) + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) { + throw new Error('no permission to manage roles') + } + if (userId === this.guild.guild.ownerId) { + throw new Error('cannot change owner role assignments') + } + await this._initGuildRoles(this.guild.guild.id) + const link = await this.guildRoles.setMemberRoleIds( + this.guild.guild.id, + userId, + roleIds + ) + this.guild.gossipMemberCustomRoles(link) + this.emit('guild-member-roles', link) + await this._fanoutAppEvent(APP_EVENTS.MEMBER_ROLE_UPDATE, { memberRoles: link }) + this.emit('member', await this.db.get(COLLECTIONS.MEMBERS, { + guildId: this.guild.guild.id, + userId + })) + span.end({ userId, roleCount: (roleIds || []).length }) + return link + } catch (err) { + this.log.error('role.member error', { + userId, + error: err?.message || String(err) + }) + span.fail(err) + throw err } - if (userId === this.guild.guild.ownerId) { - throw new Error('cannot change owner role assignments') - } - await this._initGuildRoles(this.guild.guild.id) - const link = await this.guildRoles.setMemberRoleIds( - this.guild.guild.id, - userId, - roleIds - ) - this.guild.gossipMemberCustomRoles(link) - this.emit('guild-member-roles', link) - await this._fanoutAppEvent(APP_EVENTS.MEMBER_ROLE_UPDATE, { memberRoles: link }) - this.emit('member', await this.db.get(COLLECTIONS.MEMBERS, { - guildId: this.guild.guild.id, - userId - })) - return link } async getChannelSettings (channelId, guildId) { @@ -5132,69 +5187,96 @@ class PearcordPlatform extends EventEmitter { } async upsertChannelPermissionOverwrite (channelId, payload = {}) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) { - throw new Error('no permission to edit channel permissions') - } - const chId = channelId || this.activeChannelId - if (!chId) throw new Error('channelId required') - const ch = await this.db.get(COLLECTIONS.CHANNELS, { - guildId: this.guild.guild.id, - id: chId + const span = this.log.time('permission.overwrite', { + channelId: channelId || this.activeChannelId, + targetType: payload.targetType }) - if (!ch) throw new Error('channel not found') - await this._initChannelPermissions(this.guild.guild.id) - const allow = - payload.allow !== undefined - ? Number(payload.allow) || 0 - : viewToMask(payload.allowView || {}) - const deny = - payload.deny !== undefined - ? Number(payload.deny) || 0 - : viewToMask(payload.denyView || {}) - const row = await this.channelPermissions.upsertOverwrite( - this.guild.guild.id, - chId, - { - targetType: payload.targetType, - targetId: payload.targetId, - allow, - deny + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) { + throw new Error('no permission to edit channel permissions') } - ) - this.guild.gossipChannelOverwriteUpsert(row) - await this._audit('channel.overwrite', { guildId: this.guild.guild.id, targetId: chId }) - this.emit('channel-overwrite', row) - await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_UPDATE, { overwrite: row }) - return row + const chId = channelId || this.activeChannelId + if (!chId) throw new Error('channelId required') + const ch = await this.db.get(COLLECTIONS.CHANNELS, { + guildId: this.guild.guild.id, + id: chId + }) + if (!ch) throw new Error('channel not found') + await this._initChannelPermissions(this.guild.guild.id) + const allow = + payload.allow !== undefined + ? Number(payload.allow) || 0 + : viewToMask(payload.allowView || {}) + const deny = + payload.deny !== undefined + ? Number(payload.deny) || 0 + : viewToMask(payload.denyView || {}) + const row = await this.channelPermissions.upsertOverwrite( + this.guild.guild.id, + chId, + { + targetType: payload.targetType, + targetId: payload.targetId, + allow, + deny + } + ) + this.guild.gossipChannelOverwriteUpsert(row) + await this._audit('channel.overwrite', { guildId: this.guild.guild.id, targetId: chId }) + this.emit('channel-overwrite', row) + await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_UPDATE, { overwrite: row }) + span.end({ channelId: chId, targetId: payload.targetId }) + return row + } catch (err) { + this.log.error('permission.overwrite error', { + channelId: channelId || this.activeChannelId, + error: err?.message || String(err) + }) + span.fail(err) + throw err + } } async deleteChannelPermissionOverwrite (channelId, targetType, targetId) { - if (!this.guild?.guild) throw new Error('no guild') - if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) { - throw new Error('no permission to edit channel permissions') - } - const chId = channelId || this.activeChannelId - await this._initChannelPermissions(this.guild.guild.id) - const prev = await this.channelPermissions.deleteOverwrite( - this.guild.guild.id, - chId, - targetType, - targetId - ) - if (prev && this.guild) { - this.guild.gossipChannelOverwriteDelete({ - guildId: this.guild.guild.id, - channelId: chId, - targetType: prev.targetType, - targetId: prev.targetId + const span = this.log.time('permission.overwrite.delete', { channelId, targetType, targetId }) + try { + if (!this.guild?.guild) throw new Error('no guild') + if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) { + throw new Error('no permission to edit channel permissions') + } + const chId = channelId || this.activeChannelId + await this._initChannelPermissions(this.guild.guild.id) + const prev = await this.channelPermissions.deleteOverwrite( + this.guild.guild.id, + chId, + targetType, + targetId + ) + if (prev && this.guild) { + this.guild.gossipChannelOverwriteDelete({ + guildId: this.guild.guild.id, + channelId: chId, + targetType: prev.targetType, + targetId: prev.targetId + }) + } + this.emit('channel-overwrite-delete', { channelId: chId, targetType, targetId }) + await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_DELETE, { + delete: { channelId: chId, targetType, targetId } }) + span.end({ channelId: chId, targetId }) + return prev + } catch (err) { + this.log.error('permission.overwrite.delete error', { + channelId, + targetType, + targetId, + error: err?.message || String(err) + }) + span.fail(err) + throw err } - this.emit('channel-overwrite-delete', { channelId: chId, targetType, targetId }) - await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_DELETE, { - delete: { channelId: chId, targetType, targetId } - }) - return prev } async _assertVoiceCapacity (channelId) {