Create announcement channels, publish messages, follow them in the same community or another one and handle published copies
An announcement channel publishes messages to the text channels that follow it, in the same community or another one. Sending a message to the announcement channel does not publish it
These helpers take the existing client from the first bot or first Effect bot. Pass decimal IDs from trusted application configuration. The default API returns a Result. Native Effect helpers return an Effect, run with yield* inside the application program
Create an Announcement Channel
Creating a channel needs Manage in its community. Set type to Channel, whose protocol value is 5
import { ChannelType } from "@neontechspace/fluxerly"
export async function createAnnouncements(client, guildId) {
return await client.channels.create(guildId, {
type: ChannelType.Announcement,
name: "announcements",
topic: "Published community updates",
})
}
import { ChannelType, type Client } from "@neontechspace/fluxerly"
export async function createAnnouncements(client: Client, guildId: string) {
return await client.channels.create(guildId, {
type: ChannelType.Announcement,
name: "announcements",
topic: "Published community updates",
})
}
The returned announcement channel has the Guild shape. It has the same message and topic fields as a text channel
With the native Effect API:
import { ChannelType, type Client } from "@neontechspace/fluxerly/effect"
export function createAnnouncementsWithEffect(client: Client, guildId: string) {
return client.channels.create(guildId, {
type: ChannelType.Announcement,
name: "announcements",
topic: "Published community updates",
})
}
Convert an Existing Channel
Use channels with a new type to convert a text channel into an announcement channel. The same helper can convert it back to Channel. Both directions need Manage
import { ChannelType } from "@neontechspace/fluxerly"
export async function setAnnouncementChannel(client, channelId, enabled) {
return await client.channels.edit(channelId, {
type: enabled ? ChannelType.Announcement : ChannelType.Text,
})
}
import { ChannelType, type Client } from "@neontechspace/fluxerly"
export async function setAnnouncementChannel(client: Client, channelId: string, enabled: boolean) {
return await client.channels.edit(channelId, {
type: enabled ? ChannelType.Announcement : ChannelType.Text,
})
}
A text channel that follows an announcement channel cannot become one. Delete its follower webhooks first. Converting an announcement channel back to text removes its follower webhooks asynchronously. A successful edit does not wait for that removal
The SDK emits a guild event with the new type. Code that handles channel updates must check the new type rather than retaining the old channel shape
With the native Effect API:
import { ChannelType, type Client } from "@neontechspace/fluxerly/effect"
export function setAnnouncementChannelWithEffect(client: Client, channelId: string, enabled: boolean) {
return client.channels.edit(channelId, {
type: enabled ? ChannelType.Announcement : ChannelType.Text,
})
}
Send a Message, Then Publish It
Publishing sends copies to every following channel. First send a new message, not a reply, to the announcement channel, then pass the returned message to messages
export async function publishUpdate(client, channelId, content) {
const sent = await client.messages.send(channelId, content)
if (sent.isErr())
return sent
const published = await client.messages.publish(sent.value)
if (published.isErr()) {
console.warn(`Message ${sent.value.id} in channel ${channelId} was sent but not published`)
}
return published
}
import type { Client } from "@neontechspace/fluxerly"
export async function publishUpdate(client: Client, channelId: string, content: string) {
const sent = await client.messages.send(channelId, content)
if (sent.isErr()) return sent
const published = await client.messages.publish(sent.value)
if (published.isErr()) {
console.warn(`Message ${sent.value.id} in channel ${channelId} was sent but not published`)
}
return published
}
A failed send skips publication. A failed publish leaves the sent message posted, so the helper logs its ID and returns the failure unchanged
Fluxer's API and flags call publishing crossposting: Message marks the source and Message marks each copy. Completion means Fluxer accepted publication, not that every copy has arrived. A bot that can see a following channel receives each copy as a message event
With the native Effect API, a failed send skips publication and a failed publish logs the sent message's ID:
import { Effect } from "effect"
import type { Client } from "@neontechspace/fluxerly/effect"
export function publishUpdateWithEffect(client: Client, channelId: string, content: string) {
return Effect.gen(function* () {
const sent = yield* client.messages.send(channelId, content)
return yield* client.messages.publish(sent).pipe(
Effect.tapError(() =>
Effect.logWarning(`Message ${sent.id} in channel ${channelId} was sent but not published`),
),
)
})
}
Publication Limits and Later Changes
Only a message that is not a reply, forward, system notice or published copy can be published, and only once. It must be in an announcement channel. Publishing someone else's message needs Manage. Fluxer limits publication to 10 messages per channel per hour
Editing the source message updates its published copies later. Deleting the source does not delete the copies. Fluxer marks them with Message, changes their text to [Original message deleted] and removes their attachments
The messages guide explains which flags sends and edits accept
If sending succeeds but publication fails, the source message stays posted, which is why the helpers log its ID. Before publishing again, fetch that message and check Message. Fluxer sets the flag before it finishes the request and clears it only when queuing the copies fails, so a failed publish can still leave the message published. Do not rerun the whole helper after an uncertain write, because that can send another source message
Follow an Announcement Channel from a Text Channel
The source is an announcement channel. The target is a text channel in the same community or another one. The bot needs Manage in the target's community, plus View and Manage on the target channel. The bot must also be able to view the source channel
export async function followAnnouncements(client, sourceChannelId, targetChannelId) {
return await client.channels.follow(sourceChannelId, { targetChannelId })
}
import type { Client } from "@neontechspace/fluxerly"
export async function followAnnouncements(client: Client, sourceChannelId: string, targetChannelId: string) {
return await client.channels.follow(sourceChannelId, { targetChannelId })
}
Following creates a follower webhook in the target channel. The result contains channel for the followed announcement channel and webhook for the new follower webhook in the target. Keep that webhook ID to unfollow later. Fluxer usually posts a Message notice in the target, but the notice is not guaranteed. Do not wait for it to decide whether following succeeded
With the native Effect API:
import type { Client } from "@neontechspace/fluxerly/effect"
export function followAnnouncementsWithEffect(client: Client, sourceChannelId: string, targetChannelId: string) {
return client.channels.follow(sourceChannelId, { targetChannelId })
}
An application cannot send messages through a follower webhook. It has no token, so create cannot use it. The normal client method can rename it or move it to another text channel in the same community, but cannot change its avatar
See Follower Counts and Unfollow
Fetch the number of following channels and distinct receiving communities with channels. Fluxer can cache these counts for up to a minute, so a new follow or unfollow need not appear immediately
export async function readFollowerCounts(client, channelId) {
const result = await client.channels.fetchFollowerStats(channelId)
if (result.isErr())
return result
return { channels: result.value.channelCount, communities: result.value.guildCount }
}
export async function unfollowAnnouncements(client, webhookId) {
return await client.webhooks.delete(webhookId)
}
import type { Client } from "@neontechspace/fluxerly"
export async function readFollowerCounts(client: Client, channelId: string) {
const result = await client.channels.fetchFollowerStats(channelId)
if (result.isErr()) return result
return { channels: result.value.channelCount, communities: result.value.guildCount }
}
export async function unfollowAnnouncements(client: Client, webhookId: string) {
return await client.webhooks.delete(webhookId)
}
Deleting the follower webhook stops that target channel following the source. Use the webhook from the follow result, or find the follower webhook through client. Check webhook before treating an existing webhook as a follow. Deleting webhooks needs Manage
With the native Effect API:
import { Effect } from "effect"
import type { Client } from "@neontechspace/fluxerly/effect"
export function readFollowerCountsWithEffect(client: Client, channelId: string) {
return client.channels.fetchFollowerStats(channelId).pipe(
Effect.map(stats => ({ channels: stats.channelCount, communities: stats.guildCount })),
)
}
export function unfollowAnnouncementsWithEffect(client: Client, webhookId: string) {
return client.webhooks.delete(webhookId)
}
React to Copies and Follow Notices
Published copies have Message. Follow notices have Message. Register a message handler before connecting, and check these values before any check that skips bot or webhook authors so webhook-delivered copies are not skipped
import { MessageFlags, MessageType } from "@neontechspace/fluxerly"
export function observeAnnouncements(client, observe) {
return client.on("messageCreate", message => {
const isCopy = ((message.flags ?? 0) & MessageFlags.IsCrosspost) !== 0
const isFollowNotice = message.type === MessageType.ChannelFollowAdd
if (isCopy || isFollowNotice)
observe(message)
})
}
import { MessageFlags, MessageType, type Client, type Message } from "@neontechspace/fluxerly"
export function observeAnnouncements(client: Client, observe: (message: Message) => void) {
return client.on("messageCreate", message => {
const isCopy = ((message.flags ?? 0) & MessageFlags.IsCrosspost) !== 0
const isFollowNotice = message.type === MessageType.ChannelFollowAdd
if (isCopy || isFollowNotice) observe(message)
})
}
The returned subscription stops with the client. Call its close() method to stop it earlier. The events guide covers registration and delivery limits
With the native Effect API, registration belongs to the scope that runs it. Keep that scope open while the handler is needed:
import { Effect } from "effect"
import { MessageFlags, MessageType, type Client, type Message } from "@neontechspace/fluxerly/effect"
export function observeAnnouncementsWithEffect(client: Client, observe: (message: Message) => void) {
return client.on("messageCreate", message => Effect.sync(() => {
const isCopy = ((message.flags ?? 0) & MessageFlags.IsCrosspost) !== 0
const isFollowNotice = message.type === MessageType.ChannelFollowAdd
if (isCopy || isFollowNotice) observe(message)
}))
}
Find Where a Copy Came From
A published copy or follow notice can carry message. This received Message always has a source channel, but its id is optional. A follow notice points only at the source channel. Check id before using the reference to fetch or reply to a specific message
The messages method takes the copy or notice itself, not its source reference. It returns { guild }, a public snapshot of the source community. When the reference has an ID, the helper below also fetches the source message. If the bot cannot read it, the helper returns that failure
export async function readAnnouncementSource(client, message) {
const source = await client.messages.fetchCrosspostSource(message)
if (source.isErr())
return source
const reference = message.messageReference
if (!reference?.id)
return { guild: source.value.guild }
const original = await client.messages.fetch({ channelId: reference.channelId, id: reference.id })
if (original.isErr())
return original
return { guild: source.value.guild, message: original.value }
}
import type { Client, Message } from "@neontechspace/fluxerly"
export async function readAnnouncementSource(client: Client, message: Message) {
const source = await client.messages.fetchCrosspostSource(message)
if (source.isErr()) return source
const reference = message.messageReference
if (!reference?.id) return { guild: source.value.guild }
const original = await client.messages.fetch({ channelId: reference.channelId, id: reference.id })
if (original.isErr()) return original
return { guild: source.value.guild, message: original.value }
}
The public community snapshot includes its ID, name, description, icon, banner, public features and whether it is discoverable. Member and presence counts can be null. This read does not join the community or grant access to its channels. Fetching the original message can fail even when the public community read succeeds
With the native Effect API:
import { Effect } from "effect"
import type { Client, Message } from "@neontechspace/fluxerly/effect"
export function readAnnouncementSourceWithEffect(client: Client, message: Message) {
return Effect.gen(function* () {
const source = yield* client.messages.fetchCrosspostSource(message)
const reference = message.messageReference
if (!reference?.id) return { guild: source.guild }
const original = yield* client.messages.fetch({ channelId: reference.channelId, id: reference.id })
return { guild: source.guild, message: original }
})
}
Follower webhook metadata can also include source and source. Those fields appear only while the source exists and is visible to the webhook's creator. Their absence alone does not prove the source was deleted
Handle Permission and Delivery Failures
Fluxer checks permissions on the final request. Local permission checks can help explain a failure, but cannot authorize an action. Creating or converting channels needs Manage. Following and managing follower webhooks needs Manage. Publishing another author's message needs Manage. Following also fails for a duplicate follow, incompatible age-restriction or content-warning settings, a full webhook limit, or a community with announcements disabled through the ANNOUNCEMENT_ feature
The default API returns expected failures in a Result. Check is before reading value, and use describe to report a failure without logging credentials. Native Effects carry expected failures in their error channel. Use Effect as shown in Effect workflows when the application needs to turn success or failure into a value
A connected gateway is needed to observe conversions, copies and follow notices, not to make these HTTP requests. A successful request does not wait for later gateway delivery. Identify each copy by its own message ID, because a replayed event can deliver the same copy again