spaces

package
v0.0.0-...-4bafeee Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: AGPL-3.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	AuditSpaceUpdate       = "space_update"
	AuditRoomCreate        = "room_create"
	AuditRoomUpdate        = "room_update"
	AuditRoomDelete        = "room_delete"
	AuditRoleCreate        = "role_create"
	AuditRoleUpdate        = "role_update"
	AuditRoleDelete        = "role_delete"
	AuditMemberKick        = "member_kick"
	AuditMemberBanAdd      = "member_ban_add"
	AuditMemberBanRemove   = "member_ban_remove"
	AuditMemberRolesUpdate = "member_roles_update"
	AuditInviteCreate      = "invite_create"
	AuditInviteDelete      = "invite_delete"
	AuditEmojiCreate       = "emoji_create"
	AuditEmojiUpdate       = "emoji_update"
	AuditEmojiDelete       = "emoji_delete"
	AuditOverrideUpdate    = "override_update"
	AuditOverrideDelete    = "override_delete"
	// A bot installed through an application's `bot` OAuth2 scope: target is the bot's
	// user id, changes carry the permissions its role was created with.
	AuditBotAdd = "bot_add"
	// Voice moderation (internal/modules/voice): target is the member acted on.
	AuditOwnershipTransfer = "ownership_transfer"

	AuditMemberVoiceMute       = "member_voice_mute"
	AuditMemberVoiceDeafen     = "member_voice_deafen"
	AuditMemberVoiceMove       = "member_voice_move"
	AuditMemberVoiceDisconnect = "member_voice_disconnect"
)

Audit log action types (space_audit_log.action_type). Discord-style naming; the client maps each to a sentence. Targets: member_* -> user id, role_* -> role id, room_* -> room id, invite_* -> invite code, emoji_* -> emoji id, override_* -> "room:subject".

View Source
const (
	SystemRoomFlagSuppressJoin  = 1 << 0
	SystemRoomFlagSuppressLeave = 1 << 1
)

System-room flags (spaces.system_room_flags): which server-generated notices are *suppressed* in the system room, so a zero value means "post everything".

View Source
const (
	DefaultNotifAllMessages  = 0
	DefaultNotifOnlyMentions = 1
)

Default notification levels (spaces.default_message_notifications), Discord's values.

View Source
const (
	VerificationNone     = 0 // anyone may talk
	VerificationLow      = 1 // verified email address
	VerificationMedium   = 2 // + account older than VerificationAccountAge
	VerificationHigh     = 3 // + member of the space for VerificationMemberAge
	MaxVerificationLevel = VerificationHigh
)

Verification levels (spaces.verification_level): what a member who holds no role must satisfy before they can send messages in the space. Cumulative, like Discord's (there is no level 4: Strafe has no phone verification). Members with any role, and anyone with Manage Messages or Administrator, are exempt - they are the people the moderators vouch for, and raiders are not.

View Source
const (
	AutomodRepeatedMessages = 1 << 0 // the same text three times within a minute
	AutomodInviteLinks      = 1 << 1 // invite links to other spaces (and Discord servers)
	AutomodMassMentions     = 1 << 2 // more than AutomodMentionLimit distinct user/role mentions

	DefaultAutomodMentionLimit = 5
	MaxAutomodMentionLimit     = 50
)

Automod rules (spaces.automod_flags). Content rules apply to plaintext channels only - the server cannot read an E2EE room; the mention cap applies to declared mentions there.

View Source
const (
	MaxInviteAgeSeconds = 30 * 24 * 3600
	MaxInviteUses       = 1000
)
View Source
const (
	MaxSpaceNameRunes        = 100
	MaxSpaceDescriptionRunes = 1000
	MaxBanReasonRunes        = 512
)
View Source
const (
	MaxRoleNameRunes = 100
	MaxRoomNameRunes = 100
	MaxTopicRunes    = 1024
)
View Source
const (
	SystemMemberJoin  = "space_member_join"
	SystemMemberLeave = "space_member_leave"
	// SystemBirthday is posted in a space's birthday channel for an opted-in member on their
	// birthday. Payload: {user_id, message} (message is the space's custom template, or "").
	SystemBirthday = "space_birthday"
)

System message types posted to a space's system room (Space.SystemRoomID).

View Source
const EveryoneRoleName = "@everyone"

EveryoneRoleName is the fixed display name for the built-in @everyone role (cannot rename/delete).

View Source
const MaxEmojisPerSpace = 50

MaxEmojisPerSpace caps a space's custom emoji.

Variables

View Source
var (
	// ErrRemoteSpace: the space is a mirror; the origin instance is the only place it can
	// be managed from.
	ErrRemoteSpace = errors.New("this space is hosted on another instance and can only be managed from there")
	// ErrOriginUnavailable: the instance hosting the space did not answer.
	ErrOriginUnavailable = errors.New("the instance hosting this space could not be reached")
	// ErrFederationOff: an invite code names another instance but this one doesn't federate.
	ErrFederationOff = errors.New("this instance does not federate with other instances")
)
View Source
var (
	ErrSpaceNotFound               = errors.New("space not found")
	ErrNotMember                   = errors.New("not a member of this space")
	ErrNotSpaceOwner               = errors.New("only the space owner can do this")
	ErrInviteNotFound              = errors.New("invite not found")
	ErrInsufficientSpacePermission = errors.New("insufficient permissions for this space")
	ErrInvalidSpaceName            = errors.New("space name must be 1-100 characters")
	ErrNothingToPatch              = errors.New("no fields to update")
	ErrBanned                      = errors.New("banned from this space")
	ErrCannotModerateOwner         = errors.New("cannot kick or ban the space owner")
	ErrOwnerCannotLeave            = errors.New("space owner must transfer ownership or delete the space before leaving")
	ErrInvalidDescription          = errors.New("description must be at most 1000 characters")
)
View Source
var (
	// ErrAlreadyOwner is returned when a transfer names the current owner.
	ErrAlreadyOwner = errors.New("that member already owns this space")
	// ErrSpaceNameMismatch is the typed-name confirmation failing (see DeleteSpace).
	ErrSpaceNameMismatch = errors.New("the name you typed does not match this space")
)
View Source
var (
	ErrEmojiNotFound    = errors.New("emoji not found")
	ErrInvalidEmojiName = errors.New("emoji names are 2-32 letters, numbers, or underscores")
	ErrEmojiNameTaken   = errors.New("an emoji with that name already exists in this space")
	ErrEmojiLimit       = errors.New("this space has reached its custom emoji limit")
)
View Source
var (
	ErrInvalidBot   = errors.New("that account is not a bot")
	ErrNotLocalUser = errors.New("that user does not belong to this instance")
)
View Source
var (
	ErrInvalidSystemRoom      = errors.New("system messages room must be a text room in this space")
	ErrInvalidAFKRoom         = errors.New("inactive room must be a voice room in this space")
	ErrInvalidAFKTimeout      = errors.New("inactive timeout must be 1, 5, 15, 30 or 60 minutes")
	ErrInvalidNotifLevel      = errors.New("default notifications must be 0 (all messages) or 1 (only mentions)")
	ErrInvalidSystemFlags     = errors.New("unknown system message flag")
	ErrInvalidWidgetRoom      = errors.New("widget invite room must be a text room in this space")
	ErrInvalidBirthdayRoom    = errors.New("birthday channel must be a text room in this space")
	ErrInvalidBirthdayMessage = errors.New("birthday message must be at most 500 characters")
	ErrInvalidVerification    = errors.New("verification level must be 0 (none), 1 (verified email), 2 (account older than 5 minutes) or 3 (member for 10 minutes)")
	ErrInvalidAutomodFlags    = errors.New("unknown automod rule")
	ErrInvalidMentionLimit    = errors.New("mention limit must be 1-50, or 0 for the default of 5")
)
View Source
var (
	ErrRoleNotFound           = errors.New("role not found")
	ErrCannotEditEveryone     = errors.New("cannot rename or delete the @everyone role")
	ErrMissingPerm            = errors.New("missing permission")
	ErrInvalidRoom            = errors.New("room not in this space")
	ErrInvalidSlowmode        = errors.New("invalid slowmode")
	ErrInvalidRoomType        = errors.New("invalid room type")
	ErrInvalidReorder         = errors.New("invalid room order")
	ErrInvalidRoleName        = errors.New("role name must be 1-100 characters and not @everyone")
	ErrInvalidRoomName        = errors.New("room name must be at most 100 characters")
	ErrInvalidTopic           = errors.New("topic must be at most 1024 characters")
	ErrInvalidUserLimit       = errors.New("user limit must be 0 (unlimited) to 99")
	ErrInvalidBitrate         = errors.New("bitrate must be 8000-384000 bits per second, or 0 for the default")
	ErrCannotChangeOwnerRoles = errors.New("cannot change roles of the space owner")
	// ErrRoleHierarchy mirrors Discord: a non-owner can't manage a role at or above their own
	// highest role's position, and can't act on a member whose highest role outranks theirs.
	// Otherwise a Manage Roles holder could create an Administrator role and grant it to
	// themselves, or edit/demote staff above them.
	ErrRoleHierarchy = errors.New("cannot manage a role or member at or above your own highest role")
	// ErrPermissionEscalation mirrors Discord's other rule: you can only hand out
	// permissions you hold yourself. Without it a Manage Roles holder could set
	// Administrator on @everyone (which the hierarchy check deliberately exempts) and
	// become an administrator of the whole space.
	ErrPermissionEscalation = errors.New("cannot grant permissions you do not have")
	// ErrManagedRole: the role belongs to a bot (SpaceRole.BotID) - it can be edited, but
	// only its bot may hold it and it is removed when the bot leaves, never by hand.
	ErrManagedRole = errors.New("this role belongs to a bot and cannot be deleted or assigned")
)
View Source
var AFKTimeouts = map[int]bool{60: true, 300: true, 900: true, 1800: true, 3600: true}

AFKTimeouts are the accepted values for spaces.afk_timeout (seconds) - Discord's set.

View Source
var ErrInvalidInvite = errors.New("invite expiry must be at most 30 days and uses at most 1000")
View Source
var ErrNotRemoteSpace = errors.New("this space is hosted on this instance")

ErrNotRemoteSpace: the space is hosted here, so there is no origin to sync from.

Functions

func AttachFederation

func AttachFederation(m map[string]interface{}, f *rooms.Federation) map[string]interface{}

AttachFederation adds a room's global identity to its wire map.

func AttachOverrides

func AttachOverrides(m map[string]interface{}, o RoomOverrides) map[string]interface{}

AttachOverrides adds a room's overrides to its wire map under the keys the client reads.

func AttachThreadState

func AttachThreadState(m map[string]interface{}, st ThreadState)

AttachThreadState adds message_count, member_count and the viewer's `joined` to a thread's wire form (a no-op for a room that is not a thread).

func FormatInviteCode

func FormatInviteCode(code, domain string) string

FormatInviteCode is the inverse of ParseInviteCode.

func HTTPError

func HTTPError(err error) (status int, message string, ok bool)

HTTPError maps a spaces-service error to the status and message a client gets; ok is false for errors that are not the client's (internal). Shared with the federation engine, which answers another instance's members with the same statuses.

func IsUserTargetAction

func IsUserTargetAction(action string) bool

IsUserTargetAction reports whether an audit action's target_id is a user id.

func OverrideMaps

func OverrideMaps(o RoomOverrides) (roles []map[string]interface{}, users []map[string]interface{})

OverrideMaps is the wire form of a room's overrides: role overrides then user overrides, always non-nil so clients can tell "no overrides" from "not included".

func ParseInviteCode

func ParseInviteCode(raw string) (code, domain string, err error)

ParseInviteCode splits "code@domain" (domain optional) as people type it. The domain is the hosting instance; "" means this one.

func RoleMap

func RoleMap(r *SpaceRole) map[string]interface{}

RoleMap is the wire form of a role, shared by REST responses, gateway events and the READY payload so every path agrees on the shape.

func RoleMaps

func RoleMaps(roles []SpaceRole) []map[string]interface{}

RoleMaps converts a role list for a payload.

func RoomMap

func RoomMap(r *rooms.Room) map[string]interface{}

RoomMap is the wire form of a space room, shared by REST, READY and SPACE_ROOM_CREATE.

func ThreadMap

func ThreadMap(r *rooms.Room) map[string]interface{}

ThreadMap is the `thread` block of a thread room's wire form - Discord's thread_metadata plus the owner: archive and lock state, type, auto-archive timer, who started it and from which message. The counts and the viewer's membership are added by AttachThreadState.

func ValidateEmojiName

func ValidateEmojiName(name string) (string, error)

ValidateEmojiName normalises and checks a proposed name.

Types

type AuditChange

type AuditChange struct {
	Old interface{}
	New interface{}
}

AuditChange is one before/after pair for RecordAudit. Either side may be nil.

type CreateInviteInput

type CreateInviteInput struct {
	MaxAgeSeconds int `json:"max_age_seconds,omitempty"`
	MaxUses       int `json:"max_uses,omitempty"`
}

CreateInviteInput is the optional body of POST /spaces/:id/invites. Zero values mean "never expires" / "unlimited uses".

type CreateRoomInput

type CreateRoomInput struct {
	Name     string
	Type     int
	ParentID *int64
	// E2EEEnabled: nil or false = plaintext (the default for space rooms); true = E2EE from
	// the first message. Only honored for text rooms.
	E2EEEnabled *bool
	// Voice rooms only (see UpdateRoomInput).
	UserLimit *int
	Bitrate   *int
}

CreateRoomInput is the service-layer input (parent as int64). HTTP JSON is decoded in the handler (string snowflake parent_id).

type CreateSpaceInput

type CreateSpaceInput struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Icon        string `json:"icon,omitempty"`
}

CreateSpaceInput is the request body for creating a space.

type CreateSpaceRoleInput

type CreateSpaceRoleInput struct {
	Name        string `json:"name"`
	Permissions int64  `json:"permissions"`
	Color       int    `json:"color,omitempty"`
	Hoist       bool   `json:"hoist,omitempty"`
	Mentionable bool   `json:"mentionable,omitempty"`
}

CreateSpaceRoleInput creates a custom role (not @everyone).

type FederationInfo

type FederationInfo interface {
	// SpaceFederation is non-nil only for a space hosted by another instance.
	SpaceFederation(ctx context.Context, spaceID int64) *SpaceFederation
	// RoomFederations returns the global identity of each listed room that has one.
	RoomFederations(ctx context.Context, roomIDs []int64) map[int64]*rooms.Federation
}

FederationInfo is the read-only side (the gateway's READY builder needs it too).

type Federator

type Federator interface {
	FederationInfo
	AfterSpaceUpdated(ctx context.Context, sp *Space)
	AfterSpaceDeleted(ctx context.Context, sp *Space)
	AfterSpaceMemberAdded(ctx context.Context, spaceID int64, m *SpaceMember)
	AfterSpaceMemberUpdated(ctx context.Context, spaceID int64, m *SpaceMember)
	AfterSpaceMemberRemoved(ctx context.Context, spaceID, userID int64, how string)
	AfterSpaceRoleChanged(ctx context.Context, spaceID int64, role *SpaceRole, deleted bool)
	AfterSpaceRoomChanged(ctx context.Context, spaceID int64, room *rooms.Room, overrides RoomOverrides, deleted bool)
	AfterSpaceRoomsReordered(ctx context.Context, spaceID int64, list []*rooms.Room)
	AfterSpaceEmojiChanged(ctx context.Context, spaceID int64, e *SpaceEmoji, deleted bool)

	PreviewRemoteInvite(ctx context.Context, domain, code string) (*InvitePreview, error)
	JoinRemoteSpace(ctx context.Context, domain, code string, user *auth.User) (*Space, error)
	LeaveRemoteSpace(ctx context.Context, origin string, spaceID int64, user *auth.User) error
	CreateRemoteInvite(ctx context.Context, origin string, spaceID int64, user *auth.User, in *CreateInviteInput) (*SpaceInvite, error)

	// Management of a mirrored space, done at the origin as the member and applied here
	// from the origin's answer. Ids in and out are this instance's.
	RemotePatchSpace(ctx context.Context, origin string, spaceID int64, user *auth.User, in *PatchSpaceInput) (*Space, error)
	RemoteSetSpaceImage(ctx context.Context, origin string, spaceID int64, user *auth.User, kind, url string) (*Space, error)
	RemoteCreateRole(ctx context.Context, origin string, spaceID int64, user *auth.User, in *CreateSpaceRoleInput) (*SpaceRole, error)
	RemoteUpdateRole(ctx context.Context, origin string, spaceID int64, user *auth.User, roleID int64, in *UpdateSpaceRoleInput) (*SpaceRole, error)
	RemoteDeleteRole(ctx context.Context, origin string, spaceID int64, user *auth.User, roleID int64) error
	RemoteSetMemberRoles(ctx context.Context, origin string, spaceID int64, user *auth.User, targetID int64, roleIDs []int64) error
	RemoteModerate(ctx context.Context, origin string, spaceID int64, user *auth.User, action string, targetID int64, reason string) error
	RemoteListBans(ctx context.Context, origin string, spaceID int64, user *auth.User) ([]SpaceBan, map[int64]*auth.User, error)
	RemoteListInvites(ctx context.Context, origin string, spaceID int64, user *auth.User) ([]SpaceInvite, map[int64]*auth.User, error)
	RemoteDeleteInvite(ctx context.Context, origin string, spaceID int64, user *auth.User, code string) error
	RemoteListAuditLog(ctx context.Context, origin string, spaceID int64, user *auth.User, beforeID int64, limit int, action string) ([]SpaceAuditEntry, map[int64]*auth.User, error)
	RemoteCreateRoom(ctx context.Context, origin string, spaceID int64, user *auth.User, in *CreateRoomInput) (*rooms.Room, error)
	RemoteUpdateRoom(ctx context.Context, origin string, spaceID int64, user *auth.User, roomID int64, in *UpdateRoomInput) error
	RemoteDeleteRoom(ctx context.Context, origin string, spaceID int64, user *auth.User, roomID int64) error
	RemoteReorderRooms(ctx context.Context, origin string, spaceID int64, user *auth.User, in *ReorderRoomsInput) error
	RemoteMoveChannel(ctx context.Context, origin string, spaceID int64, user *auth.User, channelID int64, parent, before *int64) error
	RemoteOverride(ctx context.Context, origin string, spaceID int64, user *auth.User, roomID, roleID, targetUserID int64, allow, deny int64, remove bool) error
	RemoteTransferOwnership(ctx context.Context, origin string, spaceID int64, user *auth.User, newOwnerID int64) (*Space, error)
	RemoteDeleteSpace(ctx context.Context, origin string, spaceID int64, user *auth.User, confirmName string) error
	RemoteCreateEmoji(ctx context.Context, origin string, spaceID int64, user *auth.User, emojiID int64, name, url string, animated bool) (*SpaceEmoji, error)
	RemoteRenameEmoji(ctx context.Context, origin string, spaceID int64, user *auth.User, emojiID int64, name string) (*SpaceEmoji, error)
	RemoteDeleteEmoji(ctx context.Context, origin string, spaceID int64, user *auth.User, emojiID int64) (*SpaceEmoji, error)
	// RemoteInstallBot adds a bot of this instance to a space hosted elsewhere, as the
	// member who consented; RemoteAddMember is the spaces.join scope for such a space.
	RemoteInstallBot(ctx context.Context, origin string, spaceID int64, user, bot *auth.User, permissions int64) (granted int64, err error)
	RemoteAddMember(ctx context.Context, origin string, spaceID int64, actor, user *auth.User) (added bool, err error)
	// ResyncRemote reconciles a mirror against a fresh snapshot from its origin.
	ResyncRemote(ctx context.Context, origin string, spaceID int64) error
}

Federator is implemented by internal/federation. The After* hooks run on the origin after a local write and relay it to the mirrors; the *Remote* calls are made by a mirror on behalf of one of its users and are synchronous (the origin's answer is the user's answer, and the mirror is updated from it before the call returns). Every hook is best-effort and must not block.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

func NewHandler

func NewHandler(svc *Service, cfg *config.Config) *Handler

func (*Handler) AckAll

func (h *Handler) AckAll(c fiber.Ctx) error

AckAll marks every text/voice room in the space read for the caller. POST /spaces/:id/ack-all.

func (*Handler) BanMember

func (h *Handler) BanMember(c fiber.Ctx) error

BanMember POST /spaces/:id/bans/:userId. Body: { "reason": "..." } (optional).

func (*Handler) Create

func (h *Handler) Create(c fiber.Ctx) error

Create creates a new space. POST /spaces. Body: { "name": "...", "description": "...", "icon": "..." }.

func (*Handler) CreateInvite

func (h *Handler) CreateInvite(c fiber.Ctx) error

CreateInvite creates a new invite for the space. POST /spaces/:id/invites.

func (*Handler) CreateRole

func (h *Handler) CreateRole(c fiber.Ctx) error

CreateRole POST /spaces/:id/roles

func (*Handler) DeleteEmoji

func (h *Handler) DeleteEmoji(c fiber.Ctx) error

DeleteEmoji DELETE /spaces/:id/emojis/:emojiId

func (*Handler) DeleteInvite

func (h *Handler) DeleteInvite(c fiber.Ctx) error

DeleteInvite DELETE /spaces/:id/invites/:code - revoke an invite (Manage space, or its creator).

func (*Handler) DeleteRole

func (h *Handler) DeleteRole(c fiber.Ctx) error

DeleteRole DELETE /spaces/:id/roles/:roleId

func (*Handler) DeleteRoom

func (h *Handler) DeleteRoom(c fiber.Ctx) error

DeleteRoom DELETE /spaces/:id/rooms/:roomId

func (*Handler) DeleteRoomOverride

func (h *Handler) DeleteRoomOverride(c fiber.Ctx) error

DeleteRoomOverride DELETE /spaces/:id/rooms/:roomId/overrides/:roleId

func (*Handler) DeleteRoomUserOverride

func (h *Handler) DeleteRoomUserOverride(c fiber.Ctx) error

DeleteRoomUserOverride DELETE /spaces/:id/rooms/:roomId/overrides/users/:userId

func (*Handler) DeleteSpace

func (h *Handler) DeleteSpace(c fiber.Ctx) error

DeleteSpace DELETE /spaces/:id. Body: { "name": "..." } - the space's own name, typed back as confirmation. Owner only, and irreversible.

func (*Handler) Get

func (h *Handler) Get(c fiber.Ctx) error

Get returns a space by ID. User must be a member. GET /spaces/:id.

func (*Handler) GetEmojiByID

func (h *Handler) GetEmojiByID(c fiber.Ctx) error

GetEmojiByID GET /emojis/:emojiId - resolve an emoji used outside its home space.

func (*Handler) GetRooms

func (h *Handler) GetRooms(c fiber.Ctx) error

GetRooms returns rooms in the space (text and voice). User must be a member. GET /spaces/:id/rooms.

func (*Handler) InvitePreview

func (h *Handler) InvitePreview(c fiber.Ctx) error

InvitePreview returns public space info for an invite (no auth). GET /spaces/invites/:code.

func (*Handler) JoinByInvite

func (h *Handler) JoinByInvite(c fiber.Ctx) error

JoinByInvite joins a space via invite code. POST /spaces/invites/:code/join.

func (*Handler) KickMember

func (h *Handler) KickMember(c fiber.Ctx) error

KickMember DELETE /spaces/:id/members/:userId

func (*Handler) Leave

func (h *Handler) Leave(c fiber.Ctx) error

Leave POST /spaces/:id/leave

func (*Handler) List

func (h *Handler) List(c fiber.Ctx) error

List returns spaces the current user is a member of. GET /spaces.

func (*Handler) ListAuditLog

func (h *Handler) ListAuditLog(c fiber.Ctx) error

ListAuditLog GET /spaces/:id/audit-log?before=<entry id>&limit=50&action=<type>. Response: { entries: [...], users: { "<id>": {...} } }. Manage space.

func (*Handler) ListBans

func (h *Handler) ListBans(c fiber.Ctx) error

ListBans GET /spaces/:id/bans

func (*Handler) ListEmojis

func (h *Handler) ListEmojis(c fiber.Ctx) error

ListEmojis GET /spaces/:id/emojis

func (*Handler) ListInvites

func (h *Handler) ListInvites(c fiber.Ctx) error

ListInvites GET /spaces/:id/invites - live invites with their inviters. Manage space.

func (*Handler) ListMyEmojis

func (h *Handler) ListMyEmojis(c fiber.Ctx) error

ListMyEmojis GET /emojis - every custom emoji from every space the caller is in.

func (*Handler) ListRoles

func (h *Handler) ListRoles(c fiber.Ctx) error

ListRoles GET /spaces/:id/roles

func (*Handler) ListRoomOverrides

func (h *Handler) ListRoomOverrides(c fiber.Ctx) error

ListRoomOverrides GET /spaces/:id/rooms/:roomId/overrides

func (*Handler) ListRoomUserOverrides

func (h *Handler) ListRoomUserOverrides(c fiber.Ctx) error

ListRoomUserOverrides GET /spaces/:id/rooms/:roomId/overrides/users

func (*Handler) Members

func (h *Handler) Members(c fiber.Ctx) error

Members returns members of a space with basic user info. GET /spaces/:id/members.

func (*Handler) MoveChannel

func (h *Handler) MoveChannel(c fiber.Ctx) error

MoveChannel POST /spaces/:id/rooms/move — body: MoveChannelInput (registered before /rooms/:roomId).

func (*Handler) MySpaces

func (h *Handler) MySpaces(c fiber.Ctx) error

MySpaces GET /users/@me/spaces - the light list of the caller's spaces (Discord's /users/@me/guilds): id, name, icon, whether they own it, and their space-wide permissions. This is the one spaces endpoint an OAuth2 token may call (with the `spaces` scope); sessions and bots get it too.

func (*Handler) PatchEmoji

func (h *Handler) PatchEmoji(c fiber.Ctx) error

PatchEmoji PATCH /spaces/:id/emojis/:emojiId {"name": "..."}

func (*Handler) PatchRoom

func (h *Handler) PatchRoom(c fiber.Ctx) error

PatchRoom PATCH /spaces/:id/rooms/:roomId

func (*Handler) PatchSpace

func (h *Handler) PatchSpace(c fiber.Ctx) error

PatchSpace PATCH /spaces/:id — update space metadata (e.g. name).

func (*Handler) PostEmoji

func (h *Handler) PostEmoji(c fiber.Ctx) error

PostEmoji POST /spaces/:id/emojis (multipart: "file", "name"). Permission is checked before the upload so an unauthorised caller can't fill Nebula with images.

func (*Handler) PostRoom

func (h *Handler) PostRoom(c fiber.Ctx) error

PostRoom POST /spaces/:id/rooms

func (*Handler) PostSpaceBanner

func (h *Handler) PostSpaceBanner(c fiber.Ctx) error

PostSpaceBanner uploads a wide image to Nebula and sets the space banner. POST /spaces/:id/banner (multipart field "file"). Same shape and gating as PostSpaceIcon.

func (*Handler) PostSpaceIcon

func (h *Handler) PostSpaceIcon(c fiber.Ctx) error

PostSpaceIcon uploads an image to Nebula and sets the space icon. POST /spaces/:id/icon (multipart field "file").

func (*Handler) PutMemberRoles

func (h *Handler) PutMemberRoles(c fiber.Ctx) error

PutMemberRoles PUT /spaces/:id/members/:userId/roles body: { "role_ids": ["snowflake", ...] } (without @everyone; server merges)

func (*Handler) PutRoomOverride

func (h *Handler) PutRoomOverride(c fiber.Ctx) error

PutRoomOverride PUT /spaces/:id/rooms/:roomId/overrides/:roleId

func (*Handler) PutRoomUserOverride

func (h *Handler) PutRoomUserOverride(c fiber.Ctx) error

PutRoomUserOverride PUT /spaces/:id/rooms/:roomId/overrides/users/:userId

func (*Handler) ReorderRooms

func (h *Handler) ReorderRooms(c fiber.Ctx) error

ReorderRooms POST /spaces/:id/rooms/reorder — body: ReorderRoomsInput (registered before generic /rooms routes).

func (*Handler) Resync

func (h *Handler) Resync(c fiber.Ctx) error

Resync POST /spaces/:id/resync - reconcile a mirrored space with its origin.

func (*Handler) TransferOwnership

func (h *Handler) TransferOwnership(c fiber.Ctx) error

TransferOwnership POST /spaces/:id/transfer-ownership. Body: { "user_id": "..." }. Owner only; the new owner must already be a member.

func (*Handler) UnbanMember

func (h *Handler) UnbanMember(c fiber.Ctx) error

UnbanMember DELETE /spaces/:id/bans/:userId

func (*Handler) UpdateRole

func (h *Handler) UpdateRole(c fiber.Ctx) error

UpdateRole PATCH /spaces/:id/roles/:roleId

func (*Handler) Widget

func (h *Handler) Widget(c fiber.Ctx) error

Widget GET /spaces/:id/widget.json - public, no auth. 404 unless the widget is enabled.

type InstallTarget

type InstallTarget struct {
	Space     *Space
	Grantable int64
}

InstallTarget is a space a user may add a bot to, with the permission bits they may hand it (everything for the owner and Administrators, otherwise exactly what they hold).

type InvitePreview

type InvitePreview struct {
	Space       *Space
	InviterName string
	MemberCount int
}

InvitePreview is what an invite code reveals before joining.

type MemberWithUser

type MemberWithUser struct {
	Member SpaceMember
	User   *auth.User
}

MemberWithUser is a member row with its profile.

type MirrorEmoji

type MirrorEmoji struct {
	ID        int64
	Name      string
	URL       string
	Animated  bool
	CreatorID int64
	CreatedAt time.Time
	UpdatedAt time.Time
}

MirrorEmoji is a custom emoji as the origin describes it (its id is kept).

type MirrorMember

type MirrorMember struct {
	UserID   int64
	RoleIDs  []int64
	Nickname string
	JoinedAt time.Time
}

MirrorMember is a member as the origin describes them, already a local user id.

type MirrorRoom

type MirrorRoom struct {
	Room      *rooms.Room
	Overrides RoomOverrides
}

MirrorRoom is a room as the origin describes it, already in local ids.

type MirrorSpec

type MirrorSpec struct {
	Space   *Space
	Roles   []SpaceRole
	Rooms   []MirrorRoom
	Members []MirrorMember
	Emoji   []MirrorEmoji
	// MembersComplete: Members is the whole list. When a page could not be fetched it is
	// a prefix, and reconciling must not take the missing members for gone ones.
	MembersComplete bool
}

MirrorSpec is everything a mirror is built from.

type MoveChannelInput

type MoveChannelInput struct {
	ChannelID       string  `json:"channel_id"`
	ParentSectionID *string `json:"parent_section_id,omitempty"`
	BeforeRoomID    *string `json:"before_room_id,omitempty"`
}

MoveChannelInput moves a text/voice channel to another parent group (or top-level) in one step. Omitted or empty parent_section_id means top-level; omitted or empty before_room_id means append at end of the target group.

type OriginError

type OriginError struct {
	Status  int
	Message string
}

OriginError is the origin instance's refusal of a forwarded request, passed through to the user with the origin's status and message.

func (*OriginError) Error

func (e *OriginError) Error() string

type PatchSpaceInput

type PatchSpaceInput struct {
	Name        *string `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`
	// System messages: the text room that receives join/leave notices, and which of
	// those are suppressed (SystemRoomFlag*).
	SystemRoomID    *string `json:"system_room_id,omitempty"`
	SystemRoomFlags *int    `json:"system_room_flags,omitempty"`
	// DefaultNotif* - what members get notified for unless they override it.
	DefaultMessageNotif *int `json:"default_message_notifications,omitempty"`
	// Voice room idle members are moved to after AFKTimeout seconds (one of AFKTimeouts).
	AFKRoomID  *string `json:"afk_room_id,omitempty"`
	AFKTimeout *int    `json:"afk_timeout,omitempty"`
	// Server widget (see Space.WidgetEnabled).
	WidgetEnabled *bool   `json:"widget_enabled,omitempty"`
	WidgetRoomID  *string `json:"widget_room_id,omitempty"`
	// Birthdays: the announcement channel (empty string clears it) and an optional message
	// template.
	BirthdayChannelID *string `json:"birthday_channel_id,omitempty"`
	BirthdayMessage   *string `json:"birthday_message,omitempty"`
	// Moderation: the verification level (Verification*) and automod (Automod* flags plus
	// the mention cap, 0 for the default).
	VerificationLevel   *int `json:"verification_level,omitempty"`
	AutomodFlags        *int `json:"automod_flags,omitempty"`
	AutomodMentionLimit *int `json:"automod_mention_limit,omitempty"`
}

PatchSpaceInput patches space settings. Omitted fields are unchanged. Room ids are string snowflakes; an empty string clears the setting (JSON null can't be told apart from an omitted field once decoded into a pointer).

type PutRoomRoleOverrideInput

type PutRoomRoleOverrideInput struct {
	Allow int64 `json:"allow"`
	Deny  int64 `json:"deny"`
}

PutRoomRoleOverrideInput sets channel overrides for a role in a room.

type PutRoomUserOverrideInput

type PutRoomUserOverrideInput struct {
	Allow int64 `json:"allow"`
	Deny  int64 `json:"deny"`
}

PutRoomUserOverrideInput sets channel overrides for a user in a room.

type ReorderRoomsInput

type ReorderRoomsInput struct {
	Scope           string   `json:"scope"`                       // "sections" | "channels"
	ParentSectionID *string  `json:"parent_section_id,omitempty"` // for "channels": omit or null = top-level (parent_id null); else snowflake of section
	RoomIDs         []string `json:"room_ids"`                    // must be a permutation of the server-side group
}

ReorderRoomsInput reorders either all sections (scope "sections") or all text/voice channels in one parent group (scope "channels").

type Repository

type Repository interface {
	Create(ctx context.Context, s *Space) error
	GetByID(ctx context.Context, id int64) (*Space, error)
	// GetByIDs fetches several spaces in one query; the result is in input order with
	// nil for ids that do not exist.
	GetByIDs(ctx context.Context, ids []int64) ([]*Space, error)

	// Custom emoji (space_emoji + the by-id lookup table, kept in step).
	ListEmojis(ctx context.Context, spaceID int64) ([]SpaceEmoji, error)
	GetEmoji(ctx context.Context, spaceID, emojiID int64) (*SpaceEmoji, error)
	GetEmojiByID(ctx context.Context, emojiID int64) (*SpaceEmojiRef, error)
	// InsertEmojiRefIfAbsent writes a by-id ref only when the id is unknown (LWT).
	InsertEmojiRefIfAbsent(ctx context.Context, ref *SpaceEmojiRef) error
	CreateEmoji(ctx context.Context, e *SpaceEmoji) error
	UpdateEmojiName(ctx context.Context, spaceID, emojiID int64, name string, now time.Time) error
	DeleteEmoji(ctx context.Context, spaceID, emojiID int64) error
	AddMember(ctx context.Context, spaceID, userID int64, roleIDs []int64) error
	ListSpacesByUser(ctx context.Context, userID int64) ([]SpaceMemberRow, error)
	AddSpaceToUserSet(ctx context.Context, userID int64, spaceID int64) error
	IsMember(ctx context.Context, spaceID, userID int64) (bool, error)
	ListMembers(ctx context.Context, spaceID int64) ([]SpaceMember, error)
	ListMembersPage(ctx context.Context, spaceID, after int64, limit int) ([]SpaceMember, error)
	CountMembers(ctx context.Context, spaceID int64) (int, error)
	CreateInvite(ctx context.Context, inv *SpaceInvite) error
	GetInviteByCode(ctx context.Context, code string) (*SpaceInvite, error)
	ListInvites(ctx context.Context, spaceID int64) ([]SpaceInvite, error)
	// SetInviteUses writes the use counter to both invite tables.
	SetInviteUses(ctx context.Context, spaceID int64, code string, uses int) error
	DeleteInvite(ctx context.Context, spaceID int64, code string) error

	// UpdateSpaceFields writes the given spaces columns (column name -> value; nil = null).
	// Column names come from code, never from a request.
	UpdateSpaceFields(ctx context.Context, spaceID int64, set map[string]interface{}) error

	InsertAuditEntry(ctx context.Context, e *SpaceAuditEntry) error
	// ListAuditEntries returns up to limit entries newer-first, older than beforeID when
	// beforeID > 0.
	ListAuditEntries(ctx context.Context, spaceID, beforeID int64, limit int) ([]SpaceAuditEntry, error)

	UpdateEveryoneRoleID(ctx context.Context, spaceID, roleID int64) error
	GetMember(ctx context.Context, spaceID, userID int64) (*SpaceMember, error)
	SetMemberRoleIDs(ctx context.Context, spaceID, userID int64, roleIDs []int64) error
	// RemoveMember deletes the space_members row and the matching spaces_by_user row (kick, ban, leave).
	RemoveMember(ctx context.Context, spaceID, userID int64) error

	CreateBan(ctx context.Context, ban *SpaceBan) error
	GetBan(ctx context.Context, spaceID, userID int64) (*SpaceBan, error)
	DeleteBan(ctx context.Context, spaceID, userID int64) error
	ListBans(ctx context.Context, spaceID int64) ([]SpaceBan, error)

	InsertSpaceRole(ctx context.Context, r *SpaceRole) error
	GetSpaceRole(ctx context.Context, spaceID, roleID int64) (*SpaceRole, error)
	ListSpaceRoles(ctx context.Context, spaceID int64) ([]SpaceRole, error)
	UpdateSpaceRole(ctx context.Context, r *SpaceRole) error
	// Data-migration support (see backfill.go). ForEachSpaceRole scans every role in
	// the keyspace; ClaimDataMigration records name and reports whether this call was
	// the one that claimed it.
	ForEachSpaceRole(ctx context.Context, fn func(spaceID, roleID int64, name string, permissions int64) error) error
	UpdateSpaceRolePermissions(ctx context.Context, spaceID, roleID int64, permissions int64, updatedAt time.Time) error
	ClaimDataMigration(ctx context.Context, name string) (bool, error)
	ReleaseDataMigration(ctx context.Context, name string) error
	DeleteSpaceRole(ctx context.Context, spaceID, roleID int64) error

	UpsertRoomRoleOverride(ctx context.Context, o *SpaceRoomRoleOverride) error
	ListRoomRoleOverrides(ctx context.Context, spaceID, roomID int64) ([]SpaceRoomRoleOverride, error)
	DeleteRoomRoleOverride(ctx context.Context, spaceID, roomID, roleID int64) error

	UpsertRoomUserOverride(ctx context.Context, o *SpaceRoomUserOverride) error
	ListRoomUserOverrides(ctx context.Context, spaceID, roomID int64) ([]SpaceRoomUserOverride, error)
	DeleteRoomUserOverride(ctx context.Context, spaceID, roomID, userID int64) error

	UpdateSpaceIcon(ctx context.Context, spaceID int64, icon string, updatedAt time.Time) error
	UpdateSpaceBanner(ctx context.Context, spaceID int64, banner string, updatedAt time.Time) error
	UpdateSpaceName(ctx context.Context, spaceID int64, name, nameAcronym string, updatedAt time.Time) error

	// DeleteSpace removes the space row and every table keyed only by space_id: roles,
	// bans, the per-space invite listing and the audit log. Rows that live in another
	// partition (each member's spaces_by_user, invites by code, emoji by id) and the
	// space's rooms belong to other repositories or other partitions, so the service
	// clears those first - see Service.DeleteSpace.
	DeleteSpace(ctx context.Context, spaceID int64) error
}

func NewRepository

func NewRepository(session gocqlx.Session) Repository

type RoomMeta

type RoomMeta struct {
	ParentID int64 `json:"parent_id"`
	Synced   bool  `json:"synced"`
	Thread   bool  `json:"thread,omitempty"`
	Private  bool  `json:"private,omitempty"`
}

RoomMeta is the little bit of a room the permission resolver needs beyond its overrides: its parent, whether it is a channel synced to that parent section (follows the section's overrides instead of its own - Discord's category sync), or a thread (always resolves through its parent channel), and whether a thread is private (members only).

type RoomOrder

type RoomOrder struct {
	RoomID   int64
	ParentID *int64
	Position int
}

RoomOrder is one room's place in the channel list.

type RoomOverrides

type RoomOverrides struct {
	Roles []SpaceRoomRoleOverride `json:"roles"`
	Users []SpaceRoomUserOverride `json:"users"`
}

RoomOverrides is every permission override on one room.

type SendPolicy

type SendPolicy struct {
	VerificationLevel   int
	AutomodFlags        int
	AutomodMentionLimit int
	JoinedAt            time.Time
	// HasRole: the member holds a role beyond @everyone (exempt from the verification level).
	HasRole bool
}

SendPolicy is what sending into a space channel is gated on, read in one call by the messages module (SpaceChannelAuth.SendPolicy): the space's settings plus the sender's standing. JoinedAt is zero and HasRole false for a non-member; both are left unread when the space has neither a verification level nor automod on.

func (*SendPolicy) Enforced

func (p *SendPolicy) Enforced() bool

Enforced reports whether anything in the policy can refuse a message.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(repo Repository, roomRepo rooms.Repository, userRepo auth.UserRepository, redis *redis.Client, cfg *config.Config) *Service

func (*Service) AckAll

func (s *Service) AckAll(ctx context.Context, actorID, spaceID int64) error

AckAll marks every text/voice room in a space read up to its current last message, for the caller - Discord's per-server "Mark As Read". Best-effort per room: one room failing to ack doesn't roll back the others.

func (*Service) AddMemberViaOAuth

func (s *Service) AddMemberViaOAuth(ctx context.Context, actorID, spaceID, userID int64) (bool, error)

AddMemberViaOAuth is the `spaces.join` scope: the actor (a bot, typically) with Create Invite adds userID - who authorised it - to the space. Returns false when the user was already a member.

func (*Service) AddMirrorMember

func (s *Service) AddMirrorMember(ctx context.Context, spaceID int64, m MirrorMember) error

AddMirrorMember records a member the origin reported (or a local user whose join just succeeded there) and announces them like a local join, minus the system-room notice, which the origin posts and relays as a message.

func (*Service) AnnounceMirrorMember

func (s *Service) AnnounceMirrorMember(ctx context.Context, spaceID, userID int64)

AnnounceMirrorMember publishes SPACE_MEMBER_ADD / SPACE_CREATE for a member already stored (the joining user after CreateMirror).

func (*Service) ApplyMirrorEmoji

func (s *Service) ApplyMirrorEmoji(ctx context.Context, spaceID int64, e MirrorEmoji, deleted bool) error

ApplyMirrorEmoji stores, renames or deletes a custom emoji the origin reported.

func (*Service) ApplyMirrorReorder

func (s *Service) ApplyMirrorReorder(ctx context.Context, spaceID int64, order []RoomOrder) error

ApplyMirrorReorder moves rooms to the parents and positions the origin reported.

func (*Service) ApplyMirrorRole

func (s *Service) ApplyMirrorRole(ctx context.Context, spaceID int64, role *SpaceRole, deleted bool) error

ApplyMirrorRole stores or deletes a role the origin reported.

func (*Service) ApplyMirrorRoom

func (s *Service) ApplyMirrorRoom(ctx context.Context, spaceID int64, mr MirrorRoom, deleted bool) error

ApplyMirrorRoom stores, updates or deletes a room the origin reported.

func (*Service) ApplyMirrorSpace

func (s *Service) ApplyMirrorSpace(ctx context.Context, sp *Space) error

ApplyMirrorSpace overwrites a mirror's settings with what the origin reported.

func (*Service) BackfillAttachFilesPermission

func (s *Service) BackfillAttachFilesPermission(ctx context.Context) error

BackfillAttachFilesPermission grants Attach Files everywhere Send Messages was already granted when the bit did not exist, so an existing space keeps behaving exactly as it did: every role that can send can attach; a room override that allows sending allows attaching, one that denies sending denies it. New spaces get the bit through permissions.DefaultEveryone. Exactly once per keyspace (claimed in data_migrations), and idempotent if ever re-run: the ORs are no-ops the second time.

func (*Service) BackfillThreadPermissions

func (s *Service) BackfillThreadPermissions(ctx context.Context) error

BackfillThreadPermissions gives existing spaces Discord's thread defaults: every role (and every override) that may send messages may also start public threads and talk in them, and every role that may manage messages may manage threads - so moderators of a space created before threads existed can moderate them without a role edit. Exactly once per keyspace; idempotent if re-run (the ORs are no-ops the second time).

func (*Service) BackfillVoicePermissions

func (s *Service) BackfillVoicePermissions(ctx context.Context) error

BackfillVoicePermissions grants the default voice bits (connect, speak, video, voice activity) to every @everyone role created before voice shipped. New spaces get them through permissions.DefaultEveryone; without this, nobody in an existing space could join a voice room until an admin edited the role by hand. Custom roles are left alone: they only ever add to @everyone, so they need no change.

Exactly once: the name is claimed with a lightweight transaction first, so a restart or a second API replica cannot re-grant bits an admin has since removed on purpose.

func (*Service) BanMember

func (s *Service) BanMember(ctx context.Context, actorID, spaceID, targetUserID int64, reason string) error

BanMember removes a member (if present) and blocks them from rejoining via invite until unbanned.

func (*Service) BotInstallTargets

func (s *Service) BotInstallTargets(ctx context.Context, userID int64) ([]InstallTarget, error)

BotInstallTargets lists the spaces userID may add a bot to, for the consent screen's space picker, each with the permissions that user could grant there.

func (*Service) CanManageEmojis

func (s *Service) CanManageEmojis(ctx context.Context, actorID, spaceID int64) error

CanManageEmojis: Manage Emojis, Manage Space, or Administrator (owner implicitly).

func (*Service) CanManageSpace

func (s *Service) CanManageSpace(ctx context.Context, actorID, spaceID int64) error

CanManageSpace: Manage Space or Administrator (owner implicitly). The icon handler checks this before accepting the upload so an unauthorised member can't fill Nebula.

func (*Service) CountMembers

func (s *Service) CountMembers(ctx context.Context, spaceID int64) (int, error)

CountMembers counts a space's members, no caller check.

func (*Service) CreateEmoji

func (s *Service) CreateEmoji(ctx context.Context, actorID, spaceID, emojiID int64, name, url string, animated bool) (*SpaceEmoji, error)

CreateEmoji records an already-uploaded image as a custom emoji. The handler uploads to Nebula first (after CanManageEmojis) and passes the resulting URL here.

func (*Service) CreateInvite

func (s *Service) CreateInvite(ctx context.Context, actorID, spaceID int64, in *CreateInviteInput) (*SpaceInvite, error)

CreateInvite creates a new invite for the given space. Requires Create Invite (or Administrator) - granted to @everyone by default, matching Discord, so any member can invite unless it's been explicitly restricted. in is optional (nil = never expires, unlimited uses).

func (*Service) CreateMirror

func (s *Service) CreateMirror(ctx context.Context, spec *MirrorSpec) error

CreateMirror stores a space another instance hosts. Publishes nothing: the caller announces the member whose join produced it (AnnounceMirrorMember).

func (*Service) CreateRoom

func (s *Service) CreateRoom(ctx context.Context, actorID, spaceID int64, in *CreateRoomInput) (*rooms.Room, error)

CreateRoom creates a new room or section in the space. Requires ManageRooms.

func (*Service) CreateSpace

func (s *Service) CreateSpace(ctx context.Context, actorID int64, in *CreateSpaceInput) (*Space, error)

CreateSpace creates a new space owned by actorID and publishes SPACE_CREATE to the owner.

func (*Service) CreateSpaceRole

func (s *Service) CreateSpaceRole(ctx context.Context, actorID, spaceID int64, in *CreateSpaceRoleInput) (*SpaceRole, error)

CreateSpaceRole creates a custom role. Requires ManageRoles (or owner via SpacePermissionBase).

func (*Service) DeleteEmoji

func (s *Service) DeleteEmoji(ctx context.Context, actorID, spaceID, emojiID int64) (*SpaceEmoji, error)

DeleteEmoji removes the record and returns it so the handler can delete the image.

func (*Service) DeleteInvite

func (s *Service) DeleteInvite(ctx context.Context, actorID, spaceID int64, code string) error

DeleteInvite revokes an invite. Allowed for whoever can manage invites, and for the member who created it.

func (*Service) DeleteMirror

func (s *Service) DeleteMirror(ctx context.Context, spaceID int64) error

DeleteMirror drops a mirror: the origin deleted the space, or the last local member left it. Members hear SPACE_DELETE / SPACE_LEAVE like a local deletion.

func (*Service) DeleteRoom

func (s *Service) DeleteRoom(ctx context.Context, actorID, spaceID, roomID int64) error

DeleteRoom removes a room and its override rows. Requires ManageRooms.

func (*Service) DeleteRoomRoleOverride

func (s *Service) DeleteRoomRoleOverride(ctx context.Context, actorID, spaceID, roomID, roleID int64) error

DeleteRoomRoleOverride removes an override row. Requires ManageRoles.

func (*Service) DeleteRoomUserOverride

func (s *Service) DeleteRoomUserOverride(ctx context.Context, actorID, spaceID, roomID, targetUserID int64) error

DeleteRoomUserOverride removes a user override row. Requires ManageRoles.

func (*Service) DeleteSpace

func (s *Service) DeleteSpace(ctx context.Context, actorID, spaceID int64, confirmName string) error

DeleteSpace removes a space and everything in it. Owner only, and the caller must echo back the space's name - the same confirmation Discord asks for, here enforced by the server so it holds however the request was made.

Order matters: members go first (each one's spaces_by_user row and their client's own copy), then the things keyed outside the space partition, then the partitions themselves. A failure part-way leaves a smaller space rather than orphaned rows in somebody's sidebar.

func (*Service) DeleteSpaceRole

func (s *Service) DeleteSpaceRole(ctx context.Context, actorID, spaceID, roleID int64) error

DeleteSpaceRole removes a custom role.

func (*Service) EffectiveChannelPermissions

func (s *Service) EffectiveChannelPermissions(ctx context.Context, userID, spaceID, roomID int64) (int64, error)

EffectiveChannelPermissions returns merged space role bits plus room allow/deny overwrites. Space owner gets full channel permissions (bypasses room denies). Administrator bit bypasses room denies.

Roles and overrides come from the space snapshot (one Redis read when cached); the only per-call database read is the member row.

func (*Service) Emojis

func (s *Service) Emojis(ctx context.Context, spaceID int64) ([]SpaceEmoji, error)

Emojis lists a space's custom emoji, no caller check (for the origin's snapshot).

func (*Service) FedRoomChanged

func (s *Service) FedRoomChanged(ctx context.Context, spaceID, roomID int64, deleted bool)

FedRoomChanged tells the instances mirroring a space that one of its rooms changed.

func (*Service) GetInvitePreview

func (s *Service) GetInvitePreview(ctx context.Context, raw string) (*InvitePreview, error)

GetInvitePreview returns public space info for an invite code (no auth required). Does not join. A code of the form code@domain is answered by the instance hosting the space.

func (*Service) GetMentionCounts

func (s *Service) GetMentionCounts(ctx context.Context, userID int64) (map[int64]int, error)

GetMentionCounts returns every room's mention count for a user (one query).

func (*Service) GetSpace

func (s *Service) GetSpace(ctx context.Context, id int64) (*Space, error)

GetSpace returns a space by ID if it exists.

func (*Service) GetSpaceForUser

func (s *Service) GetSpaceForUser(ctx context.Context, userID, spaceID int64) (*Space, error)

GetSpaceForUser returns a space by ID if it exists and the user is a member.

func (*Service) GetSpaces

func (s *Service) GetSpaces(ctx context.Context, ids []int64) ([]*Space, error)

GetSpaces batch-loads spaces (nil entries for ids that do not exist).

func (*Service) GetUserRoomRow

func (s *Service) GetUserRoomRow(ctx context.Context, userID, roomID int64) (*rooms.RoomRow, error)

GetUserRoomRow exposes the rooms_by_user row for a room, for callers (GetRooms) that need to merge per-user read-state into an otherwise room-only response.

func (*Service) InstallBot

func (s *Service) InstallBot(ctx context.Context, actorID, spaceID, botUserID, perms int64) (int64, error)

InstallBot adds botUserID to the space with a role carrying `permissions` (masked to what the actor may grant - never more than they hold themselves), and returns the bits that were actually granted. Already a member: nothing changes and 0 is returned, matching a re-authorisation of an installed bot.

func (*Service) InvalidateSnapshot

func (s *Service) InvalidateSnapshot(ctx context.Context, spaceID int64)

InvalidateSnapshot drops the cached permission snapshot after a change made outside this package (a thread created or deleted changes the room list it is built from).

func (*Service) InviteBelongsToSpace

func (s *Service) InviteBelongsToSpace(ctx context.Context, code string, spaceID int64) (bool, error)

InviteBelongsToSpace is the automod invite-link rule's question: is this code one of the space's own invites (fine to post) or someone else's (blocked).

func (*Service) IsMember

func (s *Service) IsMember(ctx context.Context, spaceID, userID int64) (bool, error)

IsMember implements rooms.SpaceMemberChecker.

func (*Service) JoinByInvite

func (s *Service) JoinByInvite(ctx context.Context, actorID int64, raw string) (*Space, error)

JoinByInvite adds the user to the space referenced by the invite code (idempotent). A code of the form code@domain joins a space another instance hosts, through it.

func (*Service) JoinListed

func (s *Service) JoinListed(ctx context.Context, actorID, spaceID int64) (*Space, error)

JoinListed adds the caller to a space hosted here without an invite: the join behind the Discover directory. The discover module has checked that an administrator listed the space; this applies what an invite join applies - the ban check, the @everyone role, the join notice and events. Already a member: nothing changes.

func (*Service) KickMember

func (s *Service) KickMember(ctx context.Context, actorID, spaceID, targetUserID int64) error

KickMember removes a member from the space. They can rejoin with a new invite.

func (*Service) LeaveSpace

func (s *Service) LeaveSpace(ctx context.Context, actorID, spaceID int64) error

LeaveSpace removes actorID from the space. The owner must transfer ownership or delete the space instead - mirrors Discord, which refuses to let an owner just leave.

func (*Service) ListAuditLog

func (s *Service) ListAuditLog(ctx context.Context, actorID, spaceID, beforeID int64, limit int, action string) ([]SpaceAuditEntry, map[int64]*auth.User, error)

ListAuditLog returns a page of the space's audit log (newest first) plus every user it refers to (actors and user targets). Requires Manage space, Administrator, or owner.

func (*Service) ListBans

func (s *Service) ListBans(ctx context.Context, actorID, spaceID int64) ([]SpaceBan, map[int64]*auth.User, error)

ListBans returns the space's ban list plus the profiles of the banned users and the moderators who banned them. Requires Ban Members (or Administrator, or owner).

func (*Service) ListEmojis

func (s *Service) ListEmojis(ctx context.Context, userID, spaceID int64) ([]SpaceEmoji, error)

ListEmojis returns a space's custom emoji. Members only.

func (*Service) ListEmojisForUser

func (s *Service) ListEmojisForUser(ctx context.Context, userID int64) ([]SpaceEmoji, error)

ListEmojisForUser returns the custom emoji of every space the user is in - what the picker offers, since a member may use any of them anywhere in the app.

func (*Service) ListInvites

func (s *Service) ListInvites(ctx context.Context, actorID, spaceID int64) ([]SpaceInvite, map[int64]*auth.User, error)

ListInvites returns the space's live invites (expired ones are dropped as they are seen) with the inviters' profiles. Requires Manage space, Administrator, or owner.

func (*Service) ListMembers

func (s *Service) ListMembers(ctx context.Context, actorID, spaceID int64) ([]struct {
	Member SpaceMember
	User   *auth.User
}, error)

ListMembers returns members of a space with basic user info. Caller must be a member.

func (*Service) ListRoomRoleOverrides

func (s *Service) ListRoomRoleOverrides(ctx context.Context, actorID, spaceID, roomID int64) ([]SpaceRoomRoleOverride, error)

ListRoomRoleOverrides returns overrides for a channel. Any member.

func (*Service) ListRoomUserOverrides

func (s *Service) ListRoomUserOverrides(ctx context.Context, actorID, spaceID, roomID int64) ([]SpaceRoomUserOverride, error)

ListRoomUserOverrides returns user-specific overrides for a channel. Any member.

func (*Service) ListSpaceMemberUserIDs

func (s *Service) ListSpaceMemberUserIDs(ctx context.Context, spaceID int64) ([]int64, error)

ListSpaceMemberUserIDs returns every member's user ID in a space (for rooms_by_user fan-out on new messages).

func (*Service) ListSpaceMemberUserIDsByRoles

func (s *Service) ListSpaceMemberUserIDsByRoles(ctx context.Context, spaceID int64, roleIDs []int64, includeUnmentionable bool) ([]int64, error)

ListSpaceMemberUserIDsByRoles returns every member holding at least one of the given role IDs, deduped - for expanding a role mention (<@&roleId>) into a mention-count notify set. Two roles are deliberately excluded even if present in roleIDs:

  • the space's own @everyone role, since every member always holds it - expanding it here would silently bypass the same PermMentionEveryone gate the literal "@everyone"/"@here" text path already enforces;
  • any role with Mentionable == false, so an admin's "don't let this role be pinged" setting can't be worked around by hand-typing (or an old message containing) the raw <@&roleId> syntax.

A message that mentions an excluded role still renders it (Mentions/MentionRoles are stored as parsed either way) - it just isn't treated as a notify-worthy mention. With includeUnmentionable (the sender holds Mention @everyone, Discord's licence to ping any role) each role's own mentionable flag is ignored.

func (*Service) ListSpaceRoles

func (s *Service) ListSpaceRoles(ctx context.Context, actorID, spaceID int64) ([]SpaceRole, error)

ListSpaceRoles returns all roles in a space. Any member may list.

func (*Service) ListSpaceRooms

func (s *Service) ListSpaceRooms(ctx context.Context, userID, spaceID int64) ([]*rooms.Room, error)

ListSpaceRooms returns rooms in the space (text and voice). User must be a member.

func (*Service) ListSpaceRoomsWithOverrides

func (s *Service) ListSpaceRoomsWithOverrides(ctx context.Context, userID, spaceID int64) ([]*rooms.Room, *Snapshot, error)

ListSpaceRoomsWithOverrides is ListSpaceRooms plus every room's permission overrides, so a client can compute channel permissions locally (the way a Discord client does from the permission_overwrites on each channel) instead of fetching them per visit.

func (*Service) ListSpaceSummaries

func (s *Service) ListSpaceSummaries(ctx context.Context, userID int64) ([]SpaceSummary, error)

ListSpaceSummaries is every space the user is in with their space-wide permissions.

func (*Service) ListSpacesForUser

func (s *Service) ListSpacesForUser(ctx context.Context, userID int64) ([]SpaceMemberRow, error)

ListSpacesForUser returns all spaces the user is a member of (via spaces_by_user).

func (*Service) LocalMemberCount

func (s *Service) LocalMemberCount(ctx context.Context, spaceID int64) (int, error)

LocalMemberCount counts the members of a space who live on this instance.

func (*Service) LookupEmoji

func (s *Service) LookupEmoji(ctx context.Context, emojiID int64) (*SpaceEmojiRef, error)

LookupEmoji resolves an emoji id seen in a message, for viewers who aren't members of its home space. Nothing here is secret - the image URL is already in every message that uses it.

func (*Service) Members

func (s *Service) Members(ctx context.Context, spaceID int64) ([]MemberWithUser, error)

Members lists a space's members with profiles, no caller check (the origin builds a snapshot for a peer from this; ListMembers is the member-facing form).

func (*Service) MembersPage

func (s *Service) MembersPage(ctx context.Context, spaceID, after int64, limit int) (members []MemberWithUser, next int64, err error)

MembersPage is Members one page at a time: up to limit members whose user id is above after, in id order. next is the cursor for the page after this one, 0 when this was the last (a page that comes back full may still be the last; the next read is then empty).

func (*Service) MoveSpaceChannel

func (s *Service) MoveSpaceChannel(ctx context.Context, actorID, spaceID, channelRoomID int64, newParentSectionID *int64, beforeRoomID *int64) error

MoveSpaceChannel moves a text/voice channel to another parent (nil = top-level) and renumbers affected sibling positions. Use ReorderRooms when the channel stays in the same parent group.

func (*Service) PatchSpace

func (s *Service) PatchSpace(ctx context.Context, actorID, spaceID int64, in *PatchSpaceInput) (*Space, error)

PatchSpace updates space settings. Requires Manage space, Administrator, or owner. Every field is validated against the space (rooms must belong to it and be of the right type), only changed columns are written, and the change set is audited.

func (*Service) PublicCounts

func (s *Service) PublicCounts(ctx context.Context, spaceID int64) (members, online int, err error)

PublicCounts is how many members a space has and how many of them are online now - what a Discover card shows (the widget document computes the same for itself).

func (*Service) PutRoomRoleOverride

func (s *Service) PutRoomRoleOverride(ctx context.Context, actorID, spaceID, roomID, roleID int64, in *PutRoomRoleOverrideInput) error

PutRoomRoleOverride sets allow/deny for a role in a room. Requires ManageRoles: as on Discord, room permission overrides are part of role management, not room management.

func (*Service) PutRoomUserOverride

func (s *Service) PutRoomUserOverride(ctx context.Context, actorID, spaceID, roomID, targetUserID int64, in *PutRoomUserOverrideInput) error

PutRoomUserOverride sets allow/deny for a user in a room. Requires ManageRoles (see PutRoomRoleOverride).

func (*Service) ReconcileMirror

func (s *Service) ReconcileMirror(ctx context.Context, spec *MirrorSpec) (removedRooms []int64, err error)

ReconcileMirror brings an existing mirror in line with a fresh description from the origin: anything the origin no longer has goes, anything new or changed is applied, with the usual gateway events. Returns the ids of rooms removed here so the caller can drop their mappings.

func (*Service) RecordAudit

func (s *Service) RecordAudit(ctx context.Context, spaceID, actorID int64, action, targetID string, changes map[string]AuditChange, reason string)

RecordAudit lets other modules (voice moderation) write to the space's audit log with the same shape as the space's own actions. Best-effort like audit().

func (*Service) RecordForeignEmoji

func (s *Service) RecordForeignEmoji(ctx context.Context, ref *SpaceEmojiRef) error

RecordForeignEmoji remembers an emoji another instance described - a message or a reaction that reached us used it - so LookupEmoji can render it here. It only ever fills a gap: an id this instance already knows (one of its own spaces' emoji, or a mirrored space's) is left untouched, so a peer can add to what we can show but never change what we have. Stored under space 0, "no space of ours".

func (*Service) Region

func (s *Service) Region() string

Region is the gateway region events are published for.

func (*Service) RemoveMirrorMember

func (s *Service) RemoveMirrorMember(ctx context.Context, spaceID, userID int64) error

RemoveMirrorMember applies a removal the origin reported (kick, ban, leave) and tells the member's sessions and the rest of the space, like a local removal.

func (*Service) RenameEmoji

func (s *Service) RenameEmoji(ctx context.Context, actorID, spaceID, emojiID int64, name string) (*SpaceEmoji, error)

func (*Service) ReorderRooms

func (s *Service) ReorderRooms(ctx context.Context, actorID, spaceID int64, in *ReorderRoomsInput) error

ReorderRooms updates positions for a full sibling group (sections, top-level channels, or channels under one section).

func (*Service) Resync

func (s *Service) Resync(ctx context.Context, actorID, spaceID int64) error

Resync brings a mirrored space back in line with its origin, for a member who thinks it has drifted (a relay missed while this instance was down). A space hosted here has nothing to resync from.

func (*Service) RoomFederations

func (s *Service) RoomFederations(ctx context.Context, roomIDs []int64) map[int64]*rooms.Federation

RoomFederations is FederationInfo.RoomFederations, nil-safe.

func (*Service) SendPolicy

func (s *Service) SendPolicy(ctx context.Context, spaceID, userID int64) (*SendPolicy, error)

SendPolicy is the messages module's one read for the verification level and automod (messages.SpaceChannelAuth). The member row is only fetched when the space has something on, so a space with no moderation settings costs one read per message, not two.

func (*Service) SetFederationInfo

func (s *Service) SetFederationInfo(f FederationInfo)

SetFederationInfo wires read-only federation metadata (gateway process).

func (*Service) SetFederator

func (s *Service) SetFederator(f Federator)

SetFederator wires outbound federation (API process).

func (*Service) SetMemberRoles

func (s *Service) SetMemberRoles(ctx context.Context, actorID, spaceID, targetUserID int64, roleIDs []int64) error

SetMemberRoles replaces a member's roles. Caller must ManageRoles. @everyone is added if missing.

Discord-style hierarchy, with the same carve-out Discord makes for yourself:

  • Only the owner may change the owner's roles - but they *may*, which is how the owner gives themselves a colour or a hoisted title.
  • A non-owner may edit their own roles too (you do not outrank yourself), and anyone else whose highest role is below their own.
  • Either way, a role at or above the actor's own highest can neither be handed out nor taken away: roles the target already holds up there are carried over untouched, matching the locked checkboxes the client draws for them.

func (*Service) SetOfficial

func (s *Service) SetOfficial(ctx context.Context, spaceID int64, official bool) (*Space, error)

SetOfficial marks a space as official (an instance-blessed space) or clears the mark. Only the instance module calls this - it does the admin check - so there is no owner gate here. Writes the flag and announces the change to members via SPACE_UPDATE so the badge appears live, and returns the updated space.

func (*Service) SetSystemMessenger

func (s *Service) SetSystemMessenger(fn SystemMessenger)

SetSystemMessenger enables join/leave notices in system rooms.

func (*Service) Snapshot

func (s *Service) Snapshot(ctx context.Context, spaceID int64) (*Snapshot, error)

Snapshot returns the space's full permission snapshot, from Redis when cached.

func (*Service) SpacePermissionBase

func (s *Service) SpacePermissionBase(ctx context.Context, userID, spaceID int64) (int64, error)

SpacePermissionBase is OR of @everyone + member roles (no room overrides). For management checks. The owner and anyone holding Administrator get every space bit, Discord's rule, so a management check never has to remember to test the Administrator bit separately (role hierarchy still applies to them - only the owner is above it).

func (*Service) SpaceRoles

func (s *Service) SpaceRoles(ctx context.Context, spaceID int64) ([]SpaceRole, error)

SpaceRoles returns a space's roles sorted by position, from the snapshot.

func (*Service) SpaceRoomsWithOverrides

func (s *Service) SpaceRoomsWithOverrides(ctx context.Context, spaceID int64) ([]*rooms.Room, *Snapshot, error)

