2020-06-14 22:46:07 +00:00
|
|
|
package discord
|
|
|
|
|
|
|
|
import (
|
2020-06-16 03:57:33 +00:00
|
|
|
"context"
|
|
|
|
|
|
|
|
"github.com/diamondburned/arikawa/discord"
|
2020-06-30 03:14:44 +00:00
|
|
|
"github.com/diamondburned/arikawa/gateway"
|
2020-06-14 22:46:07 +00:00
|
|
|
"github.com/diamondburned/arikawa/state"
|
|
|
|
"github.com/diamondburned/cchat"
|
2020-06-16 03:59:34 +00:00
|
|
|
"github.com/diamondburned/cchat/services"
|
2020-06-14 22:46:07 +00:00
|
|
|
"github.com/diamondburned/cchat/text"
|
2020-06-16 03:57:33 +00:00
|
|
|
"github.com/diamondburned/ningen"
|
2020-06-14 22:46:07 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2020-06-16 03:59:34 +00:00
|
|
|
func init() {
|
|
|
|
services.RegisterService(&Service{})
|
|
|
|
}
|
|
|
|
|
2020-06-17 07:20:46 +00:00
|
|
|
// ErrInvalidSession is returned if SessionRestore is given a bad session.
|
|
|
|
var ErrInvalidSession = errors.New("invalid session")
|
|
|
|
|
2020-06-14 22:46:07 +00:00
|
|
|
type Service struct{}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ cchat.Icon = (*Service)(nil)
|
2020-06-16 03:57:33 +00:00
|
|
|
_ cchat.Service = (*Service)(nil)
|
2020-06-14 22:46:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (Service) Name() text.Rich {
|
|
|
|
return text.Rich{Content: "Discord"}
|
|
|
|
}
|
|
|
|
|
2020-06-30 03:14:44 +00:00
|
|
|
func (Service) Icon(ctx context.Context, iconer cchat.IconContainer) (func(), error) {
|
2020-07-11 00:10:28 +00:00
|
|
|
iconer.SetIcon("https://raw.githubusercontent.com/" +
|
|
|
|
"diamondburned/cchat-discord/himearikawa/discord_logo.png")
|
2020-06-30 03:14:44 +00:00
|
|
|
return func() {}, nil
|
2020-06-14 22:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (Service) Authenticate() cchat.Authenticator {
|
|
|
|
return Authenticator{}
|
|
|
|
}
|
|
|
|
|
2020-06-17 07:20:46 +00:00
|
|
|
func (s Service) RestoreSession(data map[string]string) (cchat.Session, error) {
|
|
|
|
tk, ok := data["token"]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrInvalidSession
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewSessionToken(tk)
|
|
|
|
}
|
|
|
|
|
2020-06-14 22:46:07 +00:00
|
|
|
type Authenticator struct{}
|
|
|
|
|
|
|
|
var _ cchat.Authenticator = (*Authenticator)(nil)
|
|
|
|
|
|
|
|
func (Authenticator) AuthenticateForm() []cchat.AuthenticateEntry {
|
|
|
|
// TODO: username, password and 2FA
|
|
|
|
return []cchat.AuthenticateEntry{
|
|
|
|
{
|
|
|
|
Name: "Token",
|
|
|
|
Secret: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (Authenticator) Authenticate(form []string) (cchat.Session, error) {
|
2020-06-17 07:20:46 +00:00
|
|
|
return NewSessionToken(form[0])
|
2020-06-16 03:57:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Session struct {
|
|
|
|
*ningen.State
|
|
|
|
userID discord.Snowflake
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ cchat.Icon = (*Session)(nil)
|
|
|
|
_ cchat.Session = (*Session)(nil)
|
|
|
|
_ cchat.SessionSaver = (*Session)(nil)
|
|
|
|
)
|
|
|
|
|
2020-06-17 07:20:46 +00:00
|
|
|
func NewSessionToken(token string) (*Session, error) {
|
|
|
|
s, err := state.New(token)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewSession(s)
|
|
|
|
}
|
|
|
|
|
2020-06-16 03:57:33 +00:00
|
|
|
func NewSession(s *state.State) (*Session, error) {
|
2020-06-14 22:46:07 +00:00
|
|
|
// Prefetch user.
|
2020-06-16 03:57:33 +00:00
|
|
|
u, err := s.Me()
|
2020-06-14 22:46:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "Failed to get current user")
|
|
|
|
}
|
|
|
|
|
2020-06-16 03:57:33 +00:00
|
|
|
n, err := ningen.FromState(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "Failed to create a state wrapper")
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:55:46 +00:00
|
|
|
var _ state.Store = s
|
|
|
|
|
2020-07-08 09:06:47 +00:00
|
|
|
if err := s.Open(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-14 22:46:07 +00:00
|
|
|
return &Session{
|
2020-06-16 03:57:33 +00:00
|
|
|
userID: u.ID,
|
|
|
|
State: n,
|
2020-06-14 22:46:07 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Session) ID() string {
|
2020-06-16 03:57:33 +00:00
|
|
|
return s.userID.String()
|
2020-06-14 22:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Session) Name() text.Rich {
|
2020-06-16 03:57:33 +00:00
|
|
|
u, err := s.Store.Me()
|
|
|
|
if err != nil {
|
|
|
|
// This shouldn't happen, ever.
|
|
|
|
return text.Rich{Content: "<@" + s.userID.String() + ">"}
|
|
|
|
}
|
|
|
|
|
2020-06-14 22:46:07 +00:00
|
|
|
return text.Rich{Content: u.Username + "#" + u.Discriminator}
|
|
|
|
}
|
|
|
|
|
2020-06-30 03:14:44 +00:00
|
|
|
func (s *Session) Icon(ctx context.Context, iconer cchat.IconContainer) (func(), error) {
|
2020-06-17 07:20:46 +00:00
|
|
|
u, err := s.Me()
|
2020-06-16 03:57:33 +00:00
|
|
|
if err != nil {
|
2020-06-30 03:14:44 +00:00
|
|
|
return nil, errors.Wrap(err, "Failed to get the current user")
|
2020-06-16 03:57:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Thanks to arikawa, AvatarURL is never empty.
|
2020-06-30 03:14:44 +00:00
|
|
|
iconer.SetIcon(AvatarURL(u.AvatarURL()))
|
|
|
|
|
2020-07-11 06:53:56 +00:00
|
|
|
return s.AddHandler(func(*gateway.UserUpdateEvent) {
|
|
|
|
// Bypass the event and use the state cache.
|
|
|
|
if u, err := s.Store.Me(); err == nil {
|
|
|
|
iconer.SetIcon(AvatarURL(u.AvatarURL()))
|
|
|
|
}
|
2020-06-30 03:14:44 +00:00
|
|
|
}), nil
|
2020-06-14 22:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Session) Disconnect() error {
|
|
|
|
return s.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Session) Save() (map[string]string, error) {
|
|
|
|
return map[string]string{
|
|
|
|
"token": s.Token,
|
|
|
|
}, nil
|
|
|
|
}
|
2020-06-19 08:09:41 +00:00
|
|
|
|
|
|
|
func (s *Session) Servers(container cchat.ServersContainer) error {
|
|
|
|
switch {
|
|
|
|
// If the user has guild folders:
|
|
|
|
case len(s.Ready.Settings.GuildFolders) > 0:
|
|
|
|
// TODO: account for missing guilds.
|
|
|
|
var toplevels = make([]cchat.Server, 0, len(s.Ready.Settings.GuildFolders))
|
|
|
|
|
|
|
|
for _, folder := range s.Ready.Settings.GuildFolders {
|
|
|
|
// TODO: correct.
|
|
|
|
switch {
|
|
|
|
case folder.ID.Valid():
|
|
|
|
fallthrough
|
|
|
|
case len(folder.GuildIDs) > 1:
|
|
|
|
toplevels = append(toplevels, NewGuildFolder(s, folder))
|
|
|
|
|
|
|
|
case len(folder.GuildIDs) == 1:
|
|
|
|
g, err := NewGuildFromID(s, folder.GuildIDs[0])
|
|
|
|
if err != nil {
|
2020-07-11 06:53:56 +00:00
|
|
|
continue
|
2020-06-19 08:09:41 +00:00
|
|
|
}
|
|
|
|
toplevels = append(toplevels, g)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
container.SetServers(toplevels)
|
|
|
|
|
|
|
|
// If the user doesn't have guild folders but has sorted their guilds
|
|
|
|
// before:
|
|
|
|
case len(s.Ready.Settings.GuildPositions) > 0:
|
|
|
|
var guilds = make([]cchat.Server, 0, len(s.Ready.Settings.GuildPositions))
|
|
|
|
|
|
|
|
for _, id := range s.Ready.Settings.GuildPositions {
|
|
|
|
g, err := NewGuildFromID(s, id)
|
|
|
|
if err != nil {
|
2020-07-11 06:53:56 +00:00
|
|
|
continue
|
2020-06-19 08:09:41 +00:00
|
|
|
}
|
|
|
|
guilds = append(guilds, g)
|
|
|
|
}
|
|
|
|
|
|
|
|
container.SetServers(guilds)
|
|
|
|
|
|
|
|
// None of the above:
|
|
|
|
default:
|
|
|
|
g, err := s.Guilds()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var servers = make([]cchat.Server, len(g))
|
|
|
|
for i := range g {
|
|
|
|
servers[i] = NewGuild(s, &g[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
container.SetServers(servers)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|