all: init v2 and delete old bridge
This commit is contained in:
28
pkg/connector/chatinfo.go
Normal file
28
pkg/connector/chatinfo.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
)
|
||||
|
||||
func (d *DiscordClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfo, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
58
pkg/connector/client.go
Normal file
58
pkg/connector/client.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
)
|
||||
|
||||
type DiscordClient struct {
|
||||
}
|
||||
|
||||
func (d *DiscordConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
var _ bridgev2.NetworkAPI = (*DiscordClient)(nil)
|
||||
|
||||
func (d *DiscordClient) Connect(ctx context.Context) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) Disconnect() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) IsLoggedIn() bool {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) LogoutRemote(ctx context.Context) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *bridgev2.NetworkRoomCapabilities {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
26
pkg/connector/config.go
Normal file
26
pkg/connector/config.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"go.mau.fi/util/configupgrade"
|
||||
)
|
||||
|
||||
func (d *DiscordConnector) GetConfig() (example string, data any, upgrader configupgrade.Upgrader) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
48
pkg/connector/connector.go
Normal file
48
pkg/connector/connector.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
)
|
||||
|
||||
type DiscordConnector struct {
|
||||
}
|
||||
|
||||
var _ bridgev2.NetworkConnector = (*DiscordConnector)(nil)
|
||||
|
||||
func (d *DiscordConnector) Init(bridge *bridgev2.Bridge) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordConnector) Start(ctx context.Context) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordConnector) GetName() bridgev2.BridgeName {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
26
pkg/connector/dbmeta.go
Normal file
26
pkg/connector/dbmeta.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"maunium.net/go/mautrix/bridgev2/database"
|
||||
)
|
||||
|
||||
func (d *DiscordConnector) GetDBMetaTypes() database.MetaTypes {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
72
pkg/connector/handlematrix.go
Normal file
72
pkg/connector/handlematrix.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/database"
|
||||
)
|
||||
|
||||
var (
|
||||
_ bridgev2.ReactionHandlingNetworkAPI = (*DiscordClient)(nil)
|
||||
_ bridgev2.RedactionHandlingNetworkAPI = (*DiscordClient)(nil)
|
||||
_ bridgev2.EditHandlingNetworkAPI = (*DiscordClient)(nil)
|
||||
_ bridgev2.ReadReceiptHandlingNetworkAPI = (*DiscordClient)(nil)
|
||||
_ bridgev2.TypingHandlingNetworkAPI = (*DiscordClient)(nil)
|
||||
)
|
||||
|
||||
func (d *DiscordClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (message *bridgev2.MatrixMessageResponse, err error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.MatrixEdit) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (reaction *database.Reaction, err error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) HandleMatrixReadReceipt(ctx context.Context, msg *bridgev2.MatrixReadReceipt) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
33
pkg/connector/login.go
Normal file
33
pkg/connector/login.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
)
|
||||
|
||||
func (d *DiscordConnector) GetLoginFlows() []bridgev2.LoginFlow {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordConnector) CreateLogin(ctx context.Context, user *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
34
pkg/connector/userinfo.go
Normal file
34
pkg/connector/userinfo.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
)
|
||||
|
||||
func (d *DiscordClient) IsThisUser(ctx context.Context, userID networkid.UserID) bool {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DiscordClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
54
pkg/remoteauth/README.md
Normal file
54
pkg/remoteauth/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Discord Remote Authentication
|
||||
|
||||
This library implements the desktop side of Discord's remote authentication
|
||||
protocol.
|
||||
|
||||
It is completely based off of the
|
||||
[Unofficial Discord API Documentation](https://luna.gitlab.io/discord-unofficial-docs/desktop_remote_auth.html).
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := New()
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
qrChan := make(chan *qrcode.QRCode)
|
||||
go func() {
|
||||
qrCode := <-qrChan
|
||||
fmt.Println(qrCode.ToSmallString(true))
|
||||
}()
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
|
||||
if err := client.Dial(ctx, qrChan, doneChan); err != nil {
|
||||
close(qrChan)
|
||||
close(doneChan)
|
||||
|
||||
fmt.Printf("dial error: %v\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
<-doneChan
|
||||
|
||||
user, err := client.Result()
|
||||
fmt.Printf("user: %q\n", user)
|
||||
fmt.Printf("err: %v\n", err)
|
||||
}
|
||||
```
|
||||
125
pkg/remoteauth/client.go
Normal file
125
pkg/remoteauth/client.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package remoteauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
sync.Mutex
|
||||
|
||||
URL string
|
||||
|
||||
conn *websocket.Conn
|
||||
|
||||
qrChan chan string
|
||||
doneChan chan struct{}
|
||||
|
||||
user User
|
||||
err error
|
||||
|
||||
heartbeats int
|
||||
closed bool
|
||||
|
||||
privateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// New creates a new Discord remote auth client. qrChan is a channel that will
|
||||
// receive the qrcode once it is available.
|
||||
func New() (*Client, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
URL: "wss://remote-auth-gateway.discord.gg/?v=2",
|
||||
privateKey: privateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Dial will start the QRCode login process. ctx may be used to abandon the
|
||||
// process.
|
||||
func (c *Client) Dial(ctx context.Context, qrChan chan string, doneChan chan struct{}) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
header := http.Header{}
|
||||
for key, value := range discordgo.DroidWSHeaders {
|
||||
header.Set(key, value)
|
||||
}
|
||||
|
||||
c.qrChan = qrChan
|
||||
c.doneChan = doneChan
|
||||
|
||||
conn, _, err := websocket.DefaultDialer.DialContext(ctx, c.URL, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.conn = conn
|
||||
|
||||
go c.processMessages()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Result() (User, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.user, c.err
|
||||
}
|
||||
|
||||
func (c *Client) close() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.conn.WriteMessage(
|
||||
websocket.CloseMessage,
|
||||
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
|
||||
)
|
||||
|
||||
c.closed = true
|
||||
|
||||
defer close(c.doneChan)
|
||||
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *Client) write(p clientPacket) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
payload, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.conn.WriteMessage(websocket.TextMessage, payload)
|
||||
}
|
||||
|
||||
func (c *Client) decrypt(payload string) ([]byte, error) {
|
||||
// Decode the base64 string.
|
||||
raw, err := base64.StdEncoding.DecodeString(payload)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
// Decrypt the data.
|
||||
return rsa.DecryptOAEP(sha256.New(), nil, c.privateKey, raw, nil)
|
||||
}
|
||||
70
pkg/remoteauth/clientpackets.go
Normal file
70
pkg/remoteauth/clientpackets.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package remoteauth
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type clientPacket interface {
|
||||
send(client *Client) error
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Heartbeat
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type clientHeartbeat struct {
|
||||
OP string `json:"op"`
|
||||
}
|
||||
|
||||
func (h *clientHeartbeat) send(client *Client) error {
|
||||
// make sure our op string is set
|
||||
h.OP = "heartbeat"
|
||||
|
||||
client.heartbeats += 1
|
||||
if client.heartbeats > 2 {
|
||||
return fmt.Errorf("server failed to acknowledge our heartbeats")
|
||||
}
|
||||
|
||||
return client.write(h)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type clientInit struct {
|
||||
OP string `json:"op"`
|
||||
EncodedPublicKey string `json:"encoded_public_key"`
|
||||
}
|
||||
|
||||
func (i *clientInit) send(client *Client) error {
|
||||
i.OP = "init"
|
||||
|
||||
pubkey := client.privateKey.Public()
|
||||
|
||||
raw, err := x509.MarshalPKIXPublicKey(pubkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.EncodedPublicKey = base64.RawStdEncoding.EncodeToString(raw)
|
||||
|
||||
return client.write(i)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// NonceProof
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type clientNonceProof struct {
|
||||
OP string `json:"op"`
|
||||
Proof string `json:"proof"`
|
||||
}
|
||||
|
||||
func (n *clientNonceProof) send(client *Client) error {
|
||||
n.OP = "nonce_proof"
|
||||
|
||||
// All of the other work was taken care of by the server packet as it knows
|
||||
// the payload.
|
||||
|
||||
return client.write(n)
|
||||
}
|
||||
244
pkg/remoteauth/serverpackets.go
Normal file
244
pkg/remoteauth/serverpackets.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package remoteauth
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type serverPacket interface {
|
||||
process(client *Client) error
|
||||
}
|
||||
|
||||
func (c *Client) processMessages() {
|
||||
type rawPacket struct {
|
||||
OP string `json:"op"`
|
||||
}
|
||||
|
||||
defer c.close()
|
||||
|
||||
for {
|
||||
c.Lock()
|
||||
_, packet, err := c.conn.ReadMessage()
|
||||
c.Unlock()
|
||||
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
|
||||
c.Lock()
|
||||
c.err = err
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
raw := rawPacket{}
|
||||
if err := json.Unmarshal(packet, &raw); err != nil {
|
||||
c.Lock()
|
||||
c.err = err
|
||||
c.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var dest interface{}
|
||||
switch raw.OP {
|
||||
case "hello":
|
||||
dest = new(serverHello)
|
||||
case "nonce_proof":
|
||||
dest = new(serverNonceProof)
|
||||
case "pending_remote_init":
|
||||
dest = new(serverPendingRemoteInit)
|
||||
case "pending_ticket":
|
||||
dest = new(serverPendingTicket)
|
||||
case "pending_login":
|
||||
dest = new(serverPendingLogin)
|
||||
case "cancel":
|
||||
dest = new(serverCancel)
|
||||
case "heartbeat_ack":
|
||||
dest = new(serverHeartbeatAck)
|
||||
default:
|
||||
c.Lock()
|
||||
c.err = fmt.Errorf("unknown op %s", raw.OP)
|
||||
c.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(packet, dest); err != nil {
|
||||
c.Lock()
|
||||
c.err = err
|
||||
c.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
op := dest.(serverPacket)
|
||||
err = op.process(c)
|
||||
if err != nil {
|
||||
c.Lock()
|
||||
c.err = err
|
||||
c.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Hello
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverHello struct {
|
||||
Timeout int `json:"timeout_ms"`
|
||||
HeartbeatInterval int `json:"heartbeat_interval"`
|
||||
}
|
||||
|
||||
func (h *serverHello) process(client *Client) error {
|
||||
// Create our heartbeat handler
|
||||
ticker := time.NewTicker(time.Duration(h.HeartbeatInterval) * time.Millisecond)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
//lint:ignore S1000 -
|
||||
for {
|
||||
select {
|
||||
// case <-client.ctx.Done():
|
||||
// return
|
||||
case <-ticker.C:
|
||||
h := clientHeartbeat{}
|
||||
if err := h.send(client); err != nil {
|
||||
client.Lock()
|
||||
client.err = err
|
||||
client.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
duration := time.Duration(h.Timeout) * time.Millisecond
|
||||
|
||||
<-time.After(duration)
|
||||
|
||||
client.Lock()
|
||||
client.err = fmt.Errorf("timed out after %s", duration)
|
||||
client.close()
|
||||
client.Unlock()
|
||||
}()
|
||||
|
||||
i := clientInit{}
|
||||
|
||||
return i.send(client)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// NonceProof
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverNonceProof struct {
|
||||
EncryptedNonce string `json:"encrypted_nonce"`
|
||||
}
|
||||
|
||||
func (n *serverNonceProof) process(client *Client) error {
|
||||
plaintext, err := client.decrypt(n.EncryptedNonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawProof := sha256.Sum256(plaintext)
|
||||
// The [:] syntax is to return an unsized slice as the sum function returns
|
||||
// one.
|
||||
proof := base64.RawURLEncoding.EncodeToString(rawProof[:])
|
||||
|
||||
c := clientNonceProof{Proof: proof}
|
||||
|
||||
return c.send(client)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// HeartbeatAck
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverHeartbeatAck struct{}
|
||||
|
||||
func (h *serverHeartbeatAck) process(client *Client) error {
|
||||
client.heartbeats -= 1
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// PendingRemoteInit
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverPendingRemoteInit struct {
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
}
|
||||
|
||||
func (p *serverPendingRemoteInit) process(client *Client) error {
|
||||
url := "https://discordapp.com/ra/" + p.Fingerprint
|
||||
|
||||
client.qrChan <- url
|
||||
close(client.qrChan)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// PendingFinish
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverPendingTicket struct {
|
||||
EncryptedUserPayload string `json:"encrypted_user_payload"`
|
||||
}
|
||||
|
||||
func (p *serverPendingTicket) process(client *Client) error {
|
||||
plaintext, err := client.decrypt(p.EncryptedUserPayload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.user.update(string(plaintext))
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Finish
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverPendingLogin struct {
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
|
||||
func (p *serverPendingLogin) process(client *Client) error {
|
||||
sess, err := discordgo.New("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encryptedToken, err := sess.RemoteAuthLogin(p.Ticket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plaintext, err := client.decrypt(encryptedToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client.user.Token = string(plaintext)
|
||||
|
||||
client.close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Cancel
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
type serverCancel struct{}
|
||||
|
||||
func (c *serverCancel) process(client *Client) error {
|
||||
client.close()
|
||||
|
||||
return nil
|
||||
}
|
||||
29
pkg/remoteauth/user.go
Normal file
29
pkg/remoteauth/user.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package remoteauth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
UserID string
|
||||
Discriminator string
|
||||
AvatarHash string
|
||||
Username string
|
||||
|
||||
Token string
|
||||
}
|
||||
|
||||
func (u *User) update(payload string) error {
|
||||
parts := strings.Split(payload, ":")
|
||||
if len(parts) != 4 {
|
||||
return fmt.Errorf("expected 4 parts but got %d", len(parts))
|
||||
}
|
||||
|
||||
u.UserID = parts[0]
|
||||
u.Discriminator = parts[1]
|
||||
u.AvatarHash = parts[2]
|
||||
u.Username = parts[3]
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user