Skip to content

Commit

Permalink
Minor fixes, undocumented things, and editorconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
diamondburned committed Jan 19, 2020
1 parent ce0bd07 commit ac685b0
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 36 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
charset = utf-8
indent_style = tab
indent_size = 4

[.gitlab-ci.yml]
indent_style = space
indent_size = 2
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ state storage before they're removed.
- Pluggable state storages: although only having a default state storage in the
library, it is abstracted with an interface, making it possible to implement a
custom remote or local state storage.
- REST-updated state: this library will call the REST API if it can't find
things in the state, which is useful for keeping it updated.
- No code generation: just so the library is a lot easier to maintain.

## You-should-knows
Expand Down
5 changes: 4 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ func NewClient(token string) *Client {

tw := httputil.NewTransportWrapper()
tw.Pre = func(r *http.Request) error {
r.Header.Set("Authorization", cli.Token)
if cli.Token != "" {
r.Header.Set("Authorization", cli.Token)
}

r.Header.Set("User-Agent", UserAgent)
r.Header.Set("X-RateLimit-Precision", "millisecond")

Expand Down
42 changes: 42 additions & 0 deletions api/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package api

import "github.com/diamondburned/arikawa/internal/httputil"

const (
EndpointAuth = Endpoint + "auth/"
EndpointLogin = EndpointAuth + "login"
EndpointTOTP = EndpointAuth + "mfa/totp"
)

type LoginResponse struct {
MFA bool `json:"mfa"`
SMS bool `json:"sms"`
Ticket string `json:"ticket"`
Token string `json:"token"`
}

func (c *Client) Login(email, password string) (*LoginResponse, error) {
var param struct {
Email string `json:"email"`
Password string `json:"password"`
}
param.Email = email
param.Password = password

var r *LoginResponse
return r, c.RequestJSON(&r, "POST", EndpointLogin,
httputil.WithJSONBody(c, param))
}

func (c *Client) TOTP(code, ticket string) (*LoginResponse, error) {
var param struct {
Code string `json:"code"`
Ticket string `json:"ticket"`
}
param.Code = code
param.Ticket = ticket

var r *LoginResponse
return r, c.RequestJSON(&r, "POST", EndpointTOTP,
httputil.WithJSONBody(c, param))
}
26 changes: 7 additions & 19 deletions gateway/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,7 @@ type (
HeartbeatInterval discord.Milliseconds `json:"heartbeat_interval"`
}

ReadyEvent struct {
Version int `json:"version"`

User discord.User `json:"user"`
SessionID string `json:"session_id"`

PrivateChannels []discord.Channel `json:"private_channels"`
Guilds []discord.Guild `json:"guilds"`

Shard *Shard `json:"shard"`

// Undocumented fields
Presences []discord.Presence `json:"presences,omitempty"`
Notes map[discord.Snowflake]string `json:"notes,omitempty"`
}
// Ready is too big, so it's moved to ready.go

ResumedEvent struct{}

Expand Down Expand Up @@ -185,10 +171,6 @@ type (
UserUpdateEvent discord.User
)

func (u PresenceUpdateEvent) Update(p *discord.Presence) {

}

// https://discordapp.com/developers/docs/topics/gateway#voice
type (
VoiceStateUpdateEvent discord.VoiceState
Expand All @@ -206,3 +188,9 @@ type (
ChannelID discord.Snowflake `json:"channel_id"`
}
)

// Undocumented
type (
UserGuildSettingsUpdateEvent UserGuildSettings
UserSettingsUpdateEvent UserSettings
)
19 changes: 16 additions & 3 deletions gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ type Gateway struct {
WSRetries uint // 3

// All events sent over are pointers to Event structs (structs suffixed with
// "Event")
// "Event"). This shouldn't be accessed if the Gateway is created with a
// Session.
Events chan Event

SessionID string
Expand Down Expand Up @@ -231,8 +232,7 @@ func (g *Gateway) Open() error {
}

// Start authenticates with the websocket, or resume from a dead Websocket
// connection. This function doesn't block. To block until a fatal error, use
// Wait().
// connection. This function doesn't block.
func (g *Gateway) Start() error {
// This is where we'll get our events
ch := g.WS.Listen()
Expand Down Expand Up @@ -265,6 +265,19 @@ func (g *Gateway) Start() error {
}
}

// Expect at least one event
ev := <-ch

// Check for error
if ev.Error != nil {
return errors.Wrap(ev.Error, "First error")
}

// Handle the event
if err := HandleEvent(g, ev.Data); err != nil {
return errors.Wrap(err, "WS handler error on first event")
}

// Start the event handler
g.handler = make(chan struct{})
go g.handleWS(g.handler)
Expand Down
110 changes: 110 additions & 0 deletions gateway/ready.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package gateway

import "github.com/diamondburned/arikawa/discord"

type ReadyEvent struct {
Version int `json:"version"`

User discord.User `json:"user"`
SessionID string `json:"session_id"`

PrivateChannels []discord.Channel `json:"private_channels"`
Guilds []discord.Guild `json:"guilds"`

Shard *Shard `json:"shard"`

// Undocumented fields
Settings *UserSettings `json:"user_settings"`
UserGuildSettings []UserGuildSettings `json:"user_guild_settings"`
Relationships []Relationship `json:"relationships"`
Presences []discord.Presence `json:"presences,omitempty"`
Notes map[discord.Snowflake]string `json:"notes,omitempty"`
}

type UserSettings struct {
ShowCurrentGame bool `json:"show_current_game"`
DefaultGuildsRestricted bool `json:"default_guilds_restricted"`
InlineAttachmentMedia bool `json:"inline_attachment_media"`
InlineEmbedMedia bool `json:"inline_embed_media"`
GIFAutoPlay bool `json:"gif_auto_play"`
RenderEmbeds bool `json:"render_embeds"`
RenderReactions bool `json:"render_reactions"`
AnimateEmoji bool `json:"animate_emoji"`
EnableTTSCommand bool `json:"enable_tts_command"`
MessageDisplayCompact bool `json:"message_display_compact"`
ConvertEmoticons bool `json:"convert_emoticons"`
ExplicitContentFilter uint8 `json:"explicit_content_filter"` // ???
DisableGamesTab bool `json:"disable_games_tab"`
DeveloperMode bool `json:"developer_mode"`
DetectPlatformAccounts bool `json:"detect_platform_accounts"`
StreamNotification bool `json:"stream_notification_enabled"`
AccessibilityDetection bool `json:"allow_accessbility_detection"`
ContactSync bool `json:"contact_sync_enabled"`
NativePhoneIntegration bool `json:"native_phone_integration_enabled"`

Locale string `json:"locale"`
Theme string `json:"theme"`

GuildPositions []discord.Snowflake `json:"guild_positions"`
GuildFolders []GuildFolder `json:"guild_folders"`
RestrictedGuilds []discord.Snowflake `json:"restricted_guilds"`

FriendSourceFlags struct {
All bool `json:"all"`
MutualGuilds bool `json:"mutual_guilds"`
MutualFriends bool `json:"mutual_friends"`
} `json:"friend_source_flags"`

Status discord.Status `json:"status"`
CustomStatus struct {
Text string `json:"text"`
ExpiresAt discord.Timestamp `json:"expires_at,omitempty"`
EmojiID discord.Snowflake `json:"emoji_id,string"`
EmojiName string `json:"emoji_name"`
} `json:"custom_status"`
}

// A UserGuildSettingsChannelOverride stores data for a channel override for a
// users guild settings.
type SettingsChannelOverride struct {
Muted bool `json:"muted"`
MessageNotifications int `json:"message_notifications"` // TODO: document

ChannelID discord.Snowflake `json:"channel_id"`
}

// A UserGuildSettings stores data for a users guild settings.
type UserGuildSettings struct {
SupressEveryone bool `json:"suppress_everyone"`
Muted bool `json:"muted"`
MobilePush bool `json:"mobile_push"`
MessageNotifications int `json:"message_notifications"`

GuildID discord.Snowflake `json:"guild_id"`
ChannelOverrides []SettingsChannelOverride `json:"channel_overrides"`
}

// GuildFolder holds a single folder that you see in the left guild panel.
type GuildFolder struct {
Name string `json:"name"`
ID int64 `json:"id"`
GuildIDs []string `json:"guild_ids"`
Color int64 `json:"color"`
}

// A Relationship between the logged in user and Relationship.User
type Relationship struct {
ID string `json:"id"`
User discord.User `json:"user"`
Type RelationshipType `json:"type"`
}

type RelationshipType uint8

const (
_ RelationshipType = iota
FriendRelationship
BlockedRelationship
IncomingFriendRequest
SentFriendRequest
)
48 changes: 42 additions & 6 deletions session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ import (
"github.com/pkg/errors"
)

var ErrMFA = errors.New("Account has 2FA enabled")

// Session manages both the API and Gateway. As such, Session inherits all of
// API's methods, as well has the Handler used for Gateway.
type Session struct {
*api.Client
gateway *gateway.Gateway
Gateway *gateway.Gateway

// ErrorLog logs errors, including Gateway errors.
ErrorLog func(err error) // default to log.Println

// Command handler with inherited methods.
*handler.Handler

// MFA only fields
MFA bool
Ticket string

hstop chan struct{}
}

Expand All @@ -43,14 +49,44 @@ func New(token string) (*Session, error) {
if err != nil {
return nil, errors.Wrap(err, "Failed to connect to Gateway")
}
s.gateway = g
s.gateway.ErrorLog = func(err error) {
s.Gateway = g
s.Gateway.ErrorLog = func(err error) {
s.ErrorLog(err)
}

return s, nil
}

// Login tries to log in as a normal user account; MFA is optional.
func Login(email, password, mfa string) (*Session, error) {
// Make a scratch HTTP client without a token
client := api.NewClient("")

// Try to login without TOTP
l, err := client.Login(email, password)
if err != nil {
return nil, errors.Wrap(err, "Failed to login")
}

if l.Token != "" && !l.MFA {
// We got the token, return with a new Session.
return New(l.Token)
}

// Discord requests MFA, so we need the MFA token.
if mfa == "" {
return nil, ErrMFA
}

// Retry logging in with a 2FA token
l, err = client.TOTP(mfa, l.Ticket)
if err != nil {
return nil, errors.Wrap(err, "Failed to login with 2FA")
}

return New(l.Token)
}

func NewWithGateway(gw *gateway.Gateway) *Session {
s := &Session{
// Nab off gateway's token
Expand All @@ -69,7 +105,7 @@ func NewWithGateway(gw *gateway.Gateway) *Session {
}

func (s *Session) Open() error {
if err := s.gateway.Open(); err != nil {
if err := s.Gateway.Open(); err != nil {
return errors.Wrap(err, "Failed to start gateway")
}

Expand All @@ -85,7 +121,7 @@ func (s *Session) startHandler(stop <-chan struct{}) {
select {
case <-stop:
return
case ev := <-s.gateway.Events:
case ev := <-s.Gateway.Events:
s.Handler.Call(ev)
}
}
Expand All @@ -98,5 +134,5 @@ func (s *Session) Close() error {
}

// Close the websocket
return s.gateway.Close()
return s.Gateway.Close()
}
Loading

0 comments on commit ac685b0

Please sign in to comment.