mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 10:42:52 +01:00
Merge remote-tracking branch 'origin/master' into brb.5
This commit is contained in:
commit
851617a4a5
@ -165,6 +165,12 @@ CAPDEFS = [
|
||||
url="https://github.com/ircv3/ircv3-specifications/pull/362",
|
||||
standard="Proposed IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="ZNCPlayback",
|
||||
name="znc.in/playback",
|
||||
url="https://wiki.znc.in/Playback",
|
||||
standard="ZNC vendor",
|
||||
),
|
||||
]
|
||||
|
||||
def validate_defs():
|
||||
|
@ -1108,6 +1108,10 @@ func (am *AccountManager) VHostReject(account string, reason string) (result VHo
|
||||
|
||||
func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result VHostInfo, err error) {
|
||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
||||
if input.ApprovedVHost == "" {
|
||||
err = errNoVhost
|
||||
return
|
||||
}
|
||||
output = input
|
||||
output.Enabled = enabled
|
||||
return
|
||||
|
@ -7,7 +7,7 @@ package caps
|
||||
|
||||
const (
|
||||
// number of recognized capabilities:
|
||||
numCapabs = 25
|
||||
numCapabs = 26
|
||||
// length of the uint64 array that represents the bitset:
|
||||
bitsetLen = 1
|
||||
)
|
||||
@ -112,6 +112,10 @@ const (
|
||||
// EventPlayback is the Proposed IRCv3 capability named "draft/event-playback":
|
||||
// https://github.com/ircv3/ircv3-specifications/pull/362
|
||||
EventPlayback Capability = iota
|
||||
|
||||
// ZNCPlayback is the ZNC vendor capability named "znc.in/playback":
|
||||
// https://wiki.znc.in/Playback
|
||||
ZNCPlayback Capability = iota
|
||||
)
|
||||
|
||||
// `capabilityNames[capab]` is the string name of the capability `capab`
|
||||
@ -142,5 +146,6 @@ var (
|
||||
"oragono.io/bnc",
|
||||
"znc.in/self-message",
|
||||
"draft/event-playback",
|
||||
"znc.in/playback",
|
||||
}
|
||||
)
|
||||
|
@ -624,6 +624,12 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
|
||||
rb.Flush(true)
|
||||
|
||||
// autoreplay any messages as necessary
|
||||
config := channel.server.Config()
|
||||
var items []history.Item
|
||||
if rb.session.zncPlaybackTimes != nil && (rb.session.zncPlaybackTimes.targets == nil || rb.session.zncPlaybackTimes.targets[chcfname]) {
|
||||
items, _ = channel.history.Between(rb.session.zncPlaybackTimes.after, rb.session.zncPlaybackTimes.before, false, config.History.ChathistoryMax)
|
||||
} else {
|
||||
var replayLimit int
|
||||
customReplayLimit := client.AccountSettings().AutoreplayLines
|
||||
if customReplayLimit != nil {
|
||||
@ -636,13 +642,23 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
replayLimit = channel.server.Config().History.AutoreplayOnJoin
|
||||
}
|
||||
if 0 < replayLimit {
|
||||
// TODO don't replay the client's own JOIN line?
|
||||
items := channel.history.Latest(replayLimit)
|
||||
if 0 < len(items) {
|
||||
items = channel.history.Latest(replayLimit)
|
||||
}
|
||||
}
|
||||
// remove the client's own JOIN line from the replay
|
||||
numItems := len(items)
|
||||
for i := len(items) - 1; 0 <= i; i-- {
|
||||
if items[i].Message.Msgid == message.Msgid {
|
||||
// zero'ed items will not be replayed because their `Type` field is not recognized
|
||||
items[i] = history.Item{}
|
||||
numItems--
|
||||
break
|
||||
}
|
||||
}
|
||||
if 0 < numItems {
|
||||
channel.replayHistoryItems(rb, items, true)
|
||||
rb.Flush(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// plays channel join messages (the JOIN line, topic, and names) to a session.
|
||||
|
@ -113,6 +113,7 @@ type Session struct {
|
||||
|
||||
resumeID string
|
||||
resumeDetails *ResumeDetails
|
||||
zncPlaybackTimes *zncPlaybackTimes
|
||||
}
|
||||
|
||||
// sets the session quit message, if there isn't one already
|
||||
|
@ -325,6 +325,10 @@ func init() {
|
||||
handler: whowasHandler,
|
||||
minParams: 1,
|
||||
},
|
||||
"ZNC": {
|
||||
handler: zncHandler,
|
||||
minParams: 1,
|
||||
},
|
||||
}
|
||||
|
||||
initializeServices()
|
||||
|
@ -44,6 +44,7 @@ var (
|
||||
errFeatureDisabled = errors.New(`That feature is disabled`)
|
||||
errBanned = errors.New("IP or nickmask banned")
|
||||
errInvalidParams = utils.ErrInvalidParams
|
||||
errNoVhost = errors.New(`You do not have an approved vhost`)
|
||||
)
|
||||
|
||||
// Socket Errors
|
||||
|
@ -2027,12 +2027,16 @@ func messageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
||||
}
|
||||
channel.SendSplitMessage(msg.Command, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
|
||||
} else {
|
||||
if service, isService := OragonoServices[strings.ToLower(targetString)]; isService {
|
||||
// NOTICE and TAGMSG to services are ignored
|
||||
if histType == history.Privmsg {
|
||||
lowercaseTarget := strings.ToLower(targetString)
|
||||
if service, isService := OragonoServices[lowercaseTarget]; isService {
|
||||
servicePrivmsgHandler(service, server, client, message, rb)
|
||||
}
|
||||
continue
|
||||
} else if _, isZNC := zncHandlers[lowercaseTarget]; isZNC {
|
||||
zncPrivmsgHandler(client, lowercaseTarget, message, rb)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
user := server.clients.Get(targetString)
|
||||
@ -2769,3 +2773,9 @@ func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ZNC <module> [params]
|
||||
func zncHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||
zncModuleHandler(client, msg.Params[0], msg.Params[1:], rb)
|
||||
return false
|
||||
}
|
||||
|
@ -548,6 +548,13 @@ Returns information for the given user(s).`,
|
||||
|
||||
Returns historical information on the last user with the given nickname.`,
|
||||
},
|
||||
"znc": {
|
||||
text: `ZNC <module> [params]
|
||||
|
||||
Used to emulate features of the ZNC bouncer. This command is not intended
|
||||
for direct use by end users.`,
|
||||
duplicate: true,
|
||||
},
|
||||
|
||||
// Informational
|
||||
"modes": {
|
||||
|
@ -154,7 +154,9 @@ func hsOnOffHandler(server *Server, client *Client, command string, params []str
|
||||
}
|
||||
|
||||
_, err := server.accounts.VHostSetEnabled(client, enable)
|
||||
if err != nil {
|
||||
if err == errNoVhost {
|
||||
hsNotice(rb, client.t(err.Error()))
|
||||
} else if err != nil {
|
||||
hsNotice(rb, client.t("An error occurred"))
|
||||
} else if enable {
|
||||
hsNotice(rb, client.t("Successfully enabled your vhost"))
|
||||
|
17
irc/misc_test.go
Normal file
17
irc/misc_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||
// released under the MIT license
|
||||
|
||||
package irc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestZncTimestampParser(t *testing.T) {
|
||||
assertEqual(zncWireTimeToTime("1558338348.988"), time.Unix(1558338348, 988000000), t)
|
||||
assertEqual(zncWireTimeToTime("1558338348.9"), time.Unix(1558338348, 900000000), t)
|
||||
assertEqual(zncWireTimeToTime("1558338348"), time.Unix(1558338348, 0), t)
|
||||
assertEqual(zncWireTimeToTime(".988"), time.Unix(0, 988000000), t)
|
||||
assertEqual(zncWireTimeToTime("garbage"), time.Unix(0, 0), t)
|
||||
}
|
98
irc/znc.go
Normal file
98
irc/znc.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||
// released under the MIT license
|
||||
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type zncCommandHandler func(client *Client, command string, params []string, rb *ResponseBuffer)
|
||||
|
||||
var zncHandlers = map[string]zncCommandHandler{
|
||||
"*playback": zncPlaybackHandler,
|
||||
}
|
||||
|
||||
func zncPrivmsgHandler(client *Client, command string, privmsg string, rb *ResponseBuffer) {
|
||||
zncModuleHandler(client, command, strings.Fields(privmsg), rb)
|
||||
}
|
||||
|
||||
func zncModuleHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
command = strings.ToLower(command)
|
||||
if subHandler, ok := zncHandlers[command]; ok {
|
||||
subHandler(client, command, params, rb)
|
||||
} else {
|
||||
nick := rb.target.Nick()
|
||||
rb.Add(nil, client.server.name, "NOTICE", nick, fmt.Sprintf(client.t("Oragono does not emulate the ZNC module %s"), command))
|
||||
rb.Add(nil, "*status!znc@znc.in", "NOTICE", nick, fmt.Sprintf(client.t("No such module [%s]"), command))
|
||||
}
|
||||
}
|
||||
|
||||
// "number of seconds (floating point for millisecond precision) elapsed since January 1, 1970"
|
||||
func zncWireTimeToTime(str string) (result time.Time) {
|
||||
var secondsPortion, fracPortion string
|
||||
dot := strings.IndexByte(str, '.')
|
||||
if dot == -1 {
|
||||
secondsPortion = str
|
||||
} else {
|
||||
secondsPortion = str[:dot]
|
||||
fracPortion = str[dot:]
|
||||
}
|
||||
seconds, _ := strconv.ParseInt(secondsPortion, 10, 64)
|
||||
fraction, _ := strconv.ParseFloat(fracPortion, 64)
|
||||
return time.Unix(seconds, int64(fraction*1000000000))
|
||||
}
|
||||
|
||||
type zncPlaybackTimes struct {
|
||||
after time.Time
|
||||
before time.Time
|
||||
targets map[string]bool // nil for "*" (everything), otherwise the channel names
|
||||
}
|
||||
|
||||
// https://wiki.znc.in/Playback
|
||||
// PRIVMSG *playback :play <target> [lower_bound] [upper_bound]
|
||||
// e.g., PRIVMSG *playback :play * 1558374442
|
||||
func zncPlaybackHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
if len(params) < 2 {
|
||||
return
|
||||
} else if strings.ToLower(params[0]) != "play" {
|
||||
return
|
||||
}
|
||||
targetString := params[1]
|
||||
|
||||
var after, before time.Time
|
||||
if 2 < len(params) {
|
||||
after = zncWireTimeToTime(params[2])
|
||||
}
|
||||
if 3 < len(params) {
|
||||
before = zncWireTimeToTime(params[3])
|
||||
}
|
||||
|
||||
var targets map[string]bool
|
||||
|
||||
// OK: the user's PMs get played back immediately on receiving this,
|
||||
// then we save the timestamps in the session to handle replay on future channel joins
|
||||
config := client.server.Config()
|
||||
if params[1] == "*" {
|
||||
items, _ := client.history.Between(after, before, false, config.History.ChathistoryMax)
|
||||
client.replayPrivmsgHistory(rb, items, true)
|
||||
} else {
|
||||
for _, targetName := range strings.Split(targetString, ",") {
|
||||
if cfTarget, err := CasefoldChannel(targetName); err == nil {
|
||||
if targets == nil {
|
||||
targets = make(map[string]bool)
|
||||
}
|
||||
targets[cfTarget] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rb.session.zncPlaybackTimes = &zncPlaybackTimes{
|
||||
after: after,
|
||||
before: before,
|
||||
targets: targets,
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
"== Channel Modes ==\n\nOragono supports the following channel modes:\n\n +b | Client masks that are banned from the channel (e.g. *!*@127.0.0.1)\n +e | Client masks that are exempted from bans.\n +I | Client masks that are exempted from the invite-only flag.\n +i | Invite-only mode, only invited clients can join the channel.\n +k | Key required when joining the channel.\n +l | Client join limit for the channel.\n +m | Moderated mode, only privileged clients can talk on the channel.\n +n | No-outside-messages mode, only users that are on the channel can send\n | messages to it.\n +R | Only registered users can talk in the channel.\n +s | Secret mode, channel won't show up in /LIST or whois replies.\n +t | Only channel opers can modify the topic.\n\n= Prefixes =\n\n +q (~) | Founder channel mode.\n +a (&) | Admin channel mode.\n +o (@) | Operator channel mode.\n +h (%) | Halfop channel mode.\n +v (+) | Voice channel mode.": "== Moduri de canal ==\n\nOragono suportă următoarele moduri de canal:\n\n +b | Măștile unui client, interzise pe canal (ex. *!*@127.0.0.1)\n +e | Măștile unui client, ce sunt exceptate de la ban.\n +I | Măștile clienților ce fac excepție de la fanionul numai-invitație.\n +i | Fanion doar-invitație, doar clienții invitați pot intra pe canal.\n +k | Este necesară o cheie, pentru a putea intra pe canal.\n +l | Limită de clienți ce se pot alătura canalului.\n +m | Modul moderat, doar clienții privilegiați pot trimite text pe canal.\n +n | Fanion fără-mesaje-din-afară, doar utilizatorii care se află pe canal\n | pot trimite mesaje.\n +R | Doar utilizatorii înregistrați la servicii pot trimite text pe canal.\n +s | Mod secret, canalul nu va fi afișat în /LIST sau mesaje whois.\n +t | Doar operatorii pot modifica topicul.\n\n= Prefixe =\n\n +q (~) | Mod fondator de canal.\n +a (&) | Mod administrator de canal.\n +o (@) | Mod operator de canal.\n +h (%) | Mod semi-op pe canal.\n +v (+) | Mod voce pe canal.",
|
||||
"== Server Notice Masks ==\n\nOragono supports the following server notice masks for operators:\n\n a | Local announcements.\n c | Local client connections.\n j | Local channel actions.\n k | Local kills.\n n | Local nick changes.\n o | Local oper actions.\n q | Local quits.\n t | Local /STATS usage.\n u | Local client account actions.\n x | Local X-lines (DLINE/KLINE/etc).\n\nTo set a snomask, do this with your nickname:\n\n /MODE <nick> +s <chars>\n\nFor instance, this would set the kill, oper, account and xline snomasks on dan:\n\n /MODE dan +s koux": "== Măști de atenționare de server ==\n\nOragono suportă următoarele măști de atenționare valabile pentru operatori:\n\n a | Anunțuri locale.\n c | Conexiuni clienți locali.\n j | Acțiuni canale locale.\n k | Kill locale.\n n | Schimbări locale de pseudonime.\n o | Acțiuni operatori locali.\n q | Renunțări locale.\n t | Utilizare /STATS local.\n u | Local client account actions.\n x | X-lines (DLINE/KLINE/etc) locale.\n\nPentru a seta o mască de atenționare, setează-ți pe pseudonim:\n\n /MODE <pseudonim> +s <caracter(e)>\n\nSpre exemplu, următoarea comandă ar seta modurile kill, oper, cont și xline pentru utilizatorul dan:\n\n /MODE dan +s koux",
|
||||
"== User Modes ==\n\nOragono supports the following user modes:\n\n +a | User is marked as being away. This mode is set with the /AWAY command.\n +i | User is marked as invisible (their channels are hidden from whois replies).\n +o | User is an IRC operator.\n +R | User only accepts messages from other registered users. \n +s | Server Notice Masks (see help with /HELPOP snomasks).\n +Z | User is connected via TLS.": "== Moduri utilizator ==\n\nOragono suportă următoarele moduri(fanioane) de utilizator:\n\n +a | Utilizatorul a fost marcat ca fiind fără activitate. Acest fanion poate fi setat cu comanda /AWAY.\n +i | Utilizatorul a fost marcat ca invizibil (canalele pe care se află vor fi ascunse în conținutul /whois).\n +o | Utilizatorul este un Operator IRC.\n +R | Utilizatorul acceptă mesaje doar de la alți utilizatori înregistrați. \n +s | Măști de Anunț de Server (vezi informații de ajutor cu comanda /HELPOP snomasks).\n +Z | Utilizatorul se conectează via TLS.",
|
||||
"@+client-only-tags TAGMSG <target>{,<target>}\n\nSends the given client-only tags to the given targets as a TAGMSG. See the IRCv3\nspecs for more info: http://ircv3.net/specs/core/message-tags-3.3.html": "",
|
||||
"@+client-only-tags TAGMSG <target>{,<target>}\n\nSends the given client-only tags to the given targets as a TAGMSG. See the IRCv3\nspecs for more info: http://ircv3.net/specs/core/message-tags-3.3.html": "@+client-only-tags TAGMSG <țintă>{,<țintă>}\n\nExpediază marcajele client-doar către țintele precizate ca și TAGMSG.\nVezi specificațiile IRCv3, iar pentru mai multe informații, accesează acest link:\nhttp://ircv3.net/specs/core/message-tags-3.3.html",
|
||||
"ACC LS\nACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>\nACC VERIFY <accountname> <auth_code>\n\nUsed in account registration. See the relevant specs for more info:\nhttps://oragono.io/specs.html": "",
|
||||
"AMBIANCE <target> <text to be sent>\n\nThe AMBIANCE command is used to send a scene notification to the given target.": "",
|
||||
"AUTHENTICATE\n\nUsed during SASL authentication. See the IRCv3 specs for more info:\nhttp://ircv3.net/specs/extensions/sasl-3.1.html": "",
|
||||
@ -66,5 +66,5 @@
|
||||
"WEBIRC <password> <gateway> <hostname> <ip> [:<flags>]\n\nUsed by web<->IRC gateways and bouncers, the WEBIRC command allows gateways to\npass-through the real IP addresses of clients:\nircv3.net/specs/extensions/webirc.html\n\n<flags> is a list of space-separated strings indicating various details about\nthe connection from the client to the gateway, such as:\n\n- tls: this flag indicates that the client->gateway connection is secure": "",
|
||||
"WHO <name> [o]\n\nReturns information for the given user.": "",
|
||||
"WHOIS <client>{,<client>}\n\nReturns information for the given user(s).": "",
|
||||
"WHOWAS <nickname>\n\nReturns historical information on the last user with the given nickname.": ""
|
||||
"WHOWAS <nickname>\n\nReturns historical information on the last user with the given nickname.": "WHOWAS <pseudonim>\n\nAfișează informații despre istoricul ultimului utilizator cu pseudonimul cerut."
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user