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.
* Support `MAXLIST`, `MAXTARGETS`, `MODES`, `TARGMAX` in `RPL_ISUPPORT`.
* Added support for IRCv3 capability [`chghost`](http://ircv3.net/specs/extensions/chghost-3.2.html).
* Roleplaying commands, both inside channels and between clients.
### Changed
* 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()
if err != nil {
// 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")
line, _ := message.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.
var Commands = map[string]Command{
"AMBIANCE": {
handler: sceneHandler,
minParams: 2,
},
"AUTHENTICATE": {
handler: authenticateHandler,
usablePreReg: true,
@ -127,6 +131,14 @@ var Commands = map[string]Command{
handler: noticeHandler,
minParams: 2,
},
"NPC": {
handler: npcHandler,
minParams: 3,
},
"NPCA": {
handler: npcaHandler,
minParams: 3,
},
"OPER": {
handler: operHandler,
minParams: 2,
@ -161,6 +173,10 @@ var Commands = map[string]Command{
minParams: 2,
oper: true,
},
"SCENE": {
handler: sceneHandler,
minParams: 2,
},
"QUIT": {
handler: quitHandler,
usablePreReg: true,

View File

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

View File

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

View File

@ -25,7 +25,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
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")
return false
}
@ -59,14 +59,14 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
}
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")
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")
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
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")
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.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
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("CHANTYPES", "#")
server.isupport.Add("EXCEPTS", "")
@ -277,6 +277,8 @@ func (server *Server) setISupport() {
server.isupport.Add("NETWORK", server.networkName)
server.isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
server.isupport.Add("PREFIX", "(qaohv)~&@%+")
server.isupport.Add("RPCHAN", "E")
server.isupport.Add("RPUSER", "E")
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("TOPICLEN", strconv.Itoa(server.limits.TopicLen))
@ -1436,7 +1438,7 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
target = msg.Params[0]
}
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")
return false
}