feat(platform): v0.7.5 announcement channels — mod-only post and typing gates

Wire CHANNEL_TYPES.ANNOUNCEMENT through channel settings, search, unread,
and view.canPostInActiveChannel. _assertAnnouncementPost() requires
MANAGE_MESSAGES for sendMessage/createForumPost paths; block TYPING_START
for non-moderators. Integrates pearcord-threads isAnnouncementChannel().

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-21 21:42:59 -04:00
co-authored by Cursor
parent 0716dfc692
commit d39a1e6962
+33 -3
View File
@@ -51,6 +51,7 @@ const {
groupThreadsByParent,
isThreadChannel,
isForumChannel,
isAnnouncementChannel,
sortForumPosts
} = require('pearcord-threads')
const {
@@ -595,7 +596,9 @@ class PearcordPlatform extends EventEmitter {
const channels = await this.db.find(COLLECTIONS.CHANNELS, { guildId })
const channelById = new Map(channels.map((c) => [c.id, c]))
const searchable = new Set(
channels.filter((c) => c.type === 'text' || c.type === 'thread').map((c) => c.id)
channels
.filter((c) => c.type === 'text' || c.type === 'thread' || c.type === 'announcement')
.map((c) => c.id)
)
let rows = []
if (this.db.getEngine?.() === 'hyperdb') {
@@ -1431,7 +1434,9 @@ class PearcordPlatform extends EventEmitter {
if (this.mode === 'guild' && this.guild) {
const channels = await this.guild.listChannels()
for (const ch of channels) {
if (ch.type !== 'text' && ch.type !== 'thread') continue
if (ch.type !== 'text' && ch.type !== 'thread' && ch.type !== 'announcement') {
continue
}
map[ch.id] = await this._unreadForChannel(ch.id, ch.guildId)
}
}
@@ -1980,11 +1985,20 @@ class PearcordPlatform extends EventEmitter {
return byParent
}
async _assertAnnouncementPost (ch) {
if (!ch || !isAnnouncementChannel(ch)) return
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_MESSAGES)) {
throw new Error('only moderators can post in announcement channels')
}
}
async _assertChannelWritable (ch) {
if (!ch) return
if (isThreadChannel(ch) && ch.archived) {
throw new Error('thread is archived')
}
await this._assertAnnouncementPost(ch)
}
async signalTyping () {
@@ -1994,6 +2008,10 @@ class PearcordPlatform extends EventEmitter {
const channels = await this.guild.listChannels()
const ch = channels.find((c) => c.id === this.activeChannelId)
if (isForumChannel(ch)) return null
if (isAnnouncementChannel(ch)) {
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_MESSAGES)) return null
}
if (isThreadChannel(ch) && ch.archived) return null
}
const payload = {
@@ -2370,7 +2388,14 @@ class PearcordPlatform extends EventEmitter {
}
if (guild) {
for (const ch of channels) {
if (ch.type !== 'text' && ch.type !== 'thread' && ch.type !== 'forum') continue
if (
ch.type !== 'text' &&
ch.type !== 'thread' &&
ch.type !== 'forum' &&
ch.type !== 'announcement'
) {
continue
}
const s = await this.getChannelSettings(ch.id, guild.id)
channelSettings[ch.id] = s
}
@@ -2524,6 +2549,10 @@ class PearcordPlatform extends EventEmitter {
const peerId = this._dmPeerId()
if (peerId) dmPeerReadAt = dmReadByUser[peerId] || 0
}
let canPostInActiveChannel = true
if (activeChannel && isAnnouncementChannel(activeChannel) && this.mode === 'guild') {
canPostInActiveChannel = !!myPermissions?.manageMessages
}
return {
onboarded: this.onboarded,
user,
@@ -2551,6 +2580,7 @@ class PearcordPlatform extends EventEmitter {
stats,
dbEngine: this.db.getEngine?.() || 'json',
myPermissions,
canPostInActiveChannel,
pins,
mentionNames: [...mentionNames],
channelSettings,