Browse Source
Added input handling and m_roleplay client-side handlers (...)
Added input handling and m_roleplay client-side handlers (...)
- Added Client.EmitInput() - Added ircutil.ParseArgAndText for common /msg <target> <message...> type inputs - Added target getters for Event - Added default handling for input events that haven't been killed - Removed handler_debug - Changed timeout for interaction to 2s - Fixed comment typo in event_packet.gomaster
Gisle Aune
6 years ago
11 changed files with 383 additions and 57 deletions
-
42client.go
-
46client_test.go
-
56event.go
-
27event_input.go
-
2event_packet.go
-
46handler_debug.go
-
111handlers/input.go
-
87handlers/mroleplay.go
-
4handlers/package.go
-
2internal/irctest/interaction.go
-
17ircutil/parse-arg-and-text.go
@ -0,0 +1,27 @@ |
|||||
|
package irc |
||||
|
|
||||
|
import ( |
||||
|
"strings" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// ParseInput parses an input command into an event.
|
||||
|
func ParseInput(line string) Event { |
||||
|
event := NewEvent("input", "") |
||||
|
event.Time = time.Now() |
||||
|
|
||||
|
if strings.HasPrefix(line, "/") { |
||||
|
split := strings.SplitN(line[1:], " ", 2) |
||||
|
event.verb = split[0] |
||||
|
if len(split) == 2 { |
||||
|
event.Text = split[1] |
||||
|
} |
||||
|
} else { |
||||
|
event.Text = line |
||||
|
event.verb = "text" |
||||
|
} |
||||
|
|
||||
|
event.name = event.kind + "." + event.verb |
||||
|
|
||||
|
return event |
||||
|
} |
@ -1,46 +0,0 @@ |
|||||
package irc |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"log" |
|
||||
) |
|
||||
|
|
||||
// DebugLogger is for
|
|
||||
type DebugLogger interface { |
|
||||
Println(v ...interface{}) |
|
||||
} |
|
||||
|
|
||||
type defaultDebugLogger struct{} |
|
||||
|
|
||||
func (logger *defaultDebugLogger) Println(v ...interface{}) { |
|
||||
log.Println(v...) |
|
||||
} |
|
||||
|
|
||||
// EnableDebug logs all events that passes through it, ignoring killed
|
|
||||
// events. It will always include the standard handlers, but any custom
|
|
||||
// handlers defined after EnableDebug will not have their effects shown.
|
|
||||
// You may pass `nil` as a logger to use the standard log package's Println.
|
|
||||
func EnableDebug(logger DebugLogger, indented bool) { |
|
||||
if logger != nil { |
|
||||
logger = &defaultDebugLogger{} |
|
||||
} |
|
||||
|
|
||||
Handle(func(event *Event, client *Client) { |
|
||||
var data []byte |
|
||||
var err error |
|
||||
|
|
||||
if indented { |
|
||||
data, err = json.MarshalIndent(event, "", " ") |
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
} else { |
|
||||
data, err = json.Marshal(event) |
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
logger.Println(string(data)) |
|
||||
}) |
|
||||
} |
|
@ -0,0 +1,111 @@ |
|||||
|
package handlers |
||||
|
|
||||
|
import ( |
||||
|
"git.aiterp.net/gisle/irc" |
||||
|
"git.aiterp.net/gisle/irc/ircutil" |
||||
|
) |
||||
|
|
||||
|
// Input handles the default input.
|
||||
|
func Input(event *irc.Event, client *irc.Client) { |
||||
|
switch event.Name() { |
||||
|
|
||||
|
// /msg sends an action to a target specified before the message.
|
||||
|
case "input.msg": |
||||
|
{ |
||||
|
targetName, text := ircutil.ParseArgAndText(event.Text) |
||||
|
if targetName == "" || text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /msg <target> <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
overhead := client.PrivmsgOverhead(targetName, true) |
||||
|
cuts := ircutil.CutMessage(text, overhead) |
||||
|
for _, cut := range cuts { |
||||
|
client.Sendf("PRIVMSG %s :%s", targetName, cut) |
||||
|
} |
||||
|
|
||||
|
event.Kill() |
||||
|
} |
||||
|
|
||||
|
// /text (or text without a command) sends a message to the target.
|
||||
|
case "input.text": |
||||
|
{ |
||||
|
if event.Text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /text <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
target := event.Target("query", "channel") |
||||
|
if target == nil { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel or query")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
overhead := client.PrivmsgOverhead(target.Name(), false) |
||||
|
cuts := ircutil.CutMessage(event.Text, overhead) |
||||
|
for _, cut := range cuts { |
||||
|
client.SendQueuedf("PRIVMSG %s :%s", target.Name(), cut) |
||||
|
} |
||||
|
|
||||
|
event.Kill() |
||||
|
} |
||||
|
|
||||
|
// /me and /action sends a CTCP ACTION.
|
||||
|
case "input.me", "input.action": |
||||
|
{ |
||||
|
if event.Text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /me <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
target := event.Target("query", "channel") |
||||
|
if target == nil { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel or query")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
overhead := client.PrivmsgOverhead(target.Name(), true) |
||||
|
cuts := ircutil.CutMessage(event.Text, overhead) |
||||
|
for _, cut := range cuts { |
||||
|
client.SendCTCP("ACTION", target.Name(), false, cut) |
||||
|
} |
||||
|
|
||||
|
event.Kill() |
||||
|
} |
||||
|
|
||||
|
// /describe sends an action to a target specified before the message, like /msg.
|
||||
|
case "input.describe": |
||||
|
{ |
||||
|
targetName, text := ircutil.ParseArgAndText(event.Text) |
||||
|
if targetName == "" || text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /describe <target> <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
overhead := client.PrivmsgOverhead(targetName, true) |
||||
|
cuts := ircutil.CutMessage(text, overhead) |
||||
|
for _, cut := range cuts { |
||||
|
client.SendCTCP("ACTION", targetName, false, cut) |
||||
|
} |
||||
|
|
||||
|
event.Kill() |
||||
|
} |
||||
|
|
||||
|
// /m is a shorthand for /mode that targets the current channel
|
||||
|
case "input.m": |
||||
|
{ |
||||
|
if event.Text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /m <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
channel := event.ChannelTarget() |
||||
|
if channel != nil { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
client.SendQueuedf("MODE %s %s", channel.Name(), event.Text) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,87 @@ |
|||||
|
package handlers |
||||
|
|
||||
|
import ( |
||||
|
"strings" |
||||
|
|
||||
|
"git.aiterp.net/gisle/irc" |
||||
|
"git.aiterp.net/gisle/irc/ircutil" |
||||
|
) |
||||
|
|
||||
|
// MRoleplay is a handler that adds commands for cutting NPC commands, as well as cleaning up
|
||||
|
// the input from the server. It's named after Charybdis IRCd's m_roleplay module.
|
||||
|
func MRoleplay(event *irc.Event, client *irc.Client) { |
||||
|
switch event.Name() { |
||||
|
case "packet.privmsg", "ctcp.action": |
||||
|
{ |
||||
|
// Detect m_roleplay
|
||||
|
if strings.HasPrefix(event.Nick, "\x1F") { |
||||
|
event.Nick = event.Nick[1 : len(event.Nick)-2] |
||||
|
if event.Verb() == "PRIVMSG" { |
||||
|
event.RenderTags["mRoleplay"] = "npc" |
||||
|
} else { |
||||
|
event.RenderTags["mRoleplay"] = "npca" |
||||
|
} |
||||
|
} else if strings.HasPrefix(event.Nick, "=") { |
||||
|
event.RenderTags["mRoleplay"] = "scene" |
||||
|
} else { |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
lastSpace := strings.LastIndex(event.Text, " ") |
||||
|
lastParanthesis := strings.LastIndex(event.Text, "(") |
||||
|
if lastParanthesis != -1 && lastSpace != -1 && lastParanthesis == lastSpace+1 { |
||||
|
event.Text = event.Text[:lastSpace] |
||||
|
} |
||||
|
} |
||||
|
case "input.npcc", "input.npcac": |
||||
|
{ |
||||
|
isAction := event.Verb() == "npcac" |
||||
|
nick, text := ircutil.ParseArgAndText(event.Text) |
||||
|
if nick == "" || text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /"+event.Verb()+" <nick> <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
channel := event.ChannelTarget() |
||||
|
if channel == nil { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
overhead := ircutil.MessageOverhead("\x1f"+nick+"\x1f", client.Nick(), "npc.fakeuser.invalid", channel.Name(), isAction) |
||||
|
cuts := ircutil.CutMessage(event.Text, overhead) |
||||
|
|
||||
|
for _, cut := range cuts { |
||||
|
npcCommand := "NPCA" |
||||
|
if event.Verb() == "npcc" { |
||||
|
npcCommand = "NPC" |
||||
|
} |
||||
|
|
||||
|
client.SendQueuedf("%s %s :%s", npcCommand, channel.Name(), cut) |
||||
|
} |
||||
|
|
||||
|
event.Kill() |
||||
|
} |
||||
|
case "input.scenec": |
||||
|
{ |
||||
|
if event.Text == "" { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /scenec <text...>")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
channel := event.ChannelTarget() |
||||
|
if channel == nil { |
||||
|
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel")) |
||||
|
break |
||||
|
} |
||||
|
|
||||
|
overhead := ircutil.MessageOverhead("=Scene=", client.Nick(), "npc.fakeuser.invalid", channel.Name(), false) |
||||
|
cuts := ircutil.CutMessage(event.Text, overhead) |
||||
|
for _, cut := range cuts { |
||||
|
client.SendQueuedf("SCENE %s :%s", channel.Name(), cut) |
||||
|
} |
||||
|
|
||||
|
event.Kill() |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
// Package handlers contain extra handlers that add features to an IRC client that are not
|
||||
|
// always necessary. This includes most input handlers and features. If something adds
|
||||
|
// a IRCv3 cap, it should not be here, however.
|
||||
|
package handlers |
@ -0,0 +1,17 @@ |
|||||
|
package ircutil |
||||
|
|
||||
|
import ( |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
// ParseArgAndText parses a text like "#Channel stuff and things" into "#Channel"
|
||||
|
// and "stuff and things". This is commonly used for input commands which has
|
||||
|
// no standard
|
||||
|
func ParseArgAndText(s string) (arg, text string) { |
||||
|
spaceIndex := strings.Index(s, " ") |
||||
|
if spaceIndex == -1 { |
||||
|
return s, "" |
||||
|
} |
||||
|
|
||||
|
return s[:spaceIndex], s[spaceIndex+1:] |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue