chore: refresh go tooling and dependencies

This commit is contained in:
Peter Steinberger 2026-04-24 20:36:03 +01:00
parent d83d1c2f6f
commit ad609bcc6b
No known key found for this signature in database
36 changed files with 181 additions and 175 deletions

View File

@ -6,11 +6,17 @@ linters:
- copyloopvar
- durationcheck
- errcheck
- errchkjson
- errorlint
- govet
- intrange
- ineffassign
- misspell
- modernize
- nilerr
- nilnesserr
- nolintlint
- perfsprint
- rowserrcheck
- sloglint
- sqlclosecheck
@ -18,6 +24,7 @@ linters:
- testifylint
- unconvert
- unused
- usestdlibvars
- wastedassign
formatters:

View File

@ -8,6 +8,7 @@ All notable changes to `discrawl` will be documented in this file.
- `dms` now lists local wiretap DM conversations and can read or search one DM thread with `--with`, `--last`, and `--search`, so common DM queries no longer require raw SQL.
- `search --dm` and `messages --dm` now target the local-only `@me` archive directly and skip Git snapshot auto-update, since DMs are never imported from the shared mirror.
- Go module dependencies and lint rules were refreshed for the current Go toolchain, including stricter JSON marshal checks and modern simplification rules.
### Fixes

10
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/pelletier/go-toml/v2 v2.3.0
github.com/stretchr/testify v1.11.1
golang.org/x/text v0.35.0
golang.org/x/text v0.36.0
modernc.org/sqlite v1.49.1
)
@ -15,14 +15,14 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/sys v0.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.72.0 // indirect
modernc.org/libc v1.72.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

41
go.sum
View File

@ -13,8 +13,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
@ -26,32 +26,31 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
modernc.org/cc/v4 v4.28.1 h1:XpLbkYVQ24E8tX5u8+yWGvaxerxkR/S4zqxI8ZoSBuc=
modernc.org/cc/v4 v4.28.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.33.0 h1:dspBCm75jsj8Y/ufwAMVfe375L2iYdMyQ2QG/v3hL54=
modernc.org/ccgo/v4 v4.33.0/go.mod h1:+RhXBoRYzRwaH21mV/aj6XvQRDtfjcZfAlPMsQo8CR0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
@ -60,14 +59,14 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
modernc.org/libc v1.72.1 h1:db1xwJ6u1kE3KHTFTTbe2GCrczHPKzlURP0aDC4NGD0=
modernc.org/libc v1.72.1/go.mod h1:HRMiC/PhPGLIPM7GzAFCbI+oSgE3dhZ8FWftmRrHVlY=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U=

View File