SpaceRoomsWithOverrides is the unchecked form for callers that already know the user is a member (the gateway's READY builder walks spaces_by_user).

func (*Service) StartBirthdayWorker

func (s *Service) StartBirthdayWorker(ctx context.Context)

StartBirthdayWorker runs a daily pass that wishes opted-in members a happy birthday in the birthday channel of each space that configured one. The pass runs at most once per UTC day, guarded by a Redis key so neither a restart nor a second API replica double-announces. It needs Redis (for the guard) and a system messenger (to post); without either it is a no-op.

func (*Service) TakeDownSpace

func (s *Service) TakeDownSpace(ctx context.Context, actorID, spaceID int64) error

TakeDownSpace removes a space on an instance administrator's authority, owner or not. The owner-only rule on DeleteSpace is about a space's *own* hierarchy - Administrator must not be enough to destroy someone's space. The person running the server is above that hierarchy, and this is how abuse reports about a space get acted on. Callers gate it (the instance module checks instance admin); it is recorded in the instance audit log there, not the space's, since the space is about to stop existing.

func (*Service) ThreadStates

func (s *Service) ThreadStates(ctx context.Context, userID int64, threadIDs []int64) (map[int64]ThreadState, error)

ThreadStates is the counts and the viewer's membership for a set of threads (userID 0 = no viewer, nothing joined).

func (*Service) TransferOwnership

func (s *Service) TransferOwnership(ctx context.Context, actorID, spaceID, newOwnerID int64) (*Space, error)

TransferOwnership hands the space to another member. Owner only: an Administrator cannot give the space away, and cannot take it. The previous owner stays a member with whatever roles they hold, which is what Discord does - and since role hierarchy is evaluated against the *current* owner, they lose their ceiling-free standing at once.

func (*Service) UnbanMember

func (s *Service) UnbanMember(ctx context.Context, actorID, spaceID, targetUserID int64) error

UnbanMember lifts a ban, allowing the user to rejoin via invite again. Requires Ban Members (or Administrator, or owner) - same gate as banning, no hierarchy check needed since the target isn't necessarily a current member.

func (*Service) UpdateMirrorMember

func (s *Service) UpdateMirrorMember(ctx context.Context, spaceID int64, m MirrorMember) error

UpdateMirrorMember applies a member change the origin reported (roles).

func (*Service) UpdateRoom

func (s *Service) UpdateRoom(ctx context.Context, actorID, spaceID, roomID int64, in *UpdateRoomInput) error

UpdateRoom applies a partial update to a room's metadata and/or its E2EE setting. Requires ManageRooms.

func (*Service) UpdateSpaceBanner

func (s *Service) UpdateSpaceBanner(ctx context.Context, actorID, spaceID int64, bannerURL string) (*Space, error)

UpdateSpaceBanner sets the space banner URL (caller uploads to Nebula first). Requires PermManageSpace or owner.

func (*Service) UpdateSpaceIcon

func (s *Service) UpdateSpaceIcon(ctx context.Context, actorID, spaceID int64, iconURL string) (*Space, error)

UpdateSpaceIcon sets the space icon URL (caller uploads to Nebula first). Requires PermManageSpace or owner.

func (*Service) UpdateSpaceRole

func (s *Service) UpdateSpaceRole(ctx context.Context, actorID, spaceID, roleID int64, in *UpdateSpaceRoleInput) (*SpaceRole, error)

UpdateSpaceRole updates a role. @everyone may only have permissions changed (not name). Non-owners cannot edit a role at or above their own highest role (Discord-style hierarchy).

func (*Service) UserRoomRows

func (s *Service) UserRoomRows(ctx context.Context, userID int64) (map[int64]*rooms.RoomRow, error)

UserRoomRows returns every rooms_by_user row of a user keyed by room id - one partition read that replaces a GetUserRoomRow per room when building a room list.

func (*Service) ViewersOf

func (s *Service) ViewersOf(ctx context.Context, spaceID, roomID int64) ([]int64, bool)

ViewersOf is who may view a room: (nil, true) when the default @everyone can, so an event may go space-wide; otherwise the ids of the members who can (the owner, Administrators and whoever the overrides let through), for per-member delivery.

func (*Service) VisibleSpaceRooms

func (s *Service) VisibleSpaceRooms(ctx context.Context, userID, spaceID int64) ([]*rooms.Room, *Snapshot, error)

VisibleSpaceRooms is SpaceRoomsWithOverrides filtered to the rooms the user may view (PermViewRoom), resolving every room from the one snapshot and a single member lookup rather than a permission call per room. READY uses it so a client is never told about - and so a bot is never auto-subscribed to - a channel it cannot see; the snapshot is still returned whole so the overrides of the visible rooms can be attached. A non-member (or a lookup error) yields no rooms, never the full list.

func (*Service) Widget

func (s *Service) Widget(ctx context.Context, spaceID int64) (*WidgetInfo, error)

Widget builds the public widget document. ErrSpaceNotFound when the space doesn't exist or its widget is disabled - a disabled widget must look identical to a missing space to an outsider.

type Snapshot

type Snapshot struct {
	OwnerID        int64                   `json:"owner_id"`
	EveryoneRoleID int64                   `json:"everyone_role_id"`
	Roles          []SpaceRole             `json:"roles"`
	Overrides      map[int64]RoomOverrides `json:"overrides"`
	Meta           map[int64]RoomMeta      `json:"meta"`
	BuiltAt        time.Time               `json:"built_at"`
}

Snapshot is everything needed to resolve any member's permissions anywhere in a space without touching the database again: the owner, the @everyone role, every role and every room's overrides. It is what a Discord client holds per guild after GUILD_CREATE, and here it is also what the server consults on every message send/list instead of the five Scylla reads that used to run per request.

Cached in Redis (shared by the API and the gateway) and dropped whenever a role, an override or the room list changes, so it can never be stale for longer than one mutation. The TTL is only a safety net.

func (*Snapshot) EffectiveRoomOverrides

func (snap *Snapshot) EffectiveRoomOverrides(roomID int64) RoomOverrides

EffectiveRoomOverrides returns the overrides that actually apply to a room: its parent section's when the room is synced to it (Discord category sync), otherwise its own. This is the single place inheritance is resolved, so every permission check and the wire form agree.

func (*Snapshot) RoomOverridesFor

func (snap *Snapshot) RoomOverridesFor(roomID int64) RoomOverrides

RoomOverridesFor returns a room's own overrides (zero value when it has none).

type Space

type Space struct {
	ID                int64  `db:"id" json:"id"`
	Name              string `db:"name" json:"name"`
	NameAcronym       string `db:"name_acronym" json:"name_acronym"`
	Description       string `db:"description" json:"description"`
	Icon              string `db:"icon" json:"icon"`
	Banner            string `db:"banner" json:"banner"`
	OwnerID           int64  `db:"owner_id" json:"owner_id"`
	VerificationLevel int    `db:"verification_level" json:"verification_level"`
	// Official marks a space an instance admin has blessed as part of this instance. It is
	// instance-local (never set on a mirror of a remote space) and only the instance module
	// writes it, from the admin dashboard.
	Official              bool     `db:"official" json:"official"`
	DefaultMessageNotif   int      `db:"default_message_notifications" json:"default_message_notifications"`
	ExplicitContentFilter int      `db:"explicit_content_filter" json:"explicit_content_filter"`
	Features              []string `db:"features" json:"features"`
	AFKRoomID             *int64   `db:"afk_room_id" json:"afk_room_id,omitempty"`
	AFKTimeout            int      `db:"afk_timeout" json:"afk_timeout"`
	SystemRoomID          *int64   `db:"system_room_id" json:"system_room_id,omitempty"`
	SystemRoomFlags       int      `db:"system_room_flags" json:"system_room_flags"`
	// Birthdays: the text channel opted-in members are wished a happy birthday in, and an
	// optional custom message template (empty = the client's default greeting).
	BirthdayChannelID   *int64 `db:"birthday_channel_id" json:"birthday_channel_id,omitempty"`
	BirthdayMessage     string `db:"birthday_message" json:"birthday_message,omitempty"`
	RulesRoomID         *int64 `db:"rules_room_id" json:"rules_room_id,omitempty"`
	MaxPresences        int    `db:"max_presences" json:"max_presences"`
	MaxMembers          int    `db:"max_members" json:"max_members"`
	VanityURLCode       string `db:"vanity_url_code" json:"vanity_url_code"`
	PreferredLocale     string `db:"preferred_locale" json:"preferred_locale"`
	PublicUpdatesRoomID *int64 `db:"public_updates_room_id" json:"public_updates_room_id,omitempty"`
	MaxVideoRoomUsers   int    `db:"max_video_room_users" json:"max_video_room_users"`
	EveryoneRoleID      int64  `db:"everyone_role_id" json:"everyone_role_id,omitempty"`
	// Server widget: a public JSON document (GET /spaces/:id/widget.json) with the member
	// and online counts plus, when WidgetRoomID is set, an invite that never expires, for
	// embedding a "join us" card outside Strafe. WidgetInviteCode is that invite; it is
	// created lazily the first time the widget is served and never exposed on the space
	// payload itself (only through the widget document).
	WidgetEnabled    bool   `db:"widget_enabled" json:"widget_enabled"`
	WidgetRoomID     *int64 `db:"widget_room_id" json:"widget_room_id,omitempty"`
	WidgetInviteCode string `db:"widget_invite_code" json:"-"`
	// Light automod, enforced on send with VerificationLevel (see messages/automod.go):
	// which Automod* rules are on, and the mention cap the mass-mention rule uses (0 =
	// DefaultAutomodMentionLimit).
	AutomodFlags        int       `db:"automod_flags" json:"automod_flags"`
	AutomodMentionLimit int       `db:"automod_mention_limit" json:"automod_mention_limit"`
	CreatedAt           time.Time `db:"created_at" json:"created_at"`
	UpdatedAt           time.Time `db:"updated_at" json:"updated_at"`
	// Federation is set on a mirror of a space another instance hosts (see federation.go);
	// not stored, filled from the federation mapping on read.
	Federation *SpaceFederation `db:"-" json:"federation,omitempty"`
}

Space is a Discord-style server.

type SpaceAuditEntry

type SpaceAuditEntry struct {
	SpaceID    int64     `db:"space_id"`
	ID         int64     `db:"id"`
	ActionType string    `db:"action_type"`
	UserID     int64     `db:"user_id"`
	TargetID   string    `db:"target_id"`
	Changes    string    `db:"changes"`
	Reason     string    `db:"reason"`
	CreatedAt  time.Time `db:"created_at"`
}

SpaceAuditEntry is one row of space_audit_log: who did what to which target, with the before/after values (Changes is a JSON object of field -> {old, new}) and an optional reason. Newest first by ID (snowflake).

type SpaceBan

type SpaceBan struct {
	SpaceID   int64     `db:"space_id" json:"-"`
	UserID    int64     `db:"user_id" json:"user_id"`
	Reason    string    `db:"reason" json:"reason,omitempty"`
	BannedBy  int64     `db:"banned_by" json:"banned_by"`
	CreatedAt time.Time `db:"created_at" json:"created_at"`
}

SpaceBan blocks a user from rejoining a space via invite. Maps to the space_bans table (present in migrations/013_spaces.cql since the schema was first written, but never wired up in code until now).

type SpaceEmoji

type SpaceEmoji struct {
	SpaceID   int64     `db:"space_id"`
	ID        int64     `db:"id"`
	Name      string    `db:"name"`
	ImageHash string    `db:"image_hash"`
	URL       string    `db:"url"`
	Animated  bool      `db:"animated"`
	CreatorID int64     `db:"creator_id"`
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

SpaceEmoji is a custom emoji uploaded to a space. Used in message text as <:name:id> (or <a:name:id> when animated); the image lives on Nebula at URL.

type SpaceEmojiRef

type SpaceEmojiRef struct {
	ID       int64  `db:"id"`
	SpaceID  int64  `db:"space_id"`
	Name     string `db:"name"`
	URL      string `db:"url"`
	Animated bool   `db:"animated"`
}

SpaceEmojiRef is the space_emoji_by_id row: enough for any client to render an emoji it sees in a message, regardless of whether it's a member of the home space.

type SpaceFederation

type SpaceFederation struct {
	OriginDomain string `json:"origin_domain"`
	OriginID     string `json:"origin_id"`
}

SpaceFederation is a mirrored space's global identity: the instance hosting it and the id it has there. Absent on spaces this instance hosts.

type SpaceInvite

type SpaceInvite struct {
	Code        string     `db:"code" json:"code"`
	SpaceID     int64      `db:"space_id" json:"space_id"`
	InviterID   int64      `db:"inviter_id" json:"inviter_id"`
	MaxUses     int        `db:"max_uses" json:"max_uses"`
	CurrentUses int        `db:"current_uses" json:"current_uses"`
	ExpiresAt   *time.Time `db:"expires_at" json:"expires_at,omitempty"`
	CreatedAt   time.Time  `db:"created_at" json:"created_at"`
}

SpaceInvite is an invite link/code for joining a space. MaxUses 0 = unlimited; ExpiresAt nil = never. Written to both space_invites (per-space listing) and space_invite_by_code (the join lookup).

func (*SpaceInvite) Exhausted

func (i *SpaceInvite) Exhausted() bool

Exhausted reports whether the invite has hit its use cap.

func (*SpaceInvite) Expired

func (i *SpaceInvite) Expired(now time.Time) bool

Expired reports whether the invite's expiry has passed.

type SpaceMember

type SpaceMember struct {
	SpaceID  int64     `db:"space_id"`
	UserID   int64     `db:"user_id"`
	RoleIDs  []int64   `db:"role_ids"`
	Nickname string    `db:"nickname"`
	JoinedAt time.Time `db:"joined_at"`
}

SpaceMember is a user's membership in a space.

type SpaceMemberRow

type SpaceMemberRow struct {
	UserID   int64     `db:"user_id"`
	SpaceID  int64     `db:"space_id"`
	JoinedAt time.Time `db:"joined_at"`
}

SpaceMemberRow is spaces_by_user row.

type SpaceRole

type SpaceRole struct {
	SpaceID     int64  `db:"space_id" json:"-"`
	ID          int64  `db:"id" json:"id"`
	Name        string `db:"name" json:"name"`
	Permissions int64  `db:"permissions" json:"permissions"`
	Position    int    `db:"position" json:"position"`
	Color       int    `db:"color" json:"color"`
	Hoist       bool   `db:"hoist" json:"hoist"`
	Mentionable bool   `db:"mentionable" json:"mentionable"`
	// BotID marks a role a bot install created (the bot's user id): editable, but never
	// deletable or assignable to anyone else, and removed when the bot leaves.
	BotID     int64     `db:"bot_id" json:"bot_id,omitempty"`
	CreatedAt time.Time `db:"created_at" json:"created_at"`
	UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

SpaceRole is a permission-bearing role in a space (@everyone or custom).

func (*SpaceRole) IsManaged

func (r *SpaceRole) IsManaged() bool

IsManaged reports whether the role belongs to a bot (see BotID).

type SpaceRoomRoleOverride

type SpaceRoomRoleOverride struct {
	SpaceID   int64     `db:"space_id"`
	RoomID    int64     `db:"room_id"`
	RoleID    int64     `db:"role_id"`
	Allow     int64     `db:"allow_mask"`
	Deny      int64     `db:"deny_mask"`
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

SpaceRoomRoleOverride is a Discord-style allow/deny mask for one role in one room.

type SpaceRoomUserOverride

type SpaceRoomUserOverride struct {
	SpaceID   int64     `db:"space_id"`
	RoomID    int64     `db:"room_id"`
	UserID    int64     `db:"user_id"`
	Allow     int64     `db:"allow_mask"`
	Deny      int64     `db:"deny_mask"`
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

SpaceRoomUserOverride is a Discord-style allow/deny mask for one user in one room.

type SpaceSummary

type SpaceSummary struct {
	Space       *Space
	Owner       bool
	Permissions int64
}

SpaceSummary is the light per-space row of GET /users/@me/spaces: what an OAuth2 app with the `spaces` scope (or a bot listing where it lives) needs, and nothing more.

type SystemMessenger

type SystemMessenger func(ctx context.Context, roomID int64, recipientIDs []int64, eventType, payload string) (int64, error)

SystemMessenger posts a server-generated message into a room and returns its id. The spaces service doesn't own message storage; routes wire in the same closure the rooms service uses for "X added Y" notices.

type ThreadState

type ThreadState struct {
	MessageCount int64
	MemberCount  int
	Joined       bool
}

ThreadState is the per-viewer part of a thread's wire form: Discord's message_count and member_count, and whether the viewer has joined.

type UpdateRoomInput

type UpdateRoomInput struct {
	Name            *string `json:"name,omitempty"`
	Topic           *string `json:"topic,omitempty"`
	SlowmodeSeconds *int    `json:"slowmode_seconds,omitempty"`
	// E2EEEnabled turns end-to-end encryption on or off for a text room. Off by default
	// for space rooms (see createDefaultSpaceRooms); when on, the server only ever stores
	// Megolm ciphertext for the room and clients must fan the room key out to every
	// current space member - see messages.Service.roomE2EEOff for the storage rule.
	E2EEEnabled *bool `json:"e2ee_enabled,omitempty"`
	// Voice rooms only: how many people may be connected at once (0 = unlimited, max 99)
	// and the audio bitrate in bits per second (8000-384000; 0 = the default 64 kbps).
	UserLimit *int `json:"user_limit,omitempty"`
	Bitrate   *int `json:"bitrate,omitempty"`
	// PermissionsSynced syncs a text/voice channel to its parent section's permission
	// overrides (Discord category sync), or unsyncs it. Syncing clears the channel's own
	// overrides; unsyncing copies the section's current overrides down so nothing changes.
	PermissionsSynced *bool `json:"permissions_synced,omitempty"`
}

UpdateRoomInput is a partial update: every field is optional and only the ones present in the request body are written, so a client can flip one setting (e.g. e2ee_enabled) without having to echo back every other field it isn't changing.

type UpdateSpaceRoleInput

type UpdateSpaceRoleInput struct {
	Name        *string `json:"name,omitempty"`
	Permissions *int64  `json:"permissions,omitempty"`
	Position    *int    `json:"position,omitempty"`
	Color       *int    `json:"color,omitempty"`
	Hoist       *bool   `json:"hoist,omitempty"`
	Mentionable *bool   `json:"mentionable,omitempty"`
}

UpdateSpaceRoleInput patches a role (cannot rename @everyone).

type WidgetInfo

type WidgetInfo struct {
	ID            int64
	Name          string
	Icon          string
	Description   string
	MemberCount   int
	PresenceCount int
	// InviteCode is empty when no widget room is configured.
	InviteCode string
	RoomName   string
}

WidgetInfo is the public widget document for a space: enough to render a "join us" card outside Strafe. Nothing member-identifying is included.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL