-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathChannel.cs
320 lines (285 loc) · 15.3 KB
/
Channel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Text;
using System.Timers;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WebSocketSharp.NetCore;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Lily
{
public class Channel
{
public class Payload
{
public int op { get; set; }
public class D
{
public string token { get; set; }
public int intents { get; set; }
public class Properties
{
[JsonProperty(PropertyName = "$os")]
public string os;
[JsonProperty(PropertyName = "$browser")]
public string browser;
[JsonProperty(PropertyName = "$device")]
public string device;
}
public Properties properties;
}
public D d;
}
public string ChannelId { get; private set; }
public string ServerId { get; private set; }
public string Token { get; private set; }
public event EventHandler<MessageReceiveEventArgs> MessageReceive;
private WebSocket _ws = new WebSocket("wss://gateway.discord.gg/?v=6&encoding=json");
private HttpClient _client = new HttpClient();
private Dictionary<string, TaskCompletionSource<string>> _waitingMessages = new Dictionary<string, TaskCompletionSource<string>>();
private BigInteger _nonce;
private ConcurrentQueue<TaskCompletionSource<ChannelControl>> _controlRequestQueue = new ConcurrentQueue<TaskCompletionSource<ChannelControl>>();
// private ConcurrentQueue<(string Content, Action<string> Callback, TaskCompletionSource<string> Tcs)> _messasgeQueue = new ConcurrentQueue<(string, Action<string>, TaskCompletionSource<string>)>();
private int _haltQueue = 0;
private bool _queueRunning = false;
private ChannelControl _control;
public Channel(string url, string token)
{
Token = token;
var fragments = url.Split("/");
ChannelId = fragments[fragments.Length - 1];
ServerId = fragments[fragments.Length - 2];
_nonce = BigInteger.Parse(ChannelId);
var rand = new Random();
var bytes = new byte[6];
rand.NextBytes(bytes);
_nonce += new BigInteger(bytes);
_control = new ChannelControl(this);
ConnectWebSocket();
}
private void ConnectWebSocket()
{
// Setup WebSocket
_ws.OnOpen += (sender, args) =>
{
var payload = new
{
op = 2,
d = new
{
token = Token,
intents = 513,
properties = new Dictionary<string, string>
{
// Only exists in your dreams...
{ "$os", "Windows 11 Mobile" },
{ "$browser", "edge" },
{ "$device", "surfacephone" }
}
}
};
var data = JsonConvert.SerializeObject(payload);
_ws.Send(data);
};
_ws.OnMessage += MessageReceived;
_ws.OnError += (sender, args) =>
{
if (args.Exception != null)
{
Console.WriteLine("Oops, I've lost touch with Discord!");
Console.WriteLine($"Here is the error, if you need it: ");
Console.WriteLine($"{args.Exception.GetType()}: {args.Exception.Message}");
}
else
{
Console.WriteLine("Oops, I've lost touch with Discord!");
}
Console.WriteLine("Be patient, I'm reaching out to Discord again...");
_ws = new WebSocket("wss://gateway.discord.gg/?v=6&encoding=json");
ConnectWebSocket();
};
_ws.Connect();
}
/// <summary>
/// Sends message without passing through queue. Needed for urgent messages, such as dragons.
/// </summary>
/// <param name="content">Message content</param>
/// <param name="retry">Retries if you're rate limited</param>
/// <returns></returns>
public async Task<string> SendMessageRawAsync(string content, bool retry = false)
{
await FakeTyping();
var request = new HttpRequestMessage();
request.RequestUri = new Uri($"https://discord.com/api/v9/channels/{ChannelId}/messages");
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*"));
request.Headers.AcceptLanguage.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("en-US"));
request.Headers.Authorization = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(Token);
request.Headers.Add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"91\", \"Chromium\";v=\"91\"");
request.Headers.Add("sec-ch-ua-mobile", "?0");
request.Headers.Add("sec-fetch-dest", "empty");
request.Headers.Add("sec-fetch-mode", "cors");
request.Headers.Add("sec-fetch-site", "same-origin");
request.Headers.Add("x-super-properties", "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiQ2hyb21lIiwiZGV2aWNlIjoiIiwic3lzdGVtX2xvY2FsZSI6ImVuLVVTIiwiYnJvd3Nlcl91c2VyX2FnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzkxLjAuNDQ3Mi4xMTQgU2FmYXJpLzUzNy4zNiBFZGcvOTEuMC44NjQuNTkiLCJicm93c2VyX3ZlcnNpb24iOiI5MS4wLjQ0NzIuMTE0Iiwib3NfdmVyc2lvbiI6IjEwIiwicmVmZXJyZXIiOiJodHRwczovL2Rpc2NvcmQuY29tLyIsInJlZmVycmluZ19kb21haW4iOiJkaXNjb3JkLmNvbSIsInJlZmVycmVyX2N1cnJlbnQiOiIiLCJyZWZlcnJpbmdfZG9tYWluX2N1cnJlbnQiOiIiLCJyZWxlYXNlX2NoYW5uZWwiOiJzdGFibGUiLCJjbGllbnRfYnVpbGRfbnVtYmVyIjo4OTEyOSwiY2xpZW50X2V2ZW50X3NvdXJjZSI6bnVsbH0=");
request.Headers.Referrer = new Uri($"https://discord.com/channels/{ServerId}/{ChannelId}");
request.Method = HttpMethod.Post;
var nonce = _nonce++;
request.Content = new StringContent(JsonConvert.SerializeObject(new { content = content, nonce = nonce.ToString(), tts = false }), Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request);
var text = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<JObject>(text);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
var message = data.Value<string>("message");
Console.Error.WriteLine("[Debug]: Too many requests sent to server.");
Console.Error.WriteLine($"[Debug]: Discord: {message}");
var time = data.Value<double>("retry_after");
Console.Error.WriteLine($"[Debug]: Try again after {time} seconds.");
await Task.Delay((int)Math.Ceiling(time * 1000));
if (retry)
{
return await SendMessageRawAsync(content, true);
}
else
{
return "";
}
}
response.EnsureSuccessStatusCode();
return data.Value<string>("id");
}
public async Task<ChannelControl> RequestControlAsync()
{
var tcs = new TaskCompletionSource<ChannelControl>();
_controlRequestQueue.Enqueue(tcs);
_ = RunControlRequestQueue();
return await tcs.Task;
}
public async Task RunControlRequestQueue()
{
if (_queueRunning) return;
_queueRunning = true;
Console.Error.WriteLine("[Debug]: Control request queue running...");
while (!_controlRequestQueue.IsEmpty)
{
if (_haltQueue != 0)
{
Console.Error.WriteLine("[Debug]: Queue halted.");
}
if (_controlRequestQueue.TryDequeue(out var tcs))
{
Console.Error.WriteLine("[Debug]: Dequeued.");
await _control.Wait();
Console.Error.WriteLine("[Debug]: Other owner released.");
_control.Reset();
Console.Error.WriteLine("[Debug]: Controller reset.");
tcs.SetResult(_control);
Console.Error.WriteLine("[Debug]: Done.");
}
}
_queueRunning = false;
}
private async Task FakeTyping()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri($"https://discord.com/api/v9/channels/{ChannelId}/typing");
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*"));
request.Headers.AcceptLanguage.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("en-US"));
request.Headers.Authorization = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(Token);
request.Headers.Add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"91\", \"Chromium\";v=\"91\"");
request.Headers.Add("sec-ch-ua-mobile", "?0");
request.Headers.Add("sec-fetch-dest", "empty");
request.Headers.Add("sec-fetch-mode", "cors");
request.Headers.Add("sec-fetch-site", "same-origin");
request.Headers.Add("x-super-properties", "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiQ2hyb21lIiwiZGV2aWNlIjoiIiwic3lzdGVtX2xvY2FsZSI6ImVuLVVTIiwiYnJvd3Nlcl91c2VyX2FnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzkxLjAuNDQ3Mi4xMTQgU2FmYXJpLzUzNy4zNiBFZGcvOTEuMC44NjQuNTkiLCJicm93c2VyX3ZlcnNpb24iOiI5MS4wLjQ0NzIuMTE0Iiwib3NfdmVyc2lvbiI6IjEwIiwicmVmZXJyZXIiOiJodHRwczovL2Rpc2NvcmQuY29tLyIsInJlZmVycmluZ19kb21haW4iOiJkaXNjb3JkLmNvbSIsInJlZmVycmVyX2N1cnJlbnQiOiIiLCJyZWZlcnJpbmdfZG9tYWluX2N1cnJlbnQiOiIiLCJyZWxlYXNlX2NoYW5uZWwiOiJzdGFibGUiLCJjbGllbnRfYnVpbGRfbnVtYmVyIjo4OTEyOSwiY2xpZW50X2V2ZW50X3NvdXJjZSI6bnVsbH0=");
request.Headers.Referrer = new Uri($"https://discord.com/channels/{ServerId}/{ChannelId}");
request.Method = HttpMethod.Post;
var response = await _client.SendAsync(request);
}
private async void MessageReceived(object sender, MessageEventArgs e)
{
var payload = JsonConvert.DeserializeObject<JObject>(e.Data);
var op = payload.Value<int>("op");
var t = payload.Value<string>("t");
switch (op)
{
case 10:
var heartbeatInterval = payload["d"].Value<int>("heartbeat_interval");
var interval = Heartbeat(heartbeatInterval);
break;
}
switch (t)
{
case "MESSAGE_CREATE":
{
var id = payload["d"].Value<string>("id");
var channelId = payload["d"].Value<string>("channel_id");
if (channelId != ChannelId) break;
var trueMessage = (await FetchMessageAsync(id))[0];
var author = trueMessage["author"].Value<string>("username");
var content = trueMessage.Value<string>("content");
//referenced_message
var referencedMessage = trueMessage["referenced_message"];
++_haltQueue;
var eventTcs = new TaskCompletionSource<object>();
var deferral = new Deferral(eventTcs);
MessageReceive?.Invoke(this, new MessageReceiveEventArgs{ Message = trueMessage, Deferral = deferral });
deferral.StartWaiting();
await eventTcs.Task;
--_haltQueue;
_ = RunControlRequestQueue();
// Tasks will only know messages AFTER the events processer.
_ = _control.Receive(id, trueMessage);
//Console.WriteLine($"{author}: {content}");
}
break;
case "MESSAGE_UPDATE":
{
var id = payload["d"].Value<string>("id");
var channelId = payload["d"].Value<string>("channel_id");
if (channelId != ChannelId) break;
var trueMessage = (await FetchMessageAsync(id))[0];
_control.Update(id, trueMessage);
}
break;
}
}
private async Task<JArray> FetchMessageAsync(string id)
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri($"https://discord.com/api/v9/channels/{ChannelId}/messages?limit=1&around={id}");
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*"));
request.Headers.AcceptLanguage.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("en-US"));
request.Headers.Authorization = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(Token);
request.Headers.Add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"91\", \"Chromium\";v=\"91\"");
request.Headers.Add("sec-ch-ua-mobile", "?0");
request.Headers.Add("sec-fetch-dest", "empty");
request.Headers.Add("sec-fetch-mode", "cors");
request.Headers.Add("sec-fetch-site", "same-origin");
request.Headers.Add("x-super-properties", "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiQ2hyb21lIiwiZGV2aWNlIjoiIiwic3lzdGVtX2xvY2FsZSI6ImVuLVVTIiwiYnJvd3Nlcl91c2VyX2FnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzkxLjAuNDQ3Mi4xMTQgU2FmYXJpLzUzNy4zNiBFZGcvOTEuMC44NjQuNTkiLCJicm93c2VyX3ZlcnNpb24iOiI5MS4wLjQ0NzIuMTE0Iiwib3NfdmVyc2lvbiI6IjEwIiwicmVmZXJyZXIiOiJodHRwczovL2Rpc2NvcmQuY29tLyIsInJlZmVycmluZ19kb21haW4iOiJkaXNjb3JkLmNvbSIsInJlZmVycmVyX2N1cnJlbnQiOiIiLCJyZWZlcnJpbmdfZG9tYWluX2N1cnJlbnQiOiIiLCJyZWxlYXNlX2NoYW5uZWwiOiJzdGFibGUiLCJjbGllbnRfYnVpbGRfbnVtYmVyIjo4OTEyOSwiY2xpZW50X2V2ZW50X3NvdXJjZSI6bnVsbH0=");
request.Headers.Referrer = new Uri($"https://discord.com/channels/{ServerId}/{ChannelId}/{id}");
request.Method = HttpMethod.Get;
var response = await _client.SendAsync(request);
response.EnsureSuccessStatusCode();
var str = await response.Content.ReadAsStringAsync();
Debug.WriteLine(str);
return JsonConvert.DeserializeObject<JArray>(str);
}
private Timer Heartbeat(int milliseconds)
{
var timer = new Timer(milliseconds);
timer.Elapsed += (sender, args) =>
{
Console.Beep();
_ws.Send(JsonConvert.SerializeObject(new { op = 1, d = (string)null }));
};
timer.AutoReset = true;
timer.Enabled = true;
timer.Start();
return timer;
}
}
}