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