Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
V4NSH4J
GitHub Repository: V4NSH4J/discord-mass-DM-GO
Path: blob/main/instance/websocket.go
310 views
1
// Copyright (C) 2021 github.com/dankgrinder & github.com/V4NSH4J
2
//
3
// This source code has been released under the GNU Affero General Public
4
// License v3.0. A copy of this license is available at
5
// https://www.gnu.org/licenses/agpl-3.0.en.html
6
7
package instance
8
9
import (
10
"encoding/json"
11
"fmt"
12
"math/rand"
13
"net/http"
14
"net/url"
15
"strings"
16
"time"
17
18
"github.com/V4NSH4J/discord-mass-dm-GO/utilities"
19
"github.com/gorilla/websocket"
20
)
21
22
// Define WebSocket connection struct
23
type Connection struct {
24
Members []Member
25
Token string
26
OfflineScrape chan []byte
27
AllMembers []string
28
Messages chan []byte
29
Complete bool
30
Conn *websocket.Conn
31
sessionID string
32
in chan string
33
out chan []byte
34
fatalHandler func(err error)
35
seq int
36
closeChan chan struct{}
37
Reactions chan []byte
38
}
39
40
// Input Discord token and start a new websocket connection
41
func (in *Instance) NewConnection(fatalHandler func(err error)) (*Connection, error) {
42
var dialer websocket.Dialer
43
if in.GatewayProxy == "" {
44
dialer = *websocket.DefaultDialer
45
} else {
46
47
if !strings.Contains(in.GatewayProxy, "http://") {
48
in.GatewayProxy = "http://" + in.GatewayProxy
49
}
50
proxyURL, err := url.Parse(in.GatewayProxy)
51
if err != nil {
52
return nil, err
53
}
54
dialer = websocket.Dialer{Proxy: http.ProxyURL(proxyURL)}
55
}
56
// Dial Connection to Discord
57
ws, _, err := dialer.Dial("wss://gateway.discord.gg/?v=9&encoding=json", nil)
58
if err != nil {
59
return nil, err
60
}
61
62
c := Connection{
63
Token: in.Token,
64
Conn: ws,
65
in: make(chan string),
66
out: make(chan []byte),
67
OfflineScrape: make(chan []byte),
68
Messages: make(chan []byte),
69
fatalHandler: fatalHandler,
70
closeChan: make(chan struct{}),
71
Reactions: make(chan []byte),
72
}
73
// Receive Hello message
74
interval, err := c.ReadHello()
75
if err != nil {
76
c.Conn.Close()
77
return nil, err
78
}
79
presences := []string{"online", "idle", "dnd", "offline"}
80
if in.Config.OtherSettings.GatewayStatus < 0 || in.Config.OtherSettings.GatewayStatus > 4 {
81
in.Config.OtherSettings.GatewayStatus = 0
82
}
83
var presence string
84
if in.Config.OtherSettings.GatewayStatus == 4 {
85
r := rand.Intn(4)
86
presence = presences[r]
87
88
} else {
89
presence = presences[in.Config.OtherSettings.GatewayStatus]
90
}
91
// Authenticate with Discord
92
err = c.Conn.WriteJSON(&Event{
93
Op: OpcodeIdentify,
94
Data: Data{
95
ClientState: ClientState{
96
HighestLastMessageID: "0",
97
ReadStateVersion: 0,
98
UserGuildSettingsVersion: -1,
99
},
100
Identify: Identify{
101
Token: in.Token,
102
Properties: Properties{
103
OS: "Windows",
104
Browser: "Chrome",
105
BrowserUserAgent: "Chrome/86.0.4240.75",
106
BrowserVersion: "86.0.4240.75",
107
Referrer: "https://discord.com/new",
108
ReferringDomain: "discord.com",
109
ReleaseChannel: "stable",
110
ClientBuildNumber: 73683,
111
},
112
Capabilities: 61,
113
Presence: Presence{
114
Status: presence,
115
Since: 0,
116
AFK: false,
117
},
118
Compress: false,
119
},
120
}})
121
if err != nil {
122
c.Conn.Close()
123
return nil, fmt.Errorf("error while sending authentication message: %v", err)
124
}
125
126
if c.sessionID, err = c.awaitEvent(EventNameReady); err != nil {
127
c.Conn.Close()
128
in.wsFatalHandler(err)
129
return nil, fmt.Errorf("error while waiting for ready event: %v", err)
130
}
131
go c.Ping(time.Duration(interval) * time.Millisecond)
132
go c.listen()
133
134
return &c, nil
135
136
}
137
138
// Read Hello function to read hello message from websocket return 0 if next message is not a hello message or return the heartbeat interval
139
func (c *Connection) ReadHello() (int, error) {
140
_, message, err := c.Conn.ReadMessage()
141
if err != nil {
142
return 0, err
143
}
144
var body Event
145
if err := json.Unmarshal(message, &body); err != nil {
146
return 0, fmt.Errorf("error while Unmarshalling incoming hello websocket message: %v", err)
147
}
148
if body.Op != OpcodeHello {
149
return 0, fmt.Errorf("expected OpcodeHello but got %v", body.Op)
150
}
151
152
if body.Data.HeartbeatInterval <= 0 {
153
return 0, fmt.Errorf("heartbeat interval is not valid")
154
}
155
156
return body.Data.HeartbeatInterval, nil
157
158
}
159
160
// Ping Heartbeat interval
161
162
func (c *Connection) Ping(interval time.Duration) {
163
go func() {
164
t := time.NewTicker(interval)
165
defer t.Stop()
166
for {
167
select {
168
case <-c.closeChan:
169
return
170
case <-t.C:
171
172
}
173
_ = c.Conn.WriteJSON(&Event{
174
Op: OpcodeHeartbeat,
175
})
176
}
177
}()
178
}
179
180
func (c *Connection) awaitEvent(e string) (string, error) {
181
_, b, err := c.Conn.ReadMessage()
182
if err != nil {
183
return "", fmt.Errorf("error while reading message from websocket: %v", err)
184
}
185
var body Event
186
if err = json.Unmarshal(b, &body); err != nil {
187
return "", fmt.Errorf("error while unmarshalling incoming websocket message: %v", err)
188
}
189
if body.EventName != e {
190
return "", fmt.Errorf("unexpected event name for received websocket message: %v, expected %v", body.EventName, e)
191
}
192
return body.Data.SessionID, nil
193
}
194
195
func (c *Connection) listen() {
196
for {
197
_, b, err := c.Conn.ReadMessage()
198
if err != nil {
199
c.closeChan <- struct{}{}
200
c.Conn.Close()
201
fmt.Println(err)
202
c.fatalHandler(err)
203
break
204
}
205
var body Event
206
if err := json.Unmarshal(b, &body); err != nil {
207
// All messages which don't decode properly are likely caused by the
208
// data object and are ignored for now.
209
continue
210
}
211
212
if body.EventName == "GUILD_MEMBERS_CHUNK" {
213
go func() {
214
c.OfflineScrape <- b
215
}()
216
217
}
218
if body.EventName == "MESSAGE_REACTION_ADD" {
219
go func() {
220
c.Reactions <- b
221
}()
222
}
223
if body.EventName == "GUILD_MEMBER_LIST_UPDATE" {
224
for i := 0; i < len(body.Data.Ops); i++ {
225
if len(body.Data.Ops[i].Items) == 0 && body.Data.Ops[i].Op == "SYNC" {
226
c.Complete = true
227
}
228
}
229
230
for i := 0; i < len(body.Data.Ops); i++ {
231
if body.Data.Ops[i].Op == "SYNC" {
232
for j := 0; j < len(body.Data.Ops[i].Items); j++ {
233
c.Members = append(c.Members, body.Data.Ops[i].Items[j].Member)
234
}
235
}
236
}
237
}
238
239
switch body.Op {
240
default:
241
c.seq = body.Sequence
242
if body.Data.SessionID != "" {
243
c.sessionID = body.Data.SessionID
244
}
245
if body.EventName == EventNameMessageCreate || body.EventName == EventNameMessageUpdate {
246
utilities.WriteLines("received.txt", fmt.Sprintf(`Token: %v\nMessage:%v\nAuthor:%v`, c.Token, body.Data.Message.Content, body.Data.Message.Author.Username))
247
248
}
249
case OpcodeInvalidSession:
250
c.fatalHandler(fmt.Errorf("session invalidated"))
251
c.Close()
252
case OpcodeReconnect:
253
c.fatalHandler(fmt.Errorf("reconnecting"))
254
c.Close()
255
256
}
257
}
258
}
259
260
func (c *Connection) Close() error {
261
c.fatalHandler = func(err error) {}
262
c.closeChan <- struct{}{}
263
err := c.Conn.WriteControl(
264
websocket.CloseMessage,
265
websocket.FormatCloseMessage(websocket.CloseGoingAway, "going away"),
266
time.Now().Add(time.Second*10),
267
)
268
if err != nil {
269
if c.Conn != nil {
270
c.Conn.Close()
271
}
272
}
273
return nil
274
}
275
276
// Send interface to websocket
277
func (c *Connection) WriteRaw(e interface{}) error {
278
return c.Conn.WriteJSON(e)
279
}
280
281
// Function to write event
282
func (c *Connection) WriteJSONe(e *Event) error {
283
return c.Conn.WriteJSON(e)
284
}
285
286