fix(irc): accept string auto-join channel config
This commit is contained in:
parent
9222351871
commit
6a4f619229
5 changed files with 110 additions and 5 deletions
|
|
@ -32,6 +32,7 @@ func NewIRCChannel(cfg config.IRCConfig, messageBus *bus.MessageBus) (*IRCChanne
|
||||||
if cfg.Nick == "" {
|
if cfg.Nick == "" {
|
||||||
return nil, fmt.Errorf("irc nick is required")
|
return nil, fmt.Errorf("irc nick is required")
|
||||||
}
|
}
|
||||||
|
cfg.Channels = normalizeIRCChannels(cfg.Channels)
|
||||||
|
|
||||||
base := channels.NewBaseChannel("irc", cfg, messageBus, cfg.AllowFrom,
|
base := channels.NewBaseChannel("irc", cfg, messageBus, cfg.AllowFrom,
|
||||||
channels.WithMaxMessageLength(400),
|
channels.WithMaxMessageLength(400),
|
||||||
|
|
@ -192,3 +193,24 @@ func extractHost(server string) string {
|
||||||
}
|
}
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeIRCChannels(channels config.FlexibleStringSlice) config.FlexibleStringSlice {
|
||||||
|
if len(channels) == 0 {
|
||||||
|
return channels
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := make(config.FlexibleStringSlice, 0, len(channels))
|
||||||
|
for _, channel := range channels {
|
||||||
|
channel = strings.TrimSpace(channel)
|
||||||
|
if channel == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch channel[0] {
|
||||||
|
case '#', '&', '+', '!':
|
||||||
|
normalized = append(normalized, channel)
|
||||||
|
default:
|
||||||
|
normalized = append(normalized, "#"+channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
|
|
@ -43,6 +44,23 @@ func TestNewIRCChannel(t *testing.T) {
|
||||||
t.Error("new channel should not be running")
|
t.Error("new channel should not be running")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("normalizes auto join channels", func(t *testing.T) {
|
||||||
|
cfg := config.IRCConfig{
|
||||||
|
Server: "irc.example.com:6667",
|
||||||
|
Nick: "testbot",
|
||||||
|
Channels: []string{" general ", "#already", "&ops", "+local", "!safe", "", "news"},
|
||||||
|
}
|
||||||
|
ch, err := NewIRCChannel(cfg, msgBus)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := config.FlexibleStringSlice{"#general", "#already", "&ops", "+local", "!safe", "#news"}
|
||||||
|
if !reflect.DeepEqual(ch.config.Channels, want) {
|
||||||
|
t.Fatalf("Channels = %#v, want %#v", ch.config.Channels, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractHost(t *testing.T) {
|
func TestExtractHost(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accept a single string so config patch APIs can submit comma-separated values.
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err == nil {
|
||||||
|
*f = parseFlexibleStringSlice(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Try []interface{} to handle mixed types
|
// Try []interface{} to handle mixed types
|
||||||
var raw []any
|
var raw []any
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
|
@ -57,21 +64,22 @@ func (f *FlexibleStringSlice) UnmarshalText(text []byte) error {
|
||||||
*f = nil
|
*f = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
*f = parseFlexibleStringSlice(string(text))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
s := string(text)
|
func parseFlexibleStringSlice(s string) FlexibleStringSlice {
|
||||||
// Replace Chinese comma with English comma, then split
|
|
||||||
s = strings.ReplaceAll(s, ",", ",")
|
s = strings.ReplaceAll(s, ",", ",")
|
||||||
parts := strings.Split(s, ",")
|
parts := strings.Split(s, ",")
|
||||||
|
|
||||||
result := make([]string, 0, len(parts))
|
result := make(FlexibleStringSlice, 0, len(parts))
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
part = strings.TrimSpace(part)
|
part = strings.TrimSpace(part)
|
||||||
if part != "" {
|
if part != "" {
|
||||||
result = append(result, part)
|
result = append(result, part)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*f = result
|
return result
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
|
||||||
19
pkg/config/flexible_string_slice_test.go
Normal file
19
pkg/config/flexible_string_slice_test.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlexibleStringSliceUnmarshalJSON_String(t *testing.T) {
|
||||||
|
var got FlexibleStringSlice
|
||||||
|
if err := json.Unmarshal([]byte(`"general, #ops,dev"`), &got); err != nil {
|
||||||
|
t.Fatalf("Unmarshal() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := FlexibleStringSlice{"general", "#ops", "dev"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("FlexibleStringSlice = %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
|
@ -86,3 +87,40 @@ func TestHandleUpdateConfig_DoesNotInheritDefaultModelFields(t *testing.T) {
|
||||||
t.Fatalf("model_list[0].api_base = %q, want empty string", got)
|
t.Fatalf("model_list[0].api_base = %q, want empty string", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlePatchConfig_AcceptsFlexibleStringSliceString(t *testing.T) {
|
||||||
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{
|
||||||
|
"channels": {
|
||||||
|
"irc": {
|
||||||
|
"enabled": true,
|
||||||
|
"server": "irc.example.com:6667",
|
||||||
|
"nick": "testbot",
|
||||||
|
"channels": "general, #ops,dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := config.FlexibleStringSlice{"general", "#ops", "dev"}
|
||||||
|
if !reflect.DeepEqual(cfg.Channels.IRC.Channels, want) {
|
||||||
|
t.Fatalf("channels.irc.channels = %#v, want %#v", cfg.Channels.IRC.Channels, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue