Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve presence UX #196

Merged
merged 9 commits into from Sep 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion _examples/test/examplebot.go
Expand Up @@ -33,7 +33,7 @@ func main() {
client, err := disgo.New(token,
bot.WithGatewayConfigOpts(
gateway.WithIntents(gateway.IntentsNonPrivileged, gateway.IntentMessageContent),
gateway.WithPresence(gateway.NewListeningPresence("your bullshit", discord.OnlineStatusOnline, false)),
gateway.WithPresenceOpts(gateway.WithListeningActivity("your bullshit"), gateway.WithOnlineStatus(discord.OnlineStatusDND)),
),
bot.WithCacheConfigOpts(
cache.WithCacheFlags(cache.FlagsAll),
Expand Down
28 changes: 20 additions & 8 deletions bot/client.go
Expand Up @@ -91,11 +91,11 @@ type Client interface {
// limit : The number of discord.Member(s) to return.
RequestMembersWithQuery(ctx context.Context, guildID snowflake.ID, presence bool, nonce string, query string, limit int) error

// SetPresence sends a discord.MessageDataPresenceUpdate to the gateway.Gateway.
SetPresence(ctx context.Context, presenceUpdate gateway.MessageDataPresenceUpdate) error
// SetPresence sends new presence data to the gateway.Gateway.
SetPresence(ctx context.Context, opts ...gateway.PresenceOpt) error

// SetPresenceForShard sends a discord.MessageDataPresenceUpdate to the specific gateway.Gateway.
SetPresenceForShard(ctx context.Context, shardId int, presenceUpdate gateway.MessageDataPresenceUpdate) error
// SetPresenceForShard sends new presence data to the specific gateway.Gateway.
SetPresenceForShard(ctx context.Context, shardId int, opts ...gateway.PresenceOpt) error

// MemberChunkingManager returns the MemberChunkingManager used by the Client.
MemberChunkingManager() MemberChunkingManager
Expand Down Expand Up @@ -276,22 +276,34 @@ func (c *clientImpl) RequestMembersWithQuery(ctx context.Context, guildID snowfl
})
}

func (c *clientImpl) SetPresence(ctx context.Context, presenceUpdate gateway.MessageDataPresenceUpdate) error {
func (c *clientImpl) SetPresence(ctx context.Context, opts ...gateway.PresenceOpt) error {
if !c.HasGateway() {
return discord.ErrNoGateway
}
return c.gateway.Send(ctx, gateway.OpcodePresenceUpdate, presenceUpdate)
g := c.gateway
return g.Send(ctx, gateway.OpcodePresenceUpdate, applyPresenceFromOpts(g, opts...))
}

func (c *clientImpl) SetPresenceForShard(ctx context.Context, shardId int, presenceUpdate gateway.MessageDataPresenceUpdate) error {
func (c *clientImpl) SetPresenceForShard(ctx context.Context, shardId int, opts ...gateway.PresenceOpt) error {
if !c.HasShardManager() {
return discord.ErrNoShardManager
}
shard := c.shardManager.Shard(shardId)
if shard == nil {
return discord.ErrShardNotFound
}
return shard.Send(ctx, gateway.OpcodePresenceUpdate, presenceUpdate)
return shard.Send(ctx, gateway.OpcodePresenceUpdate, applyPresenceFromOpts(shard, opts...))
}

func applyPresenceFromOpts(g gateway.Gateway, opts ...gateway.PresenceOpt) gateway.MessageDataPresenceUpdate {
presenceUpdate := g.Presence()
if presenceUpdate == nil {
presenceUpdate = &gateway.MessageDataPresenceUpdate{}
}
for _, opt := range opts {
opt(presenceUpdate)
}
return *presenceUpdate
}

func (c *clientImpl) MemberChunkingManager() MemberChunkingManager {
Expand Down
3 changes: 3 additions & 0 deletions gateway/gateway.go
Expand Up @@ -100,4 +100,7 @@ type Gateway interface {
// Latency returns the latency of the Gateway.
// This is calculated by the time it takes to send a heartbeat and receive a heartbeat ack by discord.
Latency() time.Duration

// Presence returns the current presence of the Gateway.
Presence() *MessageDataPresenceUpdate
}
12 changes: 8 additions & 4 deletions gateway/gateway_config.go
Expand Up @@ -179,10 +179,14 @@ func WithRateRateLimiterConfigOpts(opts ...RateLimiterConfigOpt) ConfigOpt {
}
}

// WithPresence sets the initial presence the bot should display.
func WithPresence(presence MessageDataPresenceUpdate) ConfigOpt {
return func(config *Config) {
config.Presence = &presence
// WithPresenceOpts allows to pass initial presence data the bot should display.
func WithPresenceOpts(opts ...PresenceOpt) ConfigOpt {
return func(config *Config) {
presenceUpdate := &MessageDataPresenceUpdate{}
for _, opt := range opts {
opt(presenceUpdate)
}
config.Presence = presenceUpdate
}
}

Expand Down
4 changes: 4 additions & 0 deletions gateway/gateway_impl.go
Expand Up @@ -204,6 +204,10 @@ func (g *gatewayImpl) Latency() time.Duration {
return g.lastHeartbeatReceived.Sub(g.lastHeartbeatSent)
}

func (g *gatewayImpl) Presence() *MessageDataPresenceUpdate {
return g.config.Presence
}

func (g *gatewayImpl) reconnectTry(ctx context.Context, try int, delay time.Duration) error {
if try >= g.config.MaxReconnectTries-1 {
return fmt.Errorf("failed to reconnect. exceeded max reconnect tries of %d reached", g.config.MaxReconnectTries)
Expand Down
97 changes: 58 additions & 39 deletions gateway/gateway_messages.go
Expand Up @@ -2,7 +2,6 @@ package gateway

import (
"fmt"
"time"

"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/json"
Expand Down Expand Up @@ -437,57 +436,77 @@ type IdentifyCommandDataProperties struct {
Device string `json:"device"` // library name
}

// NewPresence creates a new Presence with the provided properties
func NewPresence(activityType discord.ActivityType, name string, url string, status discord.OnlineStatus, afk bool) MessageDataPresenceUpdate {
var since *int64
if status == discord.OnlineStatusIdle {
unix := time.Now().Unix()
since = &unix
}
type PresenceOpt func(presenceUpdate *MessageDataPresenceUpdate)

var activities []discord.Activity
if name != "" {
activity := discord.Activity{
Name: name,
Type: activityType,
}
if activityType == discord.ActivityTypeStreaming && url != "" {
activity.URL = &url
}
activities = append(activities, activity)
}
// WithPlayingActivity creates a new "Playing ..." activity of type discord.ActivityTypeGame
func WithPlayingActivity(name string) PresenceOpt {
topi314 marked this conversation as resolved.
Show resolved Hide resolved
return withActivity(discord.Activity{
Name: name,
Type: discord.ActivityTypeGame,
})
}

return MessageDataPresenceUpdate{
Since: since,
Activities: activities,
Status: status,
AFK: afk,
// WithStreamingActivity creates a new "Streaming ..." activity of type discord.ActivityTypeStreaming
func WithStreamingActivity(name string, url string) PresenceOpt {
activity := discord.Activity{
Name: name,
Type: discord.ActivityTypeStreaming,
}
if url != "" {
activity.URL = &url
}
return withActivity(activity)
}

// WithListeningActivity creates a new "Listening to ..." activity of type discord.ActivityTypeListening
func WithListeningActivity(name string) PresenceOpt {
return withActivity(discord.Activity{
Name: name,
Type: discord.ActivityTypeListening,
})
}

// NewGamePresence creates a new Presence of type ActivityTypeGame
func NewGamePresence(name string, status discord.OnlineStatus, afk bool) MessageDataPresenceUpdate {
return NewPresence(discord.ActivityTypeGame, name, "", status, afk)
// WithWatchingActivity creates a new "Watching ..." activity of type discord.ActivityTypeWatching
func WithWatchingActivity(name string) PresenceOpt {
return withActivity(discord.Activity{
Name: name,
Type: discord.ActivityTypeWatching,
})
}

// NewStreamingPresence creates a new Presence of type ActivityTypeStreaming
func NewStreamingPresence(name string, url string, status discord.OnlineStatus, afk bool) MessageDataPresenceUpdate {
return NewPresence(discord.ActivityTypeStreaming, name, url, status, afk)
// WithCompetingActivity creates a new "Competing in ..." activity of type discord.ActivityTypeCompeting
func WithCompetingActivity(name string) PresenceOpt {
return withActivity(discord.Activity{
Name: name,
Type: discord.ActivityTypeCompeting,
})
}

// NewListeningPresence creates a new Presence of type ActivityTypeListening
func NewListeningPresence(name string, status discord.OnlineStatus, afk bool) MessageDataPresenceUpdate {
return NewPresence(discord.ActivityTypeListening, name, "", status, afk)
func withActivity(activity discord.Activity) PresenceOpt {
return func(presence *MessageDataPresenceUpdate) {
presence.Activities = []discord.Activity{activity}
}
}

// WithOnlineStatus sets the online status to the provided discord.OnlineStatus
func WithOnlineStatus(status discord.OnlineStatus) PresenceOpt {
return func(presence *MessageDataPresenceUpdate) {
presence.Status = status
}
}

// NewWatchingPresence creates a new Presence of type ActivityTypeWatching
func NewWatchingPresence(name string, status discord.OnlineStatus, afk bool) MessageDataPresenceUpdate {
return NewPresence(discord.ActivityTypeWatching, name, "", status, afk)
// WithAfk sets whether the session is afk
func WithAfk(afk bool) PresenceOpt {
return func(presence *MessageDataPresenceUpdate) {
presence.AFK = afk
}
}

// NewCompetingPresence creates a new Presence of type ActivityTypeCompeting
func NewCompetingPresence(name string, status discord.OnlineStatus, afk bool) MessageDataPresenceUpdate {
return NewPresence(discord.ActivityTypeCompeting, name, "", status, afk)
// WithSince sets when the session has gone afk
func WithSince(since *int64) PresenceOpt {
return func(presence *MessageDataPresenceUpdate) {
presence.Since = since
}
}

// MessageDataPresenceUpdate is used for updating Client's presence
Expand Down