model

package
v1.5.1-0...-475df33 Latest Latest
Warning

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

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package model defines the Network, Chan, Msg, User, and Prefix domain types.

Index

Constants

View Source
const (
	MetadataAvatar      = "avatar"
	MetadataDisplayName = "display-name"
	MetadataHomepage    = "homepage"
	MetadataColor       = "color"
	MetadataStatus      = "status"
)
View Source
const AvatarMessageTag = "+relay/avatar"

AvatarMessageTag is the vendor-prefixed client-only tag used by Relay to share an avatar URL on message-tags-capable networks that do not support persistent IRC metadata.

View Source
const MessageIDTag = "msgid"

MessageIDTag is the IRCv3 message-ids tag. It is carried by the message-tags capability and has no separate capability of its own.

Variables

This section is empty.

Functions

func IP2Hex

func IP2Hex(address string) string

IP2Hex mirrors Helper.ip2hex: an IPv4 address as 8 lowercase hex digits, or "00000000" for anything else (including IPv6 - "no ipv6 support" per the original).

func IsPublicAvatarURL

func IsPublicAvatarURL(value string) bool

IsPublicAvatarURL reports whether an avatar URL is suitable for IRC metadata or message tags. Those protocols share a URL, not the image bytes, so relative paths and loopback/private addresses cannot work for users on other Relay instances.

func IsSupportedUserMetadataKey

func IsSupportedUserMetadataKey(key string) bool

func MetadataValueValid

func MetadataValueValid(key, value string) bool

MetadataValueValid validates values before they are sent to IRC or rendered as a URL/CSS color in the browser. Empty values remove a metadata key.

func ParseMonitorLimit

func ParseMonitorLimit(value string, ok bool) *int

ParseMonitorLimit mirrors connection.ts's parseMonitorLimit: nil means unsupported (the server never sent a MONITOR ISUPPORT token, i.e. ok is false), 0 means explicitly disabled, a negative value means supported with no limit (a value-less or empty token), and a positive value is the real per-connection target limit.

func SendNickSafe

func SendNickSafe(c *irc.Connection, nick string)

SendNickSafe, SendPartSafe, and SendQuitSafe wrap the underlying irc.Connection's SendNick/SendPart/SendQuit, stripping CR/LF from the free-form argument first. Every call site in this codebase that sends a nick change, part message, or quit message should go through these rather than the raw *irc.Connection methods directly.

func SendPartSafe

func SendPartSafe(c *irc.Connection, channel, message string)

func SendQuitSafe

func SendQuitSafe(c *irc.Connection, message string)

func StripLineTerminators

func StripLineTerminators(s string) string

StripLineTerminators removes any CR/LF characters from s. IRC lines are CRLF-terminated, and some server/client parsers additionally treat a lone \r or \n as a line break on its own, so any free-form user text folded into a raw IRC line must have both stripped first - otherwise it can smuggle a second, attacker-chosen command onto the wire (e.g. a /kick reason or /part message containing an embedded line break).

func SupportedUserMetadataKeys

func SupportedUserMetadataKeys() []string

Types

type BrowserInfo

type BrowserInfo struct {
	IP       string
	Hostname string
	IsSecure bool
}

BrowserInfo carries the connecting browser's IP/hostname/security state, needed to populate WEBIRC and (with useHexIp) the IRC username. Stage 6 will source this from Client.Config.Browser once Client owns network connections.

type Chan

type Chan struct {
	ID          int       `json:"id"`
	Messages    []*Msg    `json:"messages"`
	Name        string    `json:"name"`
	Key         string    `json:"key"`
	Topic       string    `json:"topic"`
	FirstUnread int       `json:"firstUnread"`
	Unread      int       `json:"unread"`
	Highlight   int       `json:"highlight"`
	Muted       bool      `json:"muted"`
	Type        ChanType  `json:"type"`
	State       ChanState `json:"state"`

	Special  SpecialChanType `json:"special,omitempty"`
	Closed   bool            `json:"closed,omitempty"`
	NumUsers int             `json:"num_users,omitempty"`

	// Data mirrors Chan.data: the special-channel payload (ban/invite/
	// channel-list rows) shown by list.go/modelist.go.
	Data any `json:"data,omitempty"`

	// OldestMessageCursor is server-internal (Chan.oldestMessageCursor is
	// likewise never part of SharedChan - only its presence/absence feeds
	// the synthetic totalMessages field a filtered clone computes).
	OldestMessageCursor *MessageCursor `json:"-"`

	// Users is server-internal - SharedChan carries no per-channel user
	// list; "names"/"users" travel over their own dedicated events instead.
	Users map[string]*User `json:"-"` // keyed by lowercased nick

	// UserAway caches the away message last seen for a query window's peer
	// (query channels have no Users map entry to hang this off of - see
	// away.ts), so a repeated AWAY line with the same text doesn't push a
	// duplicate message. Despite chan.ts's `userAway?: boolean` type
	// annotation, the runtime value is always the away message string (or
	// "" when back) - the Go port follows the actual behavior. Server-
	// internal, not part of SharedChan.
	UserAway string `json:"-"`

	// JitsiUsers/JitsiURL mirror Chan.jitsiUsers/jitsiUrl: nicks currently
	// in this channel's Jitsi call (tracked via +relay/jitsi TAGMSG tags,
	// see irchandlers/jitsi.go) and the room URL, if any. Server-internal -
	// travels to the client via its own "jitsi:users" event, not SharedChan.
	JitsiUsers []string `json:"-"`
	JitsiURL   string   `json:"-"`

	// IsOnline mirrors Chan.isOnline: for a query window, whether its
	// MONITOR target is currently known to be connected - nil means
	// "never monitored/unknown" (mirrors both TS's `undefined` before the
	// first MONITOR reply and its explicit `null` reset on disconnect; Go
	// has no reason to distinguish the two). Server-internal - the client
	// derives its own copy from the "users:online"/"users:offline"/
	// "user:away" events (see monitor.go/away.go), not from SharedChan.
	IsOnline *bool `json:"-"`
	// contains filtered or unexported fields
}