@ -2,6 +2,7 @@ package cli
import (
"context"
"errors"
"flag"
"fmt"
"io"
@ -250,10 +251,10 @@ func (r *runtime) runWiretap(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("wiretap takes flags only"))
return usageErr(errors.New("wiretap takes flags only"))
}
if *maxFileBytes <= 0 {
return usageErr(fmt.Errorf("--max-file-bytes must be positive"))
return usageErr(errors.New("--max-file-bytes must be positive"))
}
runOnce := func(ctx context.Context) error {
stats, err := discorddesktop.Import(ctx, r.store, discorddesktop.Options{
@ -271,7 +272,7 @@ func (r *runtime) runWiretap(args []string) error {
return runOnce(r.ctx)
}
if *watchEvery < time.Second {
return usageErr(fmt.Errorf("--watch-every must be at least 1s"))
return usageErr(errors.New("--watch-every must be at least 1s"))
}
ctx, stop := signal.NotifyContext(r.ctx, os.Interrupt, syscall.SIGTERM)
defer stop()
@ -294,7 +295,7 @@ func (r *runtime) runWiretap(args []string) error {
func (r *runtime) runStatus(args []string) error {
if len(args) != 0 {
return usageErr(fmt.Errorf("status takes no arguments"))
return usageErr(errors.New("status takes no arguments"))
}
dbPath, err := config.ExpandPath(r.cfg.DBPath)
if err != nil {
@ -317,16 +318,16 @@ func (r *runtime) runEmbed(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("embed takes no positional arguments"))
return usageErr(errors.New("embed takes no positional arguments"))
}
if *limit <= 0 {
return usageErr(fmt.Errorf("--limit must be positive"))
return usageErr(errors.New("--limit must be positive"))
}
if *batchSize <= 0 {
return usageErr(fmt.Errorf("--batch-size must be positive"))
return usageErr(errors.New("--batch-size must be positive"))
}
if !r.cfg.Search.Embeddings.Enabled {
return usageErr(fmt.Errorf("embeddings are disabled in config"))
return usageErr(errors.New("embeddings are disabled in config"))
}
providerFactory := r.newEmbed
if providerFactory == nil {
@ -364,7 +365,7 @@ func (r *runtime) runEmbed(args []string) error {
func (r *runtime) runDoctor(args []string) error {
if len(args) != 0 {
return usageErr(fmt.Errorf("doctor takes no arguments"))
return usageErr(errors.New("doctor takes no arguments"))
}
report := map[string]any{
"config_path": r.configPath,

View File

@ -1365,7 +1365,7 @@ func TestRuntimeConfiguresAttachmentTextOnSyncer(t *testing.T) {
require.NoError(t, rt.withServices(true, func() error { return nil }))
require.True(t, fakeSync.attachmentTextEnabled)
cfg.Sync.AttachmentText = ptrBool(false)
cfg.Sync.AttachmentText = new(false)
require.NoError(t, config.Write(cfgPath, cfg))
require.NoError(t, rt.withServices(true, func() error { return nil }))
require.False(t, fakeSync.attachmentTextEnabled)
@ -1498,10 +1498,6 @@ func discardLogger() *slog.Logger {
return slog.New(slog.DiscardHandler)
}
func ptrBool(value bool) *bool {
return &value
}
func TestRuntimeHelpersAndSubcommands(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"flag"
"fmt"
"io"
@ -30,28 +31,28 @@ func (r *runtime) runDirectMessages(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("dms takes flags only"))
return usageErr(errors.New("dms takes flags only"))
}
if *hours < 0 {
return usageErr(fmt.Errorf("--hours must be >= 0"))
return usageErr(errors.New("--hours must be >= 0"))
}
if *days < 0 {
return usageErr(fmt.Errorf("--days must be >= 0"))
return usageErr(errors.New("--days must be >= 0"))
}
if countNonZero(*hours > 0, *days > 0, strings.TrimSpace(*since) != "") > 1 {
return usageErr(fmt.Errorf("use only one of --hours, --days, or --since"))
return usageErr(errors.New("use only one of --hours, --days, or --since"))
}
if *limit < 0 {
return usageErr(fmt.Errorf("--limit must be >= 0"))
return usageErr(errors.New("--limit must be >= 0"))
}
if *last < 0 {
return usageErr(fmt.Errorf("--last must be >= 0"))
return usageErr(errors.New("--last must be >= 0"))
}
if *all && *last > 0 && flagPassed(fs, "last") {
return usageErr(fmt.Errorf("use either --all or --last"))
return usageErr(errors.New("use either --all or --last"))
}
if flagPassed(fs, "limit") && flagPassed(fs, "last") {
return usageErr(fmt.Errorf("use either --limit or --last"))
return usageErr(errors.New("use either --limit or --last"))
}
if *list || (strings.TrimSpace(*with) == "" && strings.TrimSpace(*search) == "" && noDMMessageTimeFilter(*hours, *days, *since, *before)) {

View File

@ -1,8 +1,8 @@
package cli
import (
"errors"
"flag"
"fmt"
"strings"
"time"
@ -26,7 +26,7 @@ func (r *runtime) resolveSyncGuildsAll(guild, guilds string, all bool) ([]string
return r.resolveSyncGuilds(guild, guilds), nil
}
if len(csvList(guilds)) > 0 || strings.TrimSpace(guild) != "" {
return nil, fmt.Errorf("use either --all or --guild/--guilds")
return nil, errors.New("use either --all or --guild/--guilds")
}
return nil, nil
}
@ -42,7 +42,7 @@ func directMessageGuildScope(dm bool, guild, guilds string) ([]string, error) {
return csvList(strings.Join(requested, ",")), nil
}
if len(csvList(guilds)) > 0 || strings.TrimSpace(guild) != "" {
return nil, fmt.Errorf("use either --dm or --guild/--guilds")
return nil, errors.New("use either --dm or --guild/--guilds")
}
return []string{store.DirectMessageGuildID}, nil
}

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"flag"
"fmt"
"io"
@ -27,19 +28,19 @@ func (r *runtime) runMentions(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("mentions takes flags only"))
return usageErr(errors.New("mentions takes flags only"))
}
if *days < 0 {
return usageErr(fmt.Errorf("--days must be >= 0"))
return usageErr(errors.New("--days must be >= 0"))
}
if *days > 0 && strings.TrimSpace(*since) != "" {
return usageErr(fmt.Errorf("use either --days or --since"))
return usageErr(errors.New("use either --days or --since"))
}
if *limit < 0 {
return usageErr(fmt.Errorf("--limit must be >= 0"))
return usageErr(errors.New("--limit must be >= 0"))
}
if targetTypeValue := strings.TrimSpace(*targetType); targetTypeValue != "" && targetTypeValue != "user" && targetTypeValue != "role" {
return usageErr(fmt.Errorf("--type must be user or role"))
return usageErr(errors.New("--type must be user or role"))
}
var sinceTime time.Time
@ -73,7 +74,7 @@ func (r *runtime) runMentions(args []string) error {
sinceTime.IsZero() &&
beforeTime.IsZero() &&
len(guildIDs) == 0 {
return usageErr(fmt.Errorf("mentions needs at least one filter"))
return usageErr(errors.New("mentions needs at least one filter"))
}
rows, err := r.store.ListMentions(r.ctx, store.MentionListOptions{

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"flag"
"fmt"
"io"
@ -33,29 +34,29 @@ func (r *runtime) runMessages(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("messages takes flags only"))
return usageErr(errors.New("messages takes flags only"))
}
if *hours < 0 {
return usageErr(fmt.Errorf("--hours must be >= 0"))
return usageErr(errors.New("--hours must be >= 0"))
}
if *days < 0 {
return usageErr(fmt.Errorf("--days must be >= 0"))
return usageErr(errors.New("--days must be >= 0"))
}
if countNonZero(*hours > 0, *days > 0, strings.TrimSpace(*since) != "") > 1 {
return usageErr(fmt.Errorf("use only one of --hours, --days, or --since"))
return usageErr(errors.New("use only one of --hours, --days, or --since"))
}
if *limit < 0 {
return usageErr(fmt.Errorf("--limit must be >= 0"))
return usageErr(errors.New("--limit must be >= 0"))
}
if *last < 0 {
return usageErr(fmt.Errorf("--last must be >= 0"))
return usageErr(errors.New("--last must be >= 0"))
}
limitSet := flagPassed(fs, "limit")
if *all && *last > 0 {
return usageErr(fmt.Errorf("use either --all or --last"))
return usageErr(errors.New("use either --all or --last"))
}
if limitSet && *last > 0 {
return usageErr(fmt.Errorf("use either --limit or --last"))
return usageErr(errors.New("use either --limit or --last"))
}
if *last > 0 {
*limit = 0
@ -96,10 +97,10 @@ func (r *runtime) runMessages(args []string) error {
return usageErr(err)
}
if *dm && *syncNow {
return usageErr(fmt.Errorf("messages --sync is not supported with --dm; run wiretap or sync --source wiretap first"))
return usageErr(errors.New("messages --sync is not supported with --dm; run wiretap or sync --source wiretap first"))
}
if strings.TrimSpace(*channel) == "" && strings.TrimSpace(*author) == "" && sinceTime.IsZero() && beforeTime.IsZero() && len(guildIDs) == 0 {
return usageErr(fmt.Errorf("messages needs at least one filter"))
return usageErr(errors.New("messages needs at least one filter"))
}
if *all {
*limit = 0

View File

@ -2,6 +2,7 @@ package cli
import (
"encoding/json"
"errors"
"fmt"
"io"
"sort"
@ -69,7 +70,7 @@ func printPlain(w io.Writer, value any) error {
}
return nil
default:
return fmt.Errorf("no plain printer")
return errors.New("no plain printer")
}
}
@ -286,7 +287,7 @@ func printHuman(w io.Writer, value any) error {
}
return nil
default:
return fmt.Errorf("no human printer")
return errors.New("no human printer")
}
}

View File

@ -29,7 +29,7 @@ func (r *runtime) runSearch(args []string) error {
return usageErr(err)
}
if fs.NArg() != 1 {
return usageErr(fmt.Errorf("search requires a query"))
return usageErr(errors.New("search requires a query"))
}
guildIDs, err := directMessageGuildScope(*dm, *guildFlag, *guildsFlag)
if err != nil {
@ -107,7 +107,7 @@ func (r *runtime) searchMessagesHybrid(opts store.SearchOptions) ([]store.Search
func (r *runtime) semanticSearchOptions(opts store.SearchOptions) (store.SemanticSearchOptions, error) {
if !r.cfg.Search.Embeddings.Enabled {
return store.SemanticSearchOptions{}, fmt.Errorf("embeddings are disabled; enable [search.embeddings] first")
return store.SemanticSearchOptions{}, errors.New("embeddings are disabled; enable [search.embeddings] first")
}
providerFactory := r.newEmbed
if providerFactory == nil {
@ -158,7 +158,7 @@ func (r *runtime) runSQL(args []string) error {
return usageErr(err)
}
if *confirm && !*unsafe {
return usageErr(fmt.Errorf("--confirm requires --unsafe"))
return usageErr(errors.New("--confirm requires --unsafe"))
}
var query string
@ -184,7 +184,7 @@ func (r *runtime) runSQL(args []string) error {
return printRows(r.stdout, cols, rows)
}
if !*confirm {
return usageErr(fmt.Errorf("--unsafe requires --confirm"))
return usageErr(errors.New("--unsafe requires --confirm"))
}
if store.IsReadOnlySQL(query) {
@ -207,7 +207,7 @@ func (r *runtime) runSQL(args []string) error {
func (r *runtime) runMembers(args []string) error {
if len(args) == 0 {
return usageErr(fmt.Errorf("members requires a subcommand"))
return usageErr(errors.New("members requires a subcommand"))
}
switch args[0] {
case "list":
@ -220,7 +220,7 @@ func (r *runtime) runMembers(args []string) error {
return r.runMembersShow(args[1:])
case "search":
if len(args) < 2 {
return usageErr(fmt.Errorf("members search requires a query"))
return usageErr(errors.New("members search requires a query"))
}
rows, err := r.store.Members(r.ctx, "", strings.Join(args[1:], " "), 100)
if err != nil {
@ -240,7 +240,7 @@ func (r *runtime) runMembersShow(args []string) error {
return usageErr(err)
}
if fs.NArg() < 1 {
return usageErr(fmt.Errorf("members show requires a user id or query"))
return usageErr(errors.New("members show requires a user id or query"))
}
query := strings.Join(fs.Args(), " ")
@ -282,7 +282,7 @@ func (r *runtime) runMembersShow(args []string) error {
func (r *runtime) runChannels(args []string) error {
if len(args) == 0 {
return usageErr(fmt.Errorf("channels requires a subcommand"))
return usageErr(errors.New("channels requires a subcommand"))
}
rows, err := r.store.Channels(r.ctx, "")
if err != nil {
@ -293,7 +293,7 @@ func (r *runtime) runChannels(args []string) error {
return r.print(rows)
case "show":
if len(args) < 2 {
return usageErr(fmt.Errorf("channels show requires a channel id"))
return usageErr(errors.New("channels show requires a channel id"))
}
filtered := make([]store.ChannelRow, 0, 1)
for _, row := range rows {

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"slices"
"strings"
@ -10,7 +11,7 @@ import (
func (r *runtime) syncMessagesQuery(channel, guild, guilds string) error {
if r.syncer == nil {
return usageErr(fmt.Errorf("messages --sync requires Discord access"))
return usageErr(errors.New("messages --sync requires Discord access"))
}
opts, err := r.messageSyncOptions(channel, guild, guilds)
if err != nil {
@ -30,7 +31,7 @@ func (r *runtime) messageSyncOptions(channel, guild, guilds string) (syncer.Sync
channelFilter := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(channel), "#"))
if channelFilter == "" {
if len(opts.GuildIDs) == 0 {
return opts, fmt.Errorf("messages --sync needs --channel or --guild")
return opts, errors.New("messages --sync needs --channel or --guild")
}
return opts, nil
}

View File

@ -1,8 +1,8 @@
package cli
import (
"errors"
"flag"
"fmt"
"io"
"github.com/steipete/discrawl/internal/report"
@ -16,7 +16,7 @@ func (r *runtime) runReport(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("report takes no positional arguments"))
return usageErr(errors.New("report takes no positional arguments"))
}
activity, err := report.Build(r.ctx, r.store, report.Options{})
if err != nil {

View File

@ -1,8 +1,8 @@
package cli
import (
"errors"
"flag"
"fmt"
"io"
"os"
@ -29,7 +29,7 @@ func (r *runtime) runPublish(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("publish takes no positional arguments"))
return usageErr(errors.New("publish takes no positional arguments"))
}
opts, err := shareOptionsFromFlags(*repoPath, *remote, *branch)
if err != nil {
@ -100,7 +100,7 @@ func (r *runtime) runSubscribe(args []string) error {
}
remote := defaultShareRemote
if fs.NArg() > 1 {
return usageErr(fmt.Errorf("subscribe takes at most one remote"))
return usageErr(errors.New("subscribe takes at most one remote"))
}
if fs.NArg() == 1 {
remote = fs.Arg(0)
@ -172,7 +172,7 @@ func (r *runtime) runUpdate(args []string) error {
return usageErr(err)
}
if fs.NArg() != 0 {
return usageErr(fmt.Errorf("update takes no positional arguments"))
return usageErr(errors.New("update takes no positional arguments"))
}
opts, err := shareOptionsFromFlags(*repoPath, *remote, *branch)
if err != nil {

View File

@ -131,7 +131,7 @@ func Default() Config {
Concurrency: defaultSyncConcurrency(),
RepairEvery: "6h",
FullHistory: true,
AttachmentText: boolPtr(true),
AttachmentText: new(true),
},
Search: SearchConfig{
DefaultMode: "fts",
@ -262,7 +262,7 @@ func (c *Config) Normalize() error {
c.Sync.RepairEvery = "6h"
}
if c.Sync.AttachmentText == nil {
c.Sync.AttachmentText = boolPtr(true)
c.Sync.AttachmentText = new(true)
}
if c.Search.DefaultMode == "" {
c.Search.DefaultMode = "fts"
@ -497,10 +497,6 @@ func normalizeAccount(account string) string {
return account
}
func boolPtr(value bool) *bool {
return &value
}
func mapKeys[V any](m map[string]V) []string {
keys := make([]string, 0, len(m))
for key := range m {

View File

@ -249,7 +249,7 @@ func TestAttachmentTextExplicitFalseSurvivesNormalize(t *testing.T) {
t.Parallel()
cfg := Default()
cfg.Sync.AttachmentText = boolPtr(false)
cfg.Sync.AttachmentText = new(false)
require.NoError(t, cfg.Normalize())
require.False(t, cfg.AttachmentTextEnabled())
}

View File

@ -2,6 +2,7 @@ package discord
import (
"context"
"errors"
"fmt"
"runtime"
"slices"
@ -179,7 +180,7 @@ func (c *Client) ChannelMessage(ctx context.Context, channelID, messageID string
func (c *Client) Tail(ctx context.Context, handler EventHandler) error {
if handler == nil {
return fmt.Errorf("missing event handler")
return errors.New("missing event handler")
}
tailCtx, cancel := context.WithCancel(ctx)
defer cancel()
@ -187,10 +188,8 @@ func (c *Client) Tail(ctx context.Context, handler EventHandler) error {
errCh := make(chan error, 1)
workCh := make(chan func(context.Context) error, c.tailQueueSize)
var wg sync.WaitGroup
for i := 0; i < c.tailWorkerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for range c.tailWorkerCount {
wg.Go(func() {
for {
select {
case <-tailCtx.Done():
@ -207,7 +206,7 @@ func (c *Client) Tail(ctx context.Context, handler EventHandler) error {
}
}
}
}()
})
}
c.session.AddHandler(func(_ *discordgo.Session, evt *discordgo.MessageCreate) {
@ -299,7 +298,7 @@ func (c *Client) enqueueTailTask(
case workCh <- task:
default:
select {
case errCh <- fmt.Errorf("tail worker queue full"):
case errCh <- errors.New("tail worker queue full"):
default:
}
}

View File

@ -400,7 +400,7 @@ func TestTailFailsFastWhenWorkerQueueFills(t *testing.T) {
return
}
now := time.Now().UTC().Format(time.RFC3339)
for i := 0; i < 4; i++ {
for i := range 4 {
if err := conn.WriteJSON(map[string]any{
"op": 0,
"t": "MESSAGE_CREATE",

View File

@ -467,7 +467,7 @@ func withRawGuildID(rawJSON, guildID string) string {
func extractGzipPayloads(data []byte, maxBytes int64) [][]byte {
var out [][]byte
for offset := 0; offset < len(data)-1; offset++ {
for offset := range len(data) - 1 {
if data[offset] != 0x1f || data[offset+1] != 0x8b {
continue
}
@ -688,24 +688,24 @@ func messageReferenceID(raw map[string]any) string {
}
func syntheticGuild(id, name string) store.GuildRecord {
raw, _ := json.Marshal(map[string]any{
raw := marshalJSONString(map[string]any{
"id": id,
"name": name,
"source": "discord_desktop",
})
return store.GuildRecord{ID: id, Name: name, RawJSON: string(raw)}
}, "{}")
return store.GuildRecord{ID: id, Name: name, RawJSON: raw}
}
func syntheticChannel(id, guildID, name string) store.ChannelRecord {
if name == "" {
name = "channel-" + shortID(id)
}
raw, _ := json.Marshal(map[string]any{
raw := marshalJSONString(map[string]any{
"id": id,
"guild_id": guildID,
"name": name,
"source": "discord_desktop",
})
}, "{}")
kind := "text"
if guildID == DirectMessageGuildID {
kind = "dm"
@ -713,7 +713,7 @@ func syntheticChannel(id, guildID, name string) store.ChannelRecord {
kind = "group_dm"
}
}
return store.ChannelRecord{ID: id, GuildID: guildID, Kind: kind, Name: name, RawJSON: string(raw)}
return store.ChannelRecord{ID: id, GuildID: guildID, Kind: kind, Name: name, RawJSON: raw}
}
func guildName(id string) string {
@ -751,15 +751,14 @@ func kindForChannelType(typeValue int, dm bool) string {
}
func channelRawJSON(raw map[string]any, id, guildID, name, kind string) string {
body, _ := json.Marshal(map[string]any{
return marshalJSONString(map[string]any{
"id": id,
"guild_id": guildID,
"name": name,
"kind": kind,
"source": "discord_desktop",
"type": raw["type"],
})
return string(body)
}, "{}")
}
func messageRawJSON(raw map[string]any, id, guildID, channelID, authorID string) string {
@ -780,8 +779,7 @@ func messageRawJSON(raw map[string]any, id, guildID, channelID, authorID string)
if author := sanitizedRawAuthor(raw, authorID); len(author) > 0 {
payload["author"] = author
}
body, _ := json.Marshal(payload)
return string(body)
return marshalJSONString(payload, "{}")
}
func recipientLabel(items []any) string {
@ -929,3 +927,11 @@ func mapValues[M ~map[string]T, T any](m M) []T {
}
return out
}
func marshalJSONString(value any, fallback string) string {
raw, err := json.Marshal(value)
if err != nil {
return fallback
}
return string(raw)
}

View File

@ -80,7 +80,7 @@ type EmbeddingManifest struct {
func EnsureRepo(ctx context.Context, opts Options) error {
if strings.TrimSpace(opts.RepoPath) == "" {
return fmt.Errorf("share repo path is empty")
return errors.New("share repo path is empty")
}
if _, err := os.Stat(filepath.Join(opts.RepoPath, ".git")); err == nil {
return nil
@ -477,7 +477,7 @@ func exportEmbeddings(ctx context.Context, db *sql.DB, opts Options) (EmbeddingM
inputVersion = store.EmbeddingInputVersion
}
if provider == "" || model == "" {
return EmbeddingManifest{}, fmt.Errorf("embedding provider and model are required")
return EmbeddingManifest{}, errors.New("embedding provider and model are required")
}
relDir := filepath.ToSlash(filepath.Join("embeddings", safePathSegment(provider), safePathSegment(model), safePathSegment(inputVersion)))
if err := os.RemoveAll(filepath.Join(opts.RepoPath, "embeddings")); err != nil {

View File

@ -18,8 +18,8 @@ type DirectMessageConversationRow struct {
Name string `json:"name"`
MessageCount int `json:"message_count"`
AuthorCount int `json:"author_count"`
FirstMessageAt time.Time `json:"first_message_at,omitempty"`
LastMessageAt time.Time `json:"last_message_at,omitempty"`
FirstMessageAt time.Time `json:"first_message_at,omitzero"`
LastMessageAt time.Time `json:"last_message_at,omitzero"`
}
func (s *Store) DirectMessageConversations(ctx context.Context, opts DirectMessageConversationOptions) ([]DirectMessageConversationRow, error) {

View File

@ -3,6 +3,7 @@ package store
import (
"encoding/json"
"net/url"
"slices"
"sort"
"strconv"
"strings"
@ -147,10 +148,8 @@ func shouldIgnoreProfileValue(key, value string) bool {
}
func appendUnique(items []string, value string) []string {
for _, item := range items {
if item == value {
return items
}
if slices.Contains(items, value) {
return items
}
return append(items, value)
}

View File

@ -13,8 +13,8 @@ type MemberProfile struct {
Member MemberRow `json:"member"`
RawJSON string `json:"raw_json,omitempty"`
MessageCount int `json:"message_count"`
FirstMessageAt time.Time `json:"first_message_at,omitempty"`
LastMessageAt time.Time `json:"last_message_at,omitempty"`
FirstMessageAt time.Time `json:"first_message_at,omitzero"`
LastMessageAt time.Time `json:"last_message_at,omitzero"`
RecentMessages []MessageRow `json:"recent_messages,omitempty"`
}

View File

@ -705,10 +705,10 @@ func (s *Store) Status(ctx context.Context, dbPath, defaultGuildID string) (Stat
func (s *Store) ReadOnlyQuery(ctx context.Context, query string) ([]string, [][]string, error) {
query = strings.TrimSpace(query)
if query == "" {
return nil, nil, fmt.Errorf("empty query")
return nil, nil, errors.New("empty query")
}
if !IsReadOnlySQL(query) {
return nil, nil, fmt.Errorf("only read-only sql is allowed")
return nil, nil, errors.New("only read-only sql is allowed")
}
db, closeFn, err := s.openReadOnlyDB()
if err != nil {
@ -723,7 +723,7 @@ func (s *Store) ReadOnlyQuery(ctx context.Context, query string) ([]string, [][]
func (s *Store) Query(ctx context.Context, query string) ([]string, [][]string, error) {
query = strings.TrimSpace(query)
if query == "" {
return nil, nil, fmt.Errorf("empty query")
return nil, nil, errors.New("empty query")
}
return queryRows(ctx, s.db, query)
}
@ -731,7 +731,7 @@ func (s *Store) Query(ctx context.Context, query string) ([]string, [][]string,
func (s *Store) Exec(ctx context.Context, query string) (int64, error) {
query = strings.TrimSpace(query)
if query == "" {
return 0, fmt.Errorf("empty query")
return 0, errors.New("empty query")
}
queryCtx, cancel := withQueryTimeout(ctx)
defer cancel()
@ -762,7 +762,7 @@ func queryRows(ctx context.Context, db *sql.DB, query string) ([]string, [][]str
return nil, nil, err
}
if len(cols) == 0 {
return nil, nil, fmt.Errorf("query returned no columns")
return nil, nil, errors.New("query returned no columns")
}
var out [][]string

View File

@ -34,8 +34,8 @@ type Status struct {
MessageCount int `json:"message_count"`
MemberCount int `json:"member_count"`
EmbeddingBacklog int `json:"embedding_backlog"`
LastSyncAt time.Time `json:"last_sync_at,omitempty"`
LastTailEventAt time.Time `json:"last_tail_event_at,omitempty"`
LastSyncAt time.Time `json:"last_sync_at,omitzero"`
LastTailEventAt time.Time `json:"last_tail_event_at,omitzero"`
DefaultGuildID string `json:"default_guild_id,omitempty"`
DefaultGuildName string `json:"default_guild_name,omitempty"`
AccessibleGuildIDs []string `json:"accessible_guild_ids,omitempty"`
@ -86,7 +86,7 @@ type MemberRow struct {
Avatar string `json:"avatar,omitempty"`
RoleIDsJSON string `json:"role_ids_json"`
Bot bool `json:"bot"`
JoinedAt time.Time `json:"joined_at,omitempty"`
JoinedAt time.Time `json:"joined_at,omitzero"`
Bio string `json:"bio,omitempty"`
Pronouns string `json:"pronouns,omitempty"`
Location string `json:"location,omitempty"`
@ -110,7 +110,7 @@ type ChannelRow struct {
IsLocked bool `json:"is_locked"`
IsPrivateThread bool `json:"is_private_thread"`
ThreadParentID string `json:"thread_parent_id,omitempty"`
ArchiveTimestamp time.Time `json:"archive_timestamp,omitempty"`
ArchiveTimestamp time.Time `json:"archive_timestamp,omitzero"`
}
func Open(ctx context.Context, path string) (*Store, error) {

View File

@ -603,7 +603,7 @@ func TestSearchMessagesSemanticScoresOlderMatchesBeyondRecentWindow(t *testing.T
}))
require.NoError(t, insertTestEmbedding(ctx, s, "old-best", "ollama", "nomic-embed-text", []float32{1, 0}))
for i := 0; i < searchCandidateFloor+10; i++ {
for i := range searchCandidateFloor + 10 {
messageID := "newer-weak-" + strconv.Itoa(i)
require.NoError(t, s.UpsertMessage(ctx, MessageRecord{
ID: messageID,

View File

@ -586,7 +586,7 @@ func TestConcurrentMessageUpsertsShareSingleWriter(t *testing.T) {
var wg sync.WaitGroup
errCh := make(chan error, 8)
for i := 0; i < 8; i++ {
for i := range 8 {
wg.Add(1)
go func(i int) {
defer wg.Done()

View File

@ -2,7 +2,7 @@ package syncer
import (
"context"
"fmt"
"errors"
"path/filepath"
"testing"
"time"
@ -14,7 +14,7 @@ import (
)
func errMissingAccess() error {
return fmt.Errorf("HTTP 403 Forbidden, {\"message\": \"Missing Access\", \"code\": 50001}")
return errors.New("HTTP 403 Forbidden, {\"message\": \"Missing Access\", \"code\": 50001}")
}
func TestActiveThreadCatalogEdges(t *testing.T) {
@ -34,7 +34,7 @@ func TestActiveThreadCatalogEdges(t *testing.T) {
require.NoError(t, svc.appendActiveThreadCatalog(ctx, allChannels, "g1", []string{"forum"}))
require.Equal(t, 1, client.guildThreadCalls)
client.guildThreadErrs = map[string]error{"g1": fmt.Errorf("boom")}
client.guildThreadErrs = map[string]error{"g1": errors.New("boom")}
require.ErrorContains(t, svc.appendActiveThreadCatalog(ctx, allChannels, "g1", []string{"forum"}), "boom")
client.guildThreadErrs = nil

View File

@ -121,9 +121,7 @@ func (s *Syncer) syncMessageChannelsConcurrent(
var wg sync.WaitGroup
for range workers {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
for channel := range jobs {
if ctx.Err() != nil {
return
@ -151,7 +149,7 @@ func (s *Syncer) syncMessageChannelsConcurrent(
return
}
}
}()
})
}
go func() {

View File

@ -3,7 +3,7 @@ package syncer
import (
"bytes"
"context"
"fmt"
"errors"
"io"
"log/slog"
"strings"
@ -63,9 +63,9 @@ func TestMessageSyncProgressFinishReportsSummaryCounts(t *testing.T) {
third := &discordgo.Channel{ID: "c3", Name: "ok"}
progress.start(first)
progress.recordSkip(first, fmt.Errorf(`HTTP 403 Forbidden, {"message": "Missing Access", "code": 50001}`))
progress.recordSkip(first, errors.New(`HTTP 403 Forbidden, {"message": "Missing Access", "code": 50001}`))
progress.start(second)
progress.recordSkip(second, fmt.Errorf(`HTTP 404 Not Found, {"message": "Unknown Channel", "code": 10003}`))
progress.recordSkip(second, errors.New(`HTTP 404 Not Found, {"message": "Unknown Channel", "code": 10003}`))
progress.start(third)
progress.record(third, 42)
progress.finish(nil)

View File

@ -13,8 +13,8 @@ import (
)
func toMemberRecord(guildID string, member *discordgo.Member) store.MemberRecord {
raw, _ := json.Marshal(member)
roles, _ := json.Marshal(member.Roles)
raw := marshalJSONString(member, "{}")
roles := marshalJSONString(member.Roles, "[]")
return store.MemberRecord{
GuildID: guildID,
UserID: member.User.ID,
@ -26,13 +26,13 @@ func toMemberRecord(guildID string, member *discordgo.Member) store.MemberRecord
Avatar: member.Avatar,
Bot: member.User.Bot,
JoinedAt: member.JoinedAt.Format(time.RFC3339Nano),
RoleIDsJSON: string(roles),
RawJSON: string(raw),
RoleIDsJSON: roles,
RawJSON: raw,
}
}
func toMessageRecord(message *discordgo.Message, channelName, normalizedContent string) store.MessageRecord {
raw, _ := json.Marshal(message)
raw := marshalJSONString(message, "{}")
authorID := ""
authorName := ""
if message.Author != nil {
@ -65,10 +65,18 @@ func toMessageRecord(message *discordgo.Message, channelName, normalizedContent
ReplyToMessageID: replyTo,
Pinned: message.Pinned,
HasAttachments: len(message.Attachments) > 0,
RawJSON: string(raw),
RawJSON: raw,
}
}
func marshalJSONString(value any, fallback string) string {
raw, err := json.Marshal(value)
if err != nil {
return fallback
}
return string(raw)
}
func normalizeMessage(message *discordgo.Message) string {
return normalizeMessageParts(message, nil)
}

View File

@ -2,7 +2,6 @@ package syncer
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
@ -124,12 +123,11 @@ func (s *Syncer) syncGuild(ctx context.Context, guildID string, opts SyncOptions
if err != nil {
return SyncStats{}, fmt.Errorf("fetch guild %s: %w", guildID, err)
}
rawGuild, _ := json.Marshal(guild)
if err := s.store.UpsertGuild(ctx, store.GuildRecord{
ID: guild.ID,
Name: guild.Name,
Icon: guild.Icon,
RawJSON: string(rawGuild),
RawJSON: marshalJSONString(guild, "{}"),
}); err != nil {
return SyncStats{}, err
}
@ -160,8 +158,7 @@ func (s *Syncer) syncGuild(ctx context.Context, guildID string, opts SyncOptions
return stats, err
}
for _, channel := range channelList {
raw, _ := json.Marshal(channel)
record := toChannelRecord(channel, string(raw))
record := toChannelRecord(channel, marshalJSONString(channel, "{}"))
if err := s.store.UpsertChannel(ctx, record); err != nil {
return stats, err
}
@ -204,10 +201,7 @@ func (s *Syncer) syncGuildIncompleteBatches(ctx context.Context, guildID string,
}
stats := SyncStats{}
for start := 0; start < len(incomplete); start += fullSyncBatchSize {
end := start + fullSyncBatchSize
if end > len(incomplete) {
end = len(incomplete)
}
end := min(start+fullSyncBatchSize, len(incomplete))
batchOpts := opts
batchOpts.ChannelIDs = incomplete[start:end]
one, err := s.syncGuild(ctx, guildID, batchOpts)

View File

@ -3,7 +3,6 @@ package syncer
import (
"context"
"errors"
"fmt"
"path/filepath"
"testing"
"time"
@ -186,16 +185,16 @@ func TestHelpers(t *testing.T) {
require.Equal(t, "Nick", displayName(&discordgo.Member{Nick: "Nick", User: &discordgo.User{Username: "user"}}))
require.Equal(t, "Global", displayName(&discordgo.Member{User: &discordgo.User{GlobalName: "Global", Username: "user"}}))
require.Equal(t, "user", displayName(&discordgo.Member{User: &discordgo.User{Username: "user"}}))
require.True(t, isMissingAccess(fmt.Errorf("HTTP 403 Forbidden")))
require.True(t, isMissingAccess(fmt.Errorf("Missing Access")))
require.False(t, isMissingAccess(fmt.Errorf("boom")))
require.Equal(t, "missing_access", unavailableReason(fmt.Errorf("HTTP 403 Forbidden")))
require.Equal(t, "unknown_channel", unavailableReason(fmt.Errorf("HTTP 404 Not Found, {\"message\": \"Unknown Channel\", \"code\": 10003}")))
require.True(t, isUnknownChannel(fmt.Errorf("Unknown Channel")))
require.False(t, isUnknownChannel(fmt.Errorf("boom")))
require.True(t, isMissingAccess(errors.New("HTTP 403 Forbidden")))
require.True(t, isMissingAccess(errors.New("Missing Access")))
require.False(t, isMissingAccess(errors.New("boom")))
require.Equal(t, "missing_access", unavailableReason(errors.New("HTTP 403 Forbidden")))
require.Equal(t, "unknown_channel", unavailableReason(errors.New("HTTP 404 Not Found, {\"message\": \"Unknown Channel\", \"code\": 10003}")))
require.True(t, isUnknownChannel(errors.New("Unknown Channel")))
require.False(t, isUnknownChannel(errors.New("boom")))
require.True(t, isRetryableSyncError(context.Background(), context.DeadlineExceeded))
require.True(t, isRetryableSyncError(context.Background(), fmt.Errorf("HTTP 503 Service Unavailable")))
require.True(t, isRetryableSyncError(context.Background(), fmt.Errorf("stream error: stream ID 1; INTERNAL_ERROR")))
require.True(t, isRetryableSyncError(context.Background(), errors.New("HTTP 503 Service Unavailable")))
require.True(t, isRetryableSyncError(context.Background(), errors.New("stream error: stream ID 1; INTERNAL_ERROR")))
require.False(t, isRetryableSyncError(context.Background(), context.Canceled))
canceledCtx, cancel := context.WithCancel(context.Background())
cancel()

View File

@ -2,7 +2,7 @@ package syncer
import (
"context"
"fmt"
"errors"
"path/filepath"
"sync"
"testing"
@ -777,7 +777,7 @@ func TestSyncSkipsMissingAccessChannels(t *testing.T) {
"c1": {{ID: "10", GuildID: "g1", ChannelID: "c1", Content: "ok", Timestamp: time.Now().UTC(), Author: &discordgo.User{ID: "u1", Username: "user"}}},
},
messageErrors: map[string]error{
"c2": fmt.Errorf("HTTP 403 Forbidden, {\"message\": \"Missing Access\", \"code\": 50001}"),
"c2": errors.New("HTTP 403 Forbidden, {\"message\": \"Missing Access\", \"code\": 50001}"),
},
}
@ -814,7 +814,7 @@ func TestSyncSkipsUnknownChannels(t *testing.T) {
"c1": {{ID: "10", GuildID: "g1", ChannelID: "c1", Content: "ok", Timestamp: time.Now().UTC(), Author: &discordgo.User{ID: "u1", Username: "user"}}},
},
messageErrors: map[string]error{
"c2": fmt.Errorf("HTTP 404 Not Found, {\"message\": \"Unknown Channel\", \"code\": 10003}"),
"c2": errors.New("HTTP 404 Not Found, {\"message\": \"Unknown Channel\", \"code\": 10003}"),
},
}

View File

@ -2,7 +2,6 @@ package syncer
import (
"context"
"encoding/json"
"time"
"github.com/bwmarrin/discordgo"
@ -97,8 +96,7 @@ func (t *tailHandler) OnChannelUpsert(ctx context.Context, channel *discordgo.Ch
if !t.allowGuild(channel.GuildID) {
return nil
}
raw, _ := json.Marshal(channel)
return t.store.UpsertChannel(ctx, toChannelRecord(channel, string(raw)))
return t.store.UpsertChannel(ctx, toChannelRecord(channel, marshalJSONString(channel, "{}")))
}
func (t *tailHandler) OnMemberUpsert(ctx context.Context, guildID string, member *discordgo.Member) error {