-
Notifications
You must be signed in to change notification settings - Fork 2
/
ChzzkVideoDonationUnity.cs
396 lines (334 loc) · 10.4 KB
/
ChzzkVideoDonationUnity.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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using WebSocketSharp;
using System.Collections;
using Cysharp.Threading.Tasks;
public class ChzzkVideoDonationUnity : MonoBehaviour
{
#region Variables
//WSS(WS 말고 WSS) 쓰려면 필요함.
private enum SslProtocolsHack
{
Tls = 192,
Tls11 = 768,
Tls12 = 3072
}
WebSocket socket = null;
float timer = 0f;
bool running = false;
private const string HEARTBEAT_REQUEST = "2";
private const string HEARTBEAT_RESPONSE = "3";
#region Callbacks
/// <summary>
/// 영상 도네이션 도착시 호출되는 이벤
/// </summary>
public UnityEvent<Profile, VideoDonation> onVideoDonationArrive = new();
/// <summary>
/// 영상 도네이션 도착시 호출되는 이벤
/// </summary>
public UnityEvent<DonationControl> onVideoDonationControl = new();
/// <summary>
/// 웹소캣이 열렸을 때 호출되는 이벤트
/// </summary>
public UnityEvent onClose = new();
/// <summary>
/// 웹소캣이 닫혔을 때 호출되는 이벤트
/// </summary>
public UnityEvent onOpen = new();
#endregion Callbacks
#endregion Variables
int closedCount = 0;
bool reOpenTrying = false;
#region Unity Methods
// Start is called before the first frame update
void Start()
{
}
private void Update()
{
if (closedCount > 0)
{
onClose?.Invoke();
if (!reOpenTrying)
StartCoroutine(TryReOpen());
closedCount--;
}
if (running)
{
timer += Time.unscaledDeltaTime;
if (timer > 15)
{
socket.Send(HEARTBEAT_REQUEST);
timer = 0;
}
}
}
private void OnDestroy()
{
StopListening();
}
#endregion Unity Methods
#region Debug Methods
#endregion Debug Methods
#region Public Methods
/// <summary>
/// 전체 URL에서 필요한 ID만 추출
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// https://chzzk.naver.com/mission-donation/mission@<MissionWSSID>
public string GetMissionWSSId(string url)
{
return url.Split("@")[1];
}
/// <summary>
/// SessionURL을 받아옴
/// </summary>
/// <param name="missionWSSId">GetMissionWSSId 함수의 값을 사용</param>
/// <returns></returns>
public async UniTask<string> GetSessionURL(string missionWSSId)
{
var url = $"https://api.chzzk.naver.com/manage/v1/alerts/video@{missionWSSId}/session-url";
var request = UnityWebRequest.Get(url);
await request.SendWebRequest();
SessionUrl sessionUrl = null;
//Debug.Log(request.downloadHandler.text);
if (request.result == UnityWebRequest.Result.Success)
{
//Cid 획득
sessionUrl = JsonUtility.FromJson<SessionUrl>(request.downloadHandler.text);
}
return sessionUrl.content.sessionUrl;
}
/// <summary>
/// SessionURL에서 Auth를 추출
/// </summary>
/// <param name="sessionURL">GetSessionURL 참고</param>
/// <returns></returns>
public string MakeWssURL(string sessionUrl)
{
string auth = sessionUrl.Split("auth=")[1];
string server = sessionUrl.Split(".nchat")[0].Substring(12);
return $"wss://ssio{server}.nchat.naver.com/socket.io/?auth={auth}&EIO=3&transport=websocket";
}
string wssUrl;
public async UniTask<string> GetWssUrlFromMissionUrl(string missionUrl)
{
string wssId = GetMissionWSSId(missionUrl);
string sessionUrl = await GetSessionURL(wssId);
return MakeWssURL(sessionUrl);
}
public async void Connect(string url)
{
wssUrl = await GetWssUrlFromMissionUrl(url);
Connect().Forget();
}
public async UniTask Connect()
{
if (socket != null && socket.IsAlive)
{
socket.Close();
socket = null;
}
socket = new WebSocket(wssUrl);
//wss라서 ssl protocol을 활성화 해줘야 함.
var sslProtocolHack = (System.Security.Authentication.SslProtocols)(SslProtocolsHack.Tls12 | SslProtocolsHack.Tls11 | SslProtocolsHack.Tls);
socket.SslConfiguration.EnabledSslProtocols = sslProtocolHack;
//이벤트 등록
socket.OnMessage += ParseMessage;
socket.OnClose += CloseConnect;
socket.OnOpen += onSocketOpen;
//연결
socket.Connect();
await UniTask.CompletedTask;
}
void onSocketOpen(object sender, EventArgs e)
{
timer = 0;
running = true;
socket.Send(HEARTBEAT_REQUEST);
}
public void StopListening()
{
if (socket == null) return;
socket.Close();
socket = null;
}
#endregion Public Methods
#region Socket Event Handlers
private void ParseMessage(object sender, MessageEventArgs e)
{
Debug.Log(e.Data);
if (e.Data == HEARTBEAT_REQUEST)
{
timer = 0;
socket.Send(HEARTBEAT_RESPONSE);
return;
}
else if (e.Data == HEARTBEAT_RESPONSE)
{
return;
}else if(e.Data == "40")
{
return;
}
VideoDonationList donations = JsonUtility.FromJson<VideoDonationList>(e.Data);
switch (donations.videoDonation[0])
{
case "donation":
//List<KeyValuePair<Profile, VideoDonation>> donationList = new List<KeyValuePair<Profile, VideoDonation>>();
for (int i = 1; i < donations.videoDonation.Count; i++)
{
VideoDonation donationObject = JsonUtility.FromJson<VideoDonation>(donations.videoDonation[i]);
Debug.Log(donationObject);
Profile profile = JsonUtility.FromJson<Profile>(donationObject.profile);
//donationList.Add(new KeyValuePair<Profile, VideoDonation>(profile, donationObject));
onVideoDonationArrive.Invoke(profile, donationObject);
}
/*
foreach (KeyValuePair<Profile, VideoDonation> donation in donationList)
{
if (activeVideo.ContainsKey(donation.Value.donationId))
{
activeVideo[donation.Value.donationId] = donation;
}
else
{
activeVideo.Add(donation.Value.donationId, donation);
}
}*/
break;
case "donationControl":
List<DonationControl> controlList = new List<DonationControl>();
for (int i = 1; i < donations.videoDonation.Count; i++)
{
DonationControl controlObject = JsonUtility.FromJson<DonationControl>(donations.videoDonation[i]);
Debug.Log(controlObject);
controlList.Add(controlObject);
onVideoDonationControl.Invoke(controlObject);
}
break;
}
}
Dictionary<string, KeyValuePair<Profile, VideoDonation>> activeVideo;
private void CloseConnect(object sender, CloseEventArgs e)
{
Debug.LogError("연결이 해제되었습니다");
Debug.Log(e.Reason);
Debug.Log(e.Code);
Debug.Log(e);
closedCount += 1;
}
#endregion Socket Event Handlers
#region Private Methods
private IEnumerator TryReOpen()
{
reOpenTrying = true;
yield return new WaitForSeconds(1);
if (!socket.IsAlive)
{
socket.Connect();
}
reOpenTrying = false;
}
#endregion
#region Sub-classes
[Serializable]
public class SessionUrl
{
public string code;
public object message;
public Content content;
[Serializable]
public class Content
{
public string sessionUrl;
}
}
public class DonationControl
{
int startSecond;
int endSecond;
bool stopVideo;
bool titleExpose;
string donationId;
int payAmount;
bool isAnonymous;
bool useSpeech;
}
[Serializable]
public class VideoDonationList
{
public List<string> videoDonation;
}
[Serializable]
public class VideoDonation
{
public int startSecond;
public int endSecond;
public string videoType;
public string videoId;
public string playMode;
public bool stopVideo;
public bool titleExpose;
public string donationId;
public string profile;
public int payAmount;
public string donationText;
public bool isAnonymous;
public int tierNo;
public bool useSpeech;
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
[Serializable]
public class Profile
{
public string userIdHash;
public string nickname;
public string profileImageUrl;
public string userRoleCode;
public string badge;
public string title;
public bool verifiedMark;
public List<ActivityBadge> activityBadges;
[Serializable]
public class ActivityBadge
{
public int badgeNo;
public string badgeId;
public string imageUrl;
public bool activated;
}
public StreamingProperty streamingProperty;
[Serializable]
public class StreamingProperty
{
public Subscription subscription;
[Serializable]
public class Subscription
{
public int accumulativeMonth;
public int tier;
public Badge badge;
public class Badge
{
public string imageUrl;
}
}
public NicknameColor nicknameColor;
[Serializable]
public class NicknameColor
{
public string colorCode;
}
}
}
#endregion Sub-classes
}