Chan mirrors server/models/chan.ts - this stage's slice of it: the channel's own history buffer, user list, and the pushMessage/history-trim mechanics that don't depend on Client. Full Client-orchestrated concerns (multi-session "is this channel open" tracking via attachedClients, message-storage indexing, ZNC playback requests) land with Stage 6/8 once Client owns IRC state; PushOptions is the seam those stages hook into.

func NewChan

func NewChan(opts ChanOptions) *Chan

NewChan mirrors Chan's constructor.

func (*Chan) ClearUsers

func (c *Chan) ClearUsers()

ClearUsers mirrors the `chan.users = new Map()` reset done when we're kicked or disconnect from a channel - the server no longer tells us who's in it.

func (*Chan) FindMessage

func (c *Chan) FindMessage(id int) *Msg

FindMessage mirrors Chan.findMessage.

func (*Chan) FindMessageByMsgid

func (c *Chan) FindMessageByMsgid(msgid string) *Msg

FindMessageByMsgid mirrors Chan.findMessageByMsgid.

func (*Chan) FindUser

func (c *Chan) FindUser(nick string) *User

FindUser mirrors Chan.findUser.

func (*Chan) GetSortedUsers

func (c *Chan) GetSortedUsers(prefix *Prefix) []*User

GetSortedUsers mirrors Chan.getSortedUsers: users ordered by descending mode priority (per prefix's declared order), then case-insensitively by nick within the same mode. A nil or empty prefix returns users unsorted (matching the "no irc.network.options.PREFIX yet" early-return in chan.ts).

func (*Chan) GetUser

func (c *Chan) GetUser(nick string) *User

GetUser mirrors Chan.getUser: an existing user, or a fresh zero-value one (never stored) if the channel has no record of nick.

func (*Chan) IsLoggable

func (c *Chan) IsLoggable() bool

IsLoggable mirrors Chan.isLoggable (distinct from Msg.IsLoggable, which gates on the individual message's type).

func (*Chan) IsOperator

func (c *Chan) IsOperator(net *Network) bool

IsOperator mirrors Chan.isOperator: whether the network's own nick holds a channel mode at or above "@" (op) in this channel, per the network's declared PREFIX priority order.

func (*Chan) PushMessage

func (c *Chan) PushMessage(hub *wsproto.Hub, msg *Msg, nextID func() int, opts PushOptions)

PushMessage mirrors Chan.pushMessage: assigns the message its ID (via nextID, standing in for Client's shared idMsg counter until Client owns IRC state), updates unread/highlight/firstUnread counters, broadcasts a "msg" event to every subscriber on hub, and - unless running in public mode - appends to history and trims to MaxHistory.

The public-mode short-circuit matters: Node emits the "msg" event to the browser regardless of public mode, but never appends to `this.messages` in public mode at all ("Never store messages in public mode as the session is completely destroyed when the page gets closed" - chan.ts). Persistence (messageStorage.index) is a Stage 8 concern and isn't ported here yet.

func (*Chan) RemoveUser

func (c *Chan) RemoveUser(u *User)

RemoveUser mirrors Chan.removeUser.

func (*Chan) ReplaceUsers

func (c *Chan) ReplaceUsers(users map[string]*User)

ReplaceUsers mirrors the `chan.users = newUsers` wholesale replacement names.ts does once a NAMES reply finishes.

func (*Chan) SetMuteStatus

func (c *Chan) SetMuteStatus(muted bool)

SetMuteStatus mirrors Chan.setMuteStatus.

func (*Chan) SetUser

func (c *Chan) SetUser(u *User)

SetUser mirrors Chan.setUser.

type ChanOptions

type ChanOptions struct {
	Name    string
	Key     string
	Muted   bool
	Type    ChanType
	Special SpecialChanType
	Data    any
	// State overrides the default ChanParted, mirroring the `state`
	// override client.createChannel({state: ChanState.JOINED, ...}) passes
	// for a brand-new channel created directly from a self-join (see
	// join.ts / irchandlers/join.go) rather than a lazily-opened query/list
	// window, which should stay PARTED (the zero value) until actually
	// joined.
	State ChanState
}

ChanOptions configures NewChan.

type ChanState

type ChanState int

ChanState mirrors client/js/types/chan.ts's ChanState enum (int-valued in TS too, so the numeric values must match).

const (
	ChanParted ChanState = 0
	ChanJoined ChanState = 1
)

type ChanType

type ChanType string

ChanType mirrors client/js/types/chan.ts's ChanType enum.

const (
	ChanTypeChannel ChanType = "channel"
	ChanTypeLobby   ChanType = "lobby"
	ChanTypeQuery   ChanType = "query"
	ChanTypeSpecial ChanType = "special"
)

type IgnoreListItem

type IgnoreListItem struct {
	Nick     string
	Ident    string
	Hostname string
	// When is the unix-millisecond time this entry was added (Date.now()
	// in ignore.ts), shown by /ignorelist.
	When int64
}

IgnoreListItem mirrors server/models/network.ts's IgnoreListItem: a hostmask pattern (each field may contain '*'/'?' wildcards) to silently drop matching messages from.

type LinkPreview

type LinkPreview struct {
	Type           string `json:"type"`
	Head           string `json:"head"`
	Body           string `json:"body"`
	Thumb          string `json:"thumb"`
	Size           int    `json:"size"`
	Link           string `json:"link"`
	Shown          *bool  `json:"shown"`
	Error          string `json:"error,omitempty"`
	Message        string `json:"message,omitempty"`
	Media          string `json:"media,omitempty"`
	MediaType      string `json:"mediaType,omitempty"`
	MaxSize        int    `json:"maxSize,omitempty"`
	ThumbActualURL string `json:"thumbActualUrl,omitempty"`
}

LinkPreview mirrors client/js/types/msg.ts's LinkPreview.

type ListChannelEntry

type ListChannelEntry struct {
	Channel  string `json:"channel"`
	NumUsers int    `json:"num_users"`
	Topic    string `json:"topic"`
}

ListChannelEntry mirrors one row accumulated into Network.chanCache from RPL_LIST (322).

type MessageCursor

type MessageCursor struct {
	Time int64
	ID   int
}

MessageCursor is a keyset pagination cursor into sqlite storage (Stage 8) for the oldest message not yet loaded into Chan.Messages. Mirrors Chan.oldestMessageCursor; nil means storage has confirmed no earlier history exists.

type MessageType

type MessageType string

MessageType mirrors client/js/types/msg.ts's MessageType enum exactly - the string values are sent to the client and (Stage 8) persisted to storage, so they must match byte-for-byte.

const (
	MessageUnhandled      MessageType = "unhandled"
	MessageAction         MessageType = "action"
	MessageAway           MessageType = "away"
	MessageBack           MessageType = "back"
	MessageError          MessageType = "error"
	MessageInvite         MessageType = "invite"
	MessageJoin           MessageType = "join"
	MessageKick           MessageType = "kick"
	MessageLogin          MessageType = "login"
	MessageLogout         MessageType = "logout"
	MessageMessage        MessageType = "message"
	MessageMode           MessageType = "mode"
	MessageModeChannel    MessageType = "mode_channel"
	MessageModeUser       MessageType = "mode_user" // RPL_UMODEIS
	MessageMonospaceBlock MessageType = "monospace_block"
	MessageNick           MessageType = "nick"
	MessageNotice         MessageType = "notice"
	MessagePart           MessageType = "part"
	MessageQuit           MessageType = "quit"
	MessageCTCP           MessageType = "ctcp"
	MessageCTCPRequest    MessageType = "ctcp_request"
	MessageChghost        MessageType = "chghost"
	MessageTopic          MessageType = "topic"
	MessageTopicSetBy     MessageType = "topic_set_by"
	MessageWhois          MessageType = "whois"
	MessageRaw            MessageType = "raw"
	MessagePlugin         MessageType = "plugin"
	MessageWallops        MessageType = "wallops"
)

type Msg

type Msg struct {
	From            *UserInMessage      `json:"from,omitempty"`
	ID              int                 `json:"id"`
	SearchID        int                 `json:"searchId,omitempty"`
	NetworkUUID     string              `json:"networkUuid,omitempty"`
	ChannelName     string              `json:"channelName,omitempty"`
	Msgid           string              `json:"msgid,omitempty"`
	ReplyTo         string              `json:"replyTo,omitempty"`
	Reactions       map[string][]string `json:"reactions,omitempty"`
	Previews        []LinkPreview       `json:"previews"`
	Text            string              `json:"text"`
	Type            MessageType         `json:"type"`
	Self            bool                `json:"self"`
	Time            time.Time           `json:"time"`
	Encrypted       bool                `json:"encrypted,omitempty"`
	EncryptionError string              `json:"encryptionError,omitempty"`
	Hostmask        string              `json:"hostmask,omitempty"`
	Target          *UserInMessage      `json:"target,omitempty"`
	NewNick         string              `json:"new_nick,omitempty"`
	Highlight       bool                `json:"highlight,omitempty"`
	ShowInActive    bool                `json:"showInActive,omitempty"`
	NewIdent        string              `json:"new_ident,omitempty"`
	NewHost         string              `json:"new_host,omitempty"`
	CTCPMessage     string              `json:"ctcpMessage,omitempty"`
	Command         string              `json:"command,omitempty"`
	InvitedYou      bool                `json:"invitedYou,omitempty"`
	Gecos           string              `json:"gecos,omitempty"`
	Account         string              `json:"account,omitempty"`
	Error           string              `json:"error,omitempty"`
	Nick            string              `json:"nick,omitempty"`
	Channel         string              `json:"channel,omitempty"`
	Reason          string              `json:"reason,omitempty"`
	RawModes        any                 `json:"raw_modes,omitempty"`
	When            *time.Time          `json:"when,omitempty"`
	Whois           any                 `json:"whois,omitempty"`
	Users           []string            `json:"users,omitempty"`
	StatusmsgGroup  string              `json:"statusmsgGroup,omitempty"`
	Params          []string            `json:"params,omitempty"`
}

Msg mirrors server/models/msg.ts's Msg (the kitchen-sink shape shared by every message-shaped event: chat lines, joins/parts, mode changes, errors, WHOIS results, and more - each populating only the fields relevant to its MessageType, exactly like the Node side).

func NewMsg

func NewMsg(attr Msg) *Msg

NewMsg mirrors Msg's constructor: From/Target are deep-copied (mode+nick only) rather than referencing the caller's value, since a Msg is a point-in-time snapshot of who sent it - it must not change retroactively if that user's live state (e.g. a mode change) changes later. Type defaults to MessageMessage and Time to now, matching the JS _.defaults/ `this.time = new Date()` fallback.

func (*Msg) FindPreview

func (m *Msg) FindPreview(link string) *LinkPreview

FindPreview mirrors Msg.findPreview.

func (*Msg) IsLoggable

func (m *Msg) IsLoggable() bool

IsLoggable mirrors Msg.isLoggable.

func (*Msg) ToggleReaction

func (m *Msg) ToggleReaction(nick, emoji string) map[string][]string

ToggleReaction mirrors Msg.toggleReaction: adds nick to emoji's reactor list if absent, removes it if present, deleting the emoji key entirely once its list empties.

type Network

type Network struct {
	UUID               string
	Name               string
	Nick               string
	Host               string
	Port               int
	TLS                bool
	UserDisconnected   bool
	RejectUnauthorized bool
	Password           string
	AwayMessage        string
	Commands           []string
	Username           string
	Realname           string
	LeaveMessage       string
	SASL               string // "", "plain", or "external"
	SASLAccount        string
	SASLPassword       string
	Avatar             string  `json:"-"`
	AvatarOverride     *string `json:"-"`
	// Metadata caches server-provided user metadata for the lifetime of this
	// network, so it can be applied when users arrive in NAMES/JOIN events.
	Metadata      map[string]UserMetadata `json:"-"`
	Channels      []*Chan
	ProxyHost     string
	ProxyPort     int
	ProxyUsername string
	ProxyPassword string
	ProxyEnabled  bool

	// KeepNick is the nick to restore once available again (e.g. after a
	// collision), or "" if none is pending - mirrors Network.keepNick's
	// string-or-null.
	KeepNick string

	HighlightRegex *regexp.Regexp

	ServerOptions ServerOptions

	// MonitorList/ToBeMonitored mirror Network.monitorList/toBeMonitored:
	// lower-cased MONITOR targets currently sent to the server, and ones
	// queued behind the server's advertised limit (ServerOptions.Monitor),
	// respectively. See Monitor/MonitorBatch/RemoveMonitor/RenameMonitor.
	MonitorList   []string
	ToBeMonitored []string

	Bridge *ircbridge.Bridge

	// IsPartyline is true only for the synthetic, IRC-less network hosting
	// the partyline channel (Stage 10); Bridge is permanently nil for it.
	IsPartyline bool

	// IgnoreList mirrors Network.ignoreList: hostmask patterns whose
	// messages are silently dropped (see IsIgnoredUser).
	IgnoreList []IgnoreListItem

	// ChanCache mirrors Network.chanCache: accumulates RPL_LIST rows
	// between "list start" and "list end" before being flushed to the
	// channel-list special window (see irchandlers/list.go).
	ChanCache []ListChannelEntry

	// TypingDisabledChannels mirrors Network.typingDisabledChannels:
	// lower-cased channel names the server has told us reject messages
	// (ERR_CANNOTSENDTOCHAN or the non-standard 415), so typing
	// notifications stop being sent there.
	TypingDisabledChannels map[string]bool

	// SilentBanlistRequests mirrors Network.silentBanlistRequests: a
	// pending-count per lower-cased channel name of banlist refreshes
	// triggered internally (e.g. after a MODE +b) that shouldn't pop open
	// the legacy "Ban list for #chan" window when they complete.
	SilentBanlistRequests map[string]int

	// LastListRequest mirrors Network.lastListRequest: when /list last ran,
	// enforcing list.ts's 10-second request cooldown. Zero means never.
	LastListRequest time.Time

	// PendingWhoisPopup has no Node equivalent - a fork-only marker (keyed
	// lower-case by nick) set by incommands' whois-popup command right
	// before sending WHOIS, consumed once by irchandlers.handleWhoisEnd to
	// decide whether that lookup's result goes out as an ephemeral popup
	// event (WhoisDialog.vue) instead of the classic pushed buffer message.
	// Typing /whois directly never sets this, so it keeps the classic
	// behavior; only UI-triggered lookups (the "User information" context
	// menu action) do.
	PendingWhoisPopup map[string]bool
	// contains filtered or unexported fields
}

Network mirrors server/models/network.ts - this stage's slice of it: config/state fields, validate()/lockNetwork enforcement, ISUPPORT-derived serverOptions, STS interaction, and ownership of the ircbridge.Bridge for its connection. Actually driving the connection (join-after-registration, message routing into Chan.PushMessage, ignore-list filtering) is Stage 6's job, wired through Bridge.Subscribe.

func NewNetwork

func NewNetwork(opts NetworkOptions) *Network

NewNetwork mirrors Network's constructor, including prepending the lobby channel with the same "start muted if every existing channel is muted" heuristic.

func (*Network) AddChannel

func (n *Network) AddChannel(newChan *Chan) int

AddChannel mirrors Network.addChannel: inserts newChan at its sorted position among existing CHANNEL/QUERY entries (special channels and the lobby are left in place, never sorted against), or appends it if it's itself special.

func (*Network) AddIgnoredUser

func (n *Network) AddIgnoredUser(nick, ident, hostname string)

AddIgnoredUser mirrors ignore.ts's `network.ignoreList.push({...hostmask, when: Date.now()})`.

func (*Network) ApplyServerOptions

func (n *Network) ApplyServerOptions(conn *irc.Connection)

ApplyServerOptions mirrors the parts of createIrcFramework/network.ts that populate serverOptions from ISUPPORT, called by Stage 6's connection handler once the bridge's underlying connection has processed RPL_ISUPPORT.

func (*Network) AvatarOverrideValue

func (n *Network) AvatarOverrideValue() *string

AvatarOverrideValue returns a copy of the explicit per-network avatar override, or nil when the network inherits the account default.

func (*Network) AvatarValue

func (n *Network) AvatarValue() string

AvatarValue returns the local Relay avatar, which may be a private /uploads/ path and therefore unsuitable for IRC metadata.

func (*Network) BuildConnectOptions

func (n *Network) BuildConnectOptions(cfg *config.Loaded, browser BrowserInfo, clientCert *tls.Certificate) irc.ConnectOptions

BuildConnectOptions mirrors setIrcFrameworkOptions + createWebIrc: turns this Network's config into the irc library's ConnectOptions (plus its SASL/WebIRC/Socks5 sub-options). cfg supplies the WEBIRC entries and the useHexIp setting; browser supplies the connecting client's IP/hostname for WEBIRC; clientCert is required for SASL EXTERNAL (see irc.ConnectOptions's own doc comment on why).

func (*Network) ChannelNames

func (n *Network) ChannelNames() []string

ChannelNames returns the non-lobby channel names, for callers (Stage 6's connection handler) that explicitly JOIN each one after registration - BuildConnectOptions deliberately leaves ConnectOptions.Channels empty since, like Node's createIrcFramework, joining happens as an explicit post-registration step (so per-channel keys can be sent), not as part of the connection handshake itself.

func (*Network) ClearAvatarOverride

func (n *Network) ClearAvatarOverride(defaultAvatar string)

ClearAvatarOverride makes the network inherit the supplied account default.

func (*Network) ConfigureAvatar

func (n *Network) ConfigureAvatar(defaultAvatar string, override *string)

ConfigureAvatar applies the account default and an optional persisted network override while constructing a network.

func (*Network) ConsumeSilentBanlistRequest

func (n *Network) ConsumeSilentBanlistRequest(chanName string) bool

ConsumeSilentBanlistRequest mirrors Network.consumeSilentBanlistRequest: reports whether this banlist reply corresponds to a pending silent request, decrementing (or clearing) its count if so.

func (*Network) GetChannel

func (n *Network) GetChannel(name string) *Chan

GetChannel mirrors Network.getChannel: a case-insensitive name lookup that always skips the lobby (index 0).

func (*Network) GetLobby

func (n *Network) GetLobby() *Chan

GetLobby mirrors Network.getLobby.

func (*Network) GetUserMetadata

func (n *Network) GetUserMetadata(nick string) (UserMetadata, bool)

GetUserMetadata returns the cached metadata for nick without exposing the map to callers. Metadata is updated by both IRC and websocket handlers.

func (*Network) HasAvatarOverride

func (n *Network) HasAvatarOverride() bool

HasAvatarOverride reports whether this network has an explicit avatar setting, including an explicit empty value that hides the account default.

func (*Network) IsIgnoredUser

func (n *Network) IsIgnoredUser(nick, ident, hostname string) bool

IsIgnoredUser mirrors Network.isIgnoredUser: whether data's hostmask matches any entry in the ignore list, wildcards allowed in each field.

func (*Network) MarkSilentBanlistRequest

func (n *Network) MarkSilentBanlistRequest(chanName string)

MarkSilentBanlistRequest mirrors Network.markSilentBanlistRequest.

func (*Network) Monitor

func (n *Network) Monitor(target string)

Monitor mirrors Network.monitor: adds target to the server's MONITOR list, or queues it in ToBeMonitored if the server-advertised limit is already reached. A no-op before registration or on a network where MONITOR is unsupported/disabled (ServerOptions.Monitor nil or 0).

func (*Network) MonitorBatch

func (n *Network) MonitorBatch(targets []string)

MonitorBatch mirrors Network.monitorBatch: adds every target at once, queuing whatever doesn't fit under the server-advertised limit, and chunking the rest into as few "MONITOR + ..." lines as fit under IRC's line-length limit.

func (*Network) Quit

func (n *Network) Quit(message string, defaultLeaveMessage string, sts STSLookup)

Quit mirrors Network.quit: refreshes any active STS policy's expiration (reconnecting shouldn't reset the countdown to re-check STS) and sends QUIT with the first non-empty of message, this network's LeaveMessage, or the server-wide default.

func (*Network) RemoveChannel

func (n *Network) RemoveChannel(target *Chan)

RemoveChannel mirrors the `network.channels = _.without(network.channels, chan)` step of Client.part/Client.quit - removing chan from this network's channel list. A no-op if chan isn't present (or is the lobby, which callers must never pass here).

func (*Network) RemoveIgnoredUser

func (n *Network) RemoveIgnoredUser(nick, ident, hostname string) bool

RemoveIgnoredUser mirrors ignore.ts's /unignore lookup: removes the first ignore-list entry whose wildcard pattern matches nick/ident/hostname (Helper.compareHostmask), reporting whether one was found and removed.

func (*Network) RemoveMonitor

func (n *Network) RemoveMonitor(target string)

RemoveMonitor mirrors Network.removeMonitor: stops monitoring target, promoting the next queued ToBeMonitored target (if any) into its freed slot.

func (*Network) RenameMonitor

func (n *Network) RenameMonitor(oldTarget, newTarget string)

RenameMonitor mirrors Network.renameMonitor: swaps a monitored (or queued) target for a new name in place, preserving its slot, or monitors newTarget fresh if oldTarget wasn't tracked at all.

func (*Network) RenameUserMetadata

func (n *Network) RenameUserMetadata(oldNick, newNick string)

RenameUserMetadata moves cached metadata when the server changes a nick.

func (*Network) ResetMonitorState

func (n *Network) ResetMonitorState()

ResetMonitorState mirrors connection.ts's "socket close" handler clearing all MONITOR bookkeeping: the server has forgotten our monitor list along with the rest of the connection, so tracking it as still active would be wrong, and ServerOptions.Monitor itself resets to nil (unknown) until the next connection's ISUPPORT re-establishes it.

func (*Network) SetAvatarOverride

func (n *Network) SetAvatarOverride(avatar string)

SetAvatarOverride gives this network its own avatar, including an explicit empty value to hide the account default.

func (*Network) SetAvatarValue

func (n *Network) SetAvatarValue(avatar string)

func (*Network) SetDefaultAvatar

func (n *Network) SetDefaultAvatar(avatar string)

SetDefaultAvatar updates the inherited avatar without changing an explicit network override.

func (*Network) SetNick

func (n *Network) SetNick(nick string)

SetNick mirrors Network.setNick: updates the nick and rebuilds the case-insensitive highlight regex used to detect mentions in message text.

func (*Network) SetUserMetadata

func (n *Network) SetUserMetadata(nick string, metadata UserMetadata)

SetUserMetadata stores the cached metadata for nick.

func (*Network) SetUserMetadataValue

func (n *Network) SetUserMetadataValue(nick string, fallback UserMetadata, key, value string) (UserMetadata, bool)

SetUserMetadataValue updates one metadata key atomically. If nick has no cached entry, fallback supplies locally-known values such as an uploaded avatar that is not shareable over IRC.

func (*Network) Validate

func (n *Network) Validate(cfg *config.Loaded, sts STSLookup, hub *wsproto.Hub, nextID func() int) bool

Validate mirrors Network.validate: cleans/clamps user-supplied fields, enforces lockNetwork/public-mode overrides, and applies an active STS policy. On a hard failure it pushes an error Msg to the lobby (mirroring the `error()` closure in network.ts) and returns false; the STS-upgrade case pushes an informational Msg but still returns true, matching the TS control flow exactly (that branch has no `return false` in the original).

type NetworkOptions

type NetworkOptions struct {
	UUID     string
	Name     string
	Nick     string
	Host     string
	Port     int
	TLS      bool
	Password string
	Username string
	Realname string
	SASL     string

	SASLAccount  string
	SASLPassword string

	// Channels are pre-existing channels to restore (e.g. loaded from a
	// user's saved config); the lobby is prepended automatically and must
	// not be included here.
	Channels []*Chan

	Commands []string

	ProxyEnabled  bool
	ProxyHost     string
	ProxyPort     int
	ProxyUsername string
	ProxyPassword string

	LeaveMessage       string
	AwayMessage        string
	UserDisconnected   bool
	RejectUnauthorized bool
}

NetworkOptions configures NewNetwork.

type Prefix

type Prefix struct {
	Prefix       []PrefixEntry     `json:"prefix"`
	ModeToSymbol map[string]string `json:"modeToSymbol"`
	Symbols      []string          `json:"symbols"`
}

Prefix tracks a server's PREFIX ISUPPORT token: the ordered mode/symbol pairs, a mode->symbol lookup, and the plain symbol list. Mirrors server/models/prefix.ts field-for-field (including the outer/inner both being named "prefix", matching client/js/types/network.ts's SharedPrefix) - this whole struct is round-tripped to the client verbatim as part of a network's serverOptions.

func NewPrefix

func NewPrefix(entries []PrefixEntry) *Prefix

NewPrefix builds a Prefix from its ordered entries (highest priority first), matching Prefix's constructor. A nil entries is treated as empty.

func (*Prefix) Update

func (p *Prefix) Update(entries []PrefixEntry)

Update replaces the prefix list and recomputes the derived lookups, mirroring Prefix.update/_update_internals.

type PrefixEntry

type PrefixEntry struct {
	Symbol string `json:"symbol"`
	Mode   string `json:"mode"`
}

PrefixEntry pairs a channel-membership mode letter with the nick prefix symbol the server displays for it, in priority order (highest first). Field names/JSON tags match client/js/types/network.ts's SharedPrefixObject.

type PushOptions

type PushOptions struct {
	IsOpen          bool
	IncreasesUnread bool
	MaxHistory      int
	Public          bool

	// NotificationCategories mirrors the `notificationCategories` argument
	// pushMessage forwards straight into the emitted "msg" event, letting
	// the client decide whether to show a desktop notification/play a
	// sound without recomputing eligibility itself. nil (the zero value)
	// omits the key entirely, matching Node's `undefined` when the caller
	// (message.ts) determines the message isn't notification-eligible.
	NotificationCategories []string

	// Index mirrors the `this.writeUserLog(client, msg)` call
	// pushMessage itself makes (chan.ts) - a Client-level concern (message
	// storage) this package doesn't own, wired in by internal/session via
	// this seam instead. Never called in public mode, matching writeUserLog
	// only ever running after pushMessage's own public-mode early return.
	Index func(msg *Msg)
}

PushOptions carries the parts of pushMessage's behavior that depend on Client state not yet wired up in this stage. IsOpen mirrors the `attachedClients` "is this channel open in any session" check; MaxHistory mirrors Config.values.maxHistory (negative disables trimming); Public mirrors Config.values.public.

type STSLookup

type STSLookup interface {
	// Get reports the port an active STS policy for host demands, if any.
	Get(host string) (port int, ok bool)
	// RefreshExpiration extends an active policy's expiry, mirroring
	// https://ircv3.net/specs/extensions/sts#rescheduling-expiry-on-disconnect.
	RefreshExpiration(host string)
}

STSLookup abstracts the STS policy store (internal/sts, built in Stage 10) so Network doesn't have to depend on it before it exists. Passing nil simply skips the STS-upgrade check in Validate/Quit.

type ServerOptions

type ServerOptions struct {
	ChanTypes []string `json:"CHANTYPES"`
	Prefix    *Prefix  `json:"PREFIX"`
	Network   string   `json:"NETWORK"`

	// Monitor mirrors serverOptions.MONITOR's `number | null`: nil means
	// the server never sent a MONITOR ISUPPORT token (unsupported), 0
	// means it explicitly advertised a limit of zero (disabled), a
	// negative value means "supported, no limit" (a value-less or empty
	// MONITOR token), and a positive value is the real per-connection
	// target limit. See ParseMonitorLimit.
	Monitor *int `json:"MONITOR"`
}

ServerOptions mirrors Network.serverOptions - the subset of ISUPPORT the client needs (CHANTYPES/PREFIX/NETWORK), populated from the irc library's ISUPPORT accessors once the bridge connects (see ApplyServerOptions; Stage 6's connection handler calls it on RPL_ISUPPORT).

