2024-01-10 06:12:10 +05:30
|
|
|
package mojang
|
|
|
|
|
|
|
|
import (
|
2024-02-07 06:06:18 +05:30
|
|
|
"context"
|
2024-01-10 06:12:10 +05:30
|
|
|
"errors"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/brunomvsouza/singleflight"
|
2024-02-19 18:24:12 +05:30
|
|
|
"go.opentelemetry.io/otel/metric"
|
|
|
|
"go.uber.org/multierr"
|
2024-03-13 05:59:26 +05:30
|
|
|
|
|
|
|
"ely.by/chrly/internal/otel"
|
2024-01-10 06:12:10 +05:30
|
|
|
)
|
|
|
|
|
2024-02-19 18:24:12 +05:30
|
|
|
const ScopeName = "ely.by/chrly/internal/mojang"
|
|
|
|
|
2024-01-10 06:12:10 +05:30
|
|
|
var InvalidUsername = errors.New("the username passed doesn't meet Mojang's requirements")
|
|
|
|
|
|
|
|
// https://help.minecraft.net/hc/en-us/articles/4408950195341#h_01GE5JX1Z0CZ833A7S54Y195KV
|
|
|
|
var allowedUsernamesRegex = regexp.MustCompile(`(?i)^[0-9a-z_]{3,16}$`)
|
|
|
|
|
|
|
|
type UuidsProvider interface {
|
2024-02-07 06:06:18 +05:30
|
|
|
GetUuid(ctx context.Context, username string) (*ProfileInfo, error)
|
2024-01-10 06:12:10 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
type TexturesProvider interface {
|
2024-02-07 06:06:18 +05:30
|
|
|
GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error)
|
2024-01-10 06:12:10 +05:30
|
|
|
}
|
|
|
|
|
2024-02-19 18:24:12 +05:30
|
|
|
func NewMojangTexturesProvider(
|
|
|
|
uuidsProvider UuidsProvider,
|
|
|
|
texturesProvider TexturesProvider,
|
|
|
|
) (*MojangTexturesProvider, error) {
|
2024-03-13 05:59:26 +05:30
|
|
|
meter, err := newProviderMetrics(otel.GetMeter())
|
2024-02-19 18:24:12 +05:30
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &MojangTexturesProvider{
|
|
|
|
UuidsProvider: uuidsProvider,
|
|
|
|
TexturesProvider: texturesProvider,
|
|
|
|
metrics: meter,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-01-10 06:12:10 +05:30
|
|
|
type MojangTexturesProvider struct {
|
|
|
|
UuidsProvider
|
|
|
|
TexturesProvider
|
|
|
|
|
2024-02-19 18:24:12 +05:30
|
|
|
metrics *providerMetrics
|
|
|
|
group singleflight.Group[string, *ProfileResponse]
|
2024-01-10 06:12:10 +05:30
|
|
|
}
|
|
|
|
|
2024-02-07 06:06:18 +05:30
|
|
|
func (p *MojangTexturesProvider) GetForUsername(ctx context.Context, username string) (*ProfileResponse, error) {
|
2024-01-10 06:12:10 +05:30
|
|
|
if !allowedUsernamesRegex.MatchString(username) {
|
|
|
|
return nil, InvalidUsername
|
|
|
|
}
|
|
|
|
|
|
|
|
username = strings.ToLower(username)
|
|
|
|
|
2024-02-19 18:24:12 +05:30
|
|
|
result, err, shared := p.group.Do(username, func() (*ProfileResponse, error) {
|
2024-03-05 19:44:10 +05:30
|
|
|
var profile *ProfileInfo
|
|
|
|
var textures *ProfileResponse
|
|
|
|
var err error
|
|
|
|
|
|
|
|
defer p.recordMetrics(ctx, profile, textures, err)
|
|
|
|
|
|
|
|
profile, err = p.UuidsProvider.GetUuid(ctx, username)
|
2024-01-10 06:12:10 +05:30
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if profile == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2024-03-05 19:44:10 +05:30
|
|
|
textures, err = p.TexturesProvider.GetTextures(ctx, profile.Id)
|
2024-01-10 06:12:10 +05:30
|
|
|
|
2024-03-05 19:44:10 +05:30
|
|
|
return textures, err
|
|
|
|
})
|
2024-01-10 06:12:10 +05:30
|
|
|
|
2024-02-19 18:24:12 +05:30
|
|
|
if shared {
|
|
|
|
p.metrics.Shared.Add(ctx, 1)
|
|
|
|
}
|
|
|
|
|
2024-03-05 19:44:10 +05:30
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *MojangTexturesProvider) recordMetrics(ctx context.Context, profile *ProfileInfo, textures *ProfileResponse, err error) {
|
2024-02-19 18:24:12 +05:30
|
|
|
if err != nil {
|
|
|
|
p.metrics.Failed.Add(ctx, 1)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-05 19:44:10 +05:30
|
|
|
if profile == nil {
|
|
|
|
p.metrics.UsernameMissed.Add(ctx, 1)
|
|
|
|
p.metrics.TextureMissed.Add(ctx, 1)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.metrics.UsernameFound.Add(ctx, 1)
|
|
|
|
if textures != nil {
|
|
|
|
p.metrics.TextureFound.Add(ctx, 1)
|
2024-02-19 18:24:12 +05:30
|
|
|
} else {
|
2024-03-05 19:44:10 +05:30
|
|
|
p.metrics.TextureMissed.Add(ctx, 1)
|
2024-02-19 18:24:12 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-10 06:12:10 +05:30
|
|
|
type NilProvider struct {
|
|
|
|
}
|
|
|
|
|
2024-02-07 06:06:18 +05:30
|
|
|
func (*NilProvider) GetForUsername(ctx context.Context, username string) (*ProfileResponse, error) {
|
2024-01-10 06:12:10 +05:30
|
|
|
return nil, nil
|
|
|
|
}
|
2024-02-19 18:24:12 +05:30
|
|
|
|
|
|
|
func newProviderMetrics(meter metric.Meter) (*providerMetrics, error) {
|
|
|
|
m := &providerMetrics{}
|
|
|
|
var errors, err error
|
|
|
|
|
2024-03-05 19:44:10 +05:30
|
|
|
m.UsernameFound, err = meter.Int64Counter(
|
2024-03-13 05:59:26 +05:30
|
|
|
"mojang.provider.username_found",
|
2024-03-05 19:44:10 +05:30
|
|
|
metric.WithDescription("Number of queries for which username was found"),
|
|
|
|
metric.WithUnit("1"),
|
|
|
|
)
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
m.UsernameMissed, err = meter.Int64Counter(
|
2024-03-13 05:59:26 +05:30
|
|
|
"chrly.mojang.provider.username_missed",
|
2024-03-05 19:44:10 +05:30
|
|
|
metric.WithDescription("Number of queries for which username was not found"),
|
|
|
|
metric.WithUnit("1"),
|
|
|
|
)
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
m.TextureFound, err = meter.Int64Counter(
|
2024-03-13 05:59:26 +05:30
|
|
|
"chrly.mojang.provider.textures_found",
|
2024-03-05 19:44:10 +05:30
|
|
|
metric.WithDescription("Number of queries for which textures were successfully found"),
|
|
|
|
metric.WithUnit("1"),
|
2024-02-19 18:24:12 +05:30
|
|
|
)
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
2024-03-05 19:44:10 +05:30
|
|
|
m.TextureMissed, err = meter.Int64Counter(
|
2024-03-13 05:59:26 +05:30
|
|
|
"chrly.mojang.provider.textures_missed",
|
2024-03-05 19:44:10 +05:30
|
|
|
metric.WithDescription("Number of queries for which no textures were found"),
|
|
|
|
metric.WithUnit("1"),
|
2024-02-19 18:24:12 +05:30
|
|
|
)
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
m.Failed, err = meter.Int64Counter(
|
2024-03-13 05:59:26 +05:30
|
|
|
"chrly.mojang.provider.failed",
|
2024-03-05 19:44:10 +05:30
|
|
|
metric.WithDescription("Number of requests that ended in an error"),
|
|
|
|
metric.WithUnit("1"),
|
2024-02-19 18:24:12 +05:30
|
|
|
)
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
m.Shared, err = meter.Int64Counter(
|
2024-03-13 05:59:26 +05:30
|
|
|
"chrly.mojang.provider.singleflight.shared",
|
2024-03-05 19:44:10 +05:30
|
|
|
metric.WithDescription("Number of requests that are already being processed in another thread"),
|
|
|
|
metric.WithUnit("1"),
|
2024-02-19 18:24:12 +05:30
|
|
|
)
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
return m, errors
|
|
|
|
}
|
|
|
|
|
|
|
|
type providerMetrics struct {
|
2024-03-05 19:44:10 +05:30
|
|
|
UsernameFound metric.Int64Counter
|
|
|
|
UsernameMissed metric.Int64Counter
|
|
|
|
TextureFound metric.Int64Counter
|
|
|
|
TextureMissed metric.Int64Counter
|
|
|
|
Failed metric.Int64Counter
|
|
|
|
Shared metric.Int64Counter
|
2024-02-19 18:24:12 +05:30
|
|
|
}
|