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:
parent
ce0bd07977
commit
ac685b0df4
10
.editorconfig
Normal file
10
.editorconfig
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
42
api/login.go
Normal 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))
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
110
gateway/ready.go
Normal 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
|
||||
)
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
////
|
||||
|
|
Loading…
Reference in a new issue