buzzk 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -72
- package/lib/chat.js +168 -476
- package/lib/live.js +3 -11
- package/lib/tool.js +2 -3
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -9,16 +9,13 @@
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
뿌지직은 치지직 챗봇을 더욱 쉽게 개발할 수 있도록 돕는 비공식 라이브러리 입니다.
|
|
11
11
|
</p>
|
|
12
|
-
<p align="center">
|
|
13
|
-
챗봇 개발에 초점이 맞춰저 있어, 여러 계정에 동시 로그인할 수 없습니다.
|
|
14
|
-
</p>
|
|
15
12
|
|
|
16
13
|
---
|
|
17
14
|
|
|
18
15
|
## 📖 업데이트 내역
|
|
19
16
|
|
|
20
|
-
### 🎉 2.
|
|
21
|
-
- 공식 API
|
|
17
|
+
### 🎉 2.1 업데이트
|
|
18
|
+
- 공식 API로 거의 대부분의 작업을 대체했어요!
|
|
22
19
|
|
|
23
20
|
> [!CAUTION]
|
|
24
21
|
> 이 버전 이후부터는 공식 API와 비공식 API를 혼용하여 사용합니다.
|
|
@@ -29,6 +26,62 @@
|
|
|
29
26
|
> * 비공식 API로만 이루어진 모듈을 사용하시려면
|
|
30
27
|
> `npm install buzzk@1.11.3`
|
|
31
28
|
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ✒️ 마이그레이션 가이드 (v.2.0.x -> v.2.1.0)
|
|
32
|
+
|
|
33
|
+
<details>
|
|
34
|
+
<summary>펼쳐보기</summary>
|
|
35
|
+
|
|
36
|
+
buzzkChat
|
|
37
|
+
|
|
38
|
+
| <img src="https://github.com/Emin-G/Img/blob/main/tags/tag_change-min.png?raw=true" alt="BUZZK" width="70"> | new buzzkChat("channelID 값") |
|
|
39
|
+
|--|--|
|
|
40
|
+
| | new buzzkChat("accessToken 값") |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
| <img src="https://github.com/Emin-G/Img/blob/main/tags/tag_delete-min.png?raw=true" alt="BUZZK" width="70"> | 삭제 |
|
|
45
|
+
|--|--|
|
|
46
|
+
| | (chat).getRecentChat() |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
buzzkChat.onMessage
|
|
51
|
+
|
|
52
|
+
> 채팅 여러개를 한 번에 받던 구조 (data[o].message) (for 문으로 data[o] 돌리던 구조)에서
|
|
53
|
+
> 채팅 하나 씩 받는 구조로 변경 (data.message)
|
|
54
|
+
|
|
55
|
+
| <img src="https://github.com/Emin-G/Img/blob/main/tags/tag_delete-min.png?raw=true" alt="BUZZK" width="70"> | 삭제 |
|
|
56
|
+
|--|--|
|
|
57
|
+
| | (callback).author.imageURL |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
buzzkChat.onDonation
|
|
62
|
+
|
|
63
|
+
| <img src="https://github.com/Emin-G/Img/blob/main/tags/tag_delete-min.png?raw=true" alt="BUZZK" width="70"> | 삭제 |
|
|
64
|
+
|--|--|
|
|
65
|
+
| | (callback).author.imageURL |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
| <img src="https://github.com/Emin-G/Img/blob/main/tags/tag_delete-min.png?raw=true" alt="BUZZK" width="70"> | 삭제 |
|
|
70
|
+
|--|--|
|
|
71
|
+
| | (callback).time |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
buzzk.live
|
|
76
|
+
|
|
77
|
+
| <img src="https://github.com/Emin-G/Img/blob/main/tags/tag_delete-min.png?raw=true" alt="BUZZK" width="70"> | 삭제 |
|
|
78
|
+
|--|--|
|
|
79
|
+
| | buzzk.live.getAccess() |
|
|
80
|
+
|
|
81
|
+
</details>
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
32
85
|
## ✒️ 마이그레이션 가이드 (v.1.x -> v.2.0.0)
|
|
33
86
|
|
|
34
87
|
<details>
|
|
@@ -83,37 +136,37 @@
|
|
|
83
136
|
|
|
84
137
|
const buzzk = require("buzzk");
|
|
85
138
|
buzzk.auth("ClientID 값", "ClientSecret 값");
|
|
86
|
-
|
|
139
|
+
|
|
140
|
+
//buzzk.login("NID_AUT 쿠키 값", "NID_SES 쿠키 값");
|
|
141
|
+
//buzzk.channel.follow, unfollow 시에만 사용.
|
|
87
142
|
|
|
88
143
|
const buzzkChat = buzzk.chat;
|
|
89
144
|
|
|
90
145
|
async function test () {
|
|
91
146
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let channel = chSearch[0]; //검색 결과 첫번째 채널
|
|
95
|
-
|
|
96
|
-
const lvDetail = await buzzk.live.getDetail(channel.channelID); //현재 방송 정보
|
|
147
|
+
let oauth = buzzk.oauth.get("code 값");
|
|
97
148
|
|
|
98
|
-
let chat = new buzzkChat(
|
|
149
|
+
let chat = new buzzkChat(oauth.access);
|
|
99
150
|
await chat.connect(); //채팅창 연결
|
|
100
151
|
|
|
101
|
-
let recentChat = await chat.getRecentChat(); //최근 채팅 가져오기 (기본값 50개)
|
|
102
|
-
console.log(recentChat);
|
|
103
|
-
|
|
104
152
|
chat.onMessage(async (data) => { //채팅이 왔을 때
|
|
105
|
-
|
|
106
|
-
|
|
153
|
+
console.log(data.message);
|
|
154
|
+
|
|
155
|
+
if (data.message == "!ping") await chat.send("pong!");
|
|
156
|
+
//채팅 보내기
|
|
107
157
|
|
|
108
|
-
|
|
109
|
-
|
|
158
|
+
if (data.message == "!공지") await chat.setNotice("테스트!");
|
|
159
|
+
//채팅 상단 고정 (공지)
|
|
110
160
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
161
|
+
let userInfo = await chat.getUserInfo(data.author.id);
|
|
162
|
+
console.log(userInfo);
|
|
163
|
+
//채팅 보낸 유저의 정보
|
|
115
164
|
});
|
|
116
165
|
|
|
166
|
+
chat.onDonation(async (data) => { //후원이 왔을 때
|
|
167
|
+
//TODO
|
|
168
|
+
});
|
|
169
|
+
|
|
117
170
|
chat.onDisconnect(async () => { //채팅창 연결이 끊겼을 때
|
|
118
171
|
//TODO
|
|
119
172
|
});
|
|
@@ -144,6 +197,7 @@ dotenv와 함께 사용하는 것을 매우 권장합니다.
|
|
|
144
197
|
|
|
145
198
|
buzzk.login("NID_AUT 쿠키 값", "NID_SES 쿠키 값");
|
|
146
199
|
|
|
200
|
+
* 해당 함수는 이제 buzzk.channel.follow, unfollow 시에만 사용됩니다.
|
|
147
201
|
dotenv와 함께 사용하는 것을 매우 권장합니다.
|
|
148
202
|
|
|
149
203
|
buzzk.login(process.env.NID_AUT, process.env.NID_SES);
|
|
@@ -298,95 +352,69 @@ dotenv와 함께 사용하는 것을 매우 권장합니다.
|
|
|
298
352
|
|
|
299
353
|
### chat
|
|
300
354
|
|
|
355
|
+
✅ Official API
|
|
356
|
+
|
|
301
357
|
const buzzkChat = buzzk.chat;
|
|
302
|
-
let chat = new buzzkChat("
|
|
358
|
+
let chat = new buzzkChat("accessToken 값");
|
|
303
359
|
await chat.connect(); //채팅창 연결
|
|
304
360
|
|
|
305
361
|
>
|
|
306
362
|
|
|
307
|
-
|
|
308
|
-
console.log(recentChat);
|
|
309
|
-
|
|
310
|
-
<details>
|
|
311
|
-
<summary>return</summary>
|
|
312
|
-
|
|
313
|
-
- Return
|
|
314
|
-
- 0
|
|
315
|
-
- author
|
|
316
|
-
- id
|
|
317
|
-
- name
|
|
318
|
-
- imageURL
|
|
319
|
-
- hasMod //관리 권한을 가졌는지 (false / true)
|
|
320
|
-
- message
|
|
321
|
-
- time
|
|
322
|
-
- 1
|
|
323
|
-
- 2
|
|
324
|
-
- 3
|
|
325
|
-
- ...
|
|
326
|
-
|
|
327
|
-
</details>
|
|
363
|
+
✅ Official API
|
|
328
364
|
|
|
329
365
|
chat.onMessage((data) => { //채팅이 왔을 때
|
|
330
366
|
console.log(data);
|
|
331
|
-
|
|
332
|
-
for (let o in data) {
|
|
333
|
-
console.log(data[o].message); //메세지만 전부 꺼내기
|
|
334
|
-
}
|
|
335
367
|
});
|
|
336
368
|
|
|
337
369
|
<details>
|
|
338
370
|
<summary>callback</summary>
|
|
339
371
|
|
|
340
372
|
- Return
|
|
341
|
-
- 0
|
|
342
373
|
- author
|
|
343
374
|
- id
|
|
344
375
|
- name
|
|
345
|
-
- imageURL
|
|
346
376
|
- hasMod //관리 권한을 가졌는지 (false / true)
|
|
347
377
|
- message
|
|
378
|
+
- emoji (Object - Key: 이모지 이름, Value: 이미지 URL)
|
|
348
379
|
- time
|
|
349
|
-
- 1
|
|
350
|
-
- 2
|
|
351
|
-
- 3
|
|
352
|
-
- ...
|
|
353
380
|
|
|
354
381
|
</details>
|
|
355
382
|
|
|
383
|
+
>
|
|
384
|
+
|
|
385
|
+
✅ Official API
|
|
386
|
+
|
|
356
387
|
chat.onDonation((data) => { //도네이션이 왔을 때
|
|
357
388
|
console.log(data);
|
|
358
|
-
|
|
359
|
-
for (let o in data) {
|
|
360
|
-
console.log(data[o].amount); //후원 금액만 전부 꺼내기
|
|
361
|
-
}
|
|
362
389
|
});
|
|
363
390
|
|
|
364
391
|
<details>
|
|
365
392
|
<summary>callback</summary>
|
|
366
393
|
|
|
367
394
|
- Return
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
- time
|
|
377
|
-
- 1
|
|
378
|
-
- 2
|
|
379
|
-
- 3
|
|
380
|
-
- ...
|
|
395
|
+
- type //CHAT or VIDEO
|
|
396
|
+
- amount //후원 금액
|
|
397
|
+
- author
|
|
398
|
+
- id
|
|
399
|
+
- name
|
|
400
|
+
- hasMod //관리 권한을 가졌는지 (false / true)
|
|
401
|
+
- message
|
|
402
|
+
- emoji (Object - Key: 이모지 이름, Value: 이미지 URL)
|
|
381
403
|
|
|
382
404
|
</details>
|
|
383
405
|
|
|
406
|
+
>
|
|
407
|
+
|
|
408
|
+
✅ Official API
|
|
409
|
+
|
|
384
410
|
chat.onDisconnect(() => { //채팅창 연결이 끊겼을 때
|
|
385
411
|
//TODO
|
|
386
412
|
});
|
|
387
413
|
|
|
388
414
|
>
|
|
389
415
|
|
|
416
|
+
✅ Official API
|
|
417
|
+
|
|
390
418
|
await chat.send("ㅋㅋㅋㅋㅋㅋ"); //채팅 보내기 (login 후에만 가능)
|
|
391
419
|
|
|
392
420
|
>
|
package/lib/chat.js
CHANGED
|
@@ -1,33 +1,54 @@
|
|
|
1
1
|
const { getStatus } = require("./live.js");
|
|
2
|
-
const
|
|
3
|
-
const
|
|
2
|
+
const oauth = require("./oauth.js");
|
|
3
|
+
const { USER, reqGame } = require("./tool.js");
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const io = require("socket.io-client");
|
|
6
|
+
|
|
7
|
+
class chzzkChatData {
|
|
8
|
+
constructor(id, name, hasMod, message, emojis, time) {
|
|
9
|
+
this.author = {
|
|
10
|
+
id: id,
|
|
11
|
+
name: name,
|
|
12
|
+
hasMod: hasMod
|
|
13
|
+
};
|
|
14
|
+
this.message = message;
|
|
15
|
+
this.emojis = emojis;
|
|
16
|
+
this.time = time;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class chzzkDonationData {
|
|
21
|
+
constructor(type, amount, id, name, hasMod, message, emojis) {
|
|
22
|
+
this.type = type;
|
|
23
|
+
this.amount = amount;
|
|
24
|
+
this.author = {
|
|
25
|
+
id: id,
|
|
26
|
+
name: name,
|
|
27
|
+
hasMod: hasMod
|
|
28
|
+
};
|
|
29
|
+
this.message = message;
|
|
30
|
+
this.emojis = emojis;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
6
33
|
|
|
7
34
|
class chzzkChat {
|
|
8
|
-
constructor(
|
|
9
|
-
this.
|
|
35
|
+
constructor(accessToken) {
|
|
36
|
+
this.accessToken = accessToken;
|
|
10
37
|
}
|
|
11
38
|
|
|
12
39
|
//Private
|
|
13
40
|
#ws; //Chat Web Socket
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#callbacks = new Map();
|
|
41
|
+
#channelID;
|
|
42
|
+
#session;
|
|
43
|
+
#callbacks = {
|
|
44
|
+
message: null,
|
|
45
|
+
donation: null,
|
|
46
|
+
disconnect: null
|
|
47
|
+
};
|
|
22
48
|
|
|
23
49
|
#status = {
|
|
24
|
-
polling: false,
|
|
25
|
-
pollingID: 0,
|
|
26
|
-
ping: false,
|
|
27
|
-
pingID: 0,
|
|
28
50
|
ws: false,
|
|
29
|
-
|
|
30
|
-
reconnect: false
|
|
51
|
+
established: false
|
|
31
52
|
}
|
|
32
53
|
//Private
|
|
33
54
|
|
|
@@ -36,176 +57,105 @@ class chzzkChat {
|
|
|
36
57
|
*/
|
|
37
58
|
connect() {
|
|
38
59
|
return new Promise(async (resolve, reject) => {
|
|
39
|
-
|
|
40
|
-
if (this.#status.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!this.#status.polling) {
|
|
47
|
-
this.#status.polling = true;
|
|
48
|
-
this.#polling();
|
|
49
|
-
}
|
|
60
|
+
|
|
61
|
+
if (this.#status.ws) return resolve(false);
|
|
62
|
+
this.#status.ws = true;
|
|
63
|
+
|
|
64
|
+
let channel = await oauth.resolve(this.accessToken);
|
|
65
|
+
if (!channel) {
|
|
66
|
+
this.#status.ws = false;
|
|
50
67
|
return resolve(null);
|
|
51
68
|
}
|
|
52
|
-
this.#
|
|
53
|
-
//Get ChatID
|
|
69
|
+
this.#channelID = channel.channelID;
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//Load Balancing
|
|
73
|
-
this.#ssID = Math.floor(Math.random() * 10) + 1;
|
|
74
|
-
//Load Balancing
|
|
75
|
-
|
|
76
|
-
//Connect Web Socket
|
|
77
|
-
this.#ws = new WebSocket("wss://kr-ss" + this.#ssID + ".chat.naver.com/chat", {
|
|
78
|
-
handshakeTimeout: 60000
|
|
79
|
-
});
|
|
80
|
-
this.#status.wsID++;
|
|
81
|
-
//Connect Web Socket
|
|
71
|
+
let session = await USER.get(this.accessToken, "open/v1/sessions/auth");
|
|
72
|
+
if (!session || session.code != 200) {
|
|
73
|
+
this.#status.ws = false;
|
|
74
|
+
return resolve(null);
|
|
75
|
+
}
|
|
76
|
+
session = session.content.url;
|
|
77
|
+
console.log(session);
|
|
78
|
+
|
|
79
|
+
const socketOption = {
|
|
80
|
+
reconnection: false,
|
|
81
|
+
"force new connection": true,
|
|
82
|
+
"connect timeout": 6000,
|
|
83
|
+
transports: ["websocket"]
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
this.#ws = io.connect(session, socketOption);
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
this.#ws.on("connect", async (data) => {
|
|
89
|
+
console.log("[WS] Connected!");
|
|
90
|
+
});
|
|
86
91
|
|
|
87
|
-
this.#ws.on("
|
|
92
|
+
this.#ws.on("SYSTEM", async (data) => {
|
|
93
|
+
data = JSON.parse(data);
|
|
94
|
+
console.log(data);
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
if (data.type == "connected") {
|
|
97
|
+
this.#session = data.data.sessionKey;
|
|
98
|
+
|
|
99
|
+
let subChat = await USER.post(this.accessToken, "open/v1/sessions/events/subscribe/chat?sessionKey=" + this.#session, null);
|
|
100
|
+
let subDona = await USER.post(this.accessToken, "open/v1/sessions/events/subscribe/donation?sessionKey=" + this.#session, null);
|
|
101
|
+
if (!subChat || subChat.type == "subscribed" || !subDona || subDona.type == "subscribed") return resolve(null);
|
|
102
|
+
this.#status.established = true;
|
|
103
|
+
console.log("[WS] Subscribed!");
|
|
104
|
+
return resolve(true);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
//WS Message
|
|
108
|
+
this.#ws.on("CHAT", (data) => {
|
|
109
|
+
data = JSON.parse(data);
|
|
110
|
+
console.log(data);
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
let hasMod = false;
|
|
113
|
+
for (let o in data.profile.badges) {
|
|
114
|
+
if (data.profile.badges[o].imageUrl == "https://ssl.pstatic.net/static/nng/glive/icon/streamer.png") hasMod = true;
|
|
115
|
+
if (data.profile.badges[o].imageUrl == "https://ssl.pstatic.net/static/nng/glive/icon/manager.png") hasMod = true;
|
|
116
|
+
}
|
|
98
117
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
let chatData = new chzzkChatData(data.senderChannelId, data.profile.nickname, hasMod, data.content, data.emojis, data.messageTime);
|
|
119
|
+
if (!this.#callbacks.message) return;
|
|
120
|
+
return this.#callbacks.message(chatData);
|
|
121
|
+
});
|
|
102
122
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
this.#ws.on("DONATION", (data) => {
|
|
124
|
+
data = JSON.parse(data);
|
|
125
|
+
console.log(data);
|
|
106
126
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
127
|
+
let hasMod = false;
|
|
128
|
+
for (let o in data.profile.badges) {
|
|
129
|
+
if (data.profile.badges[o].imageUrl == "https://ssl.pstatic.net/static/nng/glive/icon/streamer.png") hasMod = true;
|
|
130
|
+
if (data.profile.badges[o].imageUrl == "https://ssl.pstatic.net/static/nng/glive/icon/manager.png") hasMod = true;
|
|
131
|
+
}
|
|
112
132
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"ver": "3",
|
|
116
|
-
"cmd": 100,
|
|
117
|
-
"svcid": "game",
|
|
118
|
-
"cid": this.#chatID,
|
|
119
|
-
"bdy": {
|
|
120
|
-
"uid": this.#uid,
|
|
121
|
-
"devType":2001,
|
|
122
|
-
"accTkn": this.#accTkn,
|
|
123
|
-
"auth":"SEND",
|
|
124
|
-
"devName": "BUZZK/" + vm.getVersion(),
|
|
125
|
-
"libVer": vm.getVersion(),
|
|
126
|
-
"locale": "ko",
|
|
127
|
-
"osVer": "Windows/10",
|
|
128
|
-
"timezone": "Asia/Seoul"
|
|
129
|
-
},
|
|
130
|
-
"tid": 1
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
this.#ws.send(JSON.stringify(connectOpt));
|
|
134
|
-
return resolve(true);
|
|
135
|
-
//WS First Connect
|
|
133
|
+
let donationData = new chzzkDonationData(data.donationType, data.payAmount, data.donatorChannelId, data.donatorNickname, hasMod, data.donationText, data.emojis);
|
|
134
|
+
return this.#callbacks.donation(donationData);
|
|
136
135
|
});
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
136
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (wsID != this.#status.wsID) return;
|
|
145
|
-
if (this.#status.reconnect) return;
|
|
146
|
-
this.#ws = null;
|
|
147
|
-
this.#status.ws = false;
|
|
148
|
-
this.#status.wsID++;
|
|
149
|
-
this.#status.ping = false;
|
|
150
|
-
this.#status.pingID++;
|
|
151
|
-
setTimeout(() => {
|
|
152
|
-
this.#status.reconnect = true;
|
|
153
|
-
this.connect();
|
|
154
|
-
}, 2000);
|
|
137
|
+
this.#ws.on("error", (error) => {
|
|
138
|
+
console.log(error);
|
|
139
|
+
|
|
140
|
+
if (!this.#status.established) return resolve(null);
|
|
155
141
|
});
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
142
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.#ws.on("message", async (data) => {
|
|
162
|
-
if (wsID != this.#status.wsID) return;
|
|
143
|
+
this.#ws.on("disconnect", (data) => {
|
|
144
|
+
console.log("[WS] Disconnected! (" + data + ")");
|
|
163
145
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
146
|
+
this.#status.ws = false;
|
|
147
|
+
this.#status.established = false;
|
|
148
|
+
this.#callbacks.disconnect(data);
|
|
167
149
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
150
|
+
if (this.#ws) this.#ws.off();
|
|
151
|
+
if (!this.#status.established) resolve(null);
|
|
171
152
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//Update Var
|
|
177
|
-
|
|
178
|
-
//Ping Pong
|
|
179
|
-
if (data.cmd == 0) {
|
|
180
|
-
let pongOpt = {
|
|
181
|
-
"ver": "2",
|
|
182
|
-
"cmd": 10000
|
|
183
|
-
};
|
|
184
|
-
this.#ws.send(JSON.stringify(pongOpt));
|
|
185
|
-
}
|
|
186
|
-
//Ping Pong
|
|
187
|
-
|
|
188
|
-
//Connected
|
|
189
|
-
else if (data.cmd == 10100) {
|
|
190
|
-
if (!this.#status.ping) {
|
|
191
|
-
this.#status.ping = true;
|
|
192
|
-
this.#status.pingID++;
|
|
193
|
-
this.#ping(this.#status.pingID);
|
|
194
|
-
}
|
|
195
|
-
if (!this.#status.polling) {
|
|
196
|
-
this.#status.polling = true;
|
|
197
|
-
this.#polling();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
for (let o in this.#callbacks) {
|
|
201
|
-
if (this.#callbacks[o].type === "message") this.#onMessageHandler(this.#status.wsID, this.#callbacks[o].callback);
|
|
202
|
-
else if (this.#callbacks[o].type === "donation") this.#onDonationHandler(this.#status.wsID, this.#callbacks[o].callback);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
resolve(true);
|
|
206
|
-
}
|
|
207
|
-
//Connected
|
|
153
|
+
this.#ws = null;
|
|
154
|
+
this.#channelID = null;
|
|
155
|
+
this.#session = null;
|
|
156
|
+
return this.connect();
|
|
208
157
|
});
|
|
158
|
+
|
|
209
159
|
});
|
|
210
160
|
}
|
|
211
161
|
|
|
@@ -215,299 +165,83 @@ class chzzkChat {
|
|
|
215
165
|
*/
|
|
216
166
|
send(message) {
|
|
217
167
|
return new Promise(async (resolve, reject) => {
|
|
218
|
-
if (!this.#status.ws) return resolve(
|
|
168
|
+
if (!this.#ws || !this.#status.ws || !this.#status.established) return resolve(false);
|
|
169
|
+
console.log(message);
|
|
219
170
|
|
|
220
|
-
let
|
|
221
|
-
|
|
222
|
-
"osType":"PC",
|
|
223
|
-
"streamingChannelId": this.channelID,
|
|
224
|
-
"emojis":"",
|
|
225
|
-
"extraToken": this.#extTkn
|
|
171
|
+
let reqMessage = {
|
|
172
|
+
message: message
|
|
226
173
|
}
|
|
227
174
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
let sendOpt = {
|
|
231
|
-
"ver": "3",
|
|
232
|
-
"cmd": 3101,
|
|
233
|
-
"svcid": this.#svcid,
|
|
234
|
-
"cid": this.#chatID,
|
|
235
|
-
"sid": this.#sid,
|
|
236
|
-
"retry": false,
|
|
237
|
-
"bdy": {
|
|
238
|
-
"msg": message,
|
|
239
|
-
"msgTypeCode": 1,
|
|
240
|
-
"extras": JSON.stringify(extras),
|
|
241
|
-
|
|
242
|
-
"msgTime": date.getTime()
|
|
243
|
-
},
|
|
244
|
-
"tid": 3
|
|
245
|
-
};
|
|
175
|
+
let chatRes = await USER.post(this.accessToken, "open/v1/chats/send", JSON.stringify(reqMessage));
|
|
176
|
+
if (!chatRes || chatRes.code != 200) return resolve(false);
|
|
246
177
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
178
|
+
return resolve(true);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
250
181
|
|
|
251
|
-
|
|
252
|
-
|
|
182
|
+
/**
|
|
183
|
+
* @param {string} message
|
|
184
|
+
* @returns {Promise<boolean>}
|
|
185
|
+
*/
|
|
186
|
+
setNotice(message) {
|
|
187
|
+
return new Promise(async (resolve, reject) => {
|
|
188
|
+
if (!this.#ws || !this.#status.ws || !this.#status.established) return resolve(false);
|
|
189
|
+
|
|
190
|
+
let reqMessage = {
|
|
191
|
+
message: message
|
|
253
192
|
}
|
|
254
193
|
|
|
255
|
-
|
|
256
|
-
|
|
194
|
+
let noticeRes = await USER.post(this.accessToken, "open/v1/chats/notice", JSON.stringify(reqMessage));
|
|
195
|
+
if (!noticeRes || noticeRes.code != 200) return resolve(false);
|
|
257
196
|
|
|
197
|
+
return resolve(true);
|
|
258
198
|
});
|
|
259
199
|
}
|
|
260
200
|
|
|
261
201
|
/**
|
|
262
|
-
* @typedef {Object
|
|
202
|
+
* @typedef {Object} chzzkMessage
|
|
203
|
+
* @property {chzzkAuthor} author
|
|
204
|
+
* @property {string} message
|
|
205
|
+
* @property {Object} emoji
|
|
206
|
+
* @property {number} time
|
|
263
207
|
*/
|
|
264
208
|
|
|
265
209
|
/**
|
|
266
|
-
* @typedef {Object}
|
|
267
|
-
* @property {
|
|
210
|
+
* @typedef {Object} chzzkDonation
|
|
211
|
+
* @property {string} type
|
|
212
|
+
* @property {string} amount
|
|
213
|
+
* @property {chzzkAuthor} author
|
|
268
214
|
* @property {string} message
|
|
215
|
+
* @property {Object} emoji
|
|
269
216
|
* @property {number} time
|
|
270
217
|
*/
|
|
271
218
|
|
|
272
219
|
/**
|
|
273
|
-
* @typedef {Object}
|
|
220
|
+
* @typedef {Object} chzzkAuthor
|
|
274
221
|
* @property {string} id
|
|
275
222
|
* @property {string} name
|
|
276
|
-
* @property {string} imageURL
|
|
277
223
|
* @property {boolean} hasMod
|
|
278
224
|
*/
|
|
279
225
|
|
|
280
226
|
/**
|
|
281
|
-
* @param {function(
|
|
227
|
+
* @param {function(chzzkMessage)} callback
|
|
282
228
|
*/
|
|
283
229
|
onMessage(callback) {
|
|
284
|
-
this.#callbacks
|
|
285
|
-
type: "message",
|
|
286
|
-
callback: callback
|
|
287
|
-
};
|
|
288
|
-
this.#onMessageHandler(this.#status.wsID, callback);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
#onMessageHandler (wsID, callback) {
|
|
292
|
-
if (!this.#status.ws) return callback(null);
|
|
293
|
-
|
|
294
|
-
this.#ws.on("message", async (data) => {
|
|
295
|
-
if (wsID != this.#status.wsID) return;
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
data = await JSON.parse(data);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
catch (error) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (data.cmd == 93101) {
|
|
306
|
-
|
|
307
|
-
data = data.bdy;
|
|
308
|
-
let msgList = new Map();
|
|
309
|
-
|
|
310
|
-
for (let o in data) {
|
|
311
|
-
if (data[o].profile) {
|
|
312
|
-
try {
|
|
313
|
-
data[o].profile = await JSON.parse(data[o].profile);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
catch (error) {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
data[o].profile = {
|
|
322
|
-
nickname: null,
|
|
323
|
-
profileImageUrl: null,
|
|
324
|
-
userRoleCode: null
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
let modVal = false;
|
|
330
|
-
if (data[o].profile.userRoleCode === "streamer" || data[o].profile.userRoleCode === "streaming_channel_manager" || data[o].profile.userRoleCode === "streaming_chat_manager") modVal = true;
|
|
331
|
-
|
|
332
|
-
msgList[Object.keys(msgList).length] = {
|
|
333
|
-
author: {
|
|
334
|
-
id: data[o].uid,
|
|
335
|
-
name: data[o].profile.nickname,
|
|
336
|
-
imageURL: data[o].profile.profileImageUrl,
|
|
337
|
-
hasMod: modVal
|
|
338
|
-
},
|
|
339
|
-
message: data[o].msg,
|
|
340
|
-
time: data[o].msgTime
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
catch(error) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
callback(msgList);
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
});
|
|
230
|
+
this.#callbacks.message = callback;
|
|
353
231
|
}
|
|
354
232
|
|
|
233
|
+
/**
|
|
234
|
+
* @param {function(chzzkDonation)} callback
|
|
235
|
+
*/
|
|
355
236
|
onDonation(callback) {
|
|
356
|
-
this.#callbacks
|
|
357
|
-
type: "donation",
|
|
358
|
-
callback: callback
|
|
359
|
-
};
|
|
360
|
-
this.#onDonationHandler(callback);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
#onDonationHandler (callback) {
|
|
364
|
-
if (!this.#status.ws) return callback(null);
|
|
365
|
-
|
|
366
|
-
this.#ws.on("message", async (data) => {
|
|
367
|
-
try {
|
|
368
|
-
data = await JSON.parse(data);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
catch (error) {
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (data.cmd == 93102) {
|
|
376
|
-
|
|
377
|
-
data = data.bdy;
|
|
378
|
-
let msgList = new Map();
|
|
379
|
-
|
|
380
|
-
for (let o in data) {
|
|
381
|
-
if (data[o].profile) {
|
|
382
|
-
try {
|
|
383
|
-
data[o].profile = await JSON.parse(data[o].profile);
|
|
384
|
-
data[o].extras = await JSON.parse(data[o].extras);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
catch (error) {
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
data[o].profile = {
|
|
393
|
-
nickname: null,
|
|
394
|
-
profileImageUrl: null,
|
|
395
|
-
userRoleCode: null
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
let modVal = false;
|
|
401
|
-
if (data[o].profile.userRoleCode === "streamer" || data[o].profile.userRoleCode === "streaming_channel_manager" || data[o].profile.userRoleCode === "streaming_chat_manager") modVal = true;
|
|
402
|
-
|
|
403
|
-
msgList[Object.keys(msgList).length] = {
|
|
404
|
-
amount: data[o].extras.payAmount,
|
|
405
|
-
author: {
|
|
406
|
-
id: data[o].uid,
|
|
407
|
-
name: data[o].profile.nickname,
|
|
408
|
-
imageURL: data[o].profile.profileImageUrl,
|
|
409
|
-
hasMod: modVal
|
|
410
|
-
},
|
|
411
|
-
message: data[o].msg,
|
|
412
|
-
time: data[o].msgTime
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
catch(error) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
callback(msgList);
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
});
|
|
237
|
+
this.#callbacks.donation = callback;
|
|
425
238
|
}
|
|
426
239
|
|
|
427
240
|
/**
|
|
428
241
|
* @param {function} callback
|
|
429
242
|
*/
|
|
430
243
|
onDisconnect(callback) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.#ws.on("close", () => {
|
|
434
|
-
callback();
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* @param {?number} size
|
|
440
|
-
* @return {Promise<chzzkMessages>}
|
|
441
|
-
*/
|
|
442
|
-
getRecentChat(size) {
|
|
443
|
-
return new Promise(async (resolve, reject) => {
|
|
444
|
-
if (!this.#status.ws) return resolve(null);
|
|
445
|
-
|
|
446
|
-
let reqOpt = {
|
|
447
|
-
"ver": "3",
|
|
448
|
-
"cmd": 5101,
|
|
449
|
-
"svcid": this.#svcid,
|
|
450
|
-
"cid": this.#chatID,
|
|
451
|
-
"sid": this.#sid,
|
|
452
|
-
"bdy": {
|
|
453
|
-
"recentMessageCount": size || 50
|
|
454
|
-
},
|
|
455
|
-
"tid": 2
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
this.#ws.send(JSON.stringify(reqOpt));
|
|
459
|
-
|
|
460
|
-
this.#ws.on("message", async (data) => {
|
|
461
|
-
try {
|
|
462
|
-
data = await JSON.parse(data);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
catch (error) {
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (data.cmd == 15101) {
|
|
470
|
-
data = data.bdy.messageList;
|
|
471
|
-
let msgList = new Map();
|
|
472
|
-
|
|
473
|
-
for (let o in data) {
|
|
474
|
-
try {
|
|
475
|
-
data[o].profile = await JSON.parse(data[o].profile);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
catch (error) {
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (!data[o].profile) {
|
|
483
|
-
data[o].profile = {
|
|
484
|
-
nickname: null,
|
|
485
|
-
profileImageUrl: null,
|
|
486
|
-
userRoleCode: null
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
let modVal = false;
|
|
491
|
-
if (data[o].profile.userRoleCode === "streamer" || data[o].profile.userRoleCode === "streaming_channel_manager" || data[o].profile.userRoleCode === "streaming_chat_manager") modVal = true;
|
|
492
|
-
|
|
493
|
-
msgList[Object.keys(msgList).length] = {
|
|
494
|
-
author: {
|
|
495
|
-
id: data[o].userId,
|
|
496
|
-
name: data[o].profile.nickname,
|
|
497
|
-
imageURL: data[o].profile.profileImageUrl,
|
|
498
|
-
hasMod: modVal
|
|
499
|
-
},
|
|
500
|
-
message: data[o].content,
|
|
501
|
-
time: data[o].messageTime
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return resolve(msgList);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
});
|
|
244
|
+
this.#callbacks.disconnect = callback;
|
|
511
245
|
}
|
|
512
246
|
|
|
513
247
|
/**
|
|
@@ -526,8 +260,11 @@ class chzzkChat {
|
|
|
526
260
|
*/
|
|
527
261
|
async getUserInfo (authorID) {
|
|
528
262
|
return new Promise(async (resolve, reject) => {
|
|
529
|
-
let
|
|
530
|
-
if (
|
|
263
|
+
let status = await getStatus(this.#channelID);
|
|
264
|
+
if (!status) return resolve(null);
|
|
265
|
+
|
|
266
|
+
let profileCard = await reqGame("nng_main/v1/chats/" + status.chatID + "/users/" + authorID + "/profile-card?chatType=STREAMING");
|
|
267
|
+
if (!profileCard || profileCard.code != 200) return resolve(null);
|
|
531
268
|
profileCard = profileCard.content;
|
|
532
269
|
|
|
533
270
|
let userInfo = {
|
|
@@ -551,60 +288,15 @@ class chzzkChat {
|
|
|
551
288
|
return new Promise(async (resolve, reject) => {
|
|
552
289
|
if (!this.#status.ws) return resolve(null);
|
|
553
290
|
|
|
291
|
+
await this.#ws.off();
|
|
554
292
|
await this.#ws.close();
|
|
555
|
-
|
|
293
|
+
|
|
556
294
|
this.#status.ws = false;
|
|
557
|
-
this.#status.
|
|
295
|
+
this.#status.established = false;
|
|
296
|
+
console.log("[WS] Disconnected! (Force)");
|
|
558
297
|
return resolve(true);
|
|
559
298
|
});
|
|
560
299
|
}
|
|
561
|
-
|
|
562
|
-
async #polling () {
|
|
563
|
-
if (!this.#status.polling) return;
|
|
564
|
-
|
|
565
|
-
//Get ChatID
|
|
566
|
-
let cidRes = await getStatus(this.channelID);
|
|
567
|
-
if (cidRes && cidRes.chatID && cidRes.chatID != this.#chatID) {
|
|
568
|
-
this.#chatID = cidRes.chatID;
|
|
569
|
-
|
|
570
|
-
//Reconnect
|
|
571
|
-
this.#status.reconnect = true;
|
|
572
|
-
await this.disconnect();
|
|
573
|
-
await this.connect();
|
|
574
|
-
//Reconnect
|
|
575
|
-
}
|
|
576
|
-
//Get ChatID
|
|
577
|
-
|
|
578
|
-
let interval = Math.floor(Math.random() * 20000) + 30000;
|
|
579
|
-
|
|
580
|
-
setTimeout(() => {
|
|
581
|
-
return this.#polling();
|
|
582
|
-
}, interval);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async #ping (pingID) {
|
|
586
|
-
if (!this.#status.ws) return this.#status.ping = false;
|
|
587
|
-
if (!this.#status.ping || pingID != this.#status.pingID) return;
|
|
588
|
-
|
|
589
|
-
let interval = Math.floor(Math.random() * 20000) + 30000;
|
|
590
|
-
|
|
591
|
-
let pingOpt = {
|
|
592
|
-
"ver": "3",
|
|
593
|
-
"cmd": 0
|
|
594
|
-
};
|
|
595
|
-
try {
|
|
596
|
-
await this.#ws.send(JSON.stringify(pingOpt));
|
|
597
|
-
}
|
|
598
|
-
catch (error) {
|
|
599
|
-
return setTimeout(async () => {
|
|
600
|
-
return this.#ping(pingID);
|
|
601
|
-
}, Math.floor(Math.random() * 20000) + 30000);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
setTimeout(() => {
|
|
605
|
-
return this.#ping(pingID);
|
|
606
|
-
}, interval);
|
|
607
|
-
}
|
|
608
300
|
}
|
|
609
301
|
|
|
610
302
|
module.exports = {
|
package/lib/live.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { reqChzzk
|
|
1
|
+
const { reqChzzk } = require("./tool.js");
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {Object} chzzkLiveDetail
|
|
@@ -38,6 +38,7 @@ async function getDetail (channelID) {
|
|
|
38
38
|
return new Promise(async (resolve, reject) => {
|
|
39
39
|
|
|
40
40
|
let res = await reqChzzk("service/v3/channels/" + channelID + "/live-detail");
|
|
41
|
+
console.log(res);
|
|
41
42
|
if (res.code != 200 || !res.content) return resolve(null);
|
|
42
43
|
res = res.content;
|
|
43
44
|
|
|
@@ -112,16 +113,7 @@ async function getStatus (channelID) {
|
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
async function getAccess (chatID) {
|
|
116
|
-
return new Promise(async (resolve, reject) => {
|
|
117
|
-
let accRes = await reqGame("nng_main/v1/chats/access-token?channelId=" + chatID + "&chatType=STREAMING");
|
|
118
|
-
if (accRes.code != 200) return resolve(null);
|
|
119
|
-
return resolve(accRes.content.accessToken);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
116
|
module.exports = {
|
|
124
117
|
getDetail: getDetail,
|
|
125
|
-
getStatus: getStatus
|
|
126
|
-
getAccess: getAccess
|
|
118
|
+
getStatus: getStatus
|
|
127
119
|
}
|
package/lib/tool.js
CHANGED
|
@@ -123,7 +123,6 @@ function reqChzzk (path) {
|
|
|
123
123
|
fetch(chzzkBaseURL + path, {
|
|
124
124
|
method: "GET",
|
|
125
125
|
headers: {
|
|
126
|
-
"Cookie": "NID_AUT=" + NID.AUT + ";NID_SES=" + NID.SES,
|
|
127
126
|
"User-Agent": "BuzzkLib/" + vm.getVersion() + " (Node)"
|
|
128
127
|
}
|
|
129
128
|
})
|
|
@@ -175,7 +174,7 @@ function reqGame (path) {
|
|
|
175
174
|
fetch(gameBaseURL + path, {
|
|
176
175
|
method: "GET",
|
|
177
176
|
headers: {
|
|
178
|
-
"
|
|
177
|
+
"User-Agent": "BuzzkLib/" + vm.getVersion() + " (Node)"
|
|
179
178
|
}
|
|
180
179
|
})
|
|
181
180
|
|
|
@@ -200,7 +199,7 @@ function reqNaver (path) {
|
|
|
200
199
|
fetch(naverBaseURL + path, {
|
|
201
200
|
method: "GET",
|
|
202
201
|
headers: {
|
|
203
|
-
"
|
|
202
|
+
"User-Agent": "BuzzkLib/" + vm.getVersion() + " (Node)"
|
|
204
203
|
}
|
|
205
204
|
})
|
|
206
205
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buzzk",
|
|
3
3
|
"displayName": "BUZZK",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.1",
|
|
5
5
|
"description": "뿌지직 (BUZZK) - 치지직(CHZZK) 챗봇을 더욱 쉽게 개발할 수 있도록 돕는 비공식 라이브러리.",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"type": "commonjs",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"뿌지직"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"socket.io-client": "^
|
|
35
|
-
"ws": "^8.18.0"
|
|
34
|
+
"socket.io-client": "^2.0.3"
|
|
36
35
|
}
|
|
37
36
|
}
|