Merge pull request #881 from slingamn/issue865_roleplay.2

fix #865
This commit is contained in:
Shivaram Lingamneni 2020-03-20 02:51:20 -07:00 committed by GitHub
commit 37cd2e77c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 47 deletions

View File

@ -507,6 +507,15 @@ type Config struct {
Casemapping Casemapping Casemapping Casemapping
} }
Roleplay struct {
Enabled *bool
enabled bool
RequireChanops bool `yaml:"require-chanops"`
RequireOper bool `yaml:"require-oper"`
AddSuffix *bool `yaml:"add-suffix"`
addSuffix bool
}
Languages struct { Languages struct {
Enabled bool Enabled bool
Path string Path string
@ -844,12 +853,7 @@ func LoadConfig(filename string) (config *Config, err error) {
// set this even if STS is disabled // set this even if STS is disabled
config.Server.capValues[caps.STS] = config.Server.STS.Value() config.Server.capValues[caps.STS] = config.Server.STS.Value()
// lookup-hostnames defaults to true if unset config.Server.lookupHostnames = utils.BoolDefaultTrue(config.Server.LookupHostnames)
if config.Server.LookupHostnames != nil {
config.Server.lookupHostnames = *config.Server.LookupHostnames
} else {
config.Server.lookupHostnames = true
}
// process webirc blocks // process webirc blocks
var newWebIRC []webircConfig var newWebIRC []webircConfig
@ -1014,12 +1018,7 @@ func LoadConfig(filename string) (config *Config, err error) {
} }
config.Server.capValues[caps.Languages] = config.languageManager.CapValue() config.Server.capValues[caps.Languages] = config.languageManager.CapValue()
// RecoverFromErrors defaults to true config.Debug.recoverFromErrors = utils.BoolDefaultTrue(config.Debug.RecoverFromErrors)
if config.Debug.RecoverFromErrors != nil {
config.Debug.recoverFromErrors = *config.Debug.RecoverFromErrors
} else {
config.Debug.recoverFromErrors = true
}
// process operator definitions, store them to config.operators // process operator definitions, store them to config.operators
operclasses, err := config.OperatorClasses() operclasses, err := config.OperatorClasses()
@ -1053,12 +1052,7 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Channels.Registration.MaxChannelsPerAccount = 15 config.Channels.Registration.MaxChannelsPerAccount = 15
} }
forceTrailingPtr := config.Server.Compatibility.ForceTrailing config.Server.Compatibility.forceTrailing = utils.BoolDefaultTrue(config.Server.Compatibility.ForceTrailing)
if forceTrailingPtr != nil {
config.Server.Compatibility.forceTrailing = *forceTrailingPtr
} else {
config.Server.Compatibility.forceTrailing = true
}
config.loadMOTD() config.loadMOTD()
@ -1080,6 +1074,9 @@ func LoadConfig(filename string) (config *Config, err error) {
config.History.ZNCMax = config.History.ChathistoryMax config.History.ZNCMax = config.History.ChathistoryMax
} }
config.Roleplay.enabled = utils.BoolDefaultTrue(config.Roleplay.Enabled)
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)
config.Server.Cloaks.Initialize() config.Server.Cloaks.Initialize()
@ -1133,8 +1130,10 @@ func (config *Config) generateISupport() (err error) {
isupport.Add("NETWORK", config.Network.Name) isupport.Add("NETWORK", config.Network.Name)
isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen)) isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
isupport.Add("PREFIX", "(qaohv)~&@%+") isupport.Add("PREFIX", "(qaohv)~&@%+")
if config.Roleplay.enabled {
isupport.Add("RPCHAN", "E") isupport.Add("RPCHAN", "E")
isupport.Add("RPUSER", "E") isupport.Add("RPUSER", "E")
}
isupport.Add("STATUSMSG", "~&@%+") isupport.Add("STATUSMSG", "~&@%+")
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString, maxTargetsString)) isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString, maxTargetsString))
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen)) isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))

View File

@ -2027,7 +2027,7 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
func npcHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func npcHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
target := msg.Params[0] target := msg.Params[0]
fakeSource := msg.Params[1] fakeSource := msg.Params[1]
message := msg.Params[2] message := msg.Params[2:]
_, err := CasefoldName(fakeSource) _, err := CasefoldName(fakeSource)
if err != nil { if err != nil {
@ -2046,7 +2046,7 @@ func npcHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
target := msg.Params[0] target := msg.Params[0]
fakeSource := msg.Params[1] fakeSource := msg.Params[1]
message := msg.Params[2] message := msg.Params[2:]
sourceString := fmt.Sprintf(npcNickMask, fakeSource, client.nick) sourceString := fmt.Sprintf(npcNickMask, fakeSource, client.nick)
_, err := CasefoldName(fakeSource) _, err := CasefoldName(fakeSource)
@ -2231,7 +2231,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(oldName), client.t("No such channel")) rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(oldName), client.t("No such channel"))
return false return false
} }
if !(channel.ClientIsAtLeast(client, modes.Operator) || client.HasRoleCapabs("chanreg")) { if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("chanreg")) {
rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), oldName, client.t("You're not a channel operator")) rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), oldName, client.t("You're not a channel operator"))
return false return false
} }
@ -2334,7 +2334,7 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
// SCENE <target> <message> // SCENE <target> <message>
func sceneHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func sceneHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
target := msg.Params[0] target := msg.Params[0]
message := msg.Params[1] message := msg.Params[1:]
sourceString := fmt.Sprintf(sceneNickMask, client.nick) sourceString := fmt.Sprintf(sceneNickMask, client.nick)
sendRoleplayMessage(server, client, sourceString, target, false, message, rb) sendRoleplayMessage(server, client, sourceString, target, false, message, rb)

View File

@ -4,10 +4,11 @@
package irc package irc
import ( import (
"fmt" "bytes"
"github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/history"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/utils"
) )
const ( const (
@ -15,18 +16,40 @@ const (
sceneNickMask = "=Scene=!%s@npc.fakeuser.invalid" sceneNickMask = "=Scene=!%s@npc.fakeuser.invalid"
) )
func sendRoleplayMessage(server *Server, client *Client, source string, targetString string, isAction bool, message string, rb *ResponseBuffer) { func sendRoleplayMessage(server *Server, client *Client, source string, targetString string, isAction bool, messageParts []string, rb *ResponseBuffer) {
if isAction { config := server.Config()
message = fmt.Sprintf("\x01ACTION %s (%s)\x01", message, client.nick) if !config.Roleplay.enabled {
} else { rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Roleplaying has been disabled by the server administrators"))
// block attempts to send CTCP messages to Tor clients
// TODO(#395) clean this up
if len(message) != 0 && message[0] == '\x01' {
return return
} }
message = fmt.Sprintf("%s (%s)", message, client.nick) if config.Roleplay.RequireOper && !client.HasRoleCapabs("roleplay") {
rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Insufficient privileges"))
return
} }
// block attempts to send CTCP messages to Tor clients
if len(messageParts) > 0 && len(messageParts[0]) > 0 && messageParts[0][0] == '\x01' {
return
}
var buf bytes.Buffer
if isAction {
buf.WriteString("\x01ACTION ")
}
for i, part := range messageParts {
buf.WriteString(part)
if i != len(messageParts)-1 {
buf.WriteByte(' ')
}
}
if config.Roleplay.addSuffix {
buf.WriteString(" (")
buf.WriteString(client.Nick())
buf.WriteString(")")
}
splitMessage := utils.MakeMessage(buf.String())
target, cerr := CasefoldChannel(targetString) target, cerr := CasefoldChannel(targetString)
if cerr == nil { if cerr == nil {
channel := server.channels.Get(target) channel := server.channels.Get(target)
@ -35,27 +58,40 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
return return
} }
targetString = channel.Name()
if !channel.CanSpeak(client) { if !channel.CanSpeak(client) {
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel")) rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, targetString, client.t("Cannot send to channel"))
return return
} }
if !channel.flags.HasMode(modes.ChanRoleplaying) { if !channel.flags.HasMode(modes.ChanRoleplaying) {
rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, channel.name, client.t("Channel doesn't have roleplaying mode available")) rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Channel doesn't have roleplaying mode available"))
return
}
if config.Roleplay.RequireChanops && !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Insufficient privileges"))
return return
} }
for _, member := range channel.Members() { for _, member := range channel.Members() {
for _, session := range member.Sessions() { for _, session := range member.Sessions() {
if member == client && !session.capabilities.Has(caps.EchoMessage) { // see discussion on #865: clients do not understand how to do local echo
continue // of roleplay commands, so send them a copy whether they have echo-message
} else if rb.session == session { // or not
rb.Add(nil, source, "PRIVMSG", channel.name, message) if rb.session == session {
} else if member == client || session.capabilities.Has(caps.EchoMessage) { rb.AddSplitMessageFromClient(source, "", nil, "PRIVMSG", targetString, splitMessage)
session.Send(nil, source, "PRIVMSG", channel.name, message) } else {
session.sendSplitMsgFromClientInternal(false, source, "", nil, "PRIVMSG", targetString, splitMessage)
} }
} }
} }
channel.AddHistoryItem(history.Item{
Type: history.Privmsg,
Message: splitMessage,
Nick: source,
})
} else { } else {
target, err := CasefoldName(targetString) target, err := CasefoldName(targetString)
user := server.clients.Get(target) user := server.clients.Get(target)
@ -71,9 +107,8 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
cnick := client.Nick() cnick := client.Nick()
tnick := user.Nick() tnick := user.Nick()
user.Send(nil, source, "PRIVMSG", tnick, message) for _, session := range user.Sessions() {
if rb.session.capabilities.Has(caps.EchoMessage) { session.sendSplitMsgFromClientInternal(false, source, "", nil, "PRIVMSG", tnick, splitMessage)
rb.Add(nil, source, "PRIVMSG", tnick, message)
} }
if user.Away() { if user.Away() {
//TODO(dan): possibly implement cooldown of away notifications to users //TODO(dan): possibly implement cooldown of away notifications to users

View File

@ -82,3 +82,10 @@ func (err *IncompatibleSchemaError) Error() string {
func NanoToTimestamp(nanotime int64) string { func NanoToTimestamp(nanotime int64) string {
return time.Unix(0, nanotime).UTC().Format(IRCv3TimestampFormat) return time.Unix(0, nanotime).UTC().Format(IRCv3TimestampFormat)
} }
func BoolDefaultTrue(value *bool) bool {
if value != nil {
return *value
}
return true
}

View File

@ -492,6 +492,7 @@ oper-classes:
- "local_ban" - "local_ban"
- "local_unban" - "local_unban"
- "nofakelag" - "nofakelag"
- "roleplay"
# network operator # network operator
"network-oper": "network-oper":
@ -702,6 +703,23 @@ fakelag:
# sending any commands: # sending any commands:
cooldown: 2s cooldown: 2s
# the roleplay commands are semi-standardized extensions to IRC that allow
# sending and receiving messages from pseudo-nicknames. this can be used either
# for actual roleplaying, or for bridging IRC with other protocols.
roleplay:
# are roleplay commands enabled at all? (channels and clients still have to
# opt in individually with the +E mode)
enabled: true
# require the "roleplay" oper capability to send roleplay messages?
require-oper: false
# require channel operator permissions to send roleplay messages?
require-chanops: false
# add the real nickname, in parentheses, to the end of every roleplay message?
add-suffix: true
# message history tracking, for the RESUME extension and possibly other uses in future # message history tracking, for the RESUME extension and possibly other uses in future
history: history:
# should we store messages for later playback? # should we store messages for later playback?