mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-11 06:29:29 +01:00
commit
2e7cf3cc1e
17
default.yaml
17
default.yaml
@ -877,13 +877,18 @@ history:
|
|||||||
# (and will eventually be deleted from persistent storage, if that's enabled)
|
# (and will eventually be deleted from persistent storage, if that's enabled)
|
||||||
expire-time: 1w
|
expire-time: 1w
|
||||||
|
|
||||||
# if this is set, logged-in users cannot retrieve messages older than their
|
# this restricts access to channel history (it can be overridden by channel
|
||||||
# account registration date, and logged-out users cannot retrieve messages
|
# owners). options are: 'none' (no restrictions), 'registration-time'
|
||||||
# older than their sign-on time (modulo grace-period, see below):
|
# (logged-in users cannot retrieve messages older than their account
|
||||||
enforce-registration-date: false
|
# registration date, and anonymous users cannot retrieve messages older than
|
||||||
|
# their sign-on time, modulo the grace-period described below), and
|
||||||
|
# 'join-time' (users cannot retrieve messages older than the time they
|
||||||
|
# joined the channel, so only always-on clients can view history).
|
||||||
|
query-cutoff: 'none'
|
||||||
|
|
||||||
# but if this is set, you can retrieve messages that are up to `grace-period`
|
# if query-cutoff is set to 'registration-time', this allows retrieval
|
||||||
# older than the above cutoff time. this is recommended to allow logged-out
|
# of messages that are up to 'grace-period' older than the above cutoff.
|
||||||
|
# if you use 'registration-time', this is recommended to allow logged-out
|
||||||
# users to do session resumption / query history after disconnections.
|
# users to do session resumption / query history after disconnections.
|
||||||
grace-period: 1h
|
grace-period: 1h
|
||||||
|
|
||||||
|
@ -544,7 +544,12 @@ func (am *AccountManager) setPassword(account string, password string, hasPrivs
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) saveChannels(account string, channelToModes map[string]string) {
|
type alwaysOnChannelStatus struct {
|
||||||
|
Modes string
|
||||||
|
JoinTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) saveChannels(account string, channelToModes map[string]alwaysOnChannelStatus) {
|
||||||
j, err := json.Marshal(channelToModes)
|
j, err := json.Marshal(channelToModes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
am.server.logger.Error("internal", "couldn't marshal channel-to-modes", account, err.Error())
|
am.server.logger.Error("internal", "couldn't marshal channel-to-modes", account, err.Error())
|
||||||
@ -558,7 +563,7 @@ func (am *AccountManager) saveChannels(account string, channelToModes map[string
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) loadChannels(account string) (channelToModes map[string]string) {
|
func (am *AccountManager) loadChannels(account string) (channelToModes map[string]alwaysOnChannelStatus) {
|
||||||
key := fmt.Sprintf(keyAccountChannelToModes, account)
|
key := fmt.Sprintf(keyAccountChannelToModes, account)
|
||||||
var channelsStr string
|
var channelsStr string
|
||||||
am.server.store.View(func(tx *buntdb.Tx) error {
|
am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
|
118
irc/channel.go
118
irc/channel.go
@ -20,7 +20,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ChannelSettings struct {
|
type ChannelSettings struct {
|
||||||
History HistoryStatus
|
History HistoryStatus
|
||||||
|
QueryCutoff HistoryCutoff
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channel represents a channel that clients can join.
|
// Channel represents a channel that clients can join.
|
||||||
@ -109,7 +110,7 @@ func (channel *Channel) IsLoaded() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) resizeHistory(config *Config) {
|
func (channel *Channel) resizeHistory(config *Config) {
|
||||||
status, _ := channel.historyStatus(config)
|
status, _, _ := channel.historyStatus(config)
|
||||||
if status == HistoryEphemeral {
|
if status == HistoryEphemeral {
|
||||||
channel.history.Resize(config.History.ChannelLength, time.Duration(config.History.AutoresizeWindow))
|
channel.history.Resize(config.History.ChannelLength, time.Duration(config.History.AutoresizeWindow))
|
||||||
} else {
|
} else {
|
||||||
@ -443,11 +444,11 @@ func (channel *Channel) regenerateMembersCache() {
|
|||||||
// Names sends the list of users joined to the channel to the given client.
|
// Names sends the list of users joined to the channel to the given client.
|
||||||
func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
clientModes, isJoined := channel.members[client]
|
clientData, isJoined := channel.members[client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
isOper := client.HasMode(modes.Operator)
|
isOper := client.HasMode(modes.Operator)
|
||||||
respectAuditorium := channel.flags.HasMode(modes.Auditorium) && !isOper &&
|
respectAuditorium := channel.flags.HasMode(modes.Auditorium) && !isOper &&
|
||||||
(!isJoined || clientModes.HighestChannelUserMode() == modes.Mode(0))
|
(!isJoined || clientData.modes.HighestChannelUserMode() == modes.Mode(0))
|
||||||
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
|
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
|
||||||
isUserhostInNames := rb.session.capabilities.Has(caps.UserhostInNames)
|
isUserhostInNames := rb.session.capabilities.Has(caps.UserhostInNames)
|
||||||
|
|
||||||
@ -463,8 +464,9 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
|||||||
nick = target.Nick()
|
nick = target.Nick()
|
||||||
}
|
}
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
modeSet := channel.members[target]
|
memberData, _ := channel.members[target]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
modeSet := memberData.modes
|
||||||
if modeSet == nil {
|
if modeSet == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -519,7 +521,7 @@ func channelUserModeHasPrivsOver(clientMode modes.Mode, targetMode modes.Mode) b
|
|||||||
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
|
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
|
||||||
func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
|
func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
clientModes := channel.members[client]
|
memberData := channel.members[client]
|
||||||
founder := channel.registeredFounder
|
founder := channel.registeredFounder
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
@ -528,7 +530,7 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) b
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, mode := range modes.ChannelUserModes {
|
for _, mode := range modes.ChannelUserModes {
|
||||||
if clientModes.HasMode(mode) {
|
if memberData.modes.HasMode(mode) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if mode == permission {
|
if mode == permission {
|
||||||
@ -541,35 +543,37 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) b
|
|||||||
func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) string {
|
func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) string {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
modes, present := channel.members[client]
|
memberData, present := channel.members[client]
|
||||||
if !present {
|
if !present {
|
||||||
return ""
|
return ""
|
||||||
} else {
|
} else {
|
||||||
return modes.Prefixes(isMultiPrefix)
|
return memberData.modes.Prefixes(isMultiPrefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) ClientStatus(client *Client) (present bool, cModes modes.Modes) {
|
func (channel *Channel) ClientStatus(client *Client) (present bool, joinTimeSecs int64, cModes modes.Modes) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
modes, present := channel.members[client]
|
memberData, present := channel.members[client]
|
||||||
return present, modes.AllModes()
|
return present, time.Unix(0, memberData.joinTime).Unix(), memberData.modes.AllModes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper for persisting channel-user modes for always-on clients;
|
// helper for persisting channel-user modes for always-on clients;
|
||||||
// return the channel name and all channel-user modes for a client
|
// return the channel name and all channel-user modes for a client
|
||||||
func (channel *Channel) nameAndModes(client *Client) (chname string, modeStr string) {
|
func (channel *Channel) alwaysOnStatus(client *Client) (chname string, status alwaysOnChannelStatus) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
chname = channel.name
|
chname = channel.name
|
||||||
modeStr = channel.members[client].String()
|
data := channel.members[client]
|
||||||
|
status.Modes = data.modes.String()
|
||||||
|
status.JoinTime = data.joinTime
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite any existing channel-user modes with the stored ones
|
// overwrite any existing channel-user modes with the stored ones
|
||||||
func (channel *Channel) setModesForClient(client *Client, modeStr string) {
|
func (channel *Channel) setMemberStatus(client *Client, status alwaysOnChannelStatus) {
|
||||||
newModes := modes.NewModeSet()
|
newModes := modes.NewModeSet()
|
||||||
for _, mode := range modeStr {
|
for _, mode := range status.Modes {
|
||||||
newModes.SetMode(modes.Mode(mode), true)
|
newModes.SetMode(modes.Mode(mode), true)
|
||||||
}
|
}
|
||||||
channel.stateMutex.Lock()
|
channel.stateMutex.Lock()
|
||||||
@ -577,14 +581,17 @@ func (channel *Channel) setModesForClient(client *Client, modeStr string) {
|
|||||||
if _, ok := channel.members[client]; !ok {
|
if _, ok := channel.members[client]; !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
channel.members[client] = newModes
|
memberData := channel.members[client]
|
||||||
|
memberData.modes = newModes
|
||||||
|
memberData.joinTime = status.JoinTime
|
||||||
|
channel.members[client] = memberData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
|
func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
founder := channel.registeredFounder
|
founder := channel.registeredFounder
|
||||||
clientModes := channel.members[client]
|
clientModes := channel.members[client].modes
|
||||||
targetModes := channel.members[target]
|
targetModes := channel.members[target].modes
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
if founder != "" {
|
if founder != "" {
|
||||||
@ -612,7 +619,7 @@ func (channel *Channel) modeStrings(client *Client) (result []string) {
|
|||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
isMember := hasPrivs || channel.members[client] != nil
|
isMember := hasPrivs || channel.members.Has(client)
|
||||||
showKey := isMember && (channel.key != "")
|
showKey := isMember && (channel.key != "")
|
||||||
showUserLimit := channel.userLimit > 0
|
showUserLimit := channel.userLimit > 0
|
||||||
showForward := channel.forward != ""
|
showForward := channel.forward != ""
|
||||||
@ -660,18 +667,38 @@ func (channel *Channel) IsEmpty() bool {
|
|||||||
|
|
||||||
// figure out where history is being stored: persistent, ephemeral, or neither
|
// figure out where history is being stored: persistent, ephemeral, or neither
|
||||||
// target is only needed if we're doing persistent history
|
// target is only needed if we're doing persistent history
|
||||||
func (channel *Channel) historyStatus(config *Config) (status HistoryStatus, target string) {
|
func (channel *Channel) historyStatus(config *Config) (status HistoryStatus, target string, restrictions HistoryCutoff) {
|
||||||
if !config.History.Enabled {
|
if !config.History.Enabled {
|
||||||
return HistoryDisabled, ""
|
return HistoryDisabled, "", HistoryCutoffNone
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
target = channel.nameCasefolded
|
target = channel.nameCasefolded
|
||||||
historyStatus := channel.settings.History
|
settings := channel.settings
|
||||||
registered := channel.registeredFounder != ""
|
registered := channel.registeredFounder != ""
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
return channelHistoryStatus(config, registered, historyStatus), target
|
restrictions = settings.QueryCutoff
|
||||||
|
if restrictions == HistoryCutoffDefault {
|
||||||
|
restrictions = config.History.Restrictions.queryCutoff
|
||||||
|
}
|
||||||
|
|
||||||
|
return channelHistoryStatus(config, registered, settings.History), target, restrictions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (channel *Channel) joinTimeCutoff(client *Client) (present bool, cutoff time.Time) {
|
||||||
|
account := client.Account()
|
||||||
|
|
||||||
|
channel.stateMutex.RLock()
|
||||||
|
defer channel.stateMutex.RUnlock()
|
||||||
|
if data, ok := channel.members[client]; ok {
|
||||||
|
present = true
|
||||||
|
// report a cutoff of zero, i.e., no restriction, if the user is privileged
|
||||||
|
if !((account != "" && account == channel.registeredFounder) || data.modes.HasMode(modes.ChannelFounder) || data.modes.HasMode(modes.ChannelAdmin) || data.modes.HasMode(modes.ChannelOperator)) {
|
||||||
|
cutoff = time.Unix(0, data.joinTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func channelHistoryStatus(config *Config, registered bool, storedStatus HistoryStatus) (result HistoryStatus) {
|
func channelHistoryStatus(config *Config, registered bool, storedStatus HistoryStatus) (result HistoryStatus) {
|
||||||
@ -697,7 +724,7 @@ func (channel *Channel) AddHistoryItem(item history.Item, account string) (err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status, target := channel.historyStatus(channel.server.Config())
|
status, target, _ := channel.historyStatus(channel.server.Config())
|
||||||
if status == HistoryPersistent {
|
if status == HistoryPersistent {
|
||||||
err = channel.server.historyDB.AddChannelItem(target, item, account)
|
err = channel.server.historyDB.AddChannelItem(target, item, account)
|
||||||
} else if status == HistoryEphemeral {
|
} else if status == HistoryEphemeral {
|
||||||
@ -785,7 +812,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
|||||||
givenMode = persistentMode
|
givenMode = persistentMode
|
||||||
}
|
}
|
||||||
if givenMode != 0 {
|
if givenMode != 0 {
|
||||||
channel.members[client].SetMode(givenMode, true)
|
channel.members[client].modes.SetMode(givenMode, true)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -825,9 +852,9 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
|||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
if respectAuditorium {
|
if respectAuditorium {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
memberModes, ok := channel.members[member]
|
memberData, ok := channel.members[member]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
if !ok || memberModes.HighestChannelUserMode() == modes.Mode(0) {
|
if !ok || memberData.modes.HighestChannelUserMode() == modes.Mode(0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -955,7 +982,7 @@ func (channel *Channel) playJoinForSession(session *Session) {
|
|||||||
func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) {
|
func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
chname := channel.name
|
chname := channel.name
|
||||||
clientModes, ok := channel.members[client]
|
clientData, ok := channel.members[client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -974,15 +1001,15 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
|
|||||||
params = append(params, message)
|
params = append(params, message)
|
||||||
}
|
}
|
||||||
respectAuditorium := channel.flags.HasMode(modes.Auditorium) &&
|
respectAuditorium := channel.flags.HasMode(modes.Auditorium) &&
|
||||||
clientModes.HighestChannelUserMode() == modes.Mode(0)
|
clientData.modes.HighestChannelUserMode() == modes.Mode(0)
|
||||||
var cache MessageCache
|
var cache MessageCache
|
||||||
cache.Initialize(channel.server, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
|
cache.Initialize(channel.server, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
|
||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
if respectAuditorium {
|
if respectAuditorium {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
memberModes, ok := channel.members[member]
|
memberData, ok := channel.members[member]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
if !ok || memberModes.HighestChannelUserMode() == modes.Mode(0) {
|
if !ok || memberData.modes.HighestChannelUserMode() == modes.Mode(0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1022,12 +1049,12 @@ func (channel *Channel) Resume(session *Session, timestamp time.Time) {
|
|||||||
|
|
||||||
func (channel *Channel) resumeAndAnnounce(session *Session) {
|
func (channel *Channel) resumeAndAnnounce(session *Session) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
modeSet := channel.members[session.client]
|
memberData, found := channel.members[session.client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
if modeSet == nil {
|
if !found {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
oldModes := modeSet.String()
|
oldModes := memberData.modes.String()
|
||||||
if 0 < len(oldModes) {
|
if 0 < len(oldModes) {
|
||||||
oldModes = "+" + oldModes
|
oldModes = "+" + oldModes
|
||||||
}
|
}
|
||||||
@ -1271,8 +1298,9 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
|
|||||||
// CanSpeak returns true if the client can speak on this channel, otherwise it returns false along with the channel mode preventing the client from speaking.
|
// CanSpeak returns true if the client can speak on this channel, otherwise it returns false along with the channel mode preventing the client from speaking.
|
||||||
func (channel *Channel) CanSpeak(client *Client) (bool, modes.Mode) {
|
func (channel *Channel) CanSpeak(client *Client) (bool, modes.Mode) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
clientModes, hasClient := channel.members[client]
|
memberData, hasClient := channel.members[client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
clientModes := memberData.modes
|
||||||
|
|
||||||
if !hasClient && channel.flags.HasMode(modes.NoOutside) {
|
if !hasClient && channel.flags.HasMode(modes.NoOutside) {
|
||||||
// TODO: enforce regular +b bans on -n channels?
|
// TODO: enforce regular +b bans on -n channels?
|
||||||
@ -1347,9 +1375,9 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
|
|||||||
|
|
||||||
if channel.flags.HasMode(modes.OpModerated) {
|
if channel.flags.HasMode(modes.OpModerated) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
cuModes := channel.members[client]
|
cuData := channel.members[client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
if cuModes.HighestChannelUserMode() == modes.Mode(0) {
|
if cuData.modes.HighestChannelUserMode() == modes.Mode(0) {
|
||||||
// max(statusmsg_minmode, halfop)
|
// max(statusmsg_minmode, halfop)
|
||||||
if minPrefixMode == modes.Mode(0) || minPrefixMode == modes.Voice {
|
if minPrefixMode == modes.Mode(0) || minPrefixMode == modes.Voice {
|
||||||
minPrefixMode = modes.Halfop
|
minPrefixMode = modes.Halfop
|
||||||
@ -1402,9 +1430,9 @@ func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChang
|
|||||||
change.Arg = target.Nick()
|
change.Arg = target.Nick()
|
||||||
|
|
||||||
channel.stateMutex.Lock()
|
channel.stateMutex.Lock()
|
||||||
modeset, exists := channel.members[target]
|
memberData, exists := channel.members[target]
|
||||||
if exists {
|
if exists {
|
||||||
if modeset.SetMode(change.Mode, change.Op == modes.Add) {
|
if memberData.modes.SetMode(change.Mode, change.Op == modes.Add) {
|
||||||
applied = true
|
applied = true
|
||||||
result = change
|
result = change
|
||||||
}
|
}
|
||||||
@ -1590,19 +1618,19 @@ func (channel *Channel) auditoriumFriends(client *Client) (friends []*Client) {
|
|||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
clientModes := channel.members[client]
|
clientData, found := channel.members[client]
|
||||||
if clientModes == nil {
|
if !found {
|
||||||
return // non-members have no friends
|
return // non-members have no friends
|
||||||
}
|
}
|
||||||
if !channel.flags.HasMode(modes.Auditorium) {
|
if !channel.flags.HasMode(modes.Auditorium) {
|
||||||
return channel.membersCache // default behavior for members
|
return channel.membersCache // default behavior for members
|
||||||
}
|
}
|
||||||
if clientModes.HighestChannelUserMode() != modes.Mode(0) {
|
if clientData.modes.HighestChannelUserMode() != modes.Mode(0) {
|
||||||
return channel.membersCache // +v and up can see everyone in the auditorium
|
return channel.membersCache // +v and up can see everyone in the auditorium
|
||||||
}
|
}
|
||||||
// without +v, your friends are those with +v and up
|
// without +v, your friends are those with +v and up
|
||||||
for member, memberModes := range channel.members {
|
for member, memberData := range channel.members {
|
||||||
if memberModes.HighestChannelUserMode() != modes.Mode(0) {
|
if memberData.modes.HighestChannelUserMode() != modes.Mode(0) {
|
||||||
friends = append(friends, member)
|
friends = append(friends, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,6 +171,16 @@ SET modifies a channel's settings. The following settings are available:`,
|
|||||||
2. 'ephemeral' [a limited amount of temporary history, not stored on disk]
|
2. 'ephemeral' [a limited amount of temporary history, not stored on disk]
|
||||||
3. 'on' [history stored in a permanent database, if available]
|
3. 'on' [history stored in a permanent database, if available]
|
||||||
4. 'default' [use the server default]`,
|
4. 'default' [use the server default]`,
|
||||||
|
`$bQUERY-CUTOFF$b
|
||||||
|
'query-cutoff' lets you restrict how much channel history can be retrieved
|
||||||
|
by unprivileged users. Your options are:
|
||||||
|
1. 'none' [no restrictions]
|
||||||
|
2. 'registration-time' [users can view history from after their account was
|
||||||
|
registered, plus a grace period]
|
||||||
|
3. 'join-time' [users can biew history from after they joined the
|
||||||
|
channel; note that history will be effectively
|
||||||
|
unavailable to clients that are not always-on]
|
||||||
|
4. 'default' [use the server default]`,
|
||||||
},
|
},
|
||||||
enabled: chanregEnabled,
|
enabled: chanregEnabled,
|
||||||
minParams: 3,
|
minParams: 3,
|
||||||
@ -340,7 +350,7 @@ func csDeopHandler(service *ircService, server *Server, client *Client, command
|
|||||||
target = client
|
target = client
|
||||||
}
|
}
|
||||||
|
|
||||||
present, cumodes := channel.ClientStatus(target)
|
present, _, cumodes := channel.ClientStatus(target)
|
||||||
if !present || len(cumodes) == 0 {
|
if !present || len(cumodes) == 0 {
|
||||||
service.Notice(rb, client.t("Target has no privileges to remove"))
|
service.Notice(rb, client.t("Target has no privileges to remove"))
|
||||||
return
|
return
|
||||||
@ -764,6 +774,13 @@ func displayChannelSetting(service *ircService, settingName string, settings Cha
|
|||||||
effectiveValue := historyEnabled(config.History.Persistent.RegisteredChannels, settings.History)
|
effectiveValue := historyEnabled(config.History.Persistent.RegisteredChannels, settings.History)
|
||||||
service.Notice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History)))
|
service.Notice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History)))
|
||||||
service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue)))
|
service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue)))
|
||||||
|
case "query-cutoff":
|
||||||
|
effectiveValue := settings.QueryCutoff
|
||||||
|
if effectiveValue == HistoryCutoffDefault {
|
||||||
|
effectiveValue = config.History.Restrictions.queryCutoff
|
||||||
|
}
|
||||||
|
service.Notice(rb, fmt.Sprintf(client.t("The stored channel history query cutoff setting is: %s"), historyCutoffToString(settings.QueryCutoff)))
|
||||||
|
service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history query cutoff setting is: %s"), historyCutoffToString(effectiveValue)))
|
||||||
default:
|
default:
|
||||||
service.Notice(rb, client.t("Invalid params"))
|
service.Notice(rb, client.t("Invalid params"))
|
||||||
}
|
}
|
||||||
@ -807,6 +824,13 @@ func csSetHandler(service *ircService, server *Server, client *Client, command s
|
|||||||
}
|
}
|
||||||
channel.SetSettings(settings)
|
channel.SetSettings(settings)
|
||||||
channel.resizeHistory(server.Config())
|
channel.resizeHistory(server.Config())
|
||||||
|
case "query-cutoff":
|
||||||
|
settings.QueryCutoff, err = historyCutoffFromString(value)
|
||||||
|
if err != nil {
|
||||||
|
err = errInvalidParams
|
||||||
|
break
|
||||||
|
}
|
||||||
|
channel.SetSettings(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
|
@ -408,7 +408,7 @@ func (server *Server) RunClient(conn IRCConn) {
|
|||||||
client.run(session)
|
client.run(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToModes map[string]string, lastSeen map[string]time.Time, uModes modes.Modes, realname string) {
|
func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus map[string]alwaysOnChannelStatus, lastSeen map[string]time.Time, uModes modes.Modes, realname string) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
if lastSeen == nil && account.Settings.AutoreplayMissed {
|
if lastSeen == nil && account.Settings.AutoreplayMissed {
|
||||||
@ -472,12 +472,12 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToModes ma
|
|||||||
// XXX set this last to avoid confusing SetNick:
|
// XXX set this last to avoid confusing SetNick:
|
||||||
client.registered = true
|
client.registered = true
|
||||||
|
|
||||||
for chname, modeStr := range channelToModes {
|
for chname, status := range channelToStatus {
|
||||||
// XXX we're using isSajoin=true, to make these joins succeed even without channel key
|
// XXX we're using isSajoin=true, to make these joins succeed even without channel key
|
||||||
// this is *probably* ok as long as the persisted memberships are accurate
|
// this is *probably* ok as long as the persisted memberships are accurate
|
||||||
server.channels.Join(client, chname, "", true, nil)
|
server.channels.Join(client, chname, "", true, nil)
|
||||||
if channel := server.channels.Get(chname); channel != nil {
|
if channel := server.channels.Get(chname); channel != nil {
|
||||||
channel.setModesForClient(client, modeStr)
|
channel.setMemberStatus(client, status)
|
||||||
} else {
|
} else {
|
||||||
server.logger.Error("internal", "could not create channel", chname)
|
server.logger.Error("internal", "could not create channel", chname)
|
||||||
}
|
}
|
||||||
@ -967,7 +967,7 @@ func (session *Session) playResume() {
|
|||||||
for _, member := range channel.auditoriumFriends(client) {
|
for _, member := range channel.auditoriumFriends(client) {
|
||||||
friends.Add(member)
|
friends.Add(member)
|
||||||
}
|
}
|
||||||
status, _ := channel.historyStatus(config)
|
status, _, _ := channel.historyStatus(config)
|
||||||
if status == HistoryEphemeral {
|
if status == HistoryEphemeral {
|
||||||
lastDiscarded := channel.history.LastDiscarded()
|
lastDiscarded := channel.history.LastDiscarded()
|
||||||
if oldestLostMessage.Before(lastDiscarded) {
|
if oldestLostMessage.Before(lastDiscarded) {
|
||||||
@ -2001,10 +2001,10 @@ func (client *Client) performWrite(additionalDirtyBits uint) {
|
|||||||
|
|
||||||
if (dirtyBits & IncludeChannels) != 0 {
|
if (dirtyBits & IncludeChannels) != 0 {
|
||||||
channels := client.Channels()
|
channels := client.Channels()
|
||||||
channelToModes := make(map[string]string, len(channels))
|
channelToModes := make(map[string]alwaysOnChannelStatus, len(channels))
|
||||||
for _, channel := range channels {
|
for _, channel := range channels {
|
||||||
chname, modes := channel.nameAndModes(client)
|
chname, status := channel.alwaysOnStatus(client)
|
||||||
channelToModes[chname] = modes
|
channelToModes[chname] = status
|
||||||
}
|
}
|
||||||
client.server.accounts.saveChannels(account, channelToModes)
|
client.server.accounts.saveChannels(account, channelToModes)
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,45 @@ type listenerConfigBlock struct {
|
|||||||
HideSTS bool `yaml:"hide-sts"`
|
HideSTS bool `yaml:"hide-sts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HistoryCutoff uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
HistoryCutoffDefault HistoryCutoff = iota
|
||||||
|
HistoryCutoffNone
|
||||||
|
HistoryCutoffRegistrationTime
|
||||||
|
HistoryCutoffJoinTime
|
||||||
|
)
|
||||||
|
|
||||||
|
func historyCutoffToString(restriction HistoryCutoff) string {
|
||||||
|
switch restriction {
|
||||||
|
case HistoryCutoffDefault:
|
||||||
|
return "default"
|
||||||
|
case HistoryCutoffNone:
|
||||||
|
return "none"
|
||||||
|
case HistoryCutoffRegistrationTime:
|
||||||
|
return "registration-time"
|
||||||
|
case HistoryCutoffJoinTime:
|
||||||
|
return "join-time"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func historyCutoffFromString(str string) (result HistoryCutoff, err error) {
|
||||||
|
switch strings.ToLower(str) {
|
||||||
|
case "default":
|
||||||
|
return HistoryCutoffDefault, nil
|
||||||
|
case "none", "disabled", "off", "false":
|
||||||
|
return HistoryCutoffNone, nil
|
||||||
|
case "registration-time":
|
||||||
|
return HistoryCutoffRegistrationTime, nil
|
||||||
|
case "join-time":
|
||||||
|
return HistoryCutoffJoinTime, nil
|
||||||
|
default:
|
||||||
|
return HistoryCutoffDefault, errInvalidParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type PersistentStatus uint
|
type PersistentStatus uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -615,9 +654,12 @@ type Config struct {
|
|||||||
ChathistoryMax int `yaml:"chathistory-maxmessages"`
|
ChathistoryMax int `yaml:"chathistory-maxmessages"`
|
||||||
ZNCMax int `yaml:"znc-maxmessages"`
|
ZNCMax int `yaml:"znc-maxmessages"`
|
||||||
Restrictions struct {
|
Restrictions struct {
|
||||||
ExpireTime custime.Duration `yaml:"expire-time"`
|
ExpireTime custime.Duration `yaml:"expire-time"`
|
||||||
EnforceRegistrationDate bool `yaml:"enforce-registration-date"`
|
// legacy key, superceded by QueryCutoff:
|
||||||
GracePeriod custime.Duration `yaml:"grace-period"`
|
EnforceRegistrationDate_ bool `yaml:"enforce-registration-date"`
|
||||||
|
QueryCutoff string `yaml:"query-cutoff"`
|
||||||
|
queryCutoff HistoryCutoff
|
||||||
|
GracePeriod custime.Duration `yaml:"grace-period"`
|
||||||
}
|
}
|
||||||
Persistent struct {
|
Persistent struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
@ -1358,6 +1400,19 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
config.History.ZNCMax = config.History.ChathistoryMax
|
config.History.ZNCMax = config.History.ChathistoryMax
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.History.Restrictions.QueryCutoff != "" {
|
||||||
|
config.History.Restrictions.queryCutoff, err = historyCutoffFromString(config.History.Restrictions.QueryCutoff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value of history.query-restrictions: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if config.History.Restrictions.EnforceRegistrationDate_ {
|
||||||
|
config.History.Restrictions.queryCutoff = HistoryCutoffRegistrationTime
|
||||||
|
} else {
|
||||||
|
config.History.Restrictions.queryCutoff = HistoryCutoffNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.Roleplay.addSuffix = utils.BoolDefaultTrue(config.Roleplay.AddSuffix)
|
config.Roleplay.addSuffix = utils.BoolDefaultTrue(config.Roleplay.AddSuffix)
|
||||||
|
|
||||||
config.Datastore.MySQL.ExpireTime = time.Duration(config.History.Restrictions.ExpireTime)
|
config.Datastore.MySQL.ExpireTime = time.Duration(config.History.Restrictions.ExpireTime)
|
||||||
|
@ -24,7 +24,7 @@ const (
|
|||||||
// 'version' of the database schema
|
// 'version' of the database schema
|
||||||
keySchemaVersion = "db.version"
|
keySchemaVersion = "db.version"
|
||||||
// latest schema of the db
|
// latest schema of the db
|
||||||
latestDbSchema = 19
|
latestDbSchema = 20
|
||||||
|
|
||||||
keyCloakSecret = "crypto.cloak_secret"
|
keyCloakSecret = "crypto.cloak_secret"
|
||||||
)
|
)
|
||||||
@ -963,6 +963,51 @@ func schemaChangeV18To19(config *Config, tx *buntdb.Tx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #1490: start tracking join times for always-on clients
|
||||||
|
func schemaChangeV19To20(config *Config, tx *buntdb.Tx) error {
|
||||||
|
type joinData struct {
|
||||||
|
Modes string
|
||||||
|
JoinTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var accounts []string
|
||||||
|
var data []string
|
||||||
|
|
||||||
|
now := time.Now().UnixNano()
|
||||||
|
|
||||||
|
prefix := "account.channeltomodes "
|
||||||
|
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
||||||
|
if !strings.HasPrefix(key, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
accounts = append(accounts, strings.TrimPrefix(key, prefix))
|
||||||
|
data = append(data, value)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, account := range accounts {
|
||||||
|
var existingMap map[string]string
|
||||||
|
err := json.Unmarshal([]byte(data[i]), &existingMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newMap := make(map[string]joinData)
|
||||||
|
for channel, modeStr := range existingMap {
|
||||||
|
newMap[channel] = joinData{
|
||||||
|
Modes: modeStr,
|
||||||
|
JoinTime: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serialized, err := json.Marshal(newMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.Set(prefix+account, string(serialized), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
|
func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
|
||||||
for _, change := range allChanges {
|
for _, change := range allChanges {
|
||||||
if initialVersion == change.InitialVersion {
|
if initialVersion == change.InitialVersion {
|
||||||
@ -1063,4 +1108,9 @@ var allChanges = []SchemaChange{
|
|||||||
TargetVersion: 19,
|
TargetVersion: 19,
|
||||||
Changer: schemaChangeV18To19,
|
Changer: schemaChangeV18To19,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
InitialVersion: 19,
|
||||||
|
TargetVersion: 20,
|
||||||
|
Changer: schemaChangeV19To20,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -528,7 +528,7 @@ func (channel *Channel) Founder() string {
|
|||||||
|
|
||||||
func (channel *Channel) HighestUserMode(client *Client) (result modes.Mode) {
|
func (channel *Channel) HighestUserMode(client *Client) (result modes.Mode) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
clientModes := channel.members[client]
|
clientModes := channel.members[client].modes
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
return clientModes.HighestChannelUserMode()
|
return clientModes.HighestChannelUserMode()
|
||||||
}
|
}
|
||||||
|
@ -985,8 +985,8 @@ func extjwtHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
claims["channel"] = channel.Name()
|
claims["channel"] = channel.Name()
|
||||||
claims["joined"] = 0
|
claims["joined"] = 0
|
||||||
claims["cmodes"] = []string{}
|
claims["cmodes"] = []string{}
|
||||||
if present, cModes := channel.ClientStatus(client); present {
|
if present, joinTimeSecs, cModes := channel.ClientStatus(client); present {
|
||||||
claims["joined"] = 1
|
claims["joined"] = joinTimeSecs
|
||||||
var modeStrings []string
|
var modeStrings []string
|
||||||
for _, cMode := range cModes {
|
for _, cMode := range cModes {
|
||||||
modeStrings = append(modeStrings, string(cMode))
|
modeStrings = append(modeStrings, string(cMode))
|
||||||
@ -2660,7 +2660,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
status, _ := channel.historyStatus(config)
|
status, _, _ := channel.historyStatus(config)
|
||||||
if status == HistoryPersistent {
|
if status == HistoryPersistent {
|
||||||
rb.Add(nil, server.name, "FAIL", "RENAME", "CANNOT_RENAME", oldName, utils.SafeErrorParam(newName), client.t("Channels with persistent history cannot be renamed"))
|
rb.Add(nil, server.name, "FAIL", "RENAME", "CANNOT_RENAME", oldName, utils.SafeErrorParam(newName), client.t("Channels with persistent history cannot be renamed"))
|
||||||
return false
|
return false
|
||||||
|
@ -858,6 +858,7 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
|
|||||||
var status HistoryStatus
|
var status HistoryStatus
|
||||||
var target, correspondent string
|
var target, correspondent string
|
||||||
var hist *history.Buffer
|
var hist *history.Buffer
|
||||||
|
restriction := HistoryCutoffNone
|
||||||
channel = providedChannel
|
channel = providedChannel
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
if strings.HasPrefix(query, "#") {
|
if strings.HasPrefix(query, "#") {
|
||||||
@ -867,12 +868,15 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var joinTimeCutoff time.Time
|
||||||
if channel != nil {
|
if channel != nil {
|
||||||
if !channel.hasClient(client) {
|
if present, cutoff := channel.joinTimeCutoff(client); present {
|
||||||
|
joinTimeCutoff = cutoff
|
||||||
|
} else {
|
||||||
err = errInsufficientPrivs
|
err = errInsufficientPrivs
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status, target = channel.historyStatus(config)
|
status, target, restriction = channel.historyStatus(config)
|
||||||
switch status {
|
switch status {
|
||||||
case HistoryEphemeral:
|
case HistoryEphemeral:
|
||||||
hist = &channel.history
|
hist = &channel.history
|
||||||
@ -904,15 +908,20 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
|
|||||||
cutoff = time.Now().UTC().Add(-time.Duration(config.History.Restrictions.ExpireTime))
|
cutoff = time.Now().UTC().Add(-time.Duration(config.History.Restrictions.ExpireTime))
|
||||||
}
|
}
|
||||||
// #836: registration date cutoff is always enforced for DMs
|
// #836: registration date cutoff is always enforced for DMs
|
||||||
if config.History.Restrictions.EnforceRegistrationDate || channel == nil {
|
// either way, take the later of the two cutoffs
|
||||||
|
if restriction == HistoryCutoffRegistrationTime || channel == nil {
|
||||||
regCutoff := client.historyCutoff()
|
regCutoff := client.historyCutoff()
|
||||||
// take the later of the two cutoffs
|
|
||||||
if regCutoff.After(cutoff) {
|
if regCutoff.After(cutoff) {
|
||||||
cutoff = regCutoff
|
cutoff = regCutoff
|
||||||
}
|
}
|
||||||
|
} else if restriction == HistoryCutoffJoinTime {
|
||||||
|
if joinTimeCutoff.After(cutoff) {
|
||||||
|
cutoff = joinTimeCutoff
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #836 again: grace period is never applied to DMs
|
// #836 again: grace period is never applied to DMs
|
||||||
if !cutoff.IsZero() && channel != nil {
|
if !cutoff.IsZero() && channel != nil && restriction != HistoryCutoffJoinTime {
|
||||||
cutoff = cutoff.Add(-time.Duration(config.History.Restrictions.GracePeriod))
|
cutoff = cutoff.Add(-time.Duration(config.History.Restrictions.GracePeriod))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -966,7 +975,7 @@ func (server *Server) DeleteMessage(target, msgid, accountName string) (err erro
|
|||||||
if target[0] == '#' {
|
if target[0] == '#' {
|
||||||
channel := server.channels.Get(target)
|
channel := server.channels.Get(target)
|
||||||
if channel != nil {
|
if channel != nil {
|
||||||
if status, _ := channel.historyStatus(config); status == HistoryEphemeral {
|
if status, _, _ := channel.historyStatus(config); status == HistoryEphemeral {
|
||||||
hist = &channel.history
|
hist = &channel.history
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
irc/types.go
28
irc/types.go
@ -5,7 +5,11 @@
|
|||||||
|
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import "github.com/oragono/oragono/irc/modes"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
|
)
|
||||||
|
|
||||||
type empty struct{}
|
type empty struct{}
|
||||||
|
|
||||||
@ -28,12 +32,20 @@ func (clients ClientSet) Has(client *Client) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type memberData struct {
|
||||||
|
modes *modes.ModeSet
|
||||||
|
joinTime int64
|
||||||
|
}
|
||||||
|
|
||||||
// MemberSet is a set of members with modes.
|
// MemberSet is a set of members with modes.
|
||||||
type MemberSet map[*Client]*modes.ModeSet
|
type MemberSet map[*Client]memberData
|
||||||
|
|
||||||
// Add adds the given client to this set.
|
// Add adds the given client to this set.
|
||||||
func (members MemberSet) Add(member *Client) {
|
func (members MemberSet) Add(member *Client) {
|
||||||
members[member] = modes.NewModeSet()
|
members[member] = memberData{
|
||||||
|
modes: modes.NewModeSet(),
|
||||||
|
joinTime: time.Now().UnixNano(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the given client from this set.
|
// Remove removes the given client from this set.
|
||||||
@ -47,15 +59,5 @@ func (members MemberSet) Has(member *Client) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnyHasMode returns true if any of our clients has the given mode.
|
|
||||||
func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
|
|
||||||
for _, modes := range members {
|
|
||||||
if modes.HasMode(mode) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChannelSet is a set of channels.
|
// ChannelSet is a set of channels.
|
||||||
type ChannelSet map[*Channel]empty
|
type ChannelSet map[*Channel]empty
|
||||||
|
@ -850,10 +850,14 @@ history:
|
|||||||
# (and will eventually be deleted from persistent storage, if that's enabled)
|
# (and will eventually be deleted from persistent storage, if that's enabled)
|
||||||
expire-time: 1w
|
expire-time: 1w
|
||||||
|
|
||||||
# if this is set, logged-in users cannot retrieve messages older than their
|
# this restricts access to channel history (it can be overridden by channel
|
||||||
# account registration date, and logged-out users cannot retrieve messages
|
# owners). options are: 'none' (no restrictions), 'registration-time'
|
||||||
# older than their sign-on time (modulo grace-period, see below):
|
# (logged-in users cannot retrieve messages older than their account
|
||||||
enforce-registration-date: false
|
# registration date, and anonymous users cannot retrieve messages older than
|
||||||
|
# their sign-on time, modulo the grace-period described below), and
|
||||||
|
# 'join-time' (users cannot retrieve messages older than the time they
|
||||||
|
# joined the channel, so only always-on clients can view history).
|
||||||
|
query-cutoff: 'none'
|
||||||
|
|
||||||
# but if this is set, you can retrieve messages that are up to `grace-period`
|
# but if this is set, you can retrieve messages that are up to `grace-period`
|
||||||
# older than the above cutoff time. this is recommended to allow logged-out
|
# older than the above cutoff time. this is recommended to allow logged-out
|
||||||
|
Loading…
Reference in New Issue
Block a user