Skip to content

Commit e44408c

Browse files
authored
ratelimit (#21)
1 parent 2f89eb8 commit e44408c

File tree

7 files changed

+89
-13
lines changed

7 files changed

+89
-13
lines changed

cmd/bot/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ package main
33
import (
44
_ "first.fm/internal/commands/fm"
55
_ "first.fm/internal/commands/profile"
6+
_ "first.fm/internal/commands/register"
67
_ "first.fm/internal/commands/stats"
78
)

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ go 1.25.0
55
require (
66
github.com/disgoorg/disgo v0.19.0-rc.6.0.20250924005456-3274c76733fc
77
github.com/disgoorg/snowflake/v2 v2.0.3
8+
github.com/mattn/go-sqlite3 v1.14.32
9+
golang.org/x/time v0.13.0
810
)
911

1012
require (
1113
github.com/disgoorg/json/v2 v2.0.0 // indirect
1214
github.com/disgoorg/omit v1.0.0 // indirect
1315
github.com/gorilla/websocket v1.5.3 // indirect
14-
github.com/mattn/go-sqlite3 v1.14.32 // indirect
1516
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
1617
golang.org/x/crypto v0.39.0 // indirect
1718
golang.org/x/sys v0.33.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
2222
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
2323
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
2424
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
25+
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
26+
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
2527
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2628
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/bot/commands.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package bot
33
import (
44
"context"
55
"strings"
6+
"sync"
67
"time"
78

9+
"first.fm/internal/emojis"
810
"first.fm/internal/lastfm"
911
"first.fm/internal/logger"
1012
"github.com/disgoorg/disgo/discord"
@@ -23,9 +25,15 @@ type CommandHandler func(*CommandContext) error
2325
var (
2426
allCommands []discord.ApplicationCommandCreate
2527
registry = map[string]CommandHandler{}
28+
initOnce sync.Once
2629
)
2730

2831
func Register(meta discord.ApplicationCommandCreate, handler CommandHandler) {
32+
initOnce.Do(func() {
33+
allCommands = []discord.ApplicationCommandCreate{}
34+
registry = map[string]CommandHandler{}
35+
})
36+
2937
logger.Infow("registered command", logger.F{"name": meta.CommandName()})
3038
allCommands = append(allCommands, meta)
3139
registry[meta.CommandName()] = handler
@@ -58,9 +66,9 @@ func Dispatcher(bot *Bot) func(*events.ApplicationCommandInteractionCreate) {
5866
}
5967

6068
if err := handler(ctx); err != nil {
61-
logger.Errorw("command failed", logger.F{"name": data.CommandName(), "err": err.Error()})
62-
_ = event.CreateMessage(discord.NewMessageCreateBuilder().
63-
SetContent("error: " + err.Error()).
69+
logger.Warnw("command failed", logger.F{"name": data.CommandName(), "err": err.Error()})
70+
_ = ctx.CreateMessage(discord.NewMessageCreateBuilder().
71+
SetContentf("%s %v", emojis.EmojiCross, err).
6472
SetEphemeral(true).
6573
Build())
6674
}

internal/bot/events.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,10 @@ import (
1010

1111
func onReady(event *events.Ready) {
1212
logger.Info("started client")
13-
event.Client().SetPresence(context.Background(), gateway.WithCustomActivity("gwa gwa"))
13+
event.
14+
Client().
15+
SetPresence(
16+
context.Background(),
17+
gateway.WithCustomActivity("listen to crystal castles!"),
18+
)
1419
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package register
2+
3+
import (
4+
"errors"
5+
"strings"
6+
7+
"first.fm/internal/bot"
8+
"first.fm/internal/persistence/sqlc"
9+
"github.com/disgoorg/disgo/discord"
10+
)
11+
12+
func init() {
13+
bot.Register(data, handle)
14+
}
15+
16+
var data = discord.SlashCommandCreate{
17+
Name: "register",
18+
Description: "link your last.fm username",
19+
Options: []discord.ApplicationCommandOption{
20+
discord.ApplicationCommandOptionString{
21+
Name: "username",
22+
Description: "your last.fm username",
23+
Required: true,
24+
},
25+
},
26+
}
27+
28+
func handle(ctx *bot.CommandContext) error {
29+
username := ctx.SlashCommandInteractionData().String("username")
30+
31+
_, err := ctx.LastFM.User.Info(username)
32+
if err != nil {
33+
return errors.New("last.fm user not found")
34+
}
35+
36+
err = ctx.Queries.UpsertUser(ctx.Ctx, sqlc.UpsertUserParams{
37+
UserID: ctx.User().ID,
38+
LastfmUsername: username,
39+
})
40+
if err != nil {
41+
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
42+
return errors.New("another discord user already uses this username")
43+
}
44+
45+
return err
46+
}
47+
48+
return ctx.CreateMessage(discord.NewMessageCreateBuilder().
49+
SetContentf("successfully linked your account to **%s**", username).
50+
Build())
51+
}

internal/lastfm/api/api.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"context"
45
"encoding/xml"
56
"errors"
67
"fmt"
@@ -11,6 +12,7 @@ import (
1112
"time"
1213

1314
"first.fm/internal/lastfm"
15+
"golang.org/x/time/rate"
1416
)
1517

1618
var (
@@ -37,10 +39,11 @@ type HTTPClient interface {
3739
}
3840

3941
type API struct {
40-
APIKey string
41-
UserAgent string
42-
Retries uint
43-
Client HTTPClient
42+
APIKey string
43+
UserAgent string
44+
Retries uint
45+
Client HTTPClient
46+
rateLimiter *rate.Limiter
4447
}
4548

4649
func New(apiKey string) *API {
@@ -50,10 +53,11 @@ func New(apiKey string) *API {
5053
func NewWithTimeout(apiKey string, timeout int) *API {
5154
t := time.Duration(timeout) * time.Second
5255
return &API{
53-
APIKey: apiKey,
54-
UserAgent: DefaultUserAgent,
55-
Retries: DefaultRetries,
56-
Client: &http.Client{Timeout: t},
56+
APIKey: apiKey,
57+
UserAgent: DefaultUserAgent,
58+
Retries: DefaultRetries,
59+
Client: &http.Client{Timeout: t},
60+
rateLimiter: rate.NewLimiter(rate.Every(time.Second), 5),
5761
}
5862
}
5963

@@ -110,6 +114,10 @@ func (a API) PostBody(dest any, url, body string) error {
110114
}
111115

112116
func (a API) tryRequest(dest any, method, url, body string) error {
117+
if err := a.rateLimiter.Wait(context.Background()); err != nil {
118+
return err
119+
}
120+
113121
var (
114122
res *http.Response
115123
lfm LFMWrapper

0 commit comments

Comments
 (0)