diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6cbf57c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Check formatting + run: | + if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then + echo "Unformatted files found:" + gofmt -l . + exit 1 + fi + + - name: Run unit tests + run: go test -v -race ./... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d1bc47e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -language: go - -go: - - 1.17.2 - - 1.16.9 - -sudo : false - -notifications: - irc: - channels: - - "irc.pl0rt.org#sp0rklf" - skip_join: true - -script: -# main.go:10:2: cannot find package "github.com/fluffle/sp0rkle/bot" in any of: -# Work around by symlinking in - - if [ "$TRAVIS_REPO_SLUG" != "fluffle/sp0rkle" ] ; then ln -s "$HOME/gopath/src/github.com/$TRAVIS_REPO_SLUG" /home/travis/gopath/src/github.com/fluffle/sp0rkle ; fi - - ls -la /home/travis/gopath/src/github.com/fluffle/ - - find /home/travis/gopath/src/github.com/fluffle - - go test -v -race ./... - diff --git a/Dockerfile b/Dockerfile index 119225f..c59fead 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22 as build-env +FROM golang:1.25 as build-env WORKDIR /go/src/github.com/fluffle/sp0rkle ADD . /go/src/github.com/fluffle/sp0rkle diff --git a/bot/bot.go b/bot/bot.go index 6b72e74..c104651 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -3,7 +3,6 @@ package bot import ( "context" "flag" - "io/ioutil" "os" "strings" "sync" @@ -124,7 +123,7 @@ func GetSecret(s string) string { if strings.HasPrefix(s, "$") { return os.ExpandEnv(s) } else if strings.HasPrefix(s, "<") { - if bytes, err := ioutil.ReadFile(s[1:]); err == nil { + if bytes, err := os.ReadFile(s[1:]); err == nil { return strings.TrimSuffix(string(bytes), "\n") } return "" diff --git a/bot/context.go b/bot/context.go index 225d938..380ea2e 100644 --- a/bot/context.go +++ b/bot/context.go @@ -57,18 +57,18 @@ func (ctx *Context) Storable() (Nick, Chan) { } // ReplyN() adds a prefix of "nick: " to the reply text, -func (ctx *Context) ReplyN(fm string, args ...interface{}) { - args = append([]interface{}{ctx.Nick}, args...) +func (ctx *Context) ReplyN(fm string, args ...any) { + args = append([]any{ctx.Nick}, args...) ctx.Reply("%s: "+fm, args...) } // whereas Reply() does not. -func (ctx *Context) Reply(fm string, args ...interface{}) { +func (ctx *Context) Reply(fm string, args ...any) { ctx.conn.Privmsg(ctx.Target(), ctx.rws.Rewrite(fmt.Sprintf(fm, args...), ctx)) } -func (ctx *Context) Do(fm string, args ...interface{}) { +func (ctx *Context) Do(fm string, args ...any) { ctx.conn.Action(ctx.Target(), ctx.rws.Rewrite(fmt.Sprintf(fm, args...), ctx)) } diff --git a/collections/conf/both.go b/collections/conf/both.go index 3737609..ddacff4 100644 --- a/collections/conf/both.go +++ b/collections/conf/both.go @@ -123,7 +123,7 @@ func (b both) Float(key string, value ...float64) float64 { return mongo } -func (b both) Value(key string, value ...interface{}) interface{} { +func (b both) Value(key string, value ...any) any { switch b.Check() { case db.MONGO_ONLY: return b.mongo.Value(key, value...) diff --git a/collections/conf/config.go b/collections/conf/config.go index f616ef7..e240033 100644 --- a/collections/conf/config.go +++ b/collections/conf/config.go @@ -54,7 +54,7 @@ func Zone(nick string, tz ...string) string { type Entry struct { Ns, Key string - Value interface{} + Value any } func (e Entry) K() db.Key { diff --git a/collections/conf/inmemory.go b/collections/conf/inmemory.go index eaaa911..bcf5028 100644 --- a/collections/conf/inmemory.go +++ b/collections/conf/inmemory.go @@ -7,11 +7,11 @@ import ( type inMem struct { sync.Mutex ns string - data map[string]interface{} + data map[string]any } func InMem(ns string) Namespace { - return &inMem{ns: ns, data: make(map[string]interface{})} + return &inMem{ns: ns, data: make(map[string]any)} } func (ns *inMem) All() Entries { @@ -63,7 +63,7 @@ func (ns *inMem) Float(key string, value ...float64) float64 { return 0 } -func (ns *inMem) Value(key string, value ...interface{}) interface{} { +func (ns *inMem) Value(key string, value ...any) any { ns.Lock() defer ns.Unlock() if len(value) > 0 { diff --git a/collections/conf/namespace.go b/collections/conf/namespace.go index 082497f..ec2f2fd 100644 --- a/collections/conf/namespace.go +++ b/collections/conf/namespace.go @@ -12,7 +12,7 @@ type Namespace interface { String(key string, value ...string) string Int(key string, value ...int) int Float(key string, value ...float64) float64 - Value(key string, value ...interface{}) interface{} + Value(key string, value ...any) any Delete(key string) } @@ -27,14 +27,14 @@ func (ns *namespace) K() db.Key { var _ db.Keyer = (*namespace)(nil) -func (ns *namespace) set(key string, value interface{}) { +func (ns *namespace) set(key string, value any) { e := &Entry{Ns: ns.ns, Key: key, Value: value} if err := ns.Put(e); err != nil { logging.Error("Couldn't set config entry %q: %v", e, err) } } -func (ns *namespace) get(key string) interface{} { +func (ns *namespace) get(key string) any { e := &Entry{Ns: ns.ns, Key: key} if err := ns.Get(e.K(), e); err != nil && err != mgo.ErrNotFound && err != bbolt.ErrTxNotWritable { logging.Error("Couldn't get config entry for ns=%q key=%q: %v", ns.ns, key, err) @@ -84,7 +84,7 @@ func (ns *namespace) Float(key string, value ...float64) float64 { return 0 } -func (ns *namespace) Value(key string, value ...interface{}) interface{} { +func (ns *namespace) Value(key string, value ...any) any { if len(value) > 0 { ns.set(key, value[0]) return value[0] diff --git a/collections/factoids/factoids.go b/collections/factoids/factoids.go index 15c67c1..54369af 100644 --- a/collections/factoids/factoids.go +++ b/collections/factoids/factoids.go @@ -2,7 +2,7 @@ package factoids import ( "fmt" - "math/rand" + "math/rand/v2" "strings" "time" @@ -273,7 +273,7 @@ func (fc *Collection) GetPseudoRand(key string) *Factoid { logging.Debug("Creating seen data for key '%s'.", key) fc.seen[key] = make(map[bson.ObjectId]bool) } - res := filtered[rand.Intn(count)] + res := filtered[rand.IntN(count)] logging.Debug("Storing id %v for key '%s'.", res.Id(), key) fc.seen[key][res.Id()] = true return res diff --git a/collections/quotes/quotes.go b/collections/quotes/quotes.go index 2efc551..b1122a4 100644 --- a/collections/quotes/quotes.go +++ b/collections/quotes/quotes.go @@ -2,7 +2,7 @@ package quotes import ( "fmt" - "math/rand" + "math/rand/v2" "sync/atomic" "time" @@ -217,7 +217,7 @@ func (qc *Collection) GetPseudoRand(regex string) *Quote { logging.Debug("Creating seen data for regex %q.", regex) qc.seen[regex] = map[bson.ObjectId]bool{} } - res := filtered[rand.Intn(count)] + res := filtered[rand.IntN(count)] logging.Debug("Storing id %v for regex %q.", res.Id_, regex) qc.seen[regex][res.Id_] = true return res diff --git a/collections/reminders/reminders.go b/collections/reminders/reminders.go index b973b08..64dae64 100644 --- a/collections/reminders/reminders.go +++ b/collections/reminders/reminders.go @@ -2,7 +2,7 @@ package reminders import ( "fmt" - "sort" + "slices" "strings" "time" @@ -164,8 +164,8 @@ func (rs Reminders) Strings() []string { } func (rs Reminders) sortByRemindAt() { - sort.Slice(rs, func(i, j int) bool { - return rs[i].RemindAt.Before(rs[j].RemindAt) + slices.SortFunc(rs, func(a, b *Reminder) int { + return a.RemindAt.Compare(b.RemindAt) }) } diff --git a/collections/seen/seen.go b/collections/seen/seen.go index 9bb0429..2c94378 100644 --- a/collections/seen/seen.go +++ b/collections/seen/seen.go @@ -2,7 +2,7 @@ package seen import ( "fmt" - "sort" + "slices" "strings" "time" @@ -133,11 +133,6 @@ func (ns Nicks) Strings() []string { return s } -// Implement sort.Interface to sort by descending timestamp. -func (ns Nicks) Len() int { return len(ns) } -func (ns Nicks) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] } -func (ns Nicks) Less(i, j int) bool { return ns[i].Timestamp.After(ns[j].Timestamp) } - type migrator struct { mongo, bolt db.Collection } @@ -245,7 +240,9 @@ func (sc *Collection) SeenAnyMatching(rx string) []string { if err := sc.Match("Nick", rx, &ns); err != nil { return nil } - sort.Sort(ns) + slices.SortFunc(ns, func(a, b *Nick) int { + return b.Timestamp.Compare(a.Timestamp) + }) seen := make(map[string]bool) res := make([]string, 0, len(ns)) for _, n := range ns { diff --git a/collections/urls/urls.go b/collections/urls/urls.go index 1f8250f..a102177 100644 --- a/collections/urls/urls.go +++ b/collections/urls/urls.go @@ -2,7 +2,7 @@ package urls import ( "fmt" - "math/rand" + "math/rand/v2" "time" "github.com/fluffle/golog/logging" @@ -225,7 +225,7 @@ func (uc *Collection) GetRand(regex string) *Url { logging.Debug("Creating seen data for regex %q.", regex) uc.seen[regex] = map[bson.ObjectId]bool{} } - url := filtered[rand.Intn(count)] + url := filtered[rand.IntN(count)] logging.Debug("Storing id %v for regex %q.", url.Id_, regex) uc.seen[regex][url.Id_] = true return url diff --git a/db/bolt.go b/db/bolt.go index fdc73dc..44e0634 100644 --- a/db/bolt.go +++ b/db/bolt.go @@ -35,7 +35,7 @@ func isBson(data []byte) bool { return bytes.Equal(data[:prefixLen], bsonPrefix) } -func toBson(value interface{}) ([]byte, error) { +func toBson(value any) ([]byte, error) { marshalled, err := bson.Marshal(value) if err != nil { return nil, fmt.Errorf("bson marshal: %w", err) diff --git a/db/both.go b/db/both.go index 8abf92c..6b6bfab 100644 --- a/db/both.go +++ b/db/both.go @@ -50,7 +50,7 @@ func dupeR(vt reflect.Type, vv reflect.Value) reflect.Value { // This function rigourously tested for all of 15 minutes // at https://play.golang.org/p/IrEWIxm_PEH ;-) -func dupe(in interface{}) interface{} { +func dupe(in any) any { return dupeR(reflect.TypeOf(in), reflect.ValueOf(in)).Interface() } @@ -64,7 +64,7 @@ func (b *Both) compareErr(method string, mErr, bErr error) error { return bErr } -func (b *Both) compare(method, key string, mValue, bValue interface{}, mErr, bErr error) error { +func (b *Both) compare(method, key string, mValue, bValue any, mErr, bErr error) error { // Mongo returns ErrNotFound, Bolt returns nil, nil. if errors.Is(mErr, mgo.ErrNotFound) && bErr == nil && bValue == nil { return nil @@ -82,7 +82,7 @@ func (b *Both) compare(method, key string, mValue, bValue interface{}, mErr, bEr return b.compareErr(method, mErr, bErr) } -func (b *Both) Get(key Key, value interface{}) error { +func (b *Both) Get(key Key, value any) error { other := dupe(value) switch b.Check() { case MONGO_ONLY: @@ -99,7 +99,7 @@ func (b *Both) Get(key Key, value interface{}) error { return ErrInvalidState } -func (b *Both) Match(key, re string, value interface{}) error { +func (b *Both) Match(key, re string, value any) error { other := dupe(value) switch b.Check() { case MONGO_ONLY: @@ -116,7 +116,7 @@ func (b *Both) Match(key, re string, value interface{}) error { return ErrInvalidState } -func (b *Both) All(key Key, value interface{}) error { +func (b *Both) All(key Key, value any) error { other := dupe(value) switch b.Check() { case MONGO_ONLY: @@ -133,7 +133,7 @@ func (b *Both) All(key Key, value interface{}) error { return ErrInvalidState } -func (b *Both) Put(value interface{}) error { +func (b *Both) Put(value any) error { switch b.Check() { case MONGO_ONLY: return b.MongoC.Put(value) @@ -145,7 +145,7 @@ func (b *Both) Put(value interface{}) error { return ErrInvalidState } -func (b *Both) BatchPut(value interface{}) error { +func (b *Both) BatchPut(value any) error { switch b.Check() { case MONGO_ONLY: // BatchPut is a bolt thing, fail before migration @@ -156,7 +156,7 @@ func (b *Both) BatchPut(value interface{}) error { return ErrInvalidState } -func (b *Both) Del(value interface{}) error { +func (b *Both) Del(value any) error { switch b.Check() { case MONGO_ONLY: return b.MongoC.Del(value) diff --git a/db/db.go b/db/db.go index 40874cf..1b1e88c 100644 --- a/db/db.go +++ b/db/db.go @@ -26,13 +26,13 @@ type Database interface { } type Collection interface { - Get(Key, interface{}) error + Get(Key, any) error // GetPR(Key, interface{}) error ? - Match(string, string, interface{}) error - All(Key, interface{}) error - Put(interface{}) error - BatchPut(interface{}) error - Del(interface{}) error + Match(string, string, any) error + All(Key, any) error + Put(any) error + BatchPut(any) error + Del(any) error Next(Key, ...int) (int, error) // Turn on debugging for this collection. Debug(bool) @@ -55,7 +55,7 @@ func (c *C) Init(db Database, name string, f func(Collection)) { } type Elem interface { - Pair() (string, interface{}) + Pair() (string, any) Bytes() []byte String() string } @@ -65,7 +65,7 @@ type S struct { Name, Value string } -func (e S) Pair() (string, interface{}) { +func (e S) Pair() (string, any) { return e.Name, e.Value } @@ -87,7 +87,7 @@ type I struct { Value uint64 } -func (e I) Pair() (string, interface{}) { +func (e I) Pair() (string, any) { return e.Name, e.Value } @@ -112,7 +112,7 @@ type T struct { Value bool } -func (e T) Pair() (string, interface{}) { +func (e T) Pair() (string, any) { return e.Name, e.Value } @@ -138,7 +138,7 @@ type ID struct { Value bson.ObjectId } -func (e ID) Pair() (string, interface{}) { +func (e ID) Pair() (string, any) { return "_id", e.Value } diff --git a/db/indexed.go b/db/indexed.go index 18718d5..1201bcc 100644 --- a/db/indexed.go +++ b/db/indexed.go @@ -77,14 +77,14 @@ func (bucket *indexedBucket) Debug(on bool) { bucket.debug_ = on } -func (bucket *indexedBucket) debug(f string, args ...interface{}) { +func (bucket *indexedBucket) debug(f string, args ...any) { if bucket.debug_ { - logging.Debug("%s."+f, append([]interface{}{bucket.name}, args...)...) + logging.Debug("%s."+f, append([]any{bucket.name}, args...)...) } } -func (bucket *indexedBucket) error(f string, args ...interface{}) error { - return fmt.Errorf("%s."+f, append([]interface{}{bucket.name}, args...)...) +func (bucket *indexedBucket) error(f string, args ...any) error { + return fmt.Errorf("%s."+f, append([]any{bucket.name}, args...)...) } func (bucket *indexedBucket) values(tx *bbolt.Tx) *bbolt.Bucket { @@ -113,7 +113,7 @@ func (bucket *indexedBucket) create(tx *bbolt.Tx, elems [][]byte) (*bbolt.Bucket return b, nil } -func (bucket *indexedBucket) Get(key Key, value interface{}) error { +func (bucket *indexedBucket) Get(key Key, value any) error { elems, last := key.B() if len(last) == 0 { return bucket.error("Get(): zero length key") @@ -142,7 +142,7 @@ func (bucket *indexedBucket) Get(key Key, value interface{}) error { }) } -func (bucket *indexedBucket) All(key Key, value interface{}) error { +func (bucket *indexedBucket) All(key Key, value any) error { elems, last := key.B() if len(last) == 0 { // A zero-length key will perform a scan over the vals bucket directly, @@ -174,7 +174,7 @@ func (bucket *indexedBucket) All(key Key, value interface{}) error { }) } -func (bucket *indexedBucket) Match(field, re string, value interface{}) error { +func (bucket *indexedBucket) Match(field, re string, value any) error { if re == "" { return bucket.error("Match(): zero-length regex match") } @@ -204,7 +204,7 @@ func (bucket *indexedBucket) Match(field, re string, value interface{}) error { }) } -func (bucket *indexedBucket) Put(value interface{}) error { +func (bucket *indexedBucket) Put(value any) error { indexer, ok := value.(Indexer) if !ok { return bucket.error("Put(): don't know how to put value %#v", value) @@ -218,7 +218,7 @@ func (bucket *indexedBucket) Put(value interface{}) error { }) } -func (bucket *indexedBucket) BatchPut(value interface{}) error { +func (bucket *indexedBucket) BatchPut(value any) error { // vv == value Value vv := reflect.ValueOf(value) if vv.Kind() != reflect.Slice || !vv.Type().Elem().Implements(indexerType) { @@ -231,7 +231,7 @@ func (bucket *indexedBucket) BatchPut(value interface{}) error { } tuples := make([]kvTuple, vv.Len()) - for i := 0; i < vv.Len(); i++ { + for i := range vv.Len() { indexer, _ := vv.Index(i).Interface().(Indexer) data, err := toBson(vv.Index(i).Interface()) if err != nil { @@ -311,7 +311,7 @@ func (bucket *indexedBucket) delIndex(tx *bbolt.Tx, value Indexer) error { return nil } -func (bucket *indexedBucket) Del(value interface{}) error { +func (bucket *indexedBucket) Del(value any) error { indexer, ok := value.(Indexer) if !ok { return bucket.error("Del(): don't know how to delete value %#v", value) diff --git a/db/keyed.go b/db/keyed.go index c27bdec..47f33bd 100644 --- a/db/keyed.go +++ b/db/keyed.go @@ -59,14 +59,14 @@ func (bucket *keyedBucket) Debug(on bool) { bucket.debug_ = on } -func (bucket *keyedBucket) debug(f string, args ...interface{}) { +func (bucket *keyedBucket) debug(f string, args ...any) { if bucket.debug_ { - logging.Debug("%s."+f, append([]interface{}{bucket.name}, args...)...) + logging.Debug("%s."+f, append([]any{bucket.name}, args...)...) } } -func (bucket *keyedBucket) error(f string, args ...interface{}) error { - return fmt.Errorf("%s."+f, append([]interface{}{bucket.name}, args...)...) +func (bucket *keyedBucket) error(f string, args ...any) error { + return fmt.Errorf("%s."+f, append([]any{bucket.name}, args...)...) } func (bucket *keyedBucket) find(tx *bbolt.Tx, elems [][]byte) *bbolt.Bucket { @@ -91,7 +91,7 @@ func (bucket *keyedBucket) create(tx *bbolt.Tx, elems [][]byte) (*bbolt.Bucket, return b, nil } -func (bucket *keyedBucket) Get(key Key, value interface{}) error { +func (bucket *keyedBucket) Get(key Key, value any) error { elems, last := key.B() if len(last) == 0 { return bucket.error("Get(): zero length key") @@ -110,7 +110,7 @@ func (bucket *keyedBucket) Get(key Key, value interface{}) error { }) } -func (bucket *keyedBucket) All(key Key, value interface{}) error { +func (bucket *keyedBucket) All(key Key, value any) error { elems, last := key.B() // All implies that the last key elem is also a bucket. // We support a zero-length key to perform a scan over the root bucket. @@ -131,7 +131,7 @@ func (bucket *keyedBucket) All(key Key, value interface{}) error { }) } -func (bucket *keyedBucket) Match(field, re string, value interface{}) error { +func (bucket *keyedBucket) Match(field, re string, value any) error { if re == "" { return bucket.error("Match(): zero-length regex match") } @@ -163,7 +163,7 @@ func (bucket *keyedBucket) Match(field, re string, value interface{}) error { }) } -func (bucket *keyedBucket) Put(value interface{}) error { +func (bucket *keyedBucket) Put(value any) error { keyer, ok := value.(Keyer) if !ok { return bucket.error("Put(): don't know how to put value %#v", value) @@ -182,7 +182,7 @@ func (bucket *keyedBucket) Put(value interface{}) error { }) } -func (bucket *keyedBucket) BatchPut(value interface{}) error { +func (bucket *keyedBucket) BatchPut(value any) error { // vv == value Value vv := reflect.ValueOf(value) if vv.Kind() != reflect.Slice || !vv.Type().Elem().Implements(keyerType) { @@ -196,7 +196,7 @@ func (bucket *keyedBucket) BatchPut(value interface{}) error { } tuples := make([]kvTuple, vv.Len()) - for i := 0; i < vv.Len(); i++ { + for i := range vv.Len() { keyer, _ := vv.Index(i).Interface().(Keyer) elems, last := keyer.K().B() if len(last) == 0 { @@ -229,7 +229,7 @@ func (bucket *keyedBucket) putTx(tx *bbolt.Tx, elems [][]byte, key, value []byte return b.Put(key, value) } -func (bucket *keyedBucket) Del(value interface{}) error { +func (bucket *keyedBucket) Del(value any) error { keyer, ok := value.(Keyer) if !ok { return bucket.error("Del(): don't know how to delete value %#v", value) diff --git a/db/migration.go b/db/migration.go index 8145bc9..3c8db2c 100644 --- a/db/migration.go +++ b/db/migration.go @@ -3,7 +3,8 @@ package db import ( "errors" "fmt" - "sort" + "maps" + "slices" "strings" "sync" @@ -143,15 +144,12 @@ func MigrateTo(newState MigrationState) error { // Holding the lock while migrating prevents the Checker returned by // addMigrator from checking migration state (and thus locks up the // bot) while migration is running in the background. - migrators := map[string]*migrator{} ms.RLock() - for coll, m := range ms.migrators { - migrators[coll] = m - } + migrators := maps.Clone(ms.migrators) logging.Debug("Migrating %d collections to %s.", len(migrators), newState) ms.RUnlock() - failed := []string{} + var errs error for coll, m := range migrators { if m.state >= newState { logging.Debug("Skipping %s as it is in %s already.", coll, m.state) @@ -160,22 +158,22 @@ func MigrateTo(newState MigrationState) error { logging.Debug("Migrating %q to state %s.", coll, newState) if err := m.MigrateTo(newState); err != nil { logging.Error("Migrating %q failed: %v", coll, err) - failed = append(failed, coll) + errs = errors.Join(errs, fmt.Errorf("migrating %q: %w", coll, err)) continue } if differ, ok := m.Migrator.(Differ); ok { before, after, err := differ.Diff() if err != nil { logging.Error("Diffing %q failed: %v", coll, err) - failed = append(failed, coll) + errs = errors.Join(errs, fmt.Errorf("diffing %q: %w", coll, err)) continue } - sort.Strings(before) - sort.Strings(after) + slices.Sort(before) + slices.Sort(after) unified, err := diff.Unified(before, after) if err != nil { logging.Error("Migration diff: %v\n%s", err, strings.Join(unified, "\n")) - failed = append(failed, coll) + errs = errors.Join(errs, fmt.Errorf("diffing %q: %w", coll, err)) continue } } @@ -187,9 +185,5 @@ func MigrateTo(newState MigrationState) error { m.state = newState ms.Unlock() } - if len(failed) > 0 { - return fmt.Errorf("migration failed for: \"%s\"", - strings.Join(failed, "\", \"")) - } - return nil + return errs } diff --git a/db/mongo.go b/db/mongo.go index 57e20c1..3684cb0 100644 --- a/db/mongo.go +++ b/db/mongo.go @@ -78,28 +78,28 @@ func (m *mongoCollection) Debug(on bool) { m.debug_ = on } -func (m *mongoCollection) debug(f string, args ...interface{}) { +func (m *mongoCollection) debug(f string, args ...any) { if m.debug_ { logging.Debug("MONGO: "+f, args...) } } -func (m *mongoCollection) Get(key Key, value interface{}) error { +func (m *mongoCollection) Get(key Key, value any) error { k := key.M() m.debug("Get(%v)", k) return m.Collection.Find(k).One(value) } -func (m *mongoCollection) Match(key, regex string, value interface{}) error { +func (m *mongoCollection) Match(key, regex string, value any) error { q := bson.M{strings.ToLower(key): bson.M{"$regex": regex, "$options": "i"}} return m.Collection.Find(q).All(value) } -func (m *mongoCollection) All(key Key, value interface{}) error { +func (m *mongoCollection) All(key Key, value any) error { return m.Collection.Find(key.M()).All(value) } -func (m *mongoCollection) Put(value interface{}) (err error) { +func (m *mongoCollection) Put(value any) (err error) { switch value := value.(type) { case Keyer: _, err = m.Collection.Upsert(value.K().M(), value) @@ -111,11 +111,11 @@ func (m *mongoCollection) Put(value interface{}) (err error) { return err } -func (m *mongoCollection) BatchPut(value interface{}) error { +func (m *mongoCollection) BatchPut(value any) error { panic("no batch puts for you") } -func (m *mongoCollection) Del(value interface{}) error { +func (m *mongoCollection) Del(value any) error { switch value := value.(type) { case Keyer: return m.Collection.Remove(value.K().M()) diff --git a/db/reflect.go b/db/reflect.go index fc13261..bb0e5bd 100644 --- a/db/reflect.go +++ b/db/reflect.go @@ -12,7 +12,7 @@ type slicePtr struct { et reflect.Type } -func newSlicePtr(value interface{}) *slicePtr { +func newSlicePtr(value any) *slicePtr { pv := reflect.ValueOf(value) if pv.Kind() != reflect.Ptr || pv.Elem().Kind() != reflect.Slice { panic("provided value is not a pointer-to-slice") @@ -43,7 +43,7 @@ func (sp *slicePtr) appendElem(ev reflect.Value) { } // ... I want a pony and this might just give me one. -func (sp *slicePtr) ponyElem() interface{} { +func (sp *slicePtr) ponyElem() any { ev := sp.newElem() sp.appendElem(ev) return sp.sv.Index(sp.len() - 1).Addr().Interface() diff --git a/drivers/decisiondriver/commands.go b/drivers/decisiondriver/commands.go index 44dba70..1f16a7e 100644 --- a/drivers/decisiondriver/commands.go +++ b/drivers/decisiondriver/commands.go @@ -1,7 +1,7 @@ package decisiondriver import ( - "math/rand" + "math/rand/v2" "strings" "github.com/fluffle/sp0rkle/bot" @@ -13,7 +13,7 @@ func decideCmd(ctx *bot.Context) { ctx.ReplyN("I can't decide: %v", err) return } - chosen := strings.TrimSpace(opts[rand.Intn(len(opts))]) + chosen := strings.TrimSpace(opts[rand.IntN(len(opts))]) ctx.ReplyN("%s", chosen) } diff --git a/drivers/decisiondriver/decisiondriver.go b/drivers/decisiondriver/decisiondriver.go index e7ffb1a..d9f3cb1 100644 --- a/drivers/decisiondriver/decisiondriver.go +++ b/drivers/decisiondriver/decisiondriver.go @@ -5,7 +5,7 @@ package decisiondriver import ( "errors" "fmt" - "math/rand" + "math/rand/v2" "strconv" "strings" "unicode" diff --git a/drivers/decisiondriver/plugins.go b/drivers/decisiondriver/plugins.go index 85b4650..773683d 100644 --- a/drivers/decisiondriver/plugins.go +++ b/drivers/decisiondriver/plugins.go @@ -3,7 +3,7 @@ package decisiondriver // A simple driver to implement decisions based on random numbers. No, not 4. import ( - "math/rand" + "math/rand/v2" "strings" "github.com/fluffle/sp0rkle/bot" @@ -20,7 +20,7 @@ func randPlugin(val string, ctx *bot.Context) string { func decidePlugin(val string, ctx *bot.Context) string { f := func(s string) string { if options, err := splitDelimitedString(s); len(options) > 0 && err == nil { - return strings.TrimSpace(options[rand.Intn(len(options))]) + return strings.TrimSpace(options[rand.IntN(len(options))]) } return "" } diff --git a/drivers/factdriver/handlers.go b/drivers/factdriver/handlers.go index ea7f27d..cf914bc 100644 --- a/drivers/factdriver/handlers.go +++ b/drivers/factdriver/handlers.go @@ -1,7 +1,7 @@ package factdriver import ( - "math/rand" + "math/rand/v2" "strings" "github.com/fluffle/goirc/client" @@ -33,8 +33,8 @@ func insert(ctx *bot.Context) { // The "randomwoot" factoid contains random positive phrases for success. joy := "Woo" - if rand := fc.GetPseudoRand("randomwoot"); rand != nil { - joy = rand.Value + if f := fc.GetPseudoRand("randomwoot"); f != nil { + joy = f.Value } if err := fc.Put(fact); err != nil { diff --git a/drivers/netdriver/netdriver.go b/drivers/netdriver/netdriver.go index ca2f9b5..0ce8c51 100644 --- a/drivers/netdriver/netdriver.go +++ b/drivers/netdriver/netdriver.go @@ -1,7 +1,7 @@ package netdriver import ( - "io/ioutil" + "io" "net/http" "github.com/fluffle/goirc/client" @@ -24,7 +24,7 @@ func get(req string) ([]byte, error) { return nil, err } defer res.Body.Close() - return ioutil.ReadAll(res.Body) + return io.ReadAll(res.Body) } func Init() { diff --git a/drivers/netdriver/pushbullet.go b/drivers/netdriver/pushbullet.go index f1df1ad..cf50d28 100644 --- a/drivers/netdriver/pushbullet.go +++ b/drivers/netdriver/pushbullet.go @@ -2,7 +2,7 @@ package netdriver import ( "fmt" - "math/rand" + "math/rand/v2" "net/http" "strings" @@ -189,7 +189,7 @@ func pushDeviceHTTP(rw http.ResponseWriter, req *http.Request) { http.Redirect(rw, req, pushFailureURL("noiden"), 302) return } - s.Pin = fmt.Sprintf("%06x", rand.Intn(1e6)) + s.Pin = fmt.Sprintf("%06x", rand.IntN(1e6)) if err := push.Confirm(s); err != nil { logging.Error("Failed to send confirmation push for %s: %v", s.Nick, err) http.Redirect(rw, req, pushFailureURL("push"), 302) diff --git a/drivers/urldriver/urldriver.go b/drivers/urldriver/urldriver.go index 68a0c9e..3e4cb45 100644 --- a/drivers/urldriver/urldriver.go +++ b/drivers/urldriver/urldriver.go @@ -6,7 +6,7 @@ import ( "fmt" "hash/crc32" "io" - "math/rand" + "math/rand/v2" "net/http" "os" "strconv" @@ -84,11 +84,11 @@ func Encode(url string) string { // We shorten/cache a url with it's base-64 encoded CRC32 hash crc := crc32.ChecksumIEEE([]byte(url)) crcb := make([]byte, 4) - for i := 0; i < 4; i++ { + for i := range 4 { crcb[i] = byte((crc >> uint32(i)) & 0xff) } // Avoid collisions in shortened URLs - for i := 0; i < 10; i++ { + for range 10 { // Since we're always encoding exactly 4 bytes (32 bits) // resulting in 5 1/3 bytes of encoded data, we can drop // the two padding equals signs for brevity. @@ -98,7 +98,7 @@ func Encode(url string) string { if !(cached.Exists() || shortened.Exists()) { return s } - crcb[rand.Intn(4)]++ + crcb[rand.IntN(4)]++ } logging.Warn("Collided ten times while encoding URL.") return "" diff --git a/go.mod b/go.mod index 289628c..48f8e43 100644 --- a/go.mod +++ b/go.mod @@ -21,4 +21,4 @@ require ( gopkg.in/yaml.v2 v2.2.8 // indirect ) -go 1.22 +go 1.25 diff --git a/main.go b/main.go index fa89e0b..9955bca 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "context" _ "expvar" "flag" - "math/rand" "net/http" "os" "os/exec" @@ -52,7 +51,6 @@ func main() { } // Slightly more random than 1. - rand.Seed(time.Now().UnixNano() * int64(os.Getpid())) // Initialise bot state ctx := context.Background() @@ -90,7 +88,7 @@ func main() { go func() { called := new(int32) sigint := make(chan os.Signal, 1) - signal.Notify(sigint, syscall.SIGINT) + signal.Notify(sigint, syscall.SIGINT, syscall.SIGTERM) for _ = range sigint { if atomic.AddInt32(called, 1) > 1 { logging.Fatal("Recieved multiple interrupts, dying.") diff --git a/util/calc/calc.go b/util/calc/calc.go index 7f0aa33..b30ad61 100644 --- a/util/calc/calc.go +++ b/util/calc/calc.go @@ -71,7 +71,9 @@ func functionise1(f func(float64) float64) function { } // But a lot of the operators are f(x,y) -> z, and so we need two curries. -// ... +// +// ... +// // MMMmmmmmm. Curry. func functionise2(f func(float64, float64) float64) function { return function{2, func(ts *tokenStack) error { @@ -316,7 +318,9 @@ func (ts *tokenStack) pop() (t *token, e error) { // getNums() pops n T_NUM tokens from the stack and returns them in a slice. // It's a "helper" function for the functioniseX duo above. -// ... +// +// ... +// // OK, this is kind of horrific and probably comes from bad design decisions // I APOLOGISE FOR NOTHING func (ts *tokenStack) getNums(n int) ([]*token, error) { @@ -334,7 +338,8 @@ func (ts *tokenStack) getNums(n int) ([]*token, error) { } // shunt() implements a version of Dijkstra's Shunting-Yard algorithm: -// http://en.wikipedia.org/wiki/Shunting-yard_algorithm +// +// http://en.wikipedia.org/wiki/Shunting-yard_algorithm func shunt(input *tokenStack) (*tokenStack, error) { stack := ts(len(*input)) output := ts(len(*input)) diff --git a/util/datetime/datetime_test.go b/util/datetime/datetime_test.go index af20fec..d1e652e 100644 --- a/util/datetime/datetime_test.go +++ b/util/datetime/datetime_test.go @@ -183,7 +183,7 @@ func TestParseAllTimezonesInZoneinfo(t *testing.T) { } tests := make(timeTests, 0, n) - for i := 0; i < n; i++ { + for range n { if get4(buf) != 0x02014b50 { break } diff --git a/util/datetime/lexer.go b/util/datetime/lexer.go index 552c869..cf3e5ae 100644 --- a/util/datetime/lexer.go +++ b/util/datetime/lexer.go @@ -12,7 +12,7 @@ import ( "github.com/fluffle/sp0rkle/util" ) -func DPrintf(f string, args ...interface{}) { +func DPrintf(f string, args ...any) { if yyDebug > 0 { fmt.Printf(f, args...) } diff --git a/util/diff/patience.go b/util/diff/patience.go index f430628..c40b511 100644 --- a/util/diff/patience.go +++ b/util/diff/patience.go @@ -6,7 +6,7 @@ package diff import ( "errors" - "sort" + "slices" ) var ( @@ -223,7 +223,6 @@ func patienceDiff(a, b []string) []diff { func Unified(a, b []string) ([]string, error) { diffs := patienceDiff(a, b) - println(len(diffs)) err := ErrDiff if len(diffs) == 0 || len(diffs) == 1 && diffs[0].op == equal { err = nil @@ -252,7 +251,7 @@ type stringSlicer interface { Strings() []string } -func stringSlices(a, b interface{}) ([]string, []string, bool) { +func stringSlices(a, b any) ([]string, []string, bool) { ass, aok := a.(stringSlicer) bss, bok := b.(stringSlicer) if !(aok && bok) { @@ -261,17 +260,17 @@ func stringSlices(a, b interface{}) ([]string, []string, bool) { return ass.Strings(), bss.Strings(), true } -func SortDiff(a, b interface{}) ([]string, error) { +func SortDiff(a, b any) ([]string, error) { astrs, bstrs, ok := stringSlices(a, b) if !ok { return nil, ErrNotDiffable } - sort.Strings(astrs) - sort.Strings(bstrs) + slices.Sort(astrs) + slices.Sort(bstrs) return Unified(astrs, bstrs) } -func Diff(a, b interface{}) ([]string, error) { +func Diff(a, b any) ([]string, error) { astrs, bstrs, ok := stringSlices(a, b) if !ok { return nil, ErrNotDiffable diff --git a/util/lexer.go b/util/lexer.go index 6826da2..76587e9 100644 --- a/util/lexer.go +++ b/util/lexer.go @@ -91,7 +91,8 @@ func (l *Lexer) Rewind() { // number() is a higher-level function that extracts a number from the // input beginning at lexer.pos. A number matches the following regex: -// -?[0-9]+(.[0-9]+)?([eE][+-]?[0-9]+)? +// +// -?[0-9]+(.[0-9]+)?([eE][+-]?[0-9]+)? func (l *Lexer) Number() float64 { s := l.pos // l.start is reset through the multiple scans if l.Peek() == '-' { diff --git a/util/lexer_test.go b/util/lexer_test.go index 6aa6a4e..4b7a261 100644 --- a/util/lexer_test.go +++ b/util/lexer_test.go @@ -33,7 +33,7 @@ func TestLexerLowLevelFuncs(t *testing.T) { } // For the next three chars, make sure peek() and next() are in sync - for i := 0; i < 3; i++ { + for range 3 { if string(l.Peek()) != l.Next() { t.Errorf("Peek and next don't agree") } diff --git a/util/markov/markov.go b/util/markov/markov.go index 9c3e55e..6baa8ce 100644 --- a/util/markov/markov.go +++ b/util/markov/markov.go @@ -2,7 +2,7 @@ package markov import ( "errors" - "math/rand" + "math/rand/v2" "strconv" "strings" @@ -69,7 +69,7 @@ func generate(data Source, start string, length int) ([]string, error) { return output, NOT_ENOUGH_DATA } - r := rand.Intn(sum) + r := rand.IntN(sum) for _, child := range children { r -= child.Uses diff --git a/util/utils.go b/util/utils.go index 1e04909..9abd356 100644 --- a/util/utils.go +++ b/util/utils.go @@ -38,10 +38,11 @@ func HasPrefixedNick(text, nick string) bool { // Removes mIRC-style colours from a string. // These colours match the following BNF notation: -// colour ::= idchar | idchar colnum | idchar colnum "," colnum -// idchar ::= "\003" -// colnum ::= digit | digit digit -// digit ::= "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" +// +// colour ::= idchar | idchar colnum | idchar colnum "," colnum +// idchar ::= "\003" +// colnum ::= digit | digit digit +// digit ::= "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" func RemoveColours(s string) string { for { i := strings.Index(s, "\003") @@ -114,7 +115,8 @@ func IsFactoidAddition(s string) bool { // Does this string look like a URL to you? // This should be fairly conservative, I hope: -// s starts with http:// or https:// and contains no spaces +// +// s starts with http:// or https:// and contains no spaces func LooksURLish(s string) bool { return ((strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")) &&