1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2025-01-02 18:26:41 +00:00

Minor fixes, undocumented things, and editorconfig

This commit is contained in:
diamondburned (Forefront) 2020-01-19 13:54:16 -08:00
parent ce0bd07977
commit ac685b0df4
9 changed files with 249 additions and 36 deletions

10
.editorconfig Normal file
View file

@ -0,0 +1,10 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
[.gitlab-ci.yml]
indent_style = space
indent_size = 2

View file

@ -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

View file

@ -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")

42
api/login.go Normal file
View file

@ -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))
}

View file

@ -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{}
@ -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
@ -206,3 +188,9 @@ type (
ChannelID discord.Snowflake `json:"channel_id"`
}
)
// Undocumented
type (
UserGuildSettingsUpdateEvent UserGuildSettings
UserSettingsUpdateEvent UserSettings
)

View file

@ -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
@ -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()
@ -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)

110
gateway/ready.go Normal file
View file

@ -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
)

View file

@ -12,11 +12,13 @@ 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
@ -24,6 +26,10 @@ type Session struct {
// Command handler with inherited methods.
*handler.Handler
// MFA only fields
MFA bool
Ticket string
hstop chan struct{}
}
@ -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
@ -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")
}
@ -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)
}
}
@ -98,5 +134,5 @@ func (s *Session) Close() error {
}
// Close the websocket
return s.gateway.Close()
return s.Gateway.Close()
}

View file

@ -13,8 +13,8 @@ import (
)
var (
MaxFetchMembers = 1000
MaxFetchGuilds = 100
MaxFetchMembers uint = 1000
MaxFetchGuilds uint = 100
)
type State struct {
@ -215,7 +215,7 @@ func (s *State) Guilds() ([]discord.Guild, error) {
return c, nil
}
c, err = s.Session.Guilds(100)
c, err = s.Session.Guilds(MaxFetchGuilds)
if err != nil {
return nil, err
}
@ -244,17 +244,23 @@ func (s *State) Member(
return nil, err
}
return m, s.Store.MemberSet(guildID, m)
if err := s.Store.MemberSet(guildID, m); err != nil {
return m, err
}
return m, s.Gateway.RequestGuildMembers(gateway.RequestGuildMembersData{
GuildID: []discord.Snowflake{guildID},
Presences: true,
})
}
// Members
func (s *State) Members(guildID discord.Snowflake) ([]discord.Member, error) {
ms, err := s.Store.Members(guildID)
if err == nil {
return ms, nil
}
ms, err = s.Session.Members(guildID, 1000)
ms, err = s.Session.Members(guildID, MaxFetchMembers)
if err != nil {
return nil, err
}
@ -265,7 +271,10 @@ func (s *State) Members(guildID discord.Snowflake) ([]discord.Member, error) {
}
}
return ms, nil
return ms, s.Gateway.RequestGuildMembers(gateway.RequestGuildMembersData{
GuildID: []discord.Snowflake{guildID},
Presences: true,
})
}
////