type SpecialChanType

type SpecialChanType string

SpecialChanType mirrors client/js/types/chan.ts's SpecialChanType enum.

const (
	SpecialBanList     SpecialChanType = "list_bans"
	SpecialInviteList  SpecialChanType = "list_invites"
	SpecialChannelList SpecialChanType = "list_channels"
	SpecialIgnoreList  SpecialChanType = "list_ignored"
	SpecialPartyline   SpecialChanType = "partyline"
)

type User

type User struct {
	Nick        string   `json:"nick"`
	Modes       []string `json:"modes"`
	Away        string   `json:"away,omitempty"`
	Account     string   `json:"account,omitempty"`
	Avatar      string   `json:"avatar,omitempty"`
	DisplayName string   `json:"displayName,omitempty"`
	Homepage    string   `json:"homepage,omitempty"`
	Color       string   `json:"color,omitempty"`
	Status      string   `json:"status,omitempty"`
	LastMessage int64    `json:"lastMessage"`
}

User represents one nick's per-channel state: which membership modes they hold there, away status, and when they last spoke. Mirrors server/models/user.ts.

func NewUser

func NewUser(nick string, modeLetters []string, prefix *Prefix) *User

NewUser mirrors User's constructor: modeLetters are raw IRC mode letters (e.g. "o", "v") as reported by the irc library, converted to the server's display symbols via prefix. A nil prefix is treated as empty (no known symbols), matching `new Prefix([])` in chan.ts's getUser.

func (*User) Mode

func (u *User) Mode() string

Mode returns the highest-priority mode symbol this user holds, or "" if they hold none - mirrors the `mode` getter defined via Object.defineProperty in user.ts.

func (*User) SetModes

func (u *User) SetModes(modeLetters []string, prefix *Prefix)

SetModes mirrors User.setModes: the irc library reports channel membership as raw mode letters, but Relay works with the server's display symbols throughout.

type UserInMessage

type UserInMessage struct {
	Mode        string `json:"mode"`
	Nick        string `json:"nick,omitempty"`
	Avatar      string `json:"avatar,omitempty"`
	DisplayName string `json:"displayName,omitempty"`
	Homepage    string `json:"homepage,omitempty"`
	Color       string `json:"color,omitempty"`
	Status      string `json:"status,omitempty"`
}

UserInMessage is a shallow, point-in-time snapshot of a user's mode/nick embedded in a Msg's From/Target fields - deliberately copied rather than pointing at the live *User (see NewMsg), so a later nick or mode change doesn't retroactively rewrite history. Mirrors client/js/types/msg.ts's UserInMessage.

type UserMetadata

type UserMetadata struct {
	Avatar      string `json:"avatar,omitempty"`
	DisplayName string `json:"displayName,omitempty"`
	Homepage    string `json:"homepage,omitempty"`
	Color       string `json:"color,omitempty"`
	Status      string `json:"status,omitempty"`
}

UserMetadata is the subset of IRCv3 user metadata Relay understands and presents in its UI. The JSON names are the client-facing names; the wire keys are the constants above.

func (*UserMetadata) Set

func (m *UserMetadata) Set(key, value string) bool

func (UserMetadata) Value

func (m UserMetadata) Value(key string) string

Jump to

Keyboard shortcuts

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