mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-23 03:02:48 +01:00
commit
37cd2e77c5
@ -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)~&@%+")
|
||||||
isupport.Add("RPCHAN", "E")
|
if config.Roleplay.enabled {
|
||||||
isupport.Add("RPUSER", "E")
|
isupport.Add("RPCHAN", "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))
|
||||||
|
@ -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)
|
||||||
|
@ -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,17 +16,39 @@ 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
|
return
|
||||||
// TODO(#395) clean this up
|
|
||||||
if len(message) != 0 && message[0] == '\x01' {
|
|
||||||
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 {
|
||||||
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
18
oragono.yaml
18
oragono.yaml
@ -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?
|
||||||
|
Loading…
Reference in New Issue
Block a user