@@ -952,11 +952,16 @@ class NodelinkServer extends EventEmitter {
952952
953953 let eventName = '/v4/websocket'
954954 let guildId = null
955+ let liveId = null
955956 try {
956957 const url = new URL ( ws . data . url )
957958 const voiceMatch = url . pathname . match (
958959 / ^ \/ v 4 \/ w e b s o c k e t \/ v o i c e \/ ( [ A - Z a - z 0 - 9 ] + ) \/ ? $ /
959960 )
961+ const liveMatch = url . pathname . match (
962+ / ^ \/ v 4 \/ w e b s o c k e t \/ y o u t u b e \/ l i v e \/ ( [ ^ / ] + ) \/ ? $ /
963+ )
964+
960965 if ( voiceMatch ) {
961966 if ( ! self . options . voiceReceive ?. enabled ) {
962967 try {
@@ -966,6 +971,9 @@ class NodelinkServer extends EventEmitter {
966971 }
967972 eventName = '/v4/websocket/voice'
968973 guildId = voiceMatch [ 1 ]
974+ } else if ( liveMatch ) {
975+ eventName = '/v4/websocket/youtube/live'
976+ liveId = liveMatch [ 1 ]
969977 }
970978 } catch { }
971979
@@ -975,7 +983,7 @@ class NodelinkServer extends EventEmitter {
975983 reqShim ,
976984 clientInfo ,
977985 sessionId ,
978- guildId
986+ guildId || liveId
979987 )
980988 } ,
981989 message ( ws , message ) {
@@ -1073,8 +1081,11 @@ class NodelinkServer extends EventEmitter {
10731081 const voiceMatch = pathname . match (
10741082 / ^ \/ v 4 \/ w e b s o c k e t \/ v o i c e \/ ( [ A - Z a - z 0 - 9 ] + ) \/ ? $ /
10751083 )
1084+ const liveMatch = pathname . match (
1085+ / ^ \/ v 4 \/ w e b s o c k e t \/ y o u t u b e \/ l i v e \/ ( [ ^ / ] + ) \/ ? $ /
1086+ )
10761087
1077- if ( pathname === '/v4/websocket' || voiceMatch ) {
1088+ if ( pathname === '/v4/websocket' || voiceMatch || liveMatch ) {
10781089 if ( ! headers [ 'user-id' ] ) {
10791090 logger (
10801091 'warn' ,
@@ -1110,8 +1121,16 @@ class NodelinkServer extends EventEmitter {
11101121 } connected from ${ clientAddress } | \x1b[33mURL:\x1b[0m ${ request . url } `
11111122 )
11121123
1113- const eventName = voiceMatch ? '/v4/websocket/voice' : '/v4/websocket'
1114- const guildId = voiceMatch ? voiceMatch [ 1 ] : null
1124+ let eventName = '/v4/websocket'
1125+ let routeId = null
1126+
1127+ if ( voiceMatch ) {
1128+ eventName = '/v4/websocket/voice'
1129+ routeId = voiceMatch [ 1 ]
1130+ } else if ( liveMatch ) {
1131+ eventName = '/v4/websocket/youtube/live'
1132+ routeId = liveMatch [ 1 ]
1133+ }
11151134
11161135 if ( isBun && ! this . _usingBunServer ) {
11171136 this . socket . handleUpgrade ( request , socket , head , ( ws ) => {
@@ -1121,7 +1140,7 @@ class NodelinkServer extends EventEmitter {
11211140 request ,
11221141 clientInfo ,
11231142 sessionId ,
1124- guildId
1143+ routeId
11251144 )
11261145 } )
11271146 } else {
@@ -1132,7 +1151,7 @@ class NodelinkServer extends EventEmitter {
11321151 request ,
11331152 clientInfo ,
11341153 sessionId ,
1135- guildId
1154+ routeId
11361155 )
11371156 )
11381157 }
@@ -1169,6 +1188,65 @@ class NodelinkServer extends EventEmitter {
11691188 this . registerVoiceSocket ( guildId , socket )
11701189 }
11711190 )
1191+
1192+ this . socket . on (
1193+ '/v4/websocket/youtube/live' ,
1194+ ( socket , request , _clientInfo , _sessionId , id ) => {
1195+ let videoId = id
1196+
1197+ if ( / ^ \d { 17 , 20 } $ / . test ( id ) ) {
1198+ const player = this . sessions . getPlayer ( id )
1199+ if ( player ?. track ?. info ?. sourceName ?. includes ( 'youtube' ) ) {
1200+ videoId = player . track . info . identifier
1201+ }
1202+ }
1203+ else if ( id . length > 50 ) {
1204+ try {
1205+ const decoded = decodeTrack ( id )
1206+ if ( decoded ?. info ?. sourceName ?. includes ( 'youtube' ) ) {
1207+ videoId = decoded . info . identifier
1208+ }
1209+ } catch ( _e ) { }
1210+ }
1211+
1212+ if ( ! this . sourceWorkerManager ) {
1213+ const yt = this . sources . getSource ( 'youtube' )
1214+ if ( ! yt ) {
1215+ socket . close ( 1008 , 'YouTube source not enabled' )
1216+ return
1217+ }
1218+ yt . handleLiveChat ( socket , videoId )
1219+ return
1220+ }
1221+
1222+ logger ( 'info' , 'YouTube-LiveChat' , `Delegating live chat for video: ${ videoId } to worker` )
1223+
1224+ const resShim = {
1225+ headersSent : false ,
1226+ send : ( data ) => {
1227+ const payload = Buffer . isBuffer ( data ) ? data : Buffer . from ( String ( data ) )
1228+ socket . sendFrame ( payload , { len : payload . length , fin : true , opcode : Buffer . isBuffer ( data ) ? 0x02 : 0x01 } )
1229+ } ,
1230+ writeHead : ( status ) => {
1231+ if ( status !== 200 ) socket . close ( 1011 , 'Worker failed' )
1232+ } ,
1233+ write : ( data ) => {
1234+ const payload = Buffer . isBuffer ( data ) ? data : Buffer . from ( String ( data ) )
1235+ socket . sendFrame ( payload , { len : payload . length , fin : true , opcode : Buffer . isBuffer ( data ) ? 0x02 : 0x01 } )
1236+ } ,
1237+ end : ( ) => socket . close ( 1000 , 'Finished' ) ,
1238+ on : ( event , cb ) => socket . on ( event , cb )
1239+ }
1240+
1241+ this . sourceWorkerManager . delegate (
1242+ request ,
1243+ resShim ,
1244+ 'loadLiveChat' ,
1245+ { videoId } ,
1246+ { isWebSocket : true }
1247+ )
1248+ }
1249+ )
11721250 }
11731251
11741252 _listen ( ) {
0 commit comments