Create and Manage Threads
Create threads and forum posts, archive, lock and list threads, search them, manage their members and read their state
A thread is a channel inside a text, announcement, forum or media channel that holds its own messages. Fluxer has three thread types. Public and private threads start in text channels, and announcement threads start in announcement channels. Every post in a forum or media channel is a public thread
The client namespace creates, changes, lists and searches threads and manages their members. A thread is still a channel, so channels reads one, channels deletes one and messages with the thread's ID posts in it. Bots can use threads without a capability setting
The examples use the default API. The Effect entry point exports the same names, and each client method returns an Effect there
Recognize a Thread
Channel reads, channel events, message search results and audit-log pages return threads with their own channel shapes. The is function tells a thread from any other community channel, and type selects one thread type
import { ChannelType, isThreadChannel } from "@neontechspace/fluxerly"
export function describeChannel(channel) {
if (!isThreadChannel(channel))
return `channel ${channel.name ?? channel.id}`
const kind = channel.type === ChannelType.PrivateThread ? "private thread" : "thread"
return `${kind} ${channel.name} in channel ${channel.parentId}`
}
import { ChannelType, isThreadChannel, type GuildChannel } from "@neontechspace/fluxerly"
export function describeChannel(channel: GuildChannel) {
if (!isThreadChannel(channel)) return `channel ${channel.name ?? channel.id}`
const kind = channel.type === ChannelType.PrivateThread ? "private thread" : "thread"
return `${kind} ${channel.name} in channel ${channel.parentId}`
}
Read Thread State
A thread shape flattens Fluxer's thread metadata into archived, locked, auto, archive and created. Its parent names the channel that holds the thread, never a category. A private thread also has invitable, and a forum or media post has applied
The membership field describes the bot's own membership when Fluxer reports it. Some reads leave it out, so its absence does not prove that the bot is not a member. The member field is approximate and stops at 50
import { isThreadChannel, ThreadAutoArchiveMinutes } from "@neontechspace/fluxerly"
export async function threadState(client, channelId) {
const channel = await client.channels.fetch(channelId)
if (channel.isErr())
return channel
const thread = channel.value
if (!isThreadChannel(thread))
return undefined
return {
archived: thread.archived,
archivesAfterOneDay: thread.autoArchiveMinutes === ThreadAutoArchiveMinutes.OneDay,
joinedAt: thread.membership?.joinedAt,
}
}
import { isThreadChannel, ThreadAutoArchiveMinutes, type Client } from "@neontechspace/fluxerly"
export async function threadState(client: Client, channelId: string) {
const channel = await client.channels.fetch(channelId)
if (channel.isErr()) return channel
const thread = channel.value
if (!isThreadChannel(thread)) return undefined
return {
archived: thread.archived,
archivesAfterOneDay: thread.autoArchiveMinutes === ThreadAutoArchiveMinutes.OneDay,
joinedAt: thread.membership?.joinedAt,
}
}
Create a Thread
The threads method starts a thread in a text or announcement channel. The type defaults to Channel, and a private thread is possible only in a text channel. Creating a public thread needs Create, and a private one needs Create. The bot becomes the thread's first member
import { ChannelType, ThreadAutoArchiveMinutes } from "@neontechspace/fluxerly"
export async function startPlanning(client, channelId) {
const thread = await client.threads.create(channelId, {
name: "Release planning",
type: ChannelType.PrivateThread,
autoArchiveMinutes: ThreadAutoArchiveMinutes.OneDay,
})
if (thread.isErr())
return thread
return client.messages.send(thread.value.id, "Planning starts here")
}
import { ChannelType, ThreadAutoArchiveMinutes, type Client } from "@neontechspace/fluxerly"
export async function startPlanning(client: Client, channelId: string) {
const thread = await client.threads.create(channelId, {
name: "Release planning",
type: ChannelType.PrivateThread,
autoArchiveMinutes: ThreadAutoArchiveMinutes.OneDay,
})
if (thread.isErr()) return thread
return client.messages.send(thread.value.id, "Planning starts here")
}
To start a public thread on an existing message instead, use threads. The thread takes the message's ID, and a message can start only one thread. It needs Create and Read
export async function discussIn(client, message) {
return await client.threads.createFromMessage(message, { name: "Discussion" })
}
import type { Client, Message } from "@neontechspace/fluxerly"
export async function discussIn(client: Client, message: Message) {
return await client.threads.createFromMessage(message, { name: "Discussion" })
}
Create a Forum Post
In a forum or media channel, threads creates a post: A public thread together with its first message. The message takes the same content, embeds, files and stickers as messages, and files go in the same request. The applied setting applies up to 5 of the channel's tags. The forum channel guide describes the tags and settings a forum channel has
export async function reportCrash(client, forumId, tagId, log) {
const post = await client.threads.createPost(forumId, {
name: "Crash on start",
appliedTagIds: [tagId],
message: {
content: "The log is attached",
attachments: [{ data: log, filename: "crash.log", contentType: "text/plain" }],
},
})
return post.map(({ thread, message }) => ({ postId: thread.id, messageId: message.id }))
}
import type { Client } from "@neontechspace/fluxerly"
export async function reportCrash(client: Client, forumId: string, tagId: string, log: Uint8Array) {
const post = await client.threads.createPost(forumId, {
name: "Crash on start",
appliedTagIds: [tagId],
message: {
content: "The log is attached",
attachments: [{ data: log, filename: "crash.log", contentType: "text/plain" }],
},
})
return post.map(({ thread, message }) => ({ postId: thread.id, messageId: message.id }))
}
Archive, Lock and Pin Threads
The threads method changes the supplied settings and returns the updated thread. The thread's creator can rename it, archive it, lock it and change its archive period and tags. Unlocking, slowmode, pinning and moderated tags need Manage. A locked thread accepts changes only from members who can manage threads, and an archived thread accepts only a change that also unarchives it
export async function closeThread(client, threadId) {
return await client.threads.edit(threadId, { archived: true, locked: true }, { auditReason: "Resolved" })
}
import type { Client } from "@neontechspace/fluxerly"
export async function closeThread(client: Client, threadId: string) {
return await client.threads.edit(threadId, { archived: true, locked: true }, { auditReason: "Resolved" })
}
Setting pinned to true pins a forum or media post at the top of its channel, which holds at most one pinned post. Delete a thread with channels
List Threads
The threads method returns every active thread the bot can view in a community, newest first. A private thread appears only when the bot joined it or can manage threads in its parent channel
The threads method returns one page of archived threads of a channel. Its scope selects public threads, private threads or the private threads the bot joined. Reading them needs Read, and the private scope also needs Manage. The public and private scopes list the most recently archived threads first, so pass the last thread's archive as before to read the next page. The joined scope lists the newest threads first, so pass the last thread's id as before instead
export async function archivedThreads(client, channelId, maxPages = 5) {
const threads = []
let before
for (let page = 0; page < maxPages; page++) {
const result = await client.threads.fetchArchived(channelId, before === undefined ? {} : { before })
if (result.isErr())
return result
threads.push(...result.value.threads)
before = result.value.threads.at(-1)?.archiveTimestamp
if (!result.value.hasMore || before === undefined)
break
}
return threads
}
import type { Client, GuildThreadChannel } from "@neontechspace/fluxerly"
export async function archivedThreads(client: Client, channelId: string, maxPages = 5) {
const threads: GuildThreadChannel[] = []
let before: string | undefined
for (let page = 0; page < maxPages; page++) {
const result = await client.threads.fetchArchived(channelId, before === undefined ? {} : { before })
if (result.isErr()) return result
threads.push(...result.value.threads)
before = result.value.threads.at(-1)?.archiveTimestamp
if (!result.value.hasMore || before === undefined) break
}
return threads
}
In every list, a thread the bot joined has membership set
Search Threads
The threads method finds threads of a text, announcement, forum or media channel by name, tags and archive state, and returns one page. In a forum or media channel, the page's first holds the posts' first messages that still exist. Searching needs Read
When a community has no thread search index yet, Fluxer starts building one and the result has indexing set to true and no threads. The SDK does not retry, so search again later
export async function findCrashReports(client, forumId) {
const result = await client.threads.search(forumId, { name: "crash", archived: false, limit: 10 })
if (result.isErr())
return result
if (result.value.indexing)
return "The search index is still being built"
return result.value.threads.map((thread) => thread.name)
}
import type { Client } from "@neontechspace/fluxerly"
export async function findCrashReports(client: Client, forumId: string) {
const result = await client.threads.search(forumId, { name: "crash", archived: false, limit: 10 })
if (result.isErr()) return result
if (result.value.indexing) return "The search index is still being built"
return result.value.threads.map((thread) => thread.name)
}
Manage Thread Members
The bot joins and leaves a thread with threads and threads. Adding a community member who can view the parent channel with threads needs Send, and removing one with threads needs Manage, unless the bot created the private thread. An archived thread accepts no membership changes
The threads method reads one page of members in user ID order, and threads reads every page up to max. With with, each Thread also holds the user's community membership. Fluxer's thread member data does not name the community, so the SDK takes it from the cached thread, or reads the thread once when it is not cached
export async function memberNames(client, threadId) {
const names = []
for await (const result of client.threads.iterateMembers(threadId, { maxItems: 500, withMember: true })) {
if (result.isErr())
return result
names.push(result.value.member?.nickname ?? result.value.member?.username ?? result.value.userId)
}
return names
}
import type { Client } from "@neontechspace/fluxerly"
export async function memberNames(client: Client, threadId: string) {
const names: string[] = []
for await (const result of client.threads.iterateMembers(threadId, { maxItems: 500, withMember: true })) {
if (result.isErr()) return result
names.push(result.value.member?.nickname ?? result.value.member?.username ?? result.value.userId)
}
return names
}
Find the Thread a Message Started
A message that started a thread has the Message bit, and its thread field holds that thread while it exists. When someone starts a public or announcement thread, Fluxer also posts a Message notice in the parent channel. The notice's content is the thread name and its message names the thread. Prefix commands never run for such a notice
import { MessageFlags } from "@neontechspace/fluxerly"
export function startedThread(message) {
return ((message.flags ?? 0) & MessageFlags.HasThread) !== 0 ? message.thread : undefined
}
import { MessageFlags, type Message } from "@neontechspace/fluxerly"
export function startedThread(message: Message) {
return ((message.flags ?? 0) & MessageFlags.HasThread) !== 0 ? message.thread : undefined
}
Find Where Threads Are Available
The guilds method reports threads for each community. It is true where the bot can use threads, forum channels and media channels
export async function communitiesWithThreads(client) {
const page = await client.guilds.fetchPage()
return page.map((guilds) => guilds.filter((guild) => guild.threadsActive).map((guild) => guild.id))
}
import type { Client } from "@neontechspace/fluxerly"
export async function communitiesWithThreads(client: Client) {
const page = await client.guilds.fetchPage()
return page.map((guilds) => guilds.filter((guild) => guild.threadsActive).map((guild) => guild.id))
}
Thread Permissions
Permissions includes Manage, Create, Create and Send. A thread has no permission overwrites of its own. Fluxer computes its permissions in the parent channel and uses Send in place of Send. The permissions method does the same when it receives the parent as parent, and permissions and the require guard read the parent themselves. See communities and permissions