feat(platform): per-channel permission overwrites (Phase 82)
Integrate pearcord-channel-permissions: list/upsert/delete overwrites, channel-effective _hasPerm/_myPermissions, GUILD_SYNC bundle, and mesh ingest handlers. Requires MANAGE_CHANNELS on the active channel to edit.
This commit is contained in:
@@ -131,6 +131,12 @@ const {
|
||||
now
|
||||
} = require('pearcord-shared')
|
||||
const { PearcordGuildRoles, PERMISSION_META } = require('pearcord-guild-roles')
|
||||
const {
|
||||
PearcordChannelPermissions,
|
||||
memberHasChannelPermission,
|
||||
effectivePermissionView
|
||||
} = require('pearcord-channel-permissions')
|
||||
const { viewToMask } = require('pearcord-guild-roles/permissions')
|
||||
const { maskToPermissionView, memberPermissionMask } = require('pearcord-guild-roles/permissions')
|
||||
|
||||
function messageLayoutKind (m) {
|
||||
@@ -197,6 +203,7 @@ class PearcordPlatform extends EventEmitter {
|
||||
this.stickerRegistry = null
|
||||
this.soundboardRegistry = null
|
||||
this.guildRoles = null
|
||||
this.channelPermissions = null
|
||||
this._recentSoundPlays = []
|
||||
this.botEvents = null
|
||||
this.discovery = null
|
||||
@@ -1074,6 +1081,11 @@ class PearcordPlatform extends EventEmitter {
|
||||
await this._initGuildRoles(guild.id)
|
||||
if (this.guildRoles) roleBundle = await this.guildRoles.exportSyncBundle(guild.id)
|
||||
}
|
||||
let permBundle = { channelOverwrites: [] }
|
||||
await this._initChannelPermissions(guild.id)
|
||||
if (this.channelPermissions) {
|
||||
permBundle = await this.channelPermissions.exportSyncBundle(guild.id)
|
||||
}
|
||||
if (guild.iconHash && this.attachments) {
|
||||
const iconRow = await this.attachments.get(guild.iconHash).catch(() => null)
|
||||
if (iconRow && !attachments.some((a) => a.id === iconRow.id)) {
|
||||
@@ -1093,7 +1105,8 @@ class PearcordPlatform extends EventEmitter {
|
||||
attachments,
|
||||
members,
|
||||
guildRoles: roleBundle.guildRoles,
|
||||
memberRoleLinks: roleBundle.memberRoleLinks
|
||||
memberRoleLinks: roleBundle.memberRoleLinks,
|
||||
channelOverwrites: permBundle.channelOverwrites
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1128,6 +1141,10 @@ class PearcordPlatform extends EventEmitter {
|
||||
guildRoles: payload.guildRoles,
|
||||
memberRoleLinks: payload.memberRoleLinks
|
||||
})
|
||||
await this._initChannelPermissions(payload.guildId)
|
||||
await this.channelPermissions.ingestSyncBundle(payload.guildId, {
|
||||
channelOverwrites: payload.channelOverwrites
|
||||
})
|
||||
}
|
||||
for (const mem of payload.members || []) {
|
||||
if (!mem?.guildId || !mem?.userId) continue
|
||||
@@ -1303,6 +1320,14 @@ class PearcordPlatform extends EventEmitter {
|
||||
this.guildRoles?.ingestMemberRolesGossip(payload).catch(() => {})
|
||||
this.emit('guild-member-roles', payload)
|
||||
})
|
||||
guildInstance.on('channel-overwrite', (row) => {
|
||||
this.channelPermissions?.ingestOverwriteGossip(row).catch(() => {})
|
||||
this.emit('channel-overwrite', row)
|
||||
})
|
||||
guildInstance.on('channel-overwrite-delete', (payload) => {
|
||||
this.channelPermissions?.ingestOverwriteDelete(payload).catch(() => {})
|
||||
this.emit('channel-overwrite-delete', payload)
|
||||
})
|
||||
guildInstance.on('bot-event', (payload) => {
|
||||
this.botEvents?.ingest(payload)
|
||||
this.emit('bot-event', payload)
|
||||
@@ -1915,6 +1940,76 @@ class PearcordPlatform extends EventEmitter {
|
||||
return settings
|
||||
}
|
||||
|
||||
async listChannelPermissionOverwrites (channelId) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
const chId = channelId || this.activeChannelId
|
||||
if (!chId) throw new Error('channelId required')
|
||||
await this._initChannelPermissions(this.guild.guild.id)
|
||||
return this.channelPermissions.listForChannel(this.guild.guild.id, chId)
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
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)
|
||||
return row
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
this.emit('channel-overwrite-delete', { channelId: chId, targetType, targetId })
|
||||
return prev
|
||||
}
|
||||
|
||||
async _assertSlowmodeFor (channelId) {
|
||||
const chId = channelId || this.activeChannelId
|
||||
const user = this.identity.user
|
||||
@@ -2002,6 +2097,28 @@ class PearcordPlatform extends EventEmitter {
|
||||
return this.guildRoles
|
||||
}
|
||||
|
||||
async _initChannelPermissions (guildId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return null
|
||||
if (!this.channelPermissions || this.channelPermissions.guildId !== gid) {
|
||||
this.channelPermissions = new PearcordChannelPermissions({
|
||||
storagePath: this.storagePath,
|
||||
guildId: gid
|
||||
})
|
||||
await this.channelPermissions.ready()
|
||||
} else {
|
||||
this.channelPermissions.setGuild(gid)
|
||||
}
|
||||
return this.channelPermissions
|
||||
}
|
||||
|
||||
async _channelOverwrites (channelId) {
|
||||
if (!this.guild?.guild || !channelId) return []
|
||||
await this._initChannelPermissions(this.guild.guild.id)
|
||||
if (!this.channelPermissions) return []
|
||||
return this.channelPermissions.listForChannel(this.guild.guild.id, channelId)
|
||||
}
|
||||
|
||||
async _initBotEvents (guildId) {
|
||||
this.botEvents = new BotEventFanout({
|
||||
guildId,
|
||||
@@ -2614,16 +2731,21 @@ class PearcordPlatform extends EventEmitter {
|
||||
return { ...member, customRoleIds }
|
||||
}
|
||||
|
||||
async _hasPerm (perm) {
|
||||
async _hasPerm (perm, channelId) {
|
||||
const member = await this._resolveMemberRecord()
|
||||
if (!member) return false
|
||||
const customRoles = this.guildRoles
|
||||
? await this.guildRoles.listRoles(this.guild.guild.id)
|
||||
: []
|
||||
const chId = channelId || this.activeChannelId
|
||||
if (chId && this.mode === 'guild') {
|
||||
const overwrites = await this._channelOverwrites(chId)
|
||||
return memberHasChannelPermission(member, customRoles, overwrites, perm)
|
||||
}
|
||||
return memberHasPermission(member, customRoles, perm)
|
||||
}
|
||||
|
||||
async _myPermissions () {
|
||||
async _myPermissions (channelId) {
|
||||
const member = await this._resolveMemberRecord()
|
||||
if (!member) {
|
||||
return {
|
||||
@@ -2637,6 +2759,11 @@ class PearcordPlatform extends EventEmitter {
|
||||
const customRoles = this.guildRoles
|
||||
? await this.guildRoles.listRoles(this.guild.guild.id)
|
||||
: []
|
||||
const chId = channelId || this.activeChannelId
|
||||
if (chId && this.mode === 'guild') {
|
||||
const overwrites = await this._channelOverwrites(chId)
|
||||
return effectivePermissionView(member, customRoles, overwrites)
|
||||
}
|
||||
return maskToPermissionView(memberPermissionMask(member, customRoles))
|
||||
}
|
||||
|
||||
@@ -4610,7 +4737,12 @@ class PearcordPlatform extends EventEmitter {
|
||||
const stats = this.mode === 'dm'
|
||||
? this.dm?.getStats() || null
|
||||
: this.guild?.getStats() || null
|
||||
const myPermissions = this.mode === 'guild' ? await this._myPermissions() : null
|
||||
const myPermissions =
|
||||
this.mode === 'guild' ? await this._myPermissions(this.activeChannelId) : null
|
||||
let activeChannelOverwrites = []
|
||||
if (this.mode === 'guild' && guild && this.activeChannelId) {
|
||||
activeChannelOverwrites = await this._channelOverwrites(this.activeChannelId)
|
||||
}
|
||||
let pins = []
|
||||
const mentionNames = new Set()
|
||||
if (user) {
|
||||
@@ -4996,6 +5128,7 @@ class PearcordPlatform extends EventEmitter {
|
||||
dmReadByUser,
|
||||
activeChannelId: this.activeChannelId,
|
||||
activeChannelSummary,
|
||||
activeChannelOverwrites,
|
||||
messages,
|
||||
messageById,
|
||||
reactions,
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"pearcord-commands": "file:../pearcord-commands",
|
||||
"pearcord-emoji": "file:../pearcord-emoji",
|
||||
"pearcord-guild-roles": "file:../pearcord-guild-roles",
|
||||
"pearcord-channel-permissions": "file:../pearcord-channel-permissions",
|
||||
"pearcord-bot-events": "file:../pearcord-bot-events",
|
||||
"pearcord-bot-sdk": "file:../pearcord-bot-sdk",
|
||||
"pearcord-bot-limits": "file:../pearcord-bot-limits",
|
||||
|
||||
Reference in New Issue
Block a user