fix: address review feedback from @mengzhuo
- Add separate User and RealName config fields (fall back to Nick) - Make RequestCaps configurable (defaults to server-time, message-tags) - Refactor isBotMentioned into nickMentionedAt returning position; stripBotMention now uses nickMentionedAt internally - Replace custom isAlphanumeric with unicode.IsLetter/unicode.IsDigit - Update tests for new nickMentionedAt function
This commit is contained in:
parent
c10959b645
commit
a89ba06cb8
4 changed files with 73 additions and 32 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/ergochat/irc-go/ircevent"
|
"github.com/ergochat/irc-go/ircevent"
|
||||||
"github.com/ergochat/irc-go/ircmsg"
|
"github.com/ergochat/irc-go/ircmsg"
|
||||||
|
|
@ -102,30 +103,47 @@ func (c *IRCChannel) onPrivmsg(conn *ircevent.Connection, e ircmsg.Message) {
|
||||||
c.HandleMessage(c.ctx, peer, messageID, nick, chatID, content, nil, metadata, sender)
|
c.HandleMessage(c.ctx, peer, messageID, nick, chatID, content, nil, metadata, sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isBotMentioned checks if the bot's nick appears in the message.
|
// nickMentionedAt returns the byte index where botNick is mentioned in content
|
||||||
func isBotMentioned(content, botNick string) bool {
|
// with word-boundary checks, or -1 if not found. Also checks for "nick:" /
|
||||||
|
// "nick," prefix convention.
|
||||||
|
func nickMentionedAt(content, botNick string) int {
|
||||||
lower := strings.ToLower(content)
|
lower := strings.ToLower(content)
|
||||||
lowerNick := strings.ToLower(botNick)
|
lowerNick := strings.ToLower(botNick)
|
||||||
|
|
||||||
// "nick:" or "nick," at start (most common IRC convention)
|
// "nick:" or "nick," at start (most common IRC convention)
|
||||||
if strings.HasPrefix(lower, lowerNick+":") || strings.HasPrefix(lower, lowerNick+",") {
|
if strings.HasPrefix(lower, lowerNick+":") || strings.HasPrefix(lower, lowerNick+",") {
|
||||||
return true
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Word-boundary match anywhere in the message
|
// Word-boundary match anywhere in the message
|
||||||
idx := strings.Index(lower, lowerNick)
|
idx := strings.Index(lower, lowerNick)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return false
|
return -1
|
||||||
}
|
}
|
||||||
before := idx == 0 || !isAlphanumeric(lower[idx-1])
|
runes := []rune(lower)
|
||||||
after := idx+len(lowerNick) >= len(lower) || !isAlphanumeric(lower[idx+len(lowerNick)])
|
nickRunes := []rune(lowerNick)
|
||||||
return before && after
|
endIdx := idx + len(string(nickRunes))
|
||||||
|
before := idx == 0 || !unicode.IsLetter(runes[idx-1]) && !unicode.IsDigit(runes[idx-1])
|
||||||
|
after := endIdx >= len(lower) || !unicode.IsLetter(rune(lower[endIdx])) && !unicode.IsDigit(rune(lower[endIdx]))
|
||||||
|
if before && after {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBotMentioned checks if the bot's nick appears in the message.
|
||||||
|
func isBotMentioned(content, botNick string) bool {
|
||||||
|
return nickMentionedAt(content, botNick) >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// stripBotMention removes "nick: " or "nick, " prefix from content.
|
// stripBotMention removes "nick: " or "nick, " prefix from content.
|
||||||
func stripBotMention(content, botNick string) string {
|
func stripBotMention(content, botNick string) string {
|
||||||
lower := strings.ToLower(content)
|
idx := nickMentionedAt(content, botNick)
|
||||||
|
if idx != 0 {
|
||||||
|
return content
|
||||||
|
}
|
||||||
lowerNick := strings.ToLower(botNick)
|
lowerNick := strings.ToLower(botNick)
|
||||||
|
lower := strings.ToLower(content)
|
||||||
for _, sep := range []string{":", ","} {
|
for _, sep := range []string{":", ","} {
|
||||||
prefix := lowerNick + sep
|
prefix := lowerNick + sep
|
||||||
if strings.HasPrefix(lower, prefix) {
|
if strings.HasPrefix(lower, prefix) {
|
||||||
|
|
@ -134,7 +152,3 @@ func stripBotMention(content, botNick string) string {
|
||||||
}
|
}
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAlphanumeric(b byte) bool {
|
|
||||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '_'
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,27 @@ func (c *IRCChannel) Start(ctx context.Context) error {
|
||||||
logger.InfoC("irc", "Starting IRC channel")
|
logger.InfoC("irc", "Starting IRC channel")
|
||||||
c.ctx, c.cancel = context.WithCancel(ctx)
|
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||||
|
|
||||||
|
user := c.config.User
|
||||||
|
if user == "" {
|
||||||
|
user = c.config.Nick
|
||||||
|
}
|
||||||
|
realName := c.config.RealName
|
||||||
|
if realName == "" {
|
||||||
|
realName = c.config.Nick
|
||||||
|
}
|
||||||
|
caps := []string(c.config.RequestCaps)
|
||||||
|
if len(caps) == 0 {
|
||||||
|
caps = []string{"server-time", "message-tags"}
|
||||||
|
}
|
||||||
|
|
||||||
conn := &ircevent.Connection{
|
conn := &ircevent.Connection{
|
||||||
Server: c.config.Server,
|
Server: c.config.Server,
|
||||||
Nick: c.config.Nick,
|
Nick: c.config.Nick,
|
||||||
User: c.config.Nick,
|
User: user,
|
||||||
RealName: c.config.Nick,
|
RealName: realName,
|
||||||
Password: c.config.Password,
|
Password: c.config.Password,
|
||||||
UseTLS: c.config.TLS,
|
UseTLS: c.config.TLS,
|
||||||
RequestCaps: []string{"server-time", "message-tags"},
|
RequestCaps: caps,
|
||||||
QuitMessage: "Goodbye",
|
QuitMessage: "Goodbye",
|
||||||
Debug: false,
|
Debug: false,
|
||||||
Log: nil,
|
Log: nil,
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,33 @@ func TestExtractHost(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNickMentionedAt(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
nick string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"colon prefix", "bot: hello", "bot", 0},
|
||||||
|
{"comma prefix", "bot, hello", "bot", 0},
|
||||||
|
{"case insensitive", "BOT: hello", "bot", 0},
|
||||||
|
{"word boundary mid", "hey bot what's up", "bot", 4},
|
||||||
|
{"no mention", "hello world", "bot", -1},
|
||||||
|
{"substring mismatch", "robotics are cool", "bot", -1},
|
||||||
|
{"nick at end", "hello bot", "bot", 6},
|
||||||
|
{"empty content", "", "bot", -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := nickMentionedAt(tt.content, tt.nick)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("nickMentionedAt(%q, %q) = %d, want %d", tt.content, tt.nick, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsBotMentioned(t *testing.T) {
|
func TestIsBotMentioned(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -116,19 +143,3 @@ func TestStripBotMention(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsAlphanumeric(t *testing.T) {
|
|
||||||
alphanumeric := "azAZ09_"
|
|
||||||
for _, b := range []byte(alphanumeric) {
|
|
||||||
if !isAlphanumeric(b) {
|
|
||||||
t.Errorf("isAlphanumeric(%q) = false, want true", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nonAlpha := " !@#:,"
|
|
||||||
for _, b := range []byte(nonAlpha) {
|
|
||||||
if isAlphanumeric(b) {
|
|
||||||
t.Errorf("isAlphanumeric(%q) = true, want false", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -407,11 +407,14 @@ type IRCConfig struct {
|
||||||
Server string `json:"server" env:"PICOCLAW_CHANNELS_IRC_SERVER"`
|
Server string `json:"server" env:"PICOCLAW_CHANNELS_IRC_SERVER"`
|
||||||
TLS bool `json:"tls" env:"PICOCLAW_CHANNELS_IRC_TLS"`
|
TLS bool `json:"tls" env:"PICOCLAW_CHANNELS_IRC_TLS"`
|
||||||
Nick string `json:"nick" env:"PICOCLAW_CHANNELS_IRC_NICK"`
|
Nick string `json:"nick" env:"PICOCLAW_CHANNELS_IRC_NICK"`
|
||||||
|
User string `json:"user,omitempty" env:"PICOCLAW_CHANNELS_IRC_USER"`
|
||||||
|
RealName string `json:"real_name,omitempty" env:"PICOCLAW_CHANNELS_IRC_REAL_NAME"`
|
||||||
Password string `json:"password" env:"PICOCLAW_CHANNELS_IRC_PASSWORD"`
|
Password string `json:"password" env:"PICOCLAW_CHANNELS_IRC_PASSWORD"`
|
||||||
NickServPassword string `json:"nickserv_password" env:"PICOCLAW_CHANNELS_IRC_NICKSERV_PASSWORD"`
|
NickServPassword string `json:"nickserv_password" env:"PICOCLAW_CHANNELS_IRC_NICKSERV_PASSWORD"`
|
||||||
SASLUser string `json:"sasl_user" env:"PICOCLAW_CHANNELS_IRC_SASL_USER"`
|
SASLUser string `json:"sasl_user" env:"PICOCLAW_CHANNELS_IRC_SASL_USER"`
|
||||||
SASLPassword string `json:"sasl_password" env:"PICOCLAW_CHANNELS_IRC_SASL_PASSWORD"`
|
SASLPassword string `json:"sasl_password" env:"PICOCLAW_CHANNELS_IRC_SASL_PASSWORD"`
|
||||||
Channels FlexibleStringSlice `json:"channels" env:"PICOCLAW_CHANNELS_IRC_CHANNELS"`
|
Channels FlexibleStringSlice `json:"channels" env:"PICOCLAW_CHANNELS_IRC_CHANNELS"`
|
||||||
|
RequestCaps FlexibleStringSlice `json:"request_caps,omitempty" env:"PICOCLAW_CHANNELS_IRC_REQUEST_CAPS"`
|
||||||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_IRC_ALLOW_FROM"`
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_IRC_ALLOW_FROM"`
|
||||||
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
||||||
Typing TypingConfig `json:"typing,omitempty"`
|
Typing TypingConfig `json:"typing,omitempty"`
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue