Documentation
¶
Index ¶
- Constants
- Variables
- func AttachFederation(m map[string]interface{}, f *rooms.Federation) map[string]interface{}
- func AttachOverrides(m map[string]interface{}, o RoomOverrides) map[string]interface{}
- func AttachThreadState(m map[string]interface{}, st ThreadState)
- func FormatInviteCode(code, domain string) string
- func HTTPError(err error) (status int, message string, ok bool)
- func IsUserTargetAction(action string) bool
- func OverrideMaps(o RoomOverrides) (roles []map[string]interface{}, users []map[string]interface{})
- func ParseInviteCode(raw string) (code, domain string, err error)
- func RoleMap(r *SpaceRole) map[string]interface{}
- func RoleMaps(roles []SpaceRole) []map[string]interface{}
- func RoomMap(r *rooms.Room) map[string]interface{}
- func ThreadMap(r *rooms.Room) map[string]interface{}
- func ValidateEmojiName(name string) (string, error)
- type AuditChange
- type CreateInviteInput
- type CreateRoomInput
- type CreateSpaceInput
- type CreateSpaceRoleInput
- type FederationInfo
- type Federator
- type Handler
- func (h *Handler) AckAll(c fiber.Ctx) error
- func (h *Handler) BanMember(c fiber.Ctx) error
- func (h *Handler) Create(c fiber.Ctx) error
- func (h *Handler) CreateInvite(c fiber.Ctx) error
- func (h *Handler) CreateRole(c fiber.Ctx) error
- func (h *Handler) DeleteEmoji(c fiber.Ctx) error
- func (h *Handler) DeleteInvite(c fiber.Ctx) error
- func (h *Handler) DeleteRole(c fiber.Ctx) error
- func (h *Handler) DeleteRoom(c fiber.Ctx) error
- func (h *Handler) DeleteRoomOverride(c fiber.Ctx) error
- func (h *Handler) DeleteRoomUserOverride(c fiber.Ctx) error
- func (h *Handler) DeleteSpace(c fiber.Ctx) error
- func (h *Handler) Get(c fiber.Ctx) error
- func (h *Handler) GetEmojiByID(c fiber.Ctx) error
- func (h *Handler) GetRooms(c fiber.Ctx) error
- func (h *Handler) InvitePreview(c fiber.Ctx) error
- func (h *Handler) JoinByInvite(c fiber.Ctx) error
- func (h *Handler) KickMember(c fiber.Ctx) error
- func (h *Handler) Leave(c fiber.Ctx) error
- func (h *Handler) List(c fiber.Ctx) error
- func (h *Handler) ListAuditLog(c fiber.Ctx) error
- func (h *Handler) ListBans(c fiber.Ctx) error
- func (h *Handler) ListEmojis(c fiber.Ctx) error
- func (h *Handler) ListInvites(c fiber.Ctx) error
- func (h *Handler) ListMyEmojis(c fiber.Ctx) error
- func (h *Handler) ListRoles(c fiber.Ctx) error
- func (h *Handler) ListRoomOverrides(c fiber.Ctx) error
- func (h *Handler) ListRoomUserOverrides(c fiber.Ctx) error
- func (h *Handler) Members(c fiber.Ctx) error
- func (h *Handler) MoveChannel(c fiber.Ctx) error
- func (h *Handler) MySpaces(c fiber.Ctx) error
- func (h *Handler) PatchEmoji(c fiber.Ctx) error
- func (h *Handler) PatchRoom(c fiber.Ctx) error
- func (h *Handler) PatchSpace(c fiber.Ctx) error
- func (h *Handler) PostEmoji(c fiber.Ctx) error
- func (h *Handler) PostRoom(c fiber.Ctx) error
- func (h *Handler) PostSpaceBanner(c fiber.Ctx) error
- func (h *Handler) PostSpaceIcon(c fiber.Ctx) error
- func (h *Handler) PutMemberRoles(c fiber.Ctx) error
- func (h *Handler) PutRoomOverride(c fiber.Ctx) error
- func (h *Handler) PutRoomUserOverride(c fiber.Ctx) error
- func (h *Handler) ReorderRooms(c fiber.Ctx) error
- func (h *Handler) Resync(c fiber.Ctx) error
- func (h *Handler) TransferOwnership(c fiber.Ctx) error
- func (h *Handler) UnbanMember(c fiber.Ctx) error
- func (h *Handler) UpdateRole(c fiber.Ctx) error
- func (h *Handler) Widget(c fiber.Ctx) error
- type InstallTarget
- type InvitePreview
- type MemberWithUser
- type MirrorEmoji
- type MirrorMember
- type MirrorRoom
- type MirrorSpec
- type MoveChannelInput
- type OriginError
- type PatchSpaceInput
- type PutRoomRoleOverrideInput
- type PutRoomUserOverrideInput
- type ReorderRoomsInput
- type Repository
- type RoomMeta
- type RoomOrder
- type RoomOverrides
- type SendPolicy
- type Service
- func (s *Service) AckAll(ctx context.Context, actorID, spaceID int64) error
- func (s *Service) AddMemberViaOAuth(ctx context.Context, actorID, spaceID, userID int64) (bool, error)
- func (s *Service) AddMirrorMember(ctx context.Context, spaceID int64, m MirrorMember) error
- func (s *Service) AnnounceMirrorMember(ctx context.Context, spaceID, userID int64)
- func (s *Service) ApplyMirrorEmoji(ctx context.Context, spaceID int64, e MirrorEmoji, deleted bool) error
- func (s *Service) ApplyMirrorReorder(ctx context.Context, spaceID int64, order []RoomOrder) error
- func (s *Service) ApplyMirrorRole(ctx context.Context, spaceID int64, role *SpaceRole, deleted bool) error
- func (s *Service) ApplyMirrorRoom(ctx context.Context, spaceID int64, mr MirrorRoom, deleted bool) error
- func (s *Service) ApplyMirrorSpace(ctx context.Context, sp *Space) error
- func (s *Service) BackfillAttachFilesPermission(ctx context.Context) error
- func (s *Service) BackfillThreadPermissions(ctx context.Context) error
- func (s *Service) BackfillVoicePermissions(ctx context.Context) error
- func (s *Service) BanMember(ctx context.Context, actorID, spaceID, targetUserID int64, reason string) error
- func (s *Service) BotInstallTargets(ctx context.Context, userID int64) ([]InstallTarget, error)
- func (s *Service) CanManageEmojis(ctx context.Context, actorID, spaceID int64) error
- func (s *Service) CanManageSpace(ctx context.Context, actorID, spaceID int64) error
- func (s *Service) CountMembers(ctx context.Context, spaceID int64) (int, error)
- func (s *Service) CreateEmoji(ctx context.Context, actorID, spaceID, emojiID int64, name, url string, ...) (*SpaceEmoji, error)
- func (s *Service) CreateInvite(ctx context.Context, actorID, spaceID int64, in *CreateInviteInput) (*SpaceInvite, error)
- func (s *Service) CreateMirror(ctx context.Context, spec *MirrorSpec) error
- func (s *Service) CreateRoom(ctx context.Context, actorID, spaceID int64, in *CreateRoomInput) (*rooms.Room, error)
- func (s *Service) CreateSpace(ctx context.Context, actorID int64, in *CreateSpaceInput) (*Space, error)
- func (s *Service) CreateSpaceRole(ctx context.Context, actorID, spaceID int64, in *CreateSpaceRoleInput) (*SpaceRole, error)
- func (s *Service) DeleteEmoji(ctx context.Context, actorID, spaceID, emojiID int64) (*SpaceEmoji, error)
- func (s *Service) DeleteInvite(ctx context.Context, actorID, spaceID int64, code string) error
- func (s *Service) DeleteMirror(ctx context.Context, spaceID int64) error
- func (s *Service) DeleteRoom(ctx context.Context, actorID, spaceID, roomID int64) error
- func (s *Service) DeleteRoomRoleOverride(ctx context.Context, actorID, spaceID, roomID, roleID int64) error
- func (s *Service) DeleteRoomUserOverride(ctx context.Context, actorID, spaceID, roomID, targetUserID int64) error
- func (s *Service) DeleteSpace(ctx context.Context, actorID, spaceID int64, confirmName string) error
- func (s *Service) DeleteSpaceRole(ctx context.Context, actorID, spaceID, roleID int64) error
- func (s *Service) EffectiveChannelPermissions(ctx context.Context, userID, spaceID, roomID int64) (int64, error)
- func (s *Service) Emojis(ctx context.Context, spaceID int64) ([]SpaceEmoji, error)
- func (s *Service) FedRoomChanged(ctx context.Context, spaceID, roomID int64, deleted bool)
- func (s *Service) GetInvitePreview(ctx context.Context, raw string) (*InvitePreview, error)
- func (s *Service) GetMentionCounts(ctx context.Context, userID int64) (map[int64]int, error)
- func (s *Service) GetSpace(ctx context.Context, id int64) (*Space, error)
- func (s *Service) GetSpaceForUser(ctx context.Context, userID, spaceID int64) (*Space, error)
- func (s *Service) GetSpaces(ctx context.Context, ids []int64) ([]*Space, error)
- func (s *Service) GetUserRoomRow(ctx context.Context, userID, roomID int64) (*rooms.RoomRow, error)
- func (s *Service) InstallBot(ctx context.Context, actorID, spaceID, botUserID, perms int64) (int64, error)
- func (s *Service) InvalidateSnapshot(ctx context.Context, spaceID int64)
- func (s *Service) InviteBelongsToSpace(ctx context.Context, code string, spaceID int64) (bool, error)
- func (s *Service) IsMember(ctx context.Context, spaceID, userID int64) (bool, error)
- func (s *Service) JoinByInvite(ctx context.Context, actorID int64, raw string) (*Space, error)
- func (s *Service) JoinListed(ctx context.Context, actorID, spaceID int64) (*Space, error)
- func (s *Service) KickMember(ctx context.Context, actorID, spaceID, targetUserID int64) error
- func (s *Service) LeaveSpace(ctx context.Context, actorID, spaceID int64) error
- func (s *Service) ListAuditLog(ctx context.Context, actorID, spaceID, beforeID int64, limit int, ...) ([]SpaceAuditEntry, map[int64]*auth.User, error)
- func (s *Service) ListBans(ctx context.Context, actorID, spaceID int64) ([]SpaceBan, map[int64]*auth.User, error)
- func (s *Service) ListEmojis(ctx context.Context, userID, spaceID int64) ([]SpaceEmoji, error)
- func (s *Service) ListEmojisForUser(ctx context.Context, userID int64) ([]SpaceEmoji, error)
- func (s *Service) ListInvites(ctx context.Context, actorID, spaceID int64) ([]SpaceInvite, map[int64]*auth.User, error)
- func (s *Service) ListMembers(ctx context.Context, actorID, spaceID int64) ([]struct{ ... }, error)
- func (s *Service) ListRoomRoleOverrides(ctx context.Context, actorID, spaceID, roomID int64) ([]SpaceRoomRoleOverride, error)
- func (s *Service) ListRoomUserOverrides(ctx context.Context, actorID, spaceID, roomID int64) ([]SpaceRoomUserOverride, error)
- func (s *Service) ListSpaceMemberUserIDs(ctx context.Context, spaceID int64) ([]int64, error)
- func (s *Service) ListSpaceMemberUserIDsByRoles(ctx context.Context, spaceID int64, roleIDs []int64, includeUnmentionable bool) ([]int64, error)
- func (s *Service) ListSpaceRoles(ctx context.Context, actorID, spaceID int64) ([]SpaceRole, error)
- func (s *Service) ListSpaceRooms(ctx context.Context, userID, spaceID int64) ([]*rooms.Room, error)
- func (s *Service) ListSpaceRoomsWithOverrides(ctx context.Context, userID, spaceID int64) ([]*rooms.Room, *Snapshot, error)
- func (s *Service) ListSpaceSummaries(ctx context.Context, userID int64) ([]SpaceSummary, error)
- func (s *Service) ListSpacesForUser(ctx context.Context, userID int64) ([]SpaceMemberRow, error)
- func (s *Service) LocalMemberCount(ctx context.Context, spaceID int64) (int, error)
- func (s *Service) LookupEmoji(ctx context.Context, emojiID int64) (*SpaceEmojiRef, error)
- func (s *Service) Members(ctx context.Context, spaceID int64) ([]MemberWithUser, error)
- func (s *Service) MembersPage(ctx context.Context, spaceID, after int64, limit int) (members []MemberWithUser, next int64, err error)
- func (s *Service) MoveSpaceChannel(ctx context.Context, actorID, spaceID, channelRoomID int64, ...) error
- func (s *Service) PatchSpace(ctx context.Context, actorID, spaceID int64, in *PatchSpaceInput) (*Space, error)
- func (s *Service) PublicCounts(ctx context.Context, spaceID int64) (members, online int, err error)
- func (s *Service) PutRoomRoleOverride(ctx context.Context, actorID, spaceID, roomID, roleID int64, ...) error
- func (s *Service) PutRoomUserOverride(ctx context.Context, actorID, spaceID, roomID, targetUserID int64, ...) error
- func (s *Service) ReconcileMirror(ctx context.Context, spec *MirrorSpec) (removedRooms []int64, err error)
- func (s *Service) RecordAudit(ctx context.Context, spaceID, actorID int64, action, targetID string, ...)
- func (s *Service) RecordForeignEmoji(ctx context.Context, ref *SpaceEmojiRef) error
- func (s *Service) Region() string
- func (s *Service) RemoveMirrorMember(ctx context.Context, spaceID, userID int64) error
- func (s *Service) RenameEmoji(ctx context.Context, actorID, spaceID, emojiID int64, name string) (*SpaceEmoji, error)
- func (s *Service) ReorderRooms(ctx context.Context, actorID, spaceID int64, in *ReorderRoomsInput) error
- func (s *Service) Resync(ctx context.Context, actorID, spaceID int64) error
- func (s *Service) RoomFederations(ctx context.Context, roomIDs []int64) map[int64]*rooms.Federation
- func (s *Service) SendPolicy(ctx context.Context, spaceID, userID int64) (*SendPolicy, error)
- func (s *Service) SetFederationInfo(f FederationInfo)
- func (s *Service) SetFederator(f Federator)
- func (s *Service) SetMemberRoles(ctx context.Context, actorID, spaceID, targetUserID int64, roleIDs []int64) error
- func (s *Service) SetOfficial(ctx context.Context, spaceID int64, official bool) (*Space, error)
- func (s *Service) SetSystemMessenger(fn SystemMessenger)
- func (s *Service) Snapshot(ctx context.Context, spaceID int64) (*Snapshot, error)
- func (s *Service) SpacePermissionBase(ctx context.Context, userID, spaceID int64) (int64, error)
- func (s *Service) SpaceRoles(ctx context.Context, spaceID int64) ([]SpaceRole, error)
- func (s *Service) SpaceRoomsWithOverrides(ctx context.Context, spaceID int64) ([]*rooms.Room, *Snapshot, error)
- func (s *Service) StartBirthdayWorker(ctx context.Context)
- func (s *Service) TakeDownSpace(ctx context.Context, actorID, spaceID int64) error
- func (s *Service) ThreadStates(ctx context.Context, userID int64, threadIDs []int64) (map[int64]ThreadState, error)
- func (s *Service) TransferOwnership(ctx context.Context, actorID, spaceID, newOwnerID int64) (*Space, error)
- func (s *Service) UnbanMember(ctx context.Context, actorID, spaceID, targetUserID int64) error
- func (s *Service) UpdateMirrorMember(ctx context.Context, spaceID int64, m MirrorMember) error
- func (s *Service) UpdateRoom(ctx context.Context, actorID, spaceID, roomID int64, in *UpdateRoomInput) error
- func (s *Service) UpdateSpaceBanner(ctx context.Context, actorID, spaceID int64, bannerURL string) (*Space, error)
- func (s *Service) UpdateSpaceIcon(ctx context.Context, actorID, spaceID int64, iconURL string) (*Space, error)
- func (s *Service) UpdateSpaceRole(ctx context.Context, actorID, spaceID, roleID int64, in *UpdateSpaceRoleInput) (*SpaceRole, error)
- func (s *Service) UserRoomRows(ctx context.Context, userID int64) (map[int64]*rooms.RoomRow, error)
- func (s *Service) ViewersOf(ctx context.Context, spaceID, roomID int64) ([]int64, bool)
- func (s *Service) VisibleSpaceRooms(ctx context.Context, userID, spaceID int64) ([]*rooms.Room, *Snapshot, error)
- func (s *Service) Widget(ctx context.Context, spaceID int64) (*WidgetInfo, error)
- type Snapshot
- type Space
- type SpaceAuditEntry
- type SpaceBan
- type SpaceEmoji
- type SpaceEmojiRef
- type SpaceFederation
- type SpaceInvite
- type SpaceMember
- type SpaceMemberRow
- type SpaceRole
- type SpaceRoomRoleOverride
- type SpaceRoomUserOverride
- type SpaceSummary
- type SystemMessenger
- type ThreadState
- type UpdateRoomInput
- type UpdateSpaceRoleInput
- type WidgetInfo
Constants ¶
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".
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".
const ( DefaultNotifAllMessages = 0 DefaultNotifOnlyMentions = 1 )
Default notification levels (spaces.default_message_notifications), Discord's values.
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.
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.
const ( MaxInviteAgeSeconds = 30 * 24 * 3600 MaxInviteUses = 1000 )
const ( MaxSpaceNameRunes = 100 MaxSpaceDescriptionRunes = 1000 MaxBanReasonRunes = 512 )
const ( MaxRoleNameRunes = 100 MaxRoomNameRunes = 100 MaxTopicRunes = 1024 )
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).
const EveryoneRoleName = "@everyone"
EveryoneRoleName is the fixed display name for the built-in @everyone role (cannot rename/delete).
const MaxEmojisPerSpace = 50
MaxEmojisPerSpace caps a space's custom emoji.
Variables ¶
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 = 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") )
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") )
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") )
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") )
var ( ErrInvalidBot = errors.New("that account is not a bot") ErrNotLocalUser = errors.New("that user does not belong to this instance") )
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") )
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") )
AFKTimeouts are the accepted values for spaces.afk_timeout (seconds) - Discord's set.
var ErrInvalidInvite = errors.New("invite expiry must be at most 30 days and uses at most 1000")
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 ¶
FormatInviteCode is the inverse of ParseInviteCode.
func HTTPError ¶
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 ¶
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 ¶
ParseInviteCode splits "code@domain" (domain optional) as people type it. The domain is the hosting instance; "" means this one.
func RoleMap ¶
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 RoomMap ¶
RoomMap is the wire form of a space room, shared by REST, READY and SPACE_ROOM_CREATE.
func ThreadMap ¶
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 ¶
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 (*Handler) AckAll ¶
AckAll marks every text/voice room in the space read for the caller. POST /spaces/:id/ack-all.
func (*Handler) BanMember ¶
BanMember POST /spaces/:id/bans/:userId. Body: { "reason": "..." } (optional).
func (*Handler) Create ¶
Create creates a new space. POST /spaces. Body: { "name": "...", "description": "...", "icon": "..." }.
func (*Handler) CreateInvite ¶
CreateInvite creates a new invite for the space. POST /spaces/:id/invites.
func (*Handler) CreateRole ¶
CreateRole POST /spaces/:id/roles
func (*Handler) DeleteEmoji ¶
DeleteEmoji DELETE /spaces/:id/emojis/:emojiId
func (*Handler) DeleteInvite ¶
DeleteInvite DELETE /spaces/:id/invites/:code - revoke an invite (Manage space, or its creator).
func (*Handler) DeleteRole ¶
DeleteRole DELETE /spaces/:id/roles/:roleId
func (*Handler) DeleteRoom ¶
DeleteRoom DELETE /spaces/:id/rooms/:roomId
func (*Handler) DeleteRoomOverride ¶
DeleteRoomOverride DELETE /spaces/:id/rooms/:roomId/overrides/:roleId
func (*Handler) DeleteRoomUserOverride ¶
DeleteRoomUserOverride DELETE /spaces/:id/rooms/:roomId/overrides/users/:userId
func (*Handler) DeleteSpace ¶
DeleteSpace DELETE /spaces/:id. Body: { "name": "..." } - the space's own name, typed back as confirmation. Owner only, and irreversible.
func (*Handler) GetEmojiByID ¶
GetEmojiByID GET /emojis/:emojiId - resolve an emoji used outside its home space.
func (*Handler) GetRooms ¶
GetRooms returns rooms in the space (text and voice). User must be a member. GET /spaces/:id/rooms.
func (*Handler) InvitePreview ¶
InvitePreview returns public space info for an invite (no auth). GET /spaces/invites/:code.
func (*Handler) JoinByInvite ¶
JoinByInvite joins a space via invite code. POST /spaces/invites/:code/join.
func (*Handler) KickMember ¶
KickMember DELETE /spaces/:id/members/:userId
func (*Handler) ListAuditLog ¶
ListAuditLog GET /spaces/:id/audit-log?before=<entry id>&limit=50&action=<type>. Response: { entries: [...], users: { "<id>": {...} } }. Manage space.
func (*Handler) ListEmojis ¶
ListEmojis GET /spaces/:id/emojis
func (*Handler) ListInvites ¶
ListInvites GET /spaces/:id/invites - live invites with their inviters. Manage space.
func (*Handler) ListMyEmojis ¶
ListMyEmojis GET /emojis - every custom emoji from every space the caller is in.
func (*Handler) ListRoomOverrides ¶
ListRoomOverrides GET /spaces/:id/rooms/:roomId/overrides
func (*Handler) ListRoomUserOverrides ¶
ListRoomUserOverrides GET /spaces/:id/rooms/:roomId/overrides/users
func (*Handler) Members ¶
Members returns members of a space with basic user info. GET /spaces/:id/members.
func (*Handler) MoveChannel ¶
MoveChannel POST /spaces/:id/rooms/move — body: MoveChannelInput (registered before /rooms/:roomId).
func (*Handler) MySpaces ¶
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 ¶
PatchEmoji PATCH /spaces/:id/emojis/:emojiId {"name": "..."}
func (*Handler) PatchSpace ¶
PatchSpace PATCH /spaces/:id — update space metadata (e.g. name).
func (*Handler) PostEmoji ¶
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) PostSpaceBanner ¶
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 ¶
PostSpaceIcon uploads an image to Nebula and sets the space icon. POST /spaces/:id/icon (multipart field "file").
func (*Handler) PutMemberRoles ¶
PutMemberRoles PUT /spaces/:id/members/:userId/roles body: { "role_ids": ["snowflake", ...] } (without @everyone; server merges)
func (*Handler) PutRoomOverride ¶
PutRoomOverride PUT /spaces/:id/rooms/:roomId/overrides/:roleId
func (*Handler) PutRoomUserOverride ¶
PutRoomUserOverride PUT /spaces/:id/rooms/:roomId/overrides/users/:userId
func (*Handler) ReorderRooms ¶
ReorderRooms POST /spaces/:id/rooms/reorder — body: ReorderRoomsInput (registered before generic /rooms routes).
func (*Handler) Resync ¶
Resync POST /spaces/:id/resync - reconcile a mirrored space with its origin.
func (*Handler) TransferOwnership ¶
TransferOwnership POST /spaces/:id/transfer-ownership. Body: { "user_id": "..." }. Owner only; the new owner must already be a member.
func (*Handler) UnbanMember ¶
UnbanMember DELETE /spaces/:id/bans/:userId
func (*Handler) UpdateRole ¶
UpdateRole PATCH /spaces/:id/roles/:roleId
type InstallTarget ¶
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 ¶
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 ¶
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 ¶
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 ¶
PutRoomRoleOverrideInput sets channel overrides for a role in a room.
type PutRoomUserOverrideInput ¶
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 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ApplyMirrorSpace overwrites a mirror's settings with what the origin reported.
func (*Service) BackfillAttachFilesPermission ¶
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 ¶
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 ¶
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 ¶
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 ¶
CanManageEmojis: Manage Emojis, Manage Space, or Administrator (owner implicitly).
func (*Service) CanManageSpace ¶
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 ¶
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 ¶
DeleteInvite revokes an invite. Allowed for whoever can manage invites, and for the member who created it.
func (*Service) DeleteMirror ¶
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 ¶
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 ¶
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 ¶
Emojis lists a space's custom emoji, no caller check (for the origin's snapshot).
func (*Service) FedRoomChanged ¶
FedRoomChanged tells the instances mirroring a space that one of its rooms changed.
func (*Service) GetInvitePreview ¶
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 ¶
GetMentionCounts returns every room's mention count for a user (one query).
func (*Service) GetSpaceForUser ¶
GetSpaceForUser returns a space by ID if it exists and the user is a member.
func (*Service) GetUserRoomRow ¶
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 ¶
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) JoinByInvite ¶
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 ¶
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 ¶
KickMember removes a member from the space. They can rejoin with a new invite.
func (*Service) LeaveSpace ¶
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 ¶
ListEmojis returns a space's custom emoji. Members only.
func (*Service) ListEmojisForUser ¶
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 ¶
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 ¶
ListSpaceRoles returns all roles in a space. Any member may list.
func (*Service) ListSpaceRooms ¶
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 ¶
ListSpaceSummaries is every space the user is in with their space-wide permissions.
func (*Service) ListSpacesForUser ¶
ListSpacesForUser returns all spaces the user is a member of (via spaces_by_user).
func (*Service) LocalMemberCount ¶
LocalMemberCount counts the members of a space who live on this instance.
func (*Service) LookupEmoji ¶
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 ¶
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 ¶
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) RemoveMirrorMember ¶
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 (*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 ¶
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 ¶
RoomFederations is FederationInfo.RoomFederations, nil-safe.
func (*Service) SendPolicy ¶
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 ¶
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 ¶
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 ¶
Snapshot returns the space's full permission snapshot, from Redis when cached.
func (*Service) SpacePermissionBase ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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.
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).
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 ¶
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 ¶
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.
Source Files
¶
- audit.go
- backfill.go
- events.go
- federation.go
- handler.go
- handler_admin.go
- handler_emoji.go
- handler_icon.go
- handler_install.go
- handler_move.go
- handler_patch.go
- handler_reorder.go
- handler_settings.go
- model.go
- repo.go
- service.go
- service_admin.go
- service_birthdays.go
- service_discover.go
- service_emoji.go
- service_icon.go
- service_install.go
- service_invites.go
- service_moderation.go
- service_patch.go
- service_permissions.go
- service_roles.go
- service_sync.go
- service_system.go
- service_widget.go
- snapshot.go