From 367be7dc11951bddc91ffb318abcc3c17283c169 Mon Sep 17 00:00:00 2001 From: Nakiri Date: Sun, 1 Jun 2025 16:27:34 +0700 Subject: [PATCH] feat: websocket auto detect server info change --- apps/backend/src/index.ts | 85 +++++++++++++--- bun.lockb | Bin 105152 -> 105168 bytes docker-compose.yml | 14 +++ .../cafe/kirameki/minikuraVelocity/Main.kt | 33 ++++++- .../MinikuraWebSocketClient.kt | 91 +++++++++++++++++- .../datastore/ServerDataStore.kt | 4 +- .../listeners/ProxyTransferHandler.kt | 4 +- .../models/CustomEnvironmentVariableData.kt | 15 +++ .../models/ReverseProxyServerData.kt | 12 ++- .../minikuraVelocity/models/ServerData.kt | 12 ++- .../components/ReverseProxyServerType.kt | 5 + .../models/components/ServerType.kt | 5 + .../utils/ProxyTransferUtils.kt | 4 +- 13 files changed, 248 insertions(+), 36 deletions(-) create mode 100644 docker-compose.yml create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/CustomEnvironmentVariableData.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ReverseProxyServerType.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ServerType.kt diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 16ba27c..0fbcd20 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -36,6 +36,28 @@ const bootstrap = async () => { console.log("Default user created"); }; + +const connectedClients = new Set(); +const broadcastServerChange = (action: string, serverType: string, serverId: string) => { + const message = { + type: "SERVER_CHANGE", + action, + serverType, + serverId, + timestamp: new Date().toISOString(), + }; + + connectedClients.forEach(client => { + try { + client.send(JSON.stringify(message)); + } catch (error) { + console.error("Error sending WebSocket message:", error); + connectedClients.delete(client); + } + }); + console.log(`Notified Velocity proxy: ${action} ${serverType} ${serverId}`); +}; + const app = new Elysia() .use(swagger({ path: '/swagger', @@ -46,6 +68,26 @@ const app = new Elysia() } } })) + .ws("/ws", { + open(ws) { + const apiKey = ws.data.query.apiKey; + if (!apiKey) { + console.log("apiKey required"); + ws.close(); + return; + } + + connectedClients.add(ws); + console.log("Velocity proxy connected via WebSocket"); + }, + close(ws) { + connectedClients.delete(ws); + console.log("Velocity proxy disconnected from WebSocket"); + }, + message(ws, message) { + console.log("Received message from Velocity proxy:", message); + }, + }) .group('/api', app => app .derive(async ({ headers, cookie: { session_token }, path }) => { // Skip token validation for login route @@ -150,19 +192,6 @@ const app = new Elysia() }), } ) - .ws("/ws", { - body: t.Object({ - message: t.String(), - }), - message(ws, { message }) { - const { id } = ws.data.query; - ws.send({ - id, - message, - time: Date.now(), - }); - }, - }) .post("/logout", async ({ session, cookie: { session_token } }) => { if (!session) return { success: true }; @@ -175,6 +204,24 @@ const app = new Elysia() }; }) .get("/servers", async ({ session }) => { + // Broadcast to all connected WebSocket clients + const message = { + type: "test", + endpoint: "/servers", + timestamp: new Date().toISOString(), + }; + + connectedClients.forEach(client => { + try { + client.send(JSON.stringify(message)); + } catch (error) { + console.error("Error sending WebSocket message:", error); + connectedClients.delete(client); + } + }); + + console.log(`/servers API called, notified ${connectedClients.size} WebSocket clients`); + return await ServerService.getAllServers(!session); }) .get("/servers/:id", async ({ session, params: { id } }) => { @@ -205,6 +252,8 @@ const app = new Elysia() memory: body.memory, }); + broadcastServerChange("CREATE", "SERVER", server.id,); + return { success: true, data: { @@ -252,6 +301,8 @@ const app = new Elysia() const newServer = await ServerService.getServerById(id, !session); + broadcastServerChange("UPDATE", "SERVER", server.id); + return { success: true, data: { @@ -280,6 +331,8 @@ const app = new Elysia() where: { id }, }); + broadcastServerChange("DELETE", "SERVER", server.id); + return { success: true, }; @@ -314,6 +367,8 @@ const app = new Elysia() memory: body.memory, }); + broadcastServerChange("CREATE", "REVERSE_PROXY_SERVER", server.id); + return { success: true, data: { @@ -371,6 +426,8 @@ const app = new Elysia() !session ); + broadcastServerChange("UPDATE", "REVERSE_PROXY_SERVER", server.id); + return { success: true, data: { @@ -404,6 +461,8 @@ const app = new Elysia() where: { id }, }); + broadcastServerChange("DELETE", "REVERSE_PROXY_SERVER", server.id); + return { success: true, }; diff --git a/bun.lockb b/bun.lockb index df22dccd291c22b6020981cf7843c7a5b8d6589f..265b2312b6cfa2c60dd865bf4e50d082096812a6 100644 GIT binary patch delta 6418 zcmaJ_30PD|wypvjxPXcXLfay?Xp~l(wP~<{%j%AZJENj4Ap2s28e#-u;!K7ZA+Nr% z!nh!6)M$(-8eB6*Oy3zByAv3;wyK8OP8Q&p!$$PS% zxYo-<2=OGOp$)PRB-i-(p@jH>$3u$tm1y^c41x@ReDD$>Jt3zJBcwOvI7lzZWJn3J z)?QXtTwOy-Dys_!J~yD>3+DX&uaia{!GVtaG}_U%#1DjQlLh4XJgiYTyX zRjZ`B%28ZSNdFj>`#^f4e@TJEQRyJ$61Z@>y1J;U6!!O7RK8u4tt`|nyuJ=rMTiz@ z(k)K4VlP>u__P7MKl&RXMNXGN3U3xWio;5rBJ zf(!4Qsfq_jXxUe8+4Ld{b(ib?lGXKkL5iS1kky&HA$`DWs>3QU5!swdNDz2v8XmkLsN=?qwWR@mVAVoZ_knqfXe7UBeLKIwH zQDtdyzTFX*skWOmc@9!+oM1>XTXA(*VOiw@vVM|k&rx8{&o6dV8M0I_>=m`;_L{t+ z1rB>&K{ebziw-fd&t%o`f)1l}YB1Z9Hk|p43{<7BN5`y@$6V<+rjL;6UX~Lf>z-nV zCa|CYo0N;ai@^=9tb3SM`Vee97-e?7ReB6I5v&Jm^Rv=)))FbxXRIqymh!NJN25o_ z+Cr?-`(R06F3cEUm2QHK0&^2KbjIO?#Im`18=cAY(X#X=cshDrnbBmWU$7Rm#fB1s zEMOkHzm+a$`WTrWWjQgj6lPLAfaR#T9w0GnuE{2SgC}GYZg%@xCF2Oy88~gS>KtH6 zY<-ALYR8kN6=2n+j)a!d{wAIxl+nW717N~k%8Utd9zsba{#GeH%-Piq#X2yQEEEw9 z>0_`N3d5w5pIP-2eKX=bKvW}bfmUfBm{_nDQZNxJ9kYj6X)NoClW8^6$IH@=a6+)> z8VEBEw9@OW1#Q7_Q>?&U39MaX7^}C^%`9h>EZstzu&0DmdMT0+OaL3*2;?-Tx5>1L z<={U8ZIfMGqS#eil5`k*auU>#J*-5+IV0S4QF56Drn)PFvjI$)SDpVF42O+ip;l=q z9z~zxu#^Xup!6YDw3T%w%F?%JQzM2L(K42kB62wTh2%qEHtkPe>gcpcNkd^jhU8%A(3pFCv0$+w$rFS)k zS;DOJ3TsJ|UEFQ#YFd&m&4!|3*M`}o<>E;l(%&j|g8i~IdXIIb%hGUoDyDT6l`Aa- z6U#*|Q2}dMONK0+LK|vQY^E5iL=&-@z_1~)?W|y$n@D3Nn6RZ(hqM_?R0769^J_4X zDka6VKg$^->!zR~PDw3tSMoXlEBpQ;#hbeOz68Shr}>MMHCZk z!;8gWV$E0)QnH)1jFTl@3L*H@+8#0ecv<=qWjzyZgtb90m9?PlI1ctHXhV60VN0c} zM-dW)Qpg3Htk}RTx(~sUSkPFTbVocXV~i*<6Vp$WX|eb(ZO{0nkg=8TfFYE^_82Q2 z&h(kGRF0}gMK^e`ZrLhr1QXH6A%t9902A>;L7+UIfr%_2Gblt8%b6tWR^h}-VAoP@ z(rG-YJ_t|U@QtBHt~=5yjRF%^-NlZSR)gUWUWi!z1&``D5mi5Yv#61S6*`yYOqQi1 zXcJx|O3;4{hUJPiz{3Qb(`xpOW35t+#?S{JJ^&LwD4M!&!6I1DKpQnM{S;YhoIuDz zwHL9!&swI)bR_FS&!UOy3^+fq`S*k2q|hRA6HLs97+?d8#y6JmMai?&s4?{j`34MA zq9TNqP!xw)rB2CXA(&dh$d_`}%5ZPE=qy|6w6Uuz;3$_A8j!PGvHi#1?kZR&}4 z2~0!?CnVxK5G5kKQVup++R-&*j-`HTPF~`L^sRkrT6<0$oM{X>*!%U)jGCWU-5IlO z;MrGZw-m+HE<5+Zp`q^2(>HEhKkm`+giBkKs|GwdIb`hVkk*&WC?S+Q>-`L}n7sJ( zuK-1708e{Y^TC98gL+rkR6CvE2^X*<#{Ub*<{*!v3--d?pJ%<;8;NvyZwvgd@G z_j7g*_{$y3Z!a&W%MaAZ@89V7^QwE}=^S?SE1T{IETR|7KHtQAe0#9wD;~Pz2%0Ba z0$9W49vug+yg_MxN8;6GROf^Id7-D1ZM^)rqwLz{7~ck8c!$pieO2k1z-Ri>NWRLK zo-X-VbJ|Z?iSnjFI<+=`t<4*Jzt+}IYwHOfuG%93T3au)nYFe+t?i07X+j`R3Z#Si zg}$^Z2q_YqOC(z)kKouRIIcxOkw`w*j|PVc`#yk}yEh;Vn|dw~N$JFo-T348#w0UH679YGPB=0hm>To!%dh24El%3=9DDKoGDBcpF#^GytyyuK_E7=~!ANP!22t z96$w74ODS`KN?>D3Dk}QCxDZ{Y2Xa-84wD!e?Z;?9s-`cXrD zE!39*i-Dy;DKHdjln<=;0AaAI=~wcHQpQW0eS&F0kNU_0Ly_qAfI;y&|nuY z${z(#Q_v~cT?_mZh(LEVP$V`DKJz;h1F6AHYR1IkGpEx&koxv^u@IgeN&E5(QPg8w za~O3=j2WW-HfV*G7t+5W>8)2z-s+n>nC3>Bqs@_##QF2Q`O$kD4+XidKuzS?~8s zcIW!0ySt$mWsb3$BgqYZPv|}1QWTA*$viO%>dxDOMn}ofx#Mqtrl>~=Z>I4@LeGo8 z2Vrnt4jjK`Kfm&?qYW-p)Rp*z@E?TUkGwvC_8S;&A)-gb(D+~Bxx;deHx*B#-~jRF zv!gMm^J1Y_@|y1q%cjm!=0pq#spU;V!Fi)_XLn+o-~Q>tp%7_~GUJqM;wPfvjq@sE zZOhsIi^>mpDGE>_q>%TC!6F~>5fBFTYJoHawk~UVd;7E~MK40QqRwP7j8uPdniEbG z+-jUULo)=0|9c7(-xY%e7xHchgY(v7&Ct^Yi+!h_&@5mv4|%vntoGNYQ1Y$%0AH6w zkE)EfTWEjn`TtqwR{q>VGwB7M8H=G;d0{LHC!S|p!Mu1WgyF|f^;c^+WjyzD`#)T0 z9g<*6wwn-LSZ}K z9ETM${vZxfc3#KCT(91tTk^QTg^FznHNvEeUi#y~wi?>00_K|L;Wl8g1m|Nwkoz-B>Bncv^fgNuemZ!HO?<@fX4c>3 zjj#}>`lj9RG!#!L^$-7#-!~{>P`pmzJ(H1B=ao>#^~6t)th;eYQHU_(y<{FwfdZ}M zvxT1XO36L3=QoX85+XD`Y~m)~1Oj{MjbDT)(F?c6(wQ|ns`L9j% z{LYUiC5pnbM4aK*l2JCHd_)SSaNc6gAD8Df`1jMV!UC$s98JhJJ}(7zwG_W9#98&m z^Zgrs3iPkge83bd_^uShwSSF&CI@(Irrh2?iUJs`JPl-XmQ?WZ7x}|xc1rXAShrD#A2P!8?V>f3(lM3 zvkq!Q5y?`XnMU^;oL5>KetS4=`_o=qQL`3g2_ZD`pmc;fj$6}FlIM7-5bJp>8ZFL? zuIcmdJpWg-{gE=EazOuT4>l6sF8N&yH8`)r5`Q?h=d6#V2t)DJB1+bW8#6HB0&asC z)-K)+@wqSF74h**s4ghSU8*?)w{?yRd)d?)M{z|Ve>sB=cSS$nkU_6!55g7{MfXqub|yyPvA{-}u(P`e4$+J(Iz`p?~A6X-6h6 zTvaxQUmQ(?X$*g$iaulTYC1YaP5B=_3Tp57yY`{k;G&kMEu1#T^NoGSe*=!!!G`*^ zqrSRzJag+(O~3ZwN5iwfxPN5@cyIJiN}YMmaXh~19j!l3ir=(pXNGy5*57_@Y2TD| z!uw1R~ONbT>Y0;I!ddna8)_VByM!W78c;{lJ^-$ z4V}ehG<&2(TGa6#d{GM>9R2-{hea`)hT6tz%`a>6mlVw?a8wspR+u%JG$@8&e3$yO o>vtoh?V9ohVJmBB;2%}f$(sDd##ANST9W_PLPNO6AL)Vr0YAdc82|tP delta 6198 zcmZ`-dt6mj_TL-1z`+NI2wbm-fTuU%lc_gBK@o^c@pF7!OlI=) z^ZnaR8&Xp&$6q?8Mrh91h?Y5~nNFHw!xTALPQKE#)P6SKbI<$KDT}8GVAzWENej^-KQu&Px{xf1H2)ddJHjtf@{r)S0|* z;{7h3)B%hIGFJZ*WKYP|Cf6QgEEL=hN%g-)eF$VEWH{uNfsA#B96N}yAjoXUu8?Vv z3gjYJMMb&0md&qnmooe=Lca&^^g>W zl_`eSe0OzC`9id%8r%XIi1z17Yig=$7&`$@PP^S@)eB(%^)!QTHsy;V&M(R1hE)pD zTvI+&4J)qs&*?si495DP-2+K$S`A6wJX=$qI3M$*bgZv#NElva4$Q`dQDW=RbWjZ9qGXWBw`P9dVtNw#}dN5oFs_RA@4p)fi zfbcl?7{gc@BrVx2NOE%ISR))eAZdC{!WEF@nf*9&32Yi9)sBIrp`L+llK)j(T1g2v zr>ts0d5NngvB0R0GUdCFv~fZqX|!^8;=GEgr`i8ZFznTox=KpQYpR1M8eX_67cF$v z&MAAk#xz+^I~){)K`1E~0ags=Nv|4vY(zRaLHojR*4;&7msK6ip6|-x2K&O)0`I z9z|n|=G2S;kZcei5go27{{R~X=BJwo8_Za`ZX!B2Ls(NZWhrX%P}4<3$E*B^XhPkP zIL5FP7|7LITfk^$zM?Z%RXzqIFA$MPm3I~GBQ&J|NkZPjmjSA>#$*^J zQRN?srjeS@kHf^Jk=d3ECz3{V400+pR5H5sQI$V|{X8}PFVUW-DUtA$hV4SBt2_xt z)5TIE0al5od`&roI^-m6s8m(C2WD(2Ohie@`q@pSZV4FK(vw4J1fxViKWLr>qovZ9 zmOd8!8W{@ z4@PswjF6(QiKeld@&I+YKi7RDtm8E0IM!`C>X>MTo+_G9cL3-66R5*-CSptF8K)5z z2&pg`Y?5vRqgY-A%NCJioJuE^^ghu@q9|bBurf%*C{a8?vn#CKb>X*LzPLN-nXOHAebOuBt^Amir;^$} zo09TY{`2o0J=ZjD%aTuWdz_zHa3s9Px!4oels(%Iz8e@D_-aj6g(KF7GsfAYphuA9 z?3bVK?ufSY**k7_E&HOIi`{&0_lCsGB@HEGcl57~k~!DU`-b>8^!|J71FL0=XLi3? zrOn4i&a3YGn=4tbP5REYdR9Tn-Ifnm-yFva#XA?Amaj3N9-{E0bs{*#SFFDnU^#&J z1)?Tg)SvfldHZ4m=fy2qmlku21xwi#EeFKs-~7;0(Q!UCY-I?%!*6RZLk13a*UYiY z$syb!>qGddsRvBQL-pC{uOIBMW?gU8`Jmhlup9kZxT)72bxCGjgjsjS93)k?_Tq!& z#a_HR5>FsX8`@d4Pz%6Epfpcf{F^rr)nAvNj9Vub*X zH4mGN!W3XCFb%-@$8apsBaF@(I$`L2m)^$deV6qGB7uHD6tEgt1FQw=f#tySzzSe0 z=D8541gd~);2EF>a03-w4h-Y*o_LfKfJA^+m{v3nxB(kCfm^`0z-{0T@Evd$xCeX> z`~chs9sm!4AA$b^9sxfA*dUAp*HHW$Z~{0290lH`6@CweH-J5|BaDaloQWb1R~8Qp z03MS(oZCE$VWJLL1Uw5Y0A>JB0W*PFz-+(;3rCg@SAd=ydMt<;wcA~B=WvK zKIu%poW%R5c|XS2-`l?R&yii8kD;Ba|Gb~y>XB9ZUmDr@kq@7lYEQN&BW2{xByP2N zzu&vtQ%7{(%_}q&9La`4m@hW0U1lY7E7#=oWPX)5$Xo{>61#Sw@%`cbkPS}HY$zU8 z7z_g`7y;e3$#o8HP4j*MM;yDkf7Q%y=0ZWW4<#48-^g#SJvQDlwJpIrHe(;i4s^A7 zKmL;f0;Uy(Id}bH`-<#2l&9Fdw+6S~TUx#8!l{p;kZw#bTh1B^pU28Y(Bn+*CZWhq zs*I2iiCvRPDHz22-5&jE(#%0KqrZZUH2CL0w#w$=Jj@qQm|(dSs=QKeNrR}9`-nBj zlPNHBT>b=>?!8~|-?ZUW?20SfV8#JgQjlRtt#Iti=C)GE^!oDG(Rhs{y*`pv>HI3vz(HMe<)`Rs z^WH1;jlFaFHOI6vOgh<)b!Mzs24%pu_X6YhFKlcSSE{zacADKmvG-nav|pM~b~z+& z1zKoT(6wGZg|0S^<0S}eVqN@}w6l92nF@F;t(6;~z&FcX8N7h6l-$X!G9;7t;TD;g ziLTzun1L~0yw>)|tOg%$nr1=L1qJ9WCSi2tC*+vGvB=rROkkq|5Ft?9iQqqeG zkT2LS`h@aiQV!PCdu6k1UCXJc9(`hT1tc2|&=LB3L4DEU@4xKj*?q`t!AP}oDZ1Jk zpb&~ReZSkC%MWaamzu6%x{Y!l6nLvVN#d{a%N%6A_cCeGx2+dH)ebH5;n=ozeCV;? z$%0(yZIbhHG2aTgkyxE<&E?B&-dmt3pD$(>-ndb%4}vK$RwfHaK+i1~K-j#uM8$`1 zST_Ch`e5B#2hF2NZXvxL^56&rb&QN22{uhSNz9g0NAgx4D!b;vhWBnLFKF+M+BIkX z8%c@-iej56wLG}?u5?4#ymv%_n>#Nx_+L)JSfmG^g5?ny;<^ZxH}kl~=Dm1Y;`h|X ztkv5q%_za>VHuRqtwr7oB;TT;Q$FM9s^db{@Dt zn$PafQ{->T_((kUUn%4JyBwHR!JkVA{JPOly45H@Yvj>M(7&r_IIgDqp8& = HashMap() private val client = OkHttpClient() private val apiKey: String = System.getenv("MINIKURA_API_KEY") ?: "" - private val apiUrl: String = System.getenv("MINIKURA_API_URL") ?: "http://localhost:3000" - private val websocketUrl: String = System.getenv("MINIKURA_WEBSOCKET_URL") ?: "ws://localhost:3000/ws" + private val apiUrl: String = System.getenv("MINIKURA_API_URL") ?: "http://localhost:3000/api" + private val websocketUrl: String = System.getenv("MINIKURA_WEBSOCKET_URL") ?: "ws://localhost:3000/ws?apiKey=$apiKey" private var acceptingTransfers = AtomicBoolean(false) private val redisBungeeApi = RedisBungeeAPI.getRedisBungeeApi() @@ -87,6 +87,10 @@ class Main @Inject constructor(private val logger: Logger, private val server: P .plugin(this) .build() + val getallServerCommandMeta: CommandMeta = commandManager.metaBuilder("getallserver") + .plugin(this) + .build() + // TODO: Rework this command and support and arguments val migrateCommand = SimpleCommand { p -> val source = p.source() @@ -114,10 +118,15 @@ class Main @Inject constructor(private val logger: Logger, private val server: P source.sendMessage(Component.text("Migrating players to server '$targetServerName'...")) } + val getallServerCommand = SimpleCommand { p -> + getallServer() + } + commandManager.register(serversCommandMeta, ServerCommand.createServerCommand(server)) commandManager.register(endCommandMeta, EndCommand.createEndCommand(server)) commandManager.register(refreshCommandMeta, refreshCommand) commandManager.register(migrateCommandMeta, migrateCommand) + commandManager.register(getallServerCommandMeta, getallServerCommand) val connectionHandler = ProxyTransferHandler(servers, logger, acceptingTransfers) server.eventManager.register(this, connectionHandler) @@ -132,6 +141,22 @@ class Main @Inject constructor(private val logger: Logger, private val server: P logger.info("Minikura-Velocity has been initialized.") } + fun refreshServers() { + fetchServers() + fetchReverseProxyServers() + } + + fun getallServer() { + synchronized(ServerDataStore) { + ServerDataStore.getServers().forEach { serverData -> + logger.info("Server ID: ${serverData.id}, Type: ${serverData.type}, Description: ${serverData.description}") + } + ServerDataStore.getReverseProxyServers().forEach { reverseProxyServerData -> + logger.info("Reverse Proxy Server ID: ${reverseProxyServerData.id}, External Address: ${reverseProxyServerData.external_address}, External Port: ${reverseProxyServerData.external_port}") + } + } + } + private fun fetchReverseProxyServers() { val request = Request.Builder() .url("$apiUrl/reverse_proxy_servers") @@ -207,9 +232,9 @@ class Main @Inject constructor(private val logger: Logger, private val server: P servers.clear() for (data in serversData) { - val serverInfo = ServerInfo(data.name, InetSocketAddress(data.address, data.port)) + val serverInfo = ServerInfo(data.id, InetSocketAddress("localhost", data.listen_port)) val registeredServer = server.createRawRegisteredServer(serverInfo) - servers[data.name] = registeredServer + servers[data.id] = registeredServer this.server.registerServer(registeredServer.serverInfo) } } diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt index 94fa9f6..5c73af2 100644 --- a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt @@ -6,22 +6,105 @@ import org.java_websocket.handshake.ServerHandshake import org.slf4j.Logger import java.net.URI import java.time.Duration +import com.google.gson.JsonParser class MinikuraWebSocketClient(private val plugin: Main, private val logger: Logger, private val server: ProxyServer, serverUri: URI?) : WebSocketClient(serverUri) { + override fun onOpen(handshakedata: ServerHandshake) { - logger.info("Connected to websocket") + logger.info("Connected to WebSocket server at: ${uri}") } override fun onMessage(message: String) { logger.debug("Received: $message") + + try { + val jsonElement = JsonParser.parseString(message) + if (jsonElement.isJsonObject) { + val jsonObject = jsonElement.asJsonObject + + val type = jsonObject.get("type")?.asString + val endpoint = jsonObject.get("endpoint")?.asString + val timestamp = jsonObject.get("timestamp")?.asString + + when (type) { + "test" -> { + logger.info("API Call detected: endpoint=$endpoint, timestamp=$timestamp") + + when (endpoint) { + "/servers" -> { + logger.info("dawdawdawdawd") + } + else -> { + logger.info("API endpoint $endpoint was accessed") + } + } + } + "SERVER_CHANGE" -> { + val action = jsonObject.get("action")?.asString + val serverType = jsonObject.get("serverType")?.asString + val serverId = jsonObject.get("serverId")?.asString + + logger.info("Server change detected: action=$action, serverType=$serverType, serverId=$serverId") + + when (action) { + "CREATE" -> { + logger.info("Server '$serverId' was created") + executeRefreshCommand() + } + "UPDATE" -> { + logger.info("Server '$serverId' was updated") + executeRefreshCommand() + } + "DELETE" -> { + logger.info("Server '$serverId' was deleted") + executeRefreshCommand() + } + else -> { + logger.info("$action") + } + } + } + else -> { + logger.info("Received WebSocket message of type: $type") + } + } + } else { + logger.info("Received non-JSON WebSocket message: $message") + } + } catch (e: Exception) { + logger.warn("Failed to parse WebSocket message: $message", e) + } } override fun onError(ex: Exception) { - ex.printStackTrace() + when (ex) { + is java.net.ConnectException -> { + logger.warn("Failed to connect to WebSocket server at: ${uri}") + } + else -> { + logger.error("WebSocket error occurred", ex) + } + } } override fun onClose(code: Int, reason: String, remote: Boolean) { - logger.info("Connection closed, attempting to reconnect...") - server.scheduler.buildTask(plugin, Runnable { reconnect() }).delay(Duration.ofMillis(5000)).schedule() + logger.info("WebSocket connection closed (code: $code, reason: $reason)") + } + + private fun executeRefreshCommand() { + try { + server.scheduler.buildTask(plugin, Runnable { + try { + logger.info("Executing automatic server refresh...") + plugin.refreshServers() + plugin.getallServer() + logger.info("Server refresh completed successfully") + } catch (e: Exception) { + logger.error("Failed to refresh servers", e) + } + }).schedule() + } catch (e: Exception) { + logger.error("Failed to schedule refresh command", e) + } } } \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt index e278d05..15a58d4 100644 --- a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt @@ -29,11 +29,11 @@ object ServerDataStore { } fun getServer(name: String): ServerData? { - return servers.find { it.name == name } + return servers.find { it.id == name } } fun getReverseProxyServer(name: String): ReverseProxyServerData? { - return reverseProxyServers.find { it.name == name } + return reverseProxyServers.find { it.id == name } } fun getServers(): List { diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ProxyTransferHandler.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ProxyTransferHandler.kt index 7c3e177..17f65f8 100644 --- a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ProxyTransferHandler.kt +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ProxyTransferHandler.kt @@ -124,9 +124,7 @@ class ProxyTransferHandler( } val sortedServers = ServerDataStore.getServers() - .filter { it.join_priority != null } - .sortedBy { it.join_priority } - .mapNotNull { servers[it.name] } + .mapNotNull { servers[it.id] } for (server in sortedServers) { if (server != currentServer) { diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/CustomEnvironmentVariableData.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/CustomEnvironmentVariableData.kt new file mode 100644 index 0000000..847f664 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/CustomEnvironmentVariableData.kt @@ -0,0 +1,15 @@ +package cafe.kirameki.minikuraVelocity.models + +import java.time.LocalDateTime + +data class CustomEnvironmentVariableData( + val id: String, + val key: String, + val value: String, + val created_at: String, + val updated_at: String, + val server_id: String? = null, + val server: ServerData? = null, + val reverse_proxy_id: String? = null, + val reverse_proxy_server: ReverseProxyServerData? = null +) \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt index f3fb034..aa33fac 100644 --- a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt @@ -1,12 +1,18 @@ package cafe.kirameki.minikuraVelocity.models +import cafe.kirameki.minikuraVelocity.models.components.ReverseProxyServerType +import java.time.LocalDateTime + data class ReverseProxyServerData( val id: String, - val name: String, + val type: ReverseProxyServerType, val description: String?, - val address: String, - val port: Int, + val external_address: String, + val external_port: Int, + val listen_port: Int = 25565, + val memory: String = "512M", val api_key: String, + val env_variables: List = emptyList(), val created_at: String, val updated_at: String ) \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt index f0c5481..f7d0608 100644 --- a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt @@ -1,13 +1,15 @@ package cafe.kirameki.minikuraVelocity.models +import cafe.kirameki.minikuraVelocity.models.components.ServerType +import java.time.LocalDateTime + data class ServerData( val id: String, - val name: String, + val type: ServerType, val description: String?, - val address: String, - val port: Int, - val type: String, - val join_priority: Int?, + val listen_port: Int = 25565, + val memory: String = "1G", + val env_variables: List = emptyList(), val api_key: String, val created_at: String, val updated_at: String diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ReverseProxyServerType.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ReverseProxyServerType.kt new file mode 100644 index 0000000..308d495 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ReverseProxyServerType.kt @@ -0,0 +1,5 @@ +package cafe.kirameki.minikuraVelocity.models.components + +enum class ReverseProxyServerType { + VELOCITY, BUNGEECORD +} \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ServerType.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ServerType.kt new file mode 100644 index 0000000..526cef2 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/components/ServerType.kt @@ -0,0 +1,5 @@ +package cafe.kirameki.minikuraVelocity.models.components + +enum class ServerType { + STATEFUL, STATELESS, +} \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/ProxyTransferUtils.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/ProxyTransferUtils.kt index 86335d9..e67a336 100644 --- a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/ProxyTransferUtils.kt +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/ProxyTransferUtils.kt @@ -28,7 +28,7 @@ object ProxyTransferUtils { lateinit var acceptingTransfers: AtomicBoolean fun migratePlayersToServer(targetServer: ReverseProxyServerData, disconnectFailed : Boolean = false): ScheduledTask { - val targetAddress = InetSocketAddress(targetServer.address, targetServer.port) + val targetAddress = InetSocketAddress(targetServer.external_address, targetServer.external_port) val currentProxyName = redisBungeeApi.proxyId val playerOnThisProxy = redisBungeeApi.getPlayersOnProxy(currentProxyName) @@ -77,7 +77,7 @@ object ProxyTransferUtils { player.disconnect(Component.text("Failed to migrate you to the target server. Please reconnect.")) } } catch (e: Exception) { - logger.error("Error migrating player ${player.username} to server ${targetServer.name}: ${e.message}", e) + logger.error("Error migrating player ${player.username} to server ${targetServer.id}: ${e.message}", e) if (disconnectFailed) { player.disconnect(Component.text("Failed to migrate you to the target server. Please reconnect.")) }