feat(guild): forum channels with createForumThread and deferred thread-meta
Allow thread parents to be text or forum channels. Add createForumThread, setThreadRootMessage, and infer forumPost from parent type. Defer HyperDB thread-meta insert until rootMessageId exists (fixes null encode on posts). Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -79,8 +79,11 @@ class PearcordGuild extends EventEmitter {
|
||||
}
|
||||
if (type === CHANNEL_TYPES.THREAD) {
|
||||
const parent = await this.db.get(COLLECTIONS.CHANNELS, { guildId, id: parentId })
|
||||
if (!parent || parent.type !== CHANNEL_TYPES.TEXT) {
|
||||
throw new Error('thread parent must be a text channel')
|
||||
if (
|
||||
!parent ||
|
||||
(parent.type !== CHANNEL_TYPES.TEXT && parent.type !== CHANNEL_TYPES.FORUM)
|
||||
) {
|
||||
throw new Error('thread parent must be a text or forum channel')
|
||||
}
|
||||
}
|
||||
const channel = {
|
||||
@@ -103,6 +106,9 @@ class PearcordGuild extends EventEmitter {
|
||||
})
|
||||
return this._attachThreadMeta(channel)
|
||||
}
|
||||
if (type === CHANNEL_TYPES.THREAD) {
|
||||
return this._attachThreadMeta(channel)
|
||||
}
|
||||
return channel
|
||||
}
|
||||
|
||||
@@ -111,6 +117,7 @@ class PearcordGuild extends EventEmitter {
|
||||
}
|
||||
|
||||
async _upsertThreadMeta (meta) {
|
||||
if (!meta.rootMessageId) throw new Error('rootMessageId required for thread meta')
|
||||
const row = {
|
||||
guildId: meta.guildId,
|
||||
channelId: meta.channelId,
|
||||
@@ -125,10 +132,26 @@ class PearcordGuild extends EventEmitter {
|
||||
async _attachThreadMeta (channel) {
|
||||
if (!channel || channel.type !== CHANNEL_TYPES.THREAD) return channel
|
||||
const meta = await this._getThreadMeta(channel.guildId, channel.id)
|
||||
if (!meta) return channel
|
||||
let forumPost = false
|
||||
if (channel.parentId) {
|
||||
const parent = await this.db.get(COLLECTIONS.CHANNELS, {
|
||||
guildId: channel.guildId,
|
||||
id: channel.parentId
|
||||
})
|
||||
forumPost = parent?.type === CHANNEL_TYPES.FORUM
|
||||
}
|
||||
if (!meta) {
|
||||
return {
|
||||
...channel,
|
||||
forumPost,
|
||||
archived: false,
|
||||
createdAt: channel.createdAt || null
|
||||
}
|
||||
}
|
||||
return {
|
||||
...channel,
|
||||
rootMessageId: meta.rootMessageId,
|
||||
forumPost,
|
||||
archived: meta.archived === true,
|
||||
createdAt: meta.createdAt
|
||||
}
|
||||
@@ -498,6 +521,60 @@ class PearcordGuild extends EventEmitter {
|
||||
return this.createChannel({ name, type: CHANNEL_TYPES.CATEGORY })
|
||||
}
|
||||
|
||||
async createForumThread ({ parentChannelId, name }) {
|
||||
if (!this.guild) throw new Error('no active guild')
|
||||
if (!parentChannelId || !name) {
|
||||
throw new Error('parentChannelId and name required')
|
||||
}
|
||||
const parent = await this.db.get(COLLECTIONS.CHANNELS, {
|
||||
guildId: this.guild.id,
|
||||
id: parentChannelId
|
||||
})
|
||||
if (!parent || parent.type !== CHANNEL_TYPES.FORUM) {
|
||||
throw new Error('forum posts must be under a forum channel')
|
||||
}
|
||||
const existing = await this.listChannels()
|
||||
const threads = existing.filter(
|
||||
(c) => c.type === CHANNEL_TYPES.THREAD && c.parentId === parentChannelId
|
||||
)
|
||||
const channel = await this._createChannel(
|
||||
this.guild.id,
|
||||
String(name).slice(0, 80),
|
||||
CHANNEL_TYPES.THREAD,
|
||||
threads.length,
|
||||
parentChannelId,
|
||||
{ forumPost: true, archived: false }
|
||||
)
|
||||
this.emit('channel', channel)
|
||||
return channel
|
||||
}
|
||||
|
||||
async setThreadRootMessage (threadId, rootMessageId) {
|
||||
if (!this.guild) throw new Error('no active guild')
|
||||
if (!threadId || !rootMessageId) throw new Error('threadId and rootMessageId required')
|
||||
const ch = await this.db.get(COLLECTIONS.CHANNELS, {
|
||||
guildId: this.guild.id,
|
||||
id: threadId
|
||||
})
|
||||
if (!ch || ch.type !== CHANNEL_TYPES.THREAD) throw new Error('not a thread channel')
|
||||
const root = await this.db.get(COLLECTIONS.MESSAGES, {
|
||||
channelId: threadId,
|
||||
id: rootMessageId
|
||||
})
|
||||
if (!root) throw new Error('root message not found in thread')
|
||||
const prev = await this._getThreadMeta(this.guild.id, threadId)
|
||||
await this._upsertThreadMeta({
|
||||
guildId: this.guild.id,
|
||||
channelId: threadId,
|
||||
rootMessageId,
|
||||
archived: prev?.archived === true,
|
||||
createdAt: prev?.createdAt || now()
|
||||
})
|
||||
const merged = await this._attachThreadMeta(ch)
|
||||
this.emit('channel', merged)
|
||||
return merged
|
||||
}
|
||||
|
||||
async createThread ({ parentChannelId, rootMessageId, name }) {
|
||||
if (!this.guild) throw new Error('no active guild')
|
||||
if (!parentChannelId || !rootMessageId) {
|
||||
@@ -582,13 +659,16 @@ class PearcordGuild extends EventEmitter {
|
||||
await this.db.insert(COLLECTIONS.CHANNELS, base)
|
||||
if (channel.type === CHANNEL_TYPES.THREAD) {
|
||||
const prev = await this._getThreadMeta(channel.guildId, channel.id)
|
||||
await this._upsertThreadMeta({
|
||||
guildId: channel.guildId,
|
||||
channelId: channel.id,
|
||||
rootMessageId: channel.rootMessageId || prev?.rootMessageId,
|
||||
archived: channel.archived === true,
|
||||
createdAt: channel.createdAt || prev?.createdAt || now()
|
||||
})
|
||||
const rootMessageId = channel.rootMessageId ?? prev?.rootMessageId
|
||||
if (rootMessageId) {
|
||||
await this._upsertThreadMeta({
|
||||
guildId: channel.guildId,
|
||||
channelId: channel.id,
|
||||
rootMessageId,
|
||||
archived: channel.archived === true,
|
||||
createdAt: channel.createdAt || prev?.createdAt || now()
|
||||
})
|
||||
}
|
||||
}
|
||||
const merged = await this._attachThreadMeta(base)
|
||||
this.emit('channel-update', merged)
|
||||
|
||||
Reference in New Issue
Block a user