diff --git a/gencapdefs.py b/gencapdefs.py index 76e9d667..1c8a412b 100644 --- a/gencapdefs.py +++ b/gencapdefs.py @@ -189,6 +189,12 @@ CAPDEFS = [ url="https://github.com/ircv3/ircv3-specifications/pull/489", standard="draft IRCv3", ), + CapDef( + identifier="Persistence", + name="draft/persistence", + url="https://gist.github.com/slingamn/e3645a0d0418b736b755746bfd65f2a6", + standard="proposed IRCv3", + ), ] def validate_defs(): diff --git a/irc/caps/defs.go b/irc/caps/defs.go index b1f7192c..4e36c846 100644 --- a/irc/caps/defs.go +++ b/irc/caps/defs.go @@ -7,7 +7,7 @@ package caps const ( // number of recognized capabilities: - numCapabs = 29 + numCapabs = 30 // length of the uint64 array that represents the bitset: bitsetLen = 1 ) @@ -61,6 +61,10 @@ const ( // https://github.com/ircv3/ircv3-specifications/pull/398 Multiline Capability = iota + // Persistence is the proposed IRCv3 capability named "draft/persistence": + // https://gist.github.com/slingamn/e3645a0d0418b736b755746bfd65f2a6 + Persistence Capability = iota + // ReadMarker is the draft IRCv3 capability named "draft/read-marker": // https://github.com/ircv3/ircv3-specifications/pull/489 ReadMarker Capability = iota @@ -145,6 +149,7 @@ var ( "draft/event-playback", "draft/languages", "draft/multiline", + "draft/persistence", "draft/read-marker", "draft/relaymsg", "echo-message", diff --git a/irc/commands.go b/irc/commands.go index 1d456d1a..e4b8d15b 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -233,6 +233,10 @@ func init() { usablePreReg: true, minParams: 1, }, + "PERSISTENCE": { + handler: persistenceHandler, + minParams: 1, + }, "PING": { handler: pingHandler, usablePreReg: true, diff --git a/irc/handlers.go b/irc/handlers.go index 7f3143d6..6cec4e20 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -2599,6 +2599,81 @@ func passHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons return false } +// PERSISTENCE [params...] +func persistenceHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { + account := client.Account() + if account == "" { + rb.Add(nil, server.name, "FAIL", "PERSISTENCE", "ACCOUNT_REQUIRED", client.t("You're not logged into an account")) + return false + } + + switch strings.ToUpper(msg.Params[0]) { + case "GET": + reportPersistenceStatus(client, rb) + case "SET": + if len(msg.Params) == 1 { + goto fail + } + var desiredSetting PersistentStatus + switch strings.ToUpper(msg.Params[1]) { + case "DEFAULT": + desiredSetting = PersistentUnspecified + case "OFF": + desiredSetting = PersistentDisabled + case "ON": + desiredSetting = PersistentMandatory + default: + goto fail + } + + _, err := server.accounts.ModifyAccountSettings(account, + func(input AccountSettings) (output AccountSettings, err error) { + output = input + output.AlwaysOn = desiredSetting + return + }) + if err != nil { + server.logger.Error("internal", "couldn't modify persistence setting", err.Error()) + rb.Add(nil, server.name, "FAIL", "PERSISTENCE", "UNKNOWN_ERROR", client.t("An error occurred")) + return false + } + + reportPersistenceStatus(client, rb) + + default: + goto fail + } + + return false + +fail: + rb.Add(nil, server.name, "FAIL", "PERSISTENCE", "INVALID_PARAMS", client.t("Invalid parameters")) + return false +} + +func reportPersistenceStatus(client *Client, rb *ResponseBuffer) { + settings := client.AccountSettings() + serverSetting := client.server.Config().Accounts.Multiclient.AlwaysOn + effectiveSetting := persistenceEnabled(serverSetting, settings.AlwaysOn) + toString := func(setting PersistentStatus) string { + switch setting { + case PersistentUnspecified: + return "DEFAULT" + case PersistentDisabled: + return "OFF" + case PersistentMandatory: + return "ON" + default: + return "*" // impossible + } + } + effectiveSettingStr := "OFF" + if effectiveSetting { + effectiveSettingStr = "ON" + } + rb.Add(nil, client.server.name, "PERSISTENCE", "STATUS", toString(settings.AlwaysOn), effectiveSettingStr) +} + // PING [params...] func pingHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { rb.Add(nil, server.name, "PONG", server.name, msg.Params[0]) diff --git a/irc/help.go b/irc/help.go index 3f9271a1..2d972aa8 100644 --- a/irc/help.go +++ b/irc/help.go @@ -413,6 +413,13 @@ Leaves the given channels and shows people the given reason.`, When the server requires a connection password to join, used to send us the password.`, + }, + "persistence": { + text: `PERSISTENCE [params] + +PERSISTENCE is a command associated with an IRC protocol extension for +persistent connections. End users should probably use /NS GET ALWAYS-ON +and /NS SET ALWAYS-ON instead.`, }, "ping": { text: `PING ... diff --git a/irc/server.go b/irc/server.go index 058c7372..221635cf 100644 --- a/irc/server.go +++ b/irc/server.go @@ -420,6 +420,9 @@ func (server *Server) playRegistrationBurst(session *Session) { rb := NewResponseBuffer(session) server.RplISupport(c, rb) + if session.capabilities.Has(caps.Persistence) { + reportPersistenceStatus(c, rb) + } server.Lusers(c, rb) server.MOTD(c, rb) rb.Send(true)