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:
parent
62a0cbc1f6
commit
07e4728c15
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
20
irc/help.go
20
irc/help.go
@ -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]
|
||||||
|
10
irc/modes.go
10
irc/modes.go
@ -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] {
|
||||||
|
@ -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
94
irc/roleplay.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user