3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-22 18:52:41 +01:00

roleplay: Initial commit

This commit is contained in:
Daniel Oaks 2016-11-01 23:56:21 +10:00
parent 62a0cbc1f6
commit 07e4728c15
8 changed files with 166 additions and 29 deletions

View File

@ -15,6 +15,7 @@ New release of Oragono!
* Length of channel mode lists (ban / ban-except / invite-except) is now restricted to the limit in config. * Length of channel mode lists (ban / ban-except / invite-except) is now restricted to the limit in config.
* Support `MAXLIST`, `MAXTARGETS`, `MODES`, `TARGMAX` in `RPL_ISUPPORT`. * Support `MAXLIST`, `MAXTARGETS`, `MODES`, `TARGMAX` in `RPL_ISUPPORT`.
* Added support for IRCv3 capability [`chghost`](http://ircv3.net/specs/extensions/chghost-3.2.html). * Added support for IRCv3 capability [`chghost`](http://ircv3.net/specs/extensions/chghost-3.2.html).
* Roleplaying commands, both inside channels and between clients.
### Changed ### Changed
* In the config file, "operator" changed to "opers", and new oper class is required. * In the config file, "operator" changed to "opers", and new oper class is required.

View File

@ -461,6 +461,8 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm
line, err := message.Line() line, err := message.Line()
if err != nil { if err != nil {
// try not to fail quietly - especially useful when running tests, as a note to dig deeper // try not to fail quietly - especially useful when running tests, as a note to dig deeper
// log.Println("Error assembling message:")
// spew.Dump(message)
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
line, _ := message.Line() line, _ := message.Line()
client.socket.Write(line) client.socket.Write(line)

View File

@ -54,6 +54,10 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
// Commands holds all commands executable by a client connected to us. // Commands holds all commands executable by a client connected to us.
var Commands = map[string]Command{ var Commands = map[string]Command{
"AMBIANCE": {
handler: sceneHandler,
minParams: 2,
},
"AUTHENTICATE": { "AUTHENTICATE": {
handler: authenticateHandler, handler: authenticateHandler,
usablePreReg: true, usablePreReg: true,
@ -127,6 +131,14 @@ var Commands = map[string]Command{
handler: noticeHandler, handler: noticeHandler,
minParams: 2, minParams: 2,
}, },
"NPC": {
handler: npcHandler,
minParams: 3,
},
"NPCA": {
handler: npcaHandler,
minParams: 3,
},
"OPER": { "OPER": {
handler: operHandler, handler: operHandler,
minParams: 2, minParams: 2,
@ -161,6 +173,10 @@ var Commands = map[string]Command{
minParams: 2, minParams: 2,
oper: true, oper: true,
}, },
"SCENE": {
handler: sceneHandler,
minParams: 2,
},
"QUIT": { "QUIT": {
handler: quitHandler, handler: quitHandler,
usablePreReg: true, usablePreReg: true,

View File

@ -60,6 +60,11 @@ Oragono supports the following user modes:
// Help contains the help strings distributed with the IRCd. // Help contains the help strings distributed with the IRCd.
var Help = map[string]HelpEntry{ var Help = map[string]HelpEntry{
// Commands // Commands
"ambiance": {
text: `AMBIANCE <target> <text to be sent>
The AMBIANCE command is used to send a scene notification to the given target.`,
},
"authenticate": { "authenticate": {
text: `AUTHENTICATE text: `AUTHENTICATE
@ -182,6 +187,16 @@ Sets your nickname to the new given one.`,
text: `NOTICE <target>{,<target>} <text to be sent> text: `NOTICE <target>{,<target>} <text to be sent>
Sends the text to the given targets as a NOTICE.`, Sends the text to the given targets as a NOTICE.`,
},
"npc": {
text: `NPC <target> <sourcenick> <text to be sent>
The NPC command is used to send a message to the target as the source.`,
},
"npca": {
text: `NPCA <target> <sourcenick> <text to be sent>
The NPC command is used to send an action to the target as the source.`,
}, },
"oper": { "oper": {
text: `OPER <name> <password> text: `OPER <name> <password>
@ -219,6 +234,11 @@ Sends the text to the given targets as a PRIVMSG.`,
text: `SANICK <currentnick> <newnick> text: `SANICK <currentnick> <newnick>
Gives the given user a new nickname.`, Gives the given user a new nickname.`,
},
"scene": {
text: `SCENE <target> <text to be sent>
The SCENE command is used to send a scene notification to the given target.`,
}, },
"quit": { "quit": {
text: `QUIT [reason] text: `QUIT [reason]

View File

@ -144,12 +144,13 @@ const (
Restricted UserMode = 'r' Restricted UserMode = 'r'
ServerNotice UserMode = 's' // deprecated ServerNotice UserMode = 's' // deprecated
TLS UserMode = 'Z' TLS UserMode = 'Z'
UserRoleplaying UserMode = 'E'
WallOps UserMode = 'w' WallOps UserMode = 'w'
) )
var ( var (
SupportedUserModes = UserModes{ SupportedUserModes = UserModes{
Away, Invisible, Operator, Away, Invisible, Operator, UserRoleplaying,
} }
// supportedUserModesString acts as a cache for when we introduce users // supportedUserModesString acts as a cache for when we introduce users
supportedUserModesString = SupportedUserModes.String() supportedUserModesString = SupportedUserModes.String()
@ -157,6 +158,7 @@ var (
const ( const (
BanMask ChannelMode = 'b' // arg BanMask ChannelMode = 'b' // arg
ChanRoleplaying ChannelMode = 'E' // flag
ExceptMask ChannelMode = 'e' // arg ExceptMask ChannelMode = 'e' // arg
InviteMask ChannelMode = 'I' // arg InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag InviteOnly ChannelMode = 'i' // flag
@ -177,7 +179,7 @@ var (
SupportedChannelModes = ChannelModes{ SupportedChannelModes = ChannelModes{
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside, BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
OpOnlyTopic, Secret, UserLimit, OpOnlyTopic, Secret, UserLimit, ChanRoleplaying,
} }
// supportedChannelModesString acts as a cache for when we introduce users // supportedChannelModesString acts as a cache for when we introduce users
supportedChannelModesString = SupportedChannelModes.String() supportedChannelModesString = SupportedChannelModes.String()
@ -297,7 +299,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
for _, change := range changes { for _, change := range changes {
switch change.mode { switch change.mode {
case Invisible, ServerNotice, WallOps: case Invisible, ServerNotice, WallOps, UserRoleplaying:
switch change.op { switch change.op {
case Add: case Add:
if target.flags[change.mode] { if target.flags[change.mode] {
@ -471,7 +473,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
applied = append(applied, change) applied = append(applied, change)
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret: case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret, ChanRoleplaying:
switch change.op { switch change.op {
case Add: case Add:
if channel.flags[change.mode] { if channel.flags[change.mode] {

View File

@ -25,7 +25,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
return false return false
} }
if err != nil || len(nicknameRaw) > server.limits.NickLen { if err != nil || len(nicknameRaw) > server.limits.NickLen || nickname == "=scene=" {
client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, nicknameRaw, "Erroneous nickname") client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, nicknameRaw, "Erroneous nickname")
return false return false
} }
@ -59,14 +59,14 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
oldnick, oerr := CasefoldName(msg.Params[0]) oldnick, oerr := CasefoldName(msg.Params[0])
casefoldedNickname, err := CasefoldName(msg.Params[1]) nickname, err := CasefoldName(msg.Params[1])
if len(casefoldedNickname) < 1 { if len(nickname) < 1 {
client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given") client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
return false return false
} }
if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen { if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen || nickname == "=scene=" {
client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname") client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
return false return false
} }
@ -82,7 +82,7 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
//TODO(dan): There's probably some races here, we should be changing this in the primary server thread //TODO(dan): There's probably some races here, we should be changing this in the primary server thread
if server.clients.Get(casefoldedNickname) != nil || server.clients.Get(casefoldedNickname) != target { if server.clients.Get(nickname) != nil || server.clients.Get(nickname) != target {
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use") client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use")
return false return false
} }

94
irc/roleplay.go Normal file
View File

@ -0,0 +1,94 @@
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
"fmt"
"github.com/DanielOaks/girc-go/ircmsg"
)
const (
npcNickMask = "%s!%s@npc.fakeuser.invalid"
sceneNickMask = "=Scene=!%s@npc.fakeuser.invalid"
)
// SCENE <target> <text to be sent>
func sceneHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
target := msg.Params[0]
message := msg.Params[1]
sourceString := fmt.Sprintf(sceneNickMask, client.nick)
sendRoleplayMessage(server, client, sourceString, target, false, message)
return false
}
// NPC <target> <text to be sent>
func npcHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
target := msg.Params[0]
fakeSource := msg.Params[1]
message := msg.Params[2]
sourceString := fmt.Sprintf(npcNickMask, fakeSource, client.nick)
sendRoleplayMessage(server, client, sourceString, target, false, message)
return false
}
// NPCA <target> <text to be sent>
func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
target := msg.Params[0]
fakeSource := msg.Params[1]
message := msg.Params[2]
sourceString := fmt.Sprintf(npcNickMask, fakeSource, client.nick)
sendRoleplayMessage(server, client, sourceString, target, true, message)
return false
}
func sendRoleplayMessage(server *Server, client *Client, source string, targetString string, isAction bool, message string) {
if isAction {
message = fmt.Sprintf("\x01ACTION %s (%s)\x01", message, client.nick)
} else {
message = fmt.Sprintf("%s (%s)", message, client.nick)
}
target, cerr := CasefoldChannel(targetString)
if cerr == nil {
channel := server.channels.Get(target)
if channel == nil {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel")
return
}
if !channel.CanSpeak(client) {
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
return
}
for member := range channel.members {
if member == client && !client.capabilities[EchoMessage] {
continue
}
member.Send(nil, source, "PRIVMSG", channel.name, message)
}
} else {
target, err := CasefoldName(targetString)
user := server.clients.Get(target)
if err != nil || user == nil {
client.Send(nil, server.name, ERR_NOSUCHNICK, target, "No such nick")
return
}
user.Send(nil, source, "PRIVMSG", user.nick, message)
if client.capabilities[EchoMessage] {
client.Send(nil, source, "PRIVMSG", user.nick, message)
}
if user.flags[Away] {
//TODO(dan): possibly implement cooldown of away notifications to users
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
}
}
}

View File

@ -264,7 +264,7 @@ func (server *Server) setISupport() {
server.isupport = NewISupportList() server.isupport = NewISupportList()
server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen)) server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
server.isupport.Add("CASEMAPPING", "rfc7700") server.isupport.Add("CASEMAPPING", "rfc7700")
server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret}.String()}, ",")) server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ","))
server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen)) server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
server.isupport.Add("CHANTYPES", "#") server.isupport.Add("CHANTYPES", "#")
server.isupport.Add("EXCEPTS", "") server.isupport.Add("EXCEPTS", "")
@ -277,6 +277,8 @@ func (server *Server) setISupport() {
server.isupport.Add("NETWORK", server.networkName) server.isupport.Add("NETWORK", server.networkName)
server.isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen)) server.isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
server.isupport.Add("PREFIX", "(qaohv)~&@%+") server.isupport.Add("PREFIX", "(qaohv)~&@%+")
server.isupport.Add("RPCHAN", "E")
server.isupport.Add("RPUSER", "E")
server.isupport.Add("STATUSMSG", "~&@%+") server.isupport.Add("STATUSMSG", "~&@%+")
server.isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString)) server.isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString))
server.isupport.Add("TOPICLEN", strconv.Itoa(server.limits.TopicLen)) server.isupport.Add("TOPICLEN", strconv.Itoa(server.limits.TopicLen))
@ -1436,7 +1438,7 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
target = msg.Params[0] target = msg.Params[0]
} }
casefoldedTarget, err := Casefold(target) casefoldedTarget, err := Casefold(target)
if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) { if target != "" && (err != nil || casefoldedTarget != server.nameCasefolded) {
client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server") client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
return false return false
} }