From 1a9a2b9c8fdabd819daef7c78136d7caa4502430 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Sat, 18 Nov 2023 20:56:10 +0200 Subject: [PATCH 01/20] .gitignore --- .gitignore | 18 ++++++ .idea/Vk-Rcon-Bot.iml | 9 +++ .idea/misc.xml | 1 - .idea/workspace.xml | 40 ++++++------ dependency-reduced-pom.xml | 128 ------------------------------------- 5 files changed, 45 insertions(+), 151 deletions(-) create mode 100644 .gitignore create mode 100644 .idea/Vk-Rcon-Bot.iml delete mode 100644 dependency-reduced-pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..245ed2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Compiled class file +*.class +target/ +.idea/ +.vscode/ + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* \ No newline at end of file diff --git a/.idea/Vk-Rcon-Bot.iml b/.idea/Vk-Rcon-Bot.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Vk-Rcon-Bot.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 67e1e61..42e3abe 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/.idea/workspace.xml b/.idea/workspace.xml index f8ee810..7c24638 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,43 +5,39 @@ - - - + + + + + + - - - - + - { - "keyToString": { - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "SHARE_PROJECT_CONFIGURATION_FILES": "true", - "settings.editor.selected.configurable": "advanced.settings" + +}]]> diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml deleted file mode 100644 index 8e4eadf..0000000 --- a/dependency-reduced-pom.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - 4.0.0 - com.mefrreex.vkbot - vkrconbot - VkRconBot - 1.4 - - src/main/kotlin - src/test/kotlin - ${project.name}-${project.version} - - - org.jetbrains.kotlin - kotlin-maven-plugin - 1.7.21 - - - compile - compile - - compile - - - - test-compile - test-compile - - test-compile - - - - - - maven-shade-plugin - 2.1 - - - package - - shade - - - - - com.mefrreex.vkbot.BootstrapKt - - - - - - - - maven-surefire-plugin - 2.22.2 - - - maven-failsafe-plugin - 2.22.2 - - - org.codehaus.mojo - exec-maven-plugin - 1.6.0 - - com.mefrreex.vkbot.BootstrapKt - - - - maven-compiler-plugin - - 17 - 17 - - - - - - - mavenCentral - https://repo1.maven.org/maven2/ - - - - - org.jetbrains.kotlin - kotlin-test-junit5 - 1.7.21 - test - - - kotlin-test - org.jetbrains.kotlin - - - junit-jupiter-api - org.junit.jupiter - - - - - org.junit.jupiter - junit-jupiter-engine - 5.8.2 - test - - - junit-platform-engine - org.junit.platform - - - apiguardian-api - org.apiguardian - - - junit-jupiter-api - org.junit.jupiter - - - - - - official - UTF-8 - 17 - - - From 3ccda951b24694c49fdd5e7e02e959c6ca94593e Mon Sep 17 00:00:00 2001 From: Arkadiy <83061703+MEFRREEX@users.noreply.github.com> Date: Sat, 18 Nov 2023 20:57:34 +0200 Subject: [PATCH 02/20] Delete .idea --- .idea/Vk-Rcon-Bot.iml | 9 ---- .idea/compiler.xml | 13 ------ .idea/encodings.xml | 7 ---- .idea/jarRepositories.xml | 25 ------------ .idea/kotlinc.xml | 6 --- .idea/misc.xml | 11 ----- .idea/vcs.xml | 6 --- .idea/workspace.xml | 86 --------------------------------------- 8 files changed, 163 deletions(-) delete mode 100644 .idea/Vk-Rcon-Bot.iml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/Vk-Rcon-Bot.iml b/.idea/Vk-Rcon-Bot.iml deleted file mode 100644 index d6ebd48..0000000 --- a/.idea/Vk-Rcon-Bot.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index f4da8f0..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 942f3a2..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index 4d27ef0..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 0e65cea..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 42e3abe..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 7c24638..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1683798584490 - - - 1683800991412 - - - 1683801094541 - - - - - - \ No newline at end of file From 217d14748878393583ff96e8707c177dd5cbf803 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Sat, 18 Nov 2023 20:59:17 +0200 Subject: [PATCH 03/20] 1.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7e0cb04..36b22ea 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ vkrconbot com.mefrreex.vkbot - 1.4.0.1 + 1.5.0 jar VkRconBot From a1b75f7a938d99a90f55bdd1c2a82d5f0796ae46 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 04:10:15 +0300 Subject: [PATCH 04/20] build: migrated to gradle --- .gitignore | 3 + .gradle/8.7/checksums/checksums.lock | Bin 0 -> 17 bytes .gradle/8.7/checksums/md5-checksums.bin | Bin 0 -> 21997 bytes .gradle/8.7/checksums/sha1-checksums.bin | Bin 0 -> 22925 bytes .../8.7/dependencies-accessors/gc.properties | 0 .../8.7/executionHistory/executionHistory.bin | Bin 0 -> 112550 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .gradle/8.7/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/8.7/fileHashes/fileHashes.bin | Bin 0 -> 28047 bytes .gradle/8.7/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../8.7/fileHashes/resourceHashesCache.bin | Bin 0 -> 21659 bytes .gradle/8.7/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 2 + .gradle/buildOutputCleanup/outputFiles.bin | Bin 0 -> 18839 bytes .gradle/file-system.probe | Bin 0 -> 8 bytes .gradle/vcs-1/gc.properties | 0 build.gradle.kts | 45 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++++++ gradlew.bat | 92 +++++++ pom.xml | 131 --------- settings.gradle.kts | 5 + 24 files changed, 403 insertions(+), 131 deletions(-) create mode 100644 .gradle/8.7/checksums/checksums.lock create mode 100644 .gradle/8.7/checksums/md5-checksums.bin create mode 100644 .gradle/8.7/checksums/sha1-checksums.bin create mode 100644 .gradle/8.7/dependencies-accessors/gc.properties create mode 100644 .gradle/8.7/executionHistory/executionHistory.bin create mode 100644 .gradle/8.7/executionHistory/executionHistory.lock create mode 100644 .gradle/8.7/fileChanges/last-build.bin create mode 100644 .gradle/8.7/fileHashes/fileHashes.bin create mode 100644 .gradle/8.7/fileHashes/fileHashes.lock create mode 100644 .gradle/8.7/fileHashes/resourceHashesCache.bin create mode 100644 .gradle/8.7/gc.properties create mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 .gradle/buildOutputCleanup/cache.properties create mode 100644 .gradle/buildOutputCleanup/outputFiles.bin create mode 100644 .gradle/file-system.probe create mode 100644 .gradle/vcs-1/gc.properties create mode 100644 build.gradle.kts create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat delete mode 100644 pom.xml create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore index 245ed2c..428853a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # Compiled class file *.class +.gradle +build/ +bin/ target/ .idea/ .vscode/ diff --git a/.gradle/8.7/checksums/checksums.lock b/.gradle/8.7/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..25afe001cedaf35c831a80a4fd997ccc7b71cb99 GIT binary patch literal 17 UcmZSP>u=m~xJC6d0|cxC05fI;=Kufz literal 0 HcmV?d00001 diff --git a/.gradle/8.7/checksums/md5-checksums.bin b/.gradle/8.7/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..102af09aa853ba22a64d4f5b01159aefe1df9eb0 GIT binary patch literal 21997 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 literal 0 HcmV?d00001 diff --git a/.gradle/8.7/gc.properties b/.gradle/8.7/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..d2ec7d99a03e56458c93f5d27c9ff12fcc188ad3 GIT binary patch literal 17 UcmZSXJ9h2LTYb+)1_&?*06*~s9{>OV literal 0 HcmV?d00001 diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..42c82e8 --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Fri May 03 03:58:31 EEST 2024 +gradle.version=8.7 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000000000000000000000000000000000000..5b2939b748dc9ead20c78ed88d0c0a0abd3f8b94 GIT binary patch literal 18839 zcmeI%Pe_wt9Ki8+nKUg@65(wiL0x1eNFiP}m@;7l(O`@ex(Edx#tw!XcvJ)hVK}i4 zijsBk55pp%*kCCb2ue8U(6CcGRKzYin@D2sc%L65b{TYu@qOTZ_waq6J-p9n$GJ64 zuRm8qD~`tElofnz6ojz zF4a4v?`%X*M=YCaf6tusy;LIK)Dj<7>nEju)F+&wnNf$?y-w+$-MQ?y*C&Ij-j4l)|l$orJo%>_uIGcelfe}SbFuX{ZMn&tCyr>K0 z-xsrMDSK4)$I`>`Sv}JIqVU@p!{d`KpPFV#o6oo-fB*srAb() { + options.encoding = "UTF-8" +} + +tasks.withType() { + options.encoding = "UTF-8" +} + +tasks.withType { + archiveFileName.set("Vk-Rcon-Bot-${project.version}.jar") + manifest { + attributes["Main-Class"] = "com.mefrreex.vkbot.BootstrapKt" + } +} + +kotlin { + jvmToolchain(17) +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e6441136f3d4ba8a0da8d277868979cfbc8ad796 GIT binary patch literal 43453 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 36b22ea..0000000 --- a/pom.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - 4.0.0 - - vkrconbot - com.mefrreex.vkbot - 1.5.0 - jar - - VkRconBot - - - UTF-8 - official - 17 - - - - - mavenCentral - https://repo1.maven.org/maven2/ - - - - - - org.jetbrains.kotlin - kotlin-test-junit5 - 1.7.21 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.8.2 - test - - - org.jetbrains.kotlin - kotlin-stdlib-jdk8 - 1.7.21 - - - com.vk.api - sdk - 1.0.14 - - - org.yaml - snakeyaml - 1.21 - - - - - ${project.name}-${project.version} - src/main/kotlin - src/test/kotlin - - - org.jetbrains.kotlin - kotlin-maven-plugin - 1.7.21 - - - compile - compile - - compile - - - - test-compile - test-compile - - test-compile - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.1 - - - package - - shade - - - - - com.mefrreex.vkbot.BootstrapKt - - - - - - - - maven-surefire-plugin - 2.22.2 - - - maven-failsafe-plugin - 2.22.2 - - - org.codehaus.mojo - exec-maven-plugin - 1.6.0 - - com.mefrreex.vkbot.BootstrapKt - - - - org.apache.maven.plugins - maven-compiler-plugin - - 17 - 17 - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..a7965bb --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} + +rootProject.name = "Vk-Rcon-Bot" From f044ef99785cc28c3113570481393b73fc850cb3 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 05:29:44 +0300 Subject: [PATCH 05/20] refactor: Bootstrap and configs --- .gitignore | 2 +- .gradle/8.7/checksums/checksums.lock | Bin 17 -> 17 bytes .gradle/8.7/checksums/md5-checksums.bin | Bin 21997 -> 22497 bytes .gradle/8.7/checksums/sha1-checksums.bin | Bin 22925 -> 22979 bytes .../8.7/executionHistory/executionHistory.bin | Bin 112550 -> 102745 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 17 bytes .gradle/8.7/fileHashes/fileHashes.bin | Bin 28047 -> 29197 bytes .gradle/8.7/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../8.7/fileHashes/resourceHashesCache.bin | Bin 21659 -> 23257 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 2 +- .gradle/buildOutputCleanup/outputFiles.bin | Bin 18839 -> 18821 bytes .gradle/file-system.probe | Bin 8 -> 8 bytes build.gradle.kts | 7 +- .../kotlin/com/mefrreex/vkbot/Bootstrap.kt | 40 ++++++----- src/main/kotlin/com/mefrreex/vkbot/Bot.kt | 47 +++++++++++++ .../kotlin/com/mefrreex/vkbot/BotSettings.kt | 15 +++++ src/main/kotlin/com/mefrreex/vkbot/Server.kt | 32 --------- .../com/mefrreex/vkbot/config/Config.kt | 63 ------------------ .../mefrreex/vkbot/config/ConfigManager.kt | 33 --------- .../vkbot/config/defaults/AllowList.kt | 21 ------ .../vkbot/config/defaults/Messages.kt | 29 -------- .../vkbot/config/defaults/Settings.kt | 41 ------------ .../mefrreex/vkbot/handler/EventHandler.kt | 37 +++++----- .../kotlin/com/mefrreex/vkbot/rcon/Rcon.kt | 6 +- .../com/mefrreex/vkbot/utils/ConfigHelper.kt | 35 ++++++++++ .../com/mefrreex/vkbot/utils/Keyboards.kt | 7 +- 27 files changed, 153 insertions(+), 264 deletions(-) create mode 100644 src/main/kotlin/com/mefrreex/vkbot/Bot.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/Server.kt delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/config/Config.kt delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt diff --git a/.gitignore b/.gitignore index 428853a..fdffd0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Compiled class file *.class -.gradle +.gradle/ build/ bin/ target/ diff --git a/.gradle/8.7/checksums/checksums.lock b/.gradle/8.7/checksums/checksums.lock index 25afe001cedaf35c831a80a4fd997ccc7b71cb99..471d8b2d48663b3ea3c9d349edb523713d5f8b19 100644 GIT binary patch literal 17 UcmZR!TkEvvSk9u~3=psx068@UZvX%Q literal 17 UcmZSP>u=m~xJC6d0|cxC05fI;=Kufz diff --git a/.gradle/8.7/checksums/md5-checksums.bin b/.gradle/8.7/checksums/md5-checksums.bin index 102af09aa853ba22a64d4f5b01159aefe1df9eb0..57e6e469a361b762f4ddfd0815d1818593588f13 100644 GIT binary patch delta 2286 zcmZXVc~DbF0EZK-B2a{g^(8_Pr!;(_29AD5jUV zF}32okCT=*H@4lOeLW$sIH?4d^;>pUF{P!cAW1C)wHPd9BC@wZ=T(nHEyp zlw^f?@Da?5-C}$?r)r z;@nM`>k7M_8s&k95m)elu{ptQ#%n!^XK=|AQqS=Zp}SD=;6_ ztgIKSJ&sYUBw_x(s-15=i^)J-dKmGUqoZG@J{z>L8HhlIzz%eoJCov8?2W3zvLl3s zl*i4+T& zqw@yh3JvB~w7Bf8H_P1+4{E`D!HdLDk9p-M5f?K+uLZ&K#;sF%kZO10_P3}NiU8Y^ za5;+G#RWt5!7+(psO2uh+~xh5u1yiup@@qJ@S`P%RfJpjvQ1l}Bq}}B^7}!NC7~?G zt>^vL@GF`9UZ|BsW9~Q2n<>_~KSDfs3FiLG`m=L0KP*OE!5Zgf!%yzN(_mRAS7*&O9(+zNEEp+To^>X{SBF&DPKd;W42(TJ>&9Lz-lg^?C! znze|Bp2s}o>;=nqsm)Qu6~*8eCDD2A|o4_C0QuF+#iY_1&XrMbQ(?e{zf1=Lu4K5D)UhJSwdy z?9%=0SBT56U>+?ss(mY6n}s-kA?8~iUpPOMb)9+&B_4EaYqP%rfbyMjm{g3*_efZ&n<{uTk3+W-QB zNx*<EMME2IIKNl4HULV`dzK7z@I*#=`QB|$ul4@@qMt6T*d<@O*| z?h7`;3%w0HIsJ>x5on|tp3LXK znPa>3)SHu)hd#Dxaz@#m*PGpj99JbbKxL;Sf&2~5gonXuc$%=ty0{l@_dIi+_dJ;? z(mngR9U0{t#^ysK5S}I&b-mYuw1fJGYKxEUY0fQs+4ogZkH&p9Xa>HM6>HnreM>h- zw%X)PEA%W2hfhDeta!#n%IKPGOsVl!uKUlhGP9>Lx+ysvPM|056<)7IhK1H|vNAl2 z$?~!OCeu#5sAfk(?ARiSdqwQukTTt8vSDuj%JcRgWxT2z7pL9!i~9_XSealybt6_M zCmSo?luBGP{++G;sBslL$9lniiJo|%=1>Z?$k;#cgpX@RL;@+PZ|PIytChO%|FMC_ swg%De5@Z~VnQT;~>G{8_jF0@m*EGzS`E)Zh)RMTac4SPifFs@cf6^D;&j0`b delta 1517 zcmX}r4N#MH9Ki7n!#i9^yd2NQ%S4@;@iOo}wuKzBqFWh9ik^+8-Xb*- zOyjy=h~fzcNCb;R;z-luydi{~jl^9U*-ey?F5PuG84%QZ>i_)Q-S7U}=llOZ_iXp< zUKii*;$M&pE4Ci@9a--g{^cg`|5si~h2UgswBSCxYX$UN&-^hIUJaJ|h^a-WR zO04Y}JmbKPBVtlji_p8|yR+{vDs3Tdx$@NAm#eDM($)}HOF=85L<+3kZJw3AIeXH$ zQ~BUz>q}VsruQ!Fe5LX-S64x&h${XDt2^pMO;%Dv1*xW5^x-eJj1Eq9Eho-2qK|a^ z_~v`}I=?2ayp8T{sM~Z+IqM*vcNP>0lqebNM65#~Y7y3wsyYKl6Qsq_SRd`bbiver zCW=%&gZ@WY)8Oa}8#WVH&!Eq^$2H61gwe!JGW37Kf1OTA30R41u0n2NMuNT*bI4ZM zXw@y@jxigdHBl;S!n)M-Q~sqm%P=`?*o+?KE~%S;Feo8z4uC(Ak`-Z%(JUSHoF6GC zRrNQzn75~~{JS1Ganl@Vlc)+i*3<}hhE?|Z0I5a+dU}u2cG$81F>&of^o+9nu;Wjx zImGik=<8fTiE6i_khroHT{iRiKF;S9z1|6 zkGHs~d-*KYz0MI0t;$VsNJ#_3*nnba=wz-#n~DZII|8*0##D4qnL3RR9t{m=*$HV* z!H|}QT6R)(G|aJUk?s_%(%T@we!ilop_&~(J5vT4df71=Y_N=7MaKUoHoRtI(7pm> z#`}<)M?)+-@3RwWqTvp^wzHGXzHeb?FFQZ5qi1h%Qc0({W8x=bBwF9Q<3qmQ<|+Y? z$FGTZ9LOZ!H6jxF%v<0^3k`e=of21iJ?w-u`{sw<*qmbX0WaD75%)Ac(*e__ayVzE Iq0}t-7n&;aIsgCw diff --git a/.gradle/8.7/checksums/sha1-checksums.bin b/.gradle/8.7/checksums/sha1-checksums.bin index 16002be1be83dc2a216952c80223a8b3c4eba6e7..7b239f512ed314131d1d085729b2232e0f228096 100644 GIT binary patch delta 2377 zcma)-dojY>RjZK-_6$M9W?8+hwq> z1QldVY|u8;WM|Kw?SQzj7^vtRs`M48q|?+lv8b*uVK~+&vj&NzI?OM3)$h=?y!`?3 zuzi?cxwQWZ*EO*Wak(?*4W_$xds!E%$$eCq->e8|`>k7Rhj@qs;&AHLlMiDOpI4qk z1wjTjv{iIF-;@RJLp<^4`MbtOn1+d>_RgqH|f(Hy1FtN5*OKh;jOhdHq zN%4^*i7XfMDWQ|wT8_1DMqJpAxux)>XYuo{YlufFFt?>_DB65m$3;B&1?KEmDRNJH z-4VpYyGJ>=W$i>XgFb7HS|G+sw}IpO_~=G*5^)LU?)};{X~pB8$ek-O=bkE1=gFfD z$(;i+=bee)6dSRMinvIExmU)}@KOgrATIVC<)FsKF(zag*7$k%7L^QLrp-StqUC&M?`JX{yyh!@q;Zqvrxpq zW!1%k9?t`%?oTqD%K%-2`156kLFq$Ic!0ISA(1)4=*C-DlQ1HNGd7S!cq0&D; zX3`4To7l6)`P8)6_ez^66k@lJ!+;*0hu8`89CvtehB5PVOnu*&)+tf4E>GZlC0Usi zUe-S{U?SJ2?N9*MH#~Uxf0z0zY!y!I!EFZ8Q@*-3RhF-)`B5a@A2l2PW3(4#^7Qe| zzRsl{JL9&~rFFC!e`P-5mZrHM^CqUxJUb-X7n~uZ)=%XyC^XxCFQ# zp0KenPMJ-IuGEO6kqyW-kw4xgZ5&kFv_`C~djI~=xwQ>iPQSGhzI)J?I47O7MUA%^ zwv<-4nq(Bzh()sU?FET014ix0rCUq70>`^DKkj|&aILkdJUN#UHn=e)y|{K3g%SXp zC753zZJ(d`m}j=e-0^;fs>0zQXCi;9QC|11E!Xm3vy@-L>aQRdV*wx(7V5dT2H!+r6+oU6%kuS_+b|2E*Qu2p&rl~nk><{wm-KP!Z!_kS9yr#X` zq-*wgS8!`#M3r5voce1)z46Is-Nhcgo$%jED5auZTQqR=j zd7{62SG2){e}2_RdQmf{xbd#}bCc5MYo&*8cho*D6FRy!Bwx@%SMm+f&$NT delta 2137 zcma)-eN0nV7{+_~(7^_jFZ)GXSPD2dDl)i?ROS$oR!R%nTgr!QpkfH1V*>&bt#RVQ z1YvnCPTgRFqSG^`#cZ z?~B=qc)}#+$GDNTSARX}40&8J@fK8aIlMBg)%R6_H0#eP0?pQNspVNTU(#;pY{tBEHs8E` z^?{$6rQ0ySGcezzmMflX}5!c+o{93O^V|~~xFx+V6(e$m9+NUc9>JX$T+&p&5 z{kplvyAyGh0rT6Q#jo=kxCX>G^k6vELI5!WDL)|wH!W;UtxH!q zGffILoD%r59lkhOC&_S_V4S<5Qy|qRlwyxtSN4(liEarKn2$NTk{JH7c(s<{GR!@c zu7kZN`-%{c3&UK*`nay>4`V3e+91pW9-Ip*>A9vtoH}Ubp7eG3onmcxfbm--Bnn}h z&r0G$F!)GA%K@gzsgV;_NLK2j2kc-#r-6|N)zo(efcB6y2@VVlT}D1kze z)GodgPKkWkY5lAxbLYmlF^*xF5^;ELc)hI9^n(#l{x_3vf_O1!l?zt0ZxoDNuf3Fq zRGl94omBn3q2%>!#I;+%AeP!Gn_*PU@eapLcxJJdcD4G=aax0QY-p0NgEBb@n-#eb zs346!id_Vh#E}q9*En6xaime8+-L_4DiZSO8d23lt2!4-)g)|6AVEl1IbBB*NqC#C z%k<|AdY(-nQ-;OaS<`zLB&G8;{R21VB)?QCyICxvVmAG=WoWOm0eTL3m%aqio3 z8?GwZqrx}o271T_j29EP+5x2@L98KD%3351X*+)Ma6S6LsOz()J?sS+9?CIzS`Bsq z&*=Gu92+i64tg=rE-2B+^{=v_#mn1tnH<`!o1%}g*!7Z4#aYhszg@kO4Y^E?A=WVF z4nP$Bg-^@Flh+jGj=UzQW!;!)8qX2iXa2V3uq9<#@h%c=&dFtQN~R5$%*Ih@Nh@oaa!7^*l?wk1Y;+iOuSw!{%(e&OE&SY{<+@{b%OMh0{;@ s`&DeQE6QrM{@2?Z22)wA&=-Ghwrnyp|7%N_#RkoqJ+Or$A&6T3A9^x+(*OVf diff --git a/.gradle/8.7/executionHistory/executionHistory.bin b/.gradle/8.7/executionHistory/executionHistory.bin index 7da7bda305c885d40e14b8b8d93ad0dc4c3665d7..a2bbd068309fca74ecd3f88699c90e039701e45a 100644 GIT binary patch delta 12292 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 J6L-j#{{vf3J39aX delta 5048 zcmeHLX;f547Vd&#RrJ|_BKy9`+B66T6Zg2{hKeF84K_%#G|-5GqF^8(gtgQc_8o$v zfDJ+h43a2}D`3t4Dd z_nTkcS-dD*nxfHYJ3XGmk&7lHC&JG@Brr5ET;S;A=oB0h5$+x0v@JyBINhImlDhAi zl|`Ce?A?psw)Zb+lb7yVR1D$yCmj6o|R4OJTlL3kU^8SsG*P^ zRdno}0`j_SB)#99FDHHcz+7H>`FyzSsOpy2_TU04`rQ=GIL|ftkT$jR_SVXTb=iJ} z1NY+k4sO(aTUqLLxk3&+M14KZNbj_>w4=K~4%|h3-A)a`75PU?tgiw~qWKM|R$lGkBcE zyKnh!4o54Mj2w|eKv=kVyLYfiDCUboB_e-exV?kFw@^S;uT*;&x7l&Sj_3yUon`*A z+%X7qXn>Jau-X)WdvvQN*huSefs&?;PoO{|k)^iY{t@o$t<&AQr~5}owU3oR3F^z# zp$k-j53R=qlc-y6g^~rn!~1TiPkFU{?Y-;tY%VZGYu0vIt(Wu`t`%b53BOc*in-N%&~0%~k%Z3adR%s`AC3OjoP!0aD^O@jV{x#RqT4 ztjg2CF%Kk(+p0bmwV&X;c@=Z)5PgkKtBJdMRMnk%L=Z(Lvk$dx- z#S-21GLFl^apE@NUD`Vltv%g@;o<(leiGw(;-C;gu)jEXR4w+(YAcPaKm4<^Y3`=) z`rtJt3Cl_E6W>l~RBTkDY=`hb`qP<@S6le*xS8nY#Ux}!2WbESigeGVyoAe61@?LU z#JjqG{}qeiiiRzdfi>i2exm0&baB6P)HFHQ=!vS|Gy6uB9tsD_>@B;)6hYTb7#1N6 z8x5xGYZ`TXYuX7F%fhOT^qoGaH&b1nrJxwkec&N+@}gI211@zjq<7quj85wtz6rEe zQ^I^Yc015Qy;;iAO`9VjMSGvV8@gewYOn2!Gae!ObC{QaROrJ4dd+9{sdQvL58ixD z>u_ZB{MqfDnhT`Bh<>CD)H8#$P`~IlXr%>wU`wl5fz_x=G^fGp zQ&28Xci8~Dh7~J7mVz`aYU@Nge;b&E)RQQwBxwpo--*Dpv)L3f%PCZq$J&)&*z{icl->fpUpiCoLv)7`Sk`dK2;7l>=Xw{>HkLp&Gt>@61T4dQ*|EgvIfkzgI5URoNp#ezP9Gd zf%)(n!GfcJC8{W(m>Fs+aAH$Cc(mc5sw*c!V?&X;e_KoXCkk!#qTQ3A;|;{RPtd4S@ZuK zIKeUk2@YGaM#f=#g;b0T@*HTD1Yia;3DG$L*s<#tNAw|Cr_&SgjoNmEf+=Lyk^uBj z4xW|b8LB>F%owz0BG6Q87T$+F#W99nBWI~avGGaZ&iNIWmc##qwqPr4- zItr`e!XzSlJ`tFq>MBZkKSRK^DJ2X&wDOoddQe3vm5exM6L>Q1wI9e$X`L1z;dGh{ zHqPo<@acpAm#14B4O zHv7Y4mavfM>Q$S-SfY`fj4K*Ltd?YatH>h#JQ-N9ySlI1R)K^c@>3dyNUKCdR`WJ9vy}_@w}2a-!jJGAl>{Q&HRTk=cb5T)D4mDR`8)G}oFj zzO-d3ey7$ENaMH(tRlvo<96^c5%@Y4%VlAKS~7c-`nH^I8kT#B*O*jPq~?q-9i4{d zNOUBU8Zv$;U1|esh=6=LFhn<{6dUe^)RZ+0>#Ue{w7Sj&7L#J1N(a-}hfZak%ii7k znl9fL8BOcVemvu6$)(Kv7E&56en7u);1`IEH_vR_-Q?QwIyacSWPS7TYfON9ReTZ`gNPX%p4Ho@)TxZ^g6P)GAoA^g zMyU$lgFPx F;onc^ literal 17 UcmZQpJF;QE+!w=Y1_-bO05UBEGynhq diff --git a/.gradle/8.7/fileHashes/fileHashes.bin b/.gradle/8.7/fileHashes/fileHashes.bin index 6eab1d2f08b54714d4fe5fee767c3816d415807f..f7a9992acf25fa98e092d468a3181645e6a24afb 100644 GIT binary patch literal 29197 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 uOv9TivI`C)3}HL literal 28047 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 DI~7^8g+yg3OGRa0LZ~Q- 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 literal 21659 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 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index d2ec7d99a03e56458c93f5d27c9ff12fcc188ad3..85d1ff5575768c434d53ea114c7454c503ae0b81 100644 GIT binary patch literal 17 UcmZSnwENJbwm;9OGeCd|08kVLi2wiq literal 17 UcmZSXJ9h2LTYb+)1_&?*06*~s9{>OV diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties index 42c82e8..c9e8d14 100644 --- a/.gradle/buildOutputCleanup/cache.properties +++ b/.gradle/buildOutputCleanup/cache.properties @@ -1,2 +1,2 @@ -#Fri May 03 03:58:31 EEST 2024 +#Fri May 03 04:42:31 EEST 2024 gradle.version=8.7 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin index 5b2939b748dc9ead20c78ed88d0c0a0abd3f8b94..406a050f29fc8440a06f2bd5e5c94c4c10052a6d 100644 GIT binary patch delta 199 zcmbO}nXz>;;|3E6N1>HF_;Zy0TzSg?2A=-^pN6Hko+=s5cypuT5Alr#4q}rXJuXlF;2{LW Fg#aQtV|f4o diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index a434bad07d9d13ad8b8189810966213453ef26ef..f60d88e8b844d09c43dec4e8ee663cf5cf58096b 100644 GIT binary patch literal 8 PcmZQzVC=V%D_IBt1*`%9 literal 8 PcmZQzVC=X4ZYu!*2Rs58 diff --git a/build.gradle.kts b/build.gradle.kts index 2bbd679..c783b4d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,22 +14,23 @@ repositories { mavenLocal() maven("https://repo1.maven.org/maven2") maven("https://repo.maven.apache.org/maven2/") + maven("https://jitpack.io") } dependencies { api("com.vk.api:sdk:1.0.14") - api("org.yaml:snakeyaml:1.21") + api("com.github.MEFRREEX:configuration:main-SNAPSHOT") } tasks.build { dependsOn(tasks.shadowJar) } -tasks.withType() { +tasks.withType { options.encoding = "UTF-8" } -tasks.withType() { +tasks.withType { options.encoding = "UTF-8" } diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt index c66e777..f4b741b 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -1,32 +1,42 @@ package com.mefrreex.vkbot -import com.mefrreex.vkbot.config.ConfigManager import com.mefrreex.vkbot.logger.Logger +import com.mefrreex.vkbot.utils.ConfigHelper import java.io.File -val logger = Logger.instance -val configManager = ConfigManager() - -lateinit var server: Server - -class Bootstrap { -} +val logger = Logger.instance fun main() { logger.info("Starting the bot...") - val bootstrap = Bootstrap(); - val resources = listOf(File("config.yml"), File("allow_list.yml"), File("messages.yml")) + val resources = listOf("config.yml", "allow_list.yml", "messages.yml") resources.forEach { - if (!it.exists()) { - configManager.saveResource(it.name) - logger.info("Resource `${it.name}` saved.") + ConfigHelper.loadConfig(it) + if (!File(it).exists()) { + ConfigHelper.saveResource(it) + logger.info("Resource $it saved.") } } - server = Server(bootstrap) - server.start() + val config = ConfigHelper.getConfig(ConfigHelper.CONFIG) + if (config == null) { + logger.error("Failed to start bot. File config.yml not found.") + return + } + + try { + val token = config.nodes("vk.accessToken").asString() + if (token == "token") { + logger.warn("Specify a valid bot token in the vk.accessToken parameter") + return + } + + Bot(config.node("vk.groupId").asInt(), token) + } catch (e: Exception) { + logger.error("Failed to start the bot", e) + return + } logger.info("Bot is started!") } diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt new file mode 100644 index 0000000..2b20ab5 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt @@ -0,0 +1,47 @@ +package com.mefrreex.vkbot + +import com.mefrreex.config.Config +import com.mefrreex.vkbot.handler.EventHandler +import com.mefrreex.vkbot.utils.ConfigHelper +import com.vk.api.sdk.client.VkApiClient +import com.vk.api.sdk.client.actors.GroupActor +import com.vk.api.sdk.httpclient.HttpTransportClient + +class Bot(groupId: Int, accessToken: String) { + + private val allowList = ConfigHelper.getConfigNotNull(ConfigHelper.ALLOW_LIST) + private val messages = ConfigHelper.getConfigNotNull(ConfigHelper.MESSAGES) + + val settings = BotSettings() + + val vkClient: VkApiClient + val groupActor: GroupActor + + init { + instance = this + + val httpClient: HttpTransportClient = HttpTransportClient.getInstance() + + vkClient = VkApiClient(httpClient) + groupActor = GroupActor(groupId, accessToken) + + vkClient.groupsLongPoll().setLongPollSettings(groupActor, groupId) + .enabled(true) + .messageNew(true) + .execute() + + EventHandler(vkClient, groupActor, 1, this).run() + } + + fun isUserAllowed(userId: Int): Boolean { + return userId in allowList.node("allowed-users").asList() + } + + fun getMessage(key: String): String { + return messages.node(key).asString() + } + + companion object { + lateinit var instance: Bot + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt b/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt new file mode 100644 index 0000000..dab1c97 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt @@ -0,0 +1,15 @@ +package com.mefrreex.vkbot + +import com.mefrreex.config.Config +import com.mefrreex.vkbot.utils.ConfigHelper + +data class BotSettings( + private val config: Config = ConfigHelper.getConfigNotNull(ConfigHelper.CONFIG) +) { + val rconHost: String = config.nodes("rcon.host").asString() + val rconPort: Int = config.nodes("rcon.port").asInt() + val rconPassword: String = config.nodes("rcon.password").asString() + val commandPrefix: String = config.nodes("commands.prefix").asString() + val fastCommands: List = config.nodes("commands.fast-commands").asList() + val blockedCommands: List = config.nodes("commands.blocked-commands").asList() +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/Server.kt b/src/main/kotlin/com/mefrreex/vkbot/Server.kt deleted file mode 100644 index 648f8a3..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/Server.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.mefrreex.vkbot - -import com.mefrreex.vkbot.config.Config -import com.mefrreex.vkbot.config.defaults.Settings -import com.mefrreex.vkbot.handler.EventHandler -import com.vk.api.sdk.client.VkApiClient -import com.vk.api.sdk.client.actors.GroupActor -import com.vk.api.sdk.httpclient.HttpTransportClient - -class Server(val bootstrap: Bootstrap) { - - val config = Settings.config - val groupId = Settings.GROUP_ID.int() - val accessToken = Settings.ACCESS_TOKEN.string() - - fun start() { - - val httpClient: HttpTransportClient = HttpTransportClient.getInstance() - val vk = VkApiClient(httpClient) - - val groupActor = GroupActor(groupId, accessToken) - - vk.groupsLongPoll().setLongPollSettings(groupActor, groupId) - .enabled(true) - .messageNew(true) - .execute() - - val handler = EventHandler(vk, groupActor, 1, this) - handler.run(); - - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt b/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt deleted file mode 100644 index 5fd45d7..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/config/Config.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.mefrreex.vkbot.config - -import org.yaml.snakeyaml.DumperOptions -import org.yaml.snakeyaml.Yaml -import java.io.* -import java.nio.charset.StandardCharsets - - -class Config(private val file: File) { - - constructor(fileName: String) : this(File(fileName)) - - private var yaml: Yaml? = null - private var config: MutableMap? = null - - init { - if (!file.exists()) { - file.createNewFile() - } - val dumperOptions = DumperOptions(); - dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK; - this.yaml = Yaml(dumperOptions) - this.config = yaml!!.load(FileInputStream(file)) as MutableMap? - } - - fun getString(key: String): String? { - return getValue(key) as String? - } - - fun getInt(key: String): Int { - return getValue(key) as Int - } - - fun getBoolean(key: String): Boolean { - return getValue(key) as Boolean - } - - fun getList(key: String): List<*>? { - return getValue(key) as List<*>? - } - - fun getAll(): Map? { - return config - } - - fun getValue(key: String): Any? { - val keys = key.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - var value = config as Map? - for (i in 0 until keys.size - 1) { - value = value!![keys[i]] as Map? - } - return value!![keys[keys.size - 1]] - } - - fun set(key: String, value: Any) { - // TODO - } - - fun save() { - // TODO - } - -} diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt b/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt deleted file mode 100644 index 8be1e26..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/config/ConfigManager.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.mefrreex.vkbot.config - -import java.nio.file.Paths.* -import java.io.* - - -class ConfigManager { - - fun saveResource(target: String) { - - val currentPath = get("").toAbsolutePath() - val configPath = "/$target" - val file = File(currentPath.toString(), target) - - if (!file.exists()) { - try { - val inputStream: InputStream? = javaClass.getResourceAsStream(configPath) - val outputStream: OutputStream = FileOutputStream(file) - val buffer = ByteArray(1024) - var bytesRead: Int - while (inputStream!!.read(buffer).also { bytesRead = it } != -1) { - outputStream.write(buffer, 0, bytesRead) - } - inputStream.close() - outputStream.close() - - } catch (e: IOException) { - e.printStackTrace() - } - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt deleted file mode 100644 index 6a34b6c..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/AllowList.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.mefrreex.vkbot.config.defaults - -import com.mefrreex.vkbot.config.Config - -enum class AllowList(private val key: String) { - - ALLOWED_USERS("allowed-users"); - - companion object { - val config = Config("allow_list.yml") - } - - private fun getValue(key: String): Any? { - return config.getList(key) - } - - @Suppress("UNCHECKED_CAST") - fun get(): List { - return this.getValue(key) as List - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt deleted file mode 100644 index c6fd1a2..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Messages.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.mefrreex.vkbot.config.defaults - -import com.mefrreex.vkbot.config.Config - -enum class Messages(private val key: String) { - - USER_ID("user_id"), - USER_NOT_WHITELISTED("user_not_whitelisted"), - - START("start"), - - RCON("rcon"), - RCON_WITH_COMMANDS("rcon_with_commands"), - - COMMAND_BLOCKED("command_blocked"), - FAILED_TO_CONNECT("failed_to_connect"), - FAILED_TO_AUTHENTICATE("failed_to_authenticate"), - - COMMAND_SENDED("command_sended"), - RESPONSE_NULL("response_null"); - - companion object { - val config = Config("messages.yml") - } - - fun get(): String { - return config.getString(key)!! - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt b/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt deleted file mode 100644 index b07da1a..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/config/defaults/Settings.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.mefrreex.vkbot.config.defaults - -import com.mefrreex.vkbot.config.Config - -enum class Settings(private val key: String) { - - GROUP_ID("vk.groupId"), - ACCESS_TOKEN("vk.accessToken"), - - RCON_HOST("rcon.host"), - RCON_PORT("rcon.port"), - RCON_PASSWORD("rcon.password"), - - COMMAND_PREFIX("commands.prefix"), - FAST_COMMANDS("commands.fast-commands"), - BLOCKED_COMMANDS("commands.blocked-commands"); - - companion object { - val config = Config("config.yml") - } - - private fun getValue(key: String): Any? { - return config.getValue(key) - } - - fun string(): String { - return this.getValue(key) as String - } - - fun int(): Int { - return this.getValue(key) as Int - } - - fun list(): List<*> { - return this.getValue(key) as List<*> - } - - fun get(): Any? { - return this.getValue(key) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt index 970ca3c..c69f5a9 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt @@ -1,9 +1,6 @@ package com.mefrreex.vkbot.handler -import com.mefrreex.vkbot.Server -import com.mefrreex.vkbot.config.defaults.AllowList -import com.mefrreex.vkbot.config.defaults.Messages -import com.mefrreex.vkbot.config.defaults.Settings +import com.mefrreex.vkbot.Bot import com.mefrreex.vkbot.logger.Logger import com.mefrreex.vkbot.objects.MessageFix import com.mefrreex.vkbot.rcon.Rcon @@ -24,7 +21,7 @@ class EventHandler( private val client: VkApiClient, private val actor: GroupActor, private val waitTime: Int, - private val server: Server + private val bot: Bot ) : GroupLongPollApi(client, actor, waitTime) { @@ -37,9 +34,9 @@ class EventHandler( when { text == "начать" || text == "start" || text == "rcon" -> { - if (!AllowList.ALLOWED_USERS.get().contains(message.fromId)) { + if (!bot.isUserAllowed(message.fromId)) { client.messages().send(actor) - .message(Messages.USER_NOT_WHITELISTED.get()) + .message(bot.getMessage("user_not_whitelisted")) .keyboard(Keyboards.commandsKeyboard()) .peerId(message.peerId) .randomId(Random.nextInt(10000)) @@ -47,23 +44,23 @@ class EventHandler( return } client.messages().send(actor) - .message(Messages.START.get()) + .message(bot.getMessage("start")) .keyboard(Keyboards.commandsKeyboard()) .peerId(message.peerId) .randomId(Random.nextInt(10000)) .execute() } - text.startsWith(Settings.COMMAND_PREFIX.string()) -> { + text.startsWith(bot.settings.commandPrefix) -> { - val command = text.substring(Settings.COMMAND_PREFIX.string().length, text.length) + val command = text.substring(bot.settings.commandPrefix.length, text.length) val commandName = command.split("\\s+".toRegex())[0].removePrefix("/") when(command) { "getid" -> { client.messages().send(actor) - .message(Messages.USER_ID.get().format(message.fromId)) + .message(bot.getMessage("user_id").format(message.fromId)) .peerId(message.peerId) .randomId(Random.nextInt(10000)) .execute() @@ -71,13 +68,13 @@ class EventHandler( else -> { - if (!AllowList.ALLOWED_USERS.get().contains(message.fromId)) { + if (!bot.isUserAllowed(message.fromId)) { return } - if (Settings.BLOCKED_COMMANDS.list().contains(commandName)) { + if (bot.settings.blockedCommands.contains(commandName)) { client.messages().send(actor) - .message(Messages.COMMAND_BLOCKED.get().format(command)) + .message(bot.getMessage("command_blocked").format(command)) .peerId(message.peerId) .randomId(Random.nextInt(10000)) .execute() @@ -90,10 +87,10 @@ class EventHandler( if (it.length < 4000) { client.messages().send(actor) .message( - if (it != "") { - Messages.COMMAND_SENDED.get().format(it) + if (it.isNotBlank()) { + bot.getMessage("command_sended").format(it) } else { - Messages.RESPONSE_NULL.get() + bot.getMessage("response_null") } ) .peerId(message.peerId) @@ -111,7 +108,7 @@ class EventHandler( } catch (e: IOException) { client.messages().send(actor) - .message(Messages.FAILED_TO_CONNECT.get()) + .message(bot.getMessage("failed_to_connect")) .peerId(message.peerId) .randomId(Random.nextInt(10000)) .execute() @@ -119,7 +116,7 @@ class EventHandler( } catch (e: AuthenticationException) { client.messages().send(actor) - .message(Messages.FAILED_TO_AUTHENTICATE.get()) + .message(bot.getMessage("failed_to_authenticate")) .peerId(message.peerId) .randomId(Random.nextInt(10000)) .execute() @@ -133,7 +130,7 @@ class EventHandler( override fun parse(message: CallbackMessage): String? { if (message.type == Events.MESSAGE_NEW) { - gson.fromJson(message.getObject(), MessageFix::class.java).message?.let { + gson.fromJson(message.getObject(), MessageFix::class.java).message?.let { this.messageNewFix(it) } return "OK" diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt index 5b934ea..496ccdc 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt @@ -1,6 +1,6 @@ package com.mefrreex.vkbot.rcon -import com.mefrreex.vkbot.config.defaults.Settings +import com.mefrreex.vkbot.Bot import net.kronos.rkon.core.ex.AuthenticationException import net.kronos.rkon.core.Rcon as RconManager import kotlin.jvm.Throws @@ -15,7 +15,9 @@ object Rcon { @Throws(IOException::class, AuthenticationException::class) fun command(name: String): String { - val rcon = RconManager(Settings.RCON_HOST.string(), Settings.RCON_PORT.int(), Settings.RCON_PASSWORD.string().toByteArray()) + val settings = Bot.instance.settings + + val rcon = RconManager(settings.rconHost, settings.rconPort, settings.rconPassword.toByteArray()) return rcon.command(name.toByteArray(charset("UTF-8"))); } } \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt new file mode 100644 index 0000000..765facb --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt @@ -0,0 +1,35 @@ +package com.mefrreex.vkbot.utils + +import com.mefrreex.config.Config +import com.mefrreex.config.ConfigType +import java.io.File + +object ConfigHelper { + + const val CONFIG = "config.yml" + const val ALLOW_LIST = "allow_list.yml" + const val MESSAGES = "messages.yml" + + private val configs: MutableMap = mutableMapOf() + + fun getConfig(name: String): Config? { + return configs[name] + } + + fun getConfigNotNull(name: String): Config { + return configs[name]!! + } + + fun loadConfig(name: String) { + configs[name] = ConfigType.DETECT.createOf(name); + } + + fun saveResource(target: String, output: String = target, replace: Boolean = false) { + val resourceStream = javaClass.classLoader.getResourceAsStream(target) + val outputFile = File(output) + if (resourceStream != null && (replace || !outputFile.exists())) { + outputFile.createNewFile() + outputFile.outputStream().use { resourceStream.copyTo(it) } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt index 3dc2ee0..0d7c1cf 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt @@ -1,6 +1,6 @@ package com.mefrreex.vkbot.utils -import com.mefrreex.vkbot.config.defaults.Settings +import com.mefrreex.vkbot.Bot import com.vk.api.sdk.objects.messages.* import java.util.* @@ -8,16 +8,17 @@ object Keyboards { @Suppress("UNCHECKED_CAST") fun commandsKeyboard(): Keyboard { + val bot = Bot.instance val keyboard = Keyboard() val list: MutableList = ArrayList() - for (command in Settings.FAST_COMMANDS.list() as List) { + for (command in bot.settings.fastCommands) { list.add( KeyboardButton() .setColor(KeyboardButtonColor.POSITIVE) .setAction( KeyboardButtonAction() - .setLabel(Settings.COMMAND_PREFIX.string() + command) + .setLabel(bot.settings.commandPrefix + command) .setType(TemplateActionTypeNames.TEXT) ) ) From 8bba07f098ebaf4717d06ba0f4e1e7df59247539 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 05:30:15 +0300 Subject: [PATCH 06/20] chore: remove .gradle --- .gradle/8.7/checksums/checksums.lock | Bin 17 -> 0 bytes .gradle/8.7/checksums/md5-checksums.bin | Bin 22497 -> 0 bytes .gradle/8.7/checksums/sha1-checksums.bin | Bin 22979 -> 0 bytes .../8.7/dependencies-accessors/gc.properties | 0 .../8.7/executionHistory/executionHistory.bin | Bin 102745 -> 0 bytes .../8.7/executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .gradle/8.7/fileChanges/last-build.bin | Bin 1 -> 0 bytes .gradle/8.7/fileHashes/fileHashes.bin | Bin 29197 -> 0 bytes .gradle/8.7/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .gradle/8.7/fileHashes/resourceHashesCache.bin | Bin 23257 -> 0 bytes .gradle/8.7/gc.properties | 0 .../buildOutputCleanup/buildOutputCleanup.lock | Bin 17 -> 0 bytes .gradle/buildOutputCleanup/cache.properties | 2 -- .gradle/buildOutputCleanup/outputFiles.bin | Bin 18821 -> 0 bytes .gradle/file-system.probe | Bin 8 -> 0 bytes .gradle/vcs-1/gc.properties | 0 16 files changed, 2 deletions(-) delete mode 100644 .gradle/8.7/checksums/checksums.lock delete mode 100644 .gradle/8.7/checksums/md5-checksums.bin delete mode 100644 .gradle/8.7/checksums/sha1-checksums.bin delete mode 100644 .gradle/8.7/dependencies-accessors/gc.properties delete mode 100644 .gradle/8.7/executionHistory/executionHistory.bin delete mode 100644 .gradle/8.7/executionHistory/executionHistory.lock delete mode 100644 .gradle/8.7/fileChanges/last-build.bin delete mode 100644 .gradle/8.7/fileHashes/fileHashes.bin delete mode 100644 .gradle/8.7/fileHashes/fileHashes.lock delete mode 100644 .gradle/8.7/fileHashes/resourceHashesCache.bin delete mode 100644 .gradle/8.7/gc.properties delete mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 .gradle/buildOutputCleanup/cache.properties delete mode 100644 .gradle/buildOutputCleanup/outputFiles.bin delete mode 100644 .gradle/file-system.probe delete mode 100644 .gradle/vcs-1/gc.properties diff --git a/.gradle/8.7/checksums/checksums.lock b/.gradle/8.7/checksums/checksums.lock deleted file mode 100644 index 471d8b2d48663b3ea3c9d349edb523713d5f8b19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZR!TkEvvSk9u~3=psx068@UZvX%Q diff --git a/.gradle/8.7/checksums/md5-checksums.bin b/.gradle/8.7/checksums/md5-checksums.bin deleted file mode 100644 index 57e6e469a361b762f4ddfd0815d1818593588f13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22497 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 diff --git a/.gradle/8.7/checksums/sha1-checksums.bin b/.gradle/8.7/checksums/sha1-checksums.bin deleted file mode 100644 index 7b239f512ed314131d1d085729b2232e0f228096..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22979 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 diff --git a/.gradle/8.7/dependencies-accessors/gc.properties b/.gradle/8.7/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/8.7/executionHistory/executionHistory.bin b/.gradle/8.7/executionHistory/executionHistory.bin deleted file mode 100644 index a2bbd068309fca74ecd3f88699c90e039701e45a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102745 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 diff --git a/.gradle/8.7/fileChanges/last-build.bin b/.gradle/8.7/fileChanges/last-build.bin deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/.gradle/8.7/fileHashes/fileHashes.bin b/.gradle/8.7/fileHashes/fileHashes.bin deleted file mode 100644 index f7a9992acf25fa98e092d468a3181645e6a24afb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29197 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 uOv9TivI`C)3}HL diff --git a/.gradle/8.7/fileHashes/fileHashes.lock b/.gradle/8.7/fileHashes/fileHashes.lock deleted file mode 100644 index 00aec9625bb7cea6708b16a1924e6ebe2f144f77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZS17CE%YVqeTk1~6c{1^_T41iAnK diff --git a/.gradle/8.7/fileHashes/resourceHashesCache.bin b/.gradle/8.7/fileHashes/resourceHashesCache.bin deleted file mode 100644 index a490995b956ec71260c4e620bba0c053b3788d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23257 zcmeI(c{G&Y;|Fj)RJJUYqzT!RND)FvlAR&TBq6ee$WFE>I~7^8g+yg3OGRa0LZ~Q- zWM8spDV1;YyL0Dze)G&ZpMQVncb+*%IWO;X?>z5kRdI%@|G2V89{^rR0W4x>}5g~0Wxi2g@oQcAl|0l0<; z^yIrOJXLSpegW4ug`OfUL(jimS_HVE0ntq@<*BbfcLc6VeE+GBj0N&v=h6em3c~!I z&GxOHi@aHc@kgPjC-gpG=Iwd|T$c@cCWA_%ZNkbAf)5itSgFfeMc);;0VVXD&&StS zui|D2<7Yw7&Gu5dC_Z@{xF!brz2#^RDyQ_t#3yGDQWX##Nl z4(N|6Pr4r|UGW8O+y}iVXYke}v+6L?o%1&wDby#-Yy1p)+c$r#yFm~;$m=ga?^GA&;h$D{4_rrs=s7KGPw!fF z1J})f-X%pd(xKZw7|K4x#FMT`c%cq8FEEpnNfEy-5 zU-9)0_Mhv^1&-r_{?q+o#fb>nbl}=`L=SqgEAKTQFK}ab=)a|}iR@!O44A_BH_`70 z7S_2aSQEwxAbLT)IPHKH7jUC|=o@0qT{}673D*;B5p)XX6LB=AjD-EDBLtoDSx1zv zQVpTEGY6q>!p5oI{H=`zG@m!ot)*0NYdGI7_0hDj(&FaaaJZo(p}4dIOK` z$lw%V99QV{YTI_mXcpI>@3b8 zk)Wt-3;8o z20DKbTeXPs3BvV9w+6ZZ^=0*5-92p}ub~57P}9I9Q_Jv)C#keP7YYOO*S3e}?A&4#@FhGe&8Q}e~RK=!ZE`8C+^2XwwKOQ7L0}g*Lwx? z5)s!U;@#Xp4c*P+Y3T9Iptq9$B#jV?ex zrbaC?e%wMFYrKgZ5qM;M=Y-kD!-kGom^s~5OB2h3aeR%wMi3_b(=%x&oBO@Q3#NxyTD+gi!s#`#}&u&t! zT%3;ud2APSC(Oie%FRZt!1cZoJ>fmpF#90kdZj)I-Fe$0!@WNKLXbC_f$s8fU<>x^ zTqAI;Cq!?huBTt$N!Tws%h28S&#N_9-6q_BjhUdk?~QS9e^7W2jAMKVx@YNnU7|VH zGs3(lh&~V?h3>Pz)qjv*kMKNZ2zkS&WwFTbq55|)&Y7+72fo-0qsAT% zM&KGhpu@i@D7GE~KSTdS8$cUC8$cUC8$cUC8$cUC8$cUC8$cUC8$cUC8$cUC8$cUC z8$cUC8$cUC8$cUC8$cUC8$cUC8$cUC8$cUC8~DH30Df^6``<4X{1yOdr zI%ctHHDdLDk@J5iw8Q6IxrGy2Uo-GIUU=%6h)fuA=JHqAZ>Tw5E;7;Re7+T#p!;0- z%OZe6@$0%sX8HN8UT!1Dq4LYTPGJV!<^W`ZFTEgbC4I|wnJUR35dlg3 znmv*+7%X0@9Amq0{m8_`sIyhXQ5%{yhMXPNVi_Nh34z#mqkdcM zWc`E8f7~Qfd)CMFbRH=a*(GcgCa#B$k8;n%#AZY|AM zj-pOG|EEJe-)MDnoskI|VUsx9}*ne&^BQpylYe1ifUF`T5jj7+WMo*NHV=PI3j@*Wswisz#x{x*V z6u+L1WEP9=Ij&)5A3o8APg~ndWw#>})!K3{LNlF?4>)i5`bxU2A`{ZT+!*h+^Y1s_ zq^GrmiJ8o{%pdtV z5mh^oiSAC;xX(t?3r#-)RSfBuDv=3^@R1%bOtf}?b;u2=DJwEZ(4a2s(V*4BT@+_u zMwx^j$FF%Mc^{5WaferuG=*2Gq;Qsc`NhZtMrWB~H;Z%0-EQH%VMZseArsh)`8DM# z_N00-`J56-V(p03KBvs2@qubjOVJ?J9^@G9VgduM{#t{|wtMyI z`kC=-eM!b(Q(AibPvsD+ne11O__2rYkO@Ocp@4of4Iv)P#8qa?1Lu&5OtU%ZA+0Ox zx!X)vokYECkqIWdU=hrKoJhXRw;buzaWZ?Z`14zizRcv?b!oIWGHNgUK#p;bT2Dr@ z#|C@XTzF?vWn(s(#L+)W$)1X}HCagkqN&~Z6}lu_i!q?pw6?g?Z^B;|o~Au(j!f9h z-1rp68CfpiSodrqXs;qN!PfjfHo~*tZYsS2`>scl%sHpaN3&$@{Fa4gdn(2``#YIg zLKHtU89Z6y7BJnz|I5jE1v!h3D{D{w^yWt#vm=+Yqrc;Cpd{O(aU?&?XgwvgvM@&K zrG~yNG7;yJwq@H^bH)xW6$Pu*i$=&qwxfY;`D@&ky1ZS%&aoZ%TQ$inQ5h2Z{^+p& zob>AtbctU!MkcOTezOhGTj*SwOk=p@BKirL_|$?q&u;wg_MpP0Mr_8|No2x-y;Ot6 zama(O+eVSkt{lHEmgIdH3QT%2OXbVj>fZhxhgd!g7m8NWW5 zWDK8PmPU8)U<(Ih?n~uzp=6FzpO|I39QUx!%nyEwZ21w*$T8B4qD!i0HP`&jDR20R zZ3;prT$6-CvNCMk4`jDm%Rlk9KqjQCg4-Kg{b}ZKKU`&hQa-!(&$zCYv@# z^Okwx*ZPxeO9)5w6I0foQSVI+w)8ow<5w$_B>ESJ#iz&Yf|IZh;_?@xt{@X)O*0wX z=4ERIMwG18?ELtZ`Xpnxw=J)$iM(lF*!Y&m$^2;snP~CHeV5&2pY0=bfm7#{1DWeb z#L1*cIo0?3J9=1lsz0VCbG}e_*p^M1Ytr+dT2YJsp!^6q%h`8Z#kfyTHb*&g=1FH4 z3 z%G$fEl=U$n-US!`Ct04%vEFHSf%&XCW2kc3t$Kz9=dZ{y_-V>NbKNz@%%%rn#r>r^ zk%_Hi!_tFIBe>cjH?e%X+-YQ@y?!Nd-TR48kL^jt{nU5(k%{8uF+Z8x@)Efo`t~}t z^!U%sko2K5>+_noyZ>6sd@qyNSV9;w;oVlV#jE$Wx_M*Hr1)7TGEcBJ9k=sa?ffjJ z$C4H#;=BWqW0cf01W{#6+o}61=Kma9!>`*Xc^`-0Jbty4@6@9OPRASaGc0V##3%9g zvlAij55-#_Vh`(0e~C=Y@Lcaw5b@1md2=|v>73nfWP%`6Uf}=YQbxNn6*k9IZOPCYrMy*Y~c2**K;DHLf4TAC!Y(FH{xTs4l-AV(hQ1m)(`&nLMszgV_zgSkz?FAdujh0 z8BU40w&m?9S;b_2E70n=JhV~ugl|r;sGDO;0+~A@$7v~*80LSX?_^y~Oa7o7jhrRR zUt@e*&DzD$q0Gy>z9~{66Zur_Pm_CjWCg79Y`LTw$vl1j`_$peYIAn`P^HiAw`A^O-@G^N zrcHTv_23Pzc+W58*O9Xvy|isfs^T&`AN6c=E*=+`scyi zf^8pVf@Q|DulbYtosjLGsO3|!gM1nTI_E;u{m7hAi8VFd_tNxvm)fYptDGngkl9*` qM!JY*#u*XIoVN-d5`T~hXLHx+!OY8ajtoIDc{Y*W$OO52)PDdX&d^H$ diff --git a/.gradle/8.7/gc.properties b/.gradle/8.7/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 85d1ff5575768c434d53ea114c7454c503ae0b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZSnwENJbwm;9OGeCd|08kVLi2wiq diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index c9e8d14..0000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Fri May 03 04:42:31 EEST 2024 -gradle.version=8.7 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 406a050f29fc8440a06f2bd5e5c94c4c10052a6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18821 zcmeI%O(+Cm7{KvaANdSzsj(%+MT-)-*+SHA2_;3jY*7w!AiL%0L@6O0hBtHBydTvB$k?hvA{6u46_N*tjX1<&R z!?zvP^8dl=oUf)yKj>0DNBUO%;^^)`MU?7J>HE`dD{b0DNx0ifr5~&gK6jaR)c*D{ z=|2DFV|w03r<(7Tex&!zs49nzz-j$PT2OS5X;D?Pz-VfK1j z8@|UC0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL mKmY**5J2F+2^1PH9A})S&>6ZUeJV!yRpon6_*38dVxM Date: Fri, 3 May 2024 05:52:27 +0300 Subject: [PATCH 07/20] refactor: message listener --- build.gradle.kts | 8 + .../kotlin/com/mefrreex/vkbot/Bootstrap.kt | 2 +- src/main/kotlin/com/mefrreex/vkbot/Bot.kt | 20 ++- .../mefrreex/vkbot/handler/EventHandler.kt | 141 ------------------ .../vkbot/{objects => handler}/MessageFix.kt | 2 +- .../mefrreex/vkbot/handler/MessageHandler.kt | 97 ++++++++++++ .../com/mefrreex/vkbot/utils/Keyboards.kt | 1 - 7 files changed, 124 insertions(+), 147 deletions(-) delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt rename src/main/kotlin/com/mefrreex/vkbot/{objects => handler}/MessageFix.kt (84%) create mode 100644 src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt diff --git a/build.gradle.kts b/build.gradle.kts index c783b4d..bac9460 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,4 +43,12 @@ tasks.withType { kotlin { jvmToolchain(17) +} + +sourceSets { + main { + java { + srcDirs("src/main/kotlin") + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt index f4b741b..614544e 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -32,7 +32,7 @@ fun main() { return } - Bot(config.node("vk.groupId").asInt(), token) + Bot(config.nodes("vk.groupId").asInt(), token) } catch (e: Exception) { logger.error("Failed to start the bot", e) return diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt index 2b20ab5..523ceec 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt @@ -1,17 +1,20 @@ package com.mefrreex.vkbot -import com.mefrreex.config.Config -import com.mefrreex.vkbot.handler.EventHandler +import com.mefrreex.vkbot.handler.MessageHandler +import com.mefrreex.vkbot.logger.Logger import com.mefrreex.vkbot.utils.ConfigHelper import com.vk.api.sdk.client.VkApiClient import com.vk.api.sdk.client.actors.GroupActor import com.vk.api.sdk.httpclient.HttpTransportClient +import com.vk.api.sdk.objects.messages.Keyboard +import kotlin.random.Random class Bot(groupId: Int, accessToken: String) { private val allowList = ConfigHelper.getConfigNotNull(ConfigHelper.ALLOW_LIST) private val messages = ConfigHelper.getConfigNotNull(ConfigHelper.MESSAGES) + val logger = Logger.instance val settings = BotSettings() val vkClient: VkApiClient @@ -30,7 +33,7 @@ class Bot(groupId: Int, accessToken: String) { .messageNew(true) .execute() - EventHandler(vkClient, groupActor, 1, this).run() + MessageHandler(vkClient, groupActor, 1, this).run() } fun isUserAllowed(userId: Int): Boolean { @@ -41,6 +44,17 @@ class Bot(groupId: Int, accessToken: String) { return messages.node(key).asString() } + fun sendMessage(userId: Int, message: String, keyboard: Keyboard? = null) { + vkClient.messages().send(groupActor) + .message(message) + .apply { + keyboard?.let { keyboard(it) } + } + .peerId(userId) + .randomId(Random.nextInt(10000)) + .execute() + } + companion object { lateinit var instance: Bot } diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt deleted file mode 100644 index c69f5a9..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/EventHandler.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.mefrreex.vkbot.handler - -import com.mefrreex.vkbot.Bot -import com.mefrreex.vkbot.logger.Logger -import com.mefrreex.vkbot.objects.MessageFix -import com.mefrreex.vkbot.rcon.Rcon -import com.mefrreex.vkbot.utils.Keyboards -import com.mefrreex.vkbot.utils.TextFormat -import com.vk.api.sdk.client.VkApiClient -import com.vk.api.sdk.client.actors.GroupActor -import com.vk.api.sdk.events.Events -import com.vk.api.sdk.events.longpoll.GroupLongPollApi -import com.vk.api.sdk.objects.callback.messages.CallbackMessage -import com.vk.api.sdk.objects.messages.Message -import net.kronos.rkon.core.ex.AuthenticationException -import java.io.IOException -import kotlin.random.Random - -class EventHandler( - - private val client: VkApiClient, - private val actor: GroupActor, - private val waitTime: Int, - private val bot: Bot - -) : GroupLongPollApi(client, actor, waitTime) { - - val logger = Logger.instance - - fun messageNewFix(message: Message) { - - val text = message.text.lowercase() - - when { - - text == "начать" || text == "start" || text == "rcon" -> { - if (!bot.isUserAllowed(message.fromId)) { - client.messages().send(actor) - .message(bot.getMessage("user_not_whitelisted")) - .keyboard(Keyboards.commandsKeyboard()) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - return - } - client.messages().send(actor) - .message(bot.getMessage("start")) - .keyboard(Keyboards.commandsKeyboard()) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - } - - text.startsWith(bot.settings.commandPrefix) -> { - - val command = text.substring(bot.settings.commandPrefix.length, text.length) - val commandName = command.split("\\s+".toRegex())[0].removePrefix("/") - - when(command) { - - "getid" -> { - client.messages().send(actor) - .message(bot.getMessage("user_id").format(message.fromId)) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - } - - else -> { - - if (!bot.isUserAllowed(message.fromId)) { - return - } - - if (bot.settings.blockedCommands.contains(commandName)) { - client.messages().send(actor) - .message(bot.getMessage("command_blocked").format(command)) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - logger.warn("User ${message.fromId} tried to use the blocked command ${TextFormat.RED}`$command`${TextFormat.RESET}.") - return - } - - try { - Rcon.command(command, response = { - if (it.length < 4000) { - client.messages().send(actor) - .message( - if (it.isNotBlank()) { - bot.getMessage("command_sended").format(it) - } else { - bot.getMessage("response_null") - } - ) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - } else { - client.messages().send(actor) - .message("The command's response is too long.") - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - } - logger.info("Used ${TextFormat.YELLOW}`$command`${TextFormat.RESET} command by user ${message.fromId}") - }) - - } catch (e: IOException) { - client.messages().send(actor) - .message(bot.getMessage("failed_to_connect")) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - logger.error("Unhandled exception when trying to connect to RCON:", e) - - } catch (e: AuthenticationException) { - client.messages().send(actor) - .message(bot.getMessage("failed_to_authenticate")) - .peerId(message.peerId) - .randomId(Random.nextInt(10000)) - .execute() - logger.error("Unhandled exception on RCON authentication attempt:", e) - } - } - } - } - } - } - - override fun parse(message: CallbackMessage): String? { - if (message.type == Events.MESSAGE_NEW) { - gson.fromJson(message.getObject(), MessageFix::class.java).message?.let { - this.messageNewFix(it) - } - return "OK" - } - return super.parse(message) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt similarity index 84% rename from src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt rename to src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt index cffce71..016d843 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/objects/MessageFix.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt @@ -1,4 +1,4 @@ -package com.mefrreex.vkbot.objects +package com.mefrreex.vkbot.handler import com.google.gson.annotations.SerializedName import com.vk.api.sdk.objects.messages.Message diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt new file mode 100644 index 0000000..f95c589 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt @@ -0,0 +1,97 @@ +package com.mefrreex.vkbot.handler + +import com.mefrreex.vkbot.Bot +import com.mefrreex.vkbot.rcon.Rcon +import com.mefrreex.vkbot.utils.Keyboards +import com.mefrreex.vkbot.utils.TextFormat +import com.vk.api.sdk.client.VkApiClient +import com.vk.api.sdk.client.actors.GroupActor +import com.vk.api.sdk.events.Events +import com.vk.api.sdk.events.longpoll.GroupLongPollApi +import com.vk.api.sdk.objects.callback.messages.CallbackMessage +import com.vk.api.sdk.objects.messages.Message +import net.kronos.rkon.core.ex.AuthenticationException +import java.io.IOException + +class MessageHandler( + client: VkApiClient, + actor: GroupActor, + waitTime: Int, + private val bot: Bot +) : GroupLongPollApi(client, actor, waitTime) { + + private fun onMessageNew(message: Message) { + val text = message.text.lowercase() + + when { + + text == "начать" || text == "start" || text == "rcon" -> { + if (!bot.isUserAllowed(message.peerId)) { + bot.sendMessage(message.peerId, bot.getMessage("user_not_whitelisted")) + return + } + bot.sendMessage(message.peerId, bot.getMessage("start"), Keyboards.commandsKeyboard()) + } + + text.startsWith(bot.settings.commandPrefix) -> { + + val command = text.substring(bot.settings.commandPrefix.length, text.length) + val commandName = command.split("\\s+".toRegex())[0].removePrefix("/") + + when(command) { + + "getid" -> { + bot.sendMessage(message.peerId, bot.getMessage("user_id").format(message.fromId)) + } + + else -> { + + if (!bot.isUserAllowed(message.peerId)) { + return + } + + if (bot.settings.blockedCommands.contains(commandName)) { + bot.sendMessage(message.peerId, bot.getMessage("command_blocked").format(command)) + bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}`$command`${TextFormat.RESET}.") + return + } + + try { + Rcon.command(command, response = { + if (it.length < 4000) { + bot.sendMessage(message.peerId, if (it.isNotBlank()) { + bot.getMessage("command_sent").format(it) + } else { + bot.getMessage("response_null") + }) + } else { + bot.sendMessage(message.peerId, bot.getMessage("command_response_too_long")) + } + bot.logger.info("Used ${TextFormat.YELLOW}`$command`${TextFormat.RESET} command by user ${message.fromId}") + }) + + } catch (e: IOException) { + bot.sendMessage(message.peerId, bot.getMessage("failed_to_connect")) + bot.logger.error("Unhandled exception when trying to connect to RCON:", e) + + } catch (e: AuthenticationException) { + bot.sendMessage(message.peerId, bot.getMessage("failed_to_authenticate")) + bot.logger.error("Unhandled exception on RCON authentication attempt:", e) + } + } + } + } + } + } + + override fun parse(message: CallbackMessage): String? { + if (message.type == Events.MESSAGE_NEW) { + gson.fromJson(message.getObject(), MessageFix::class.java).message?.let { + this.onMessageNew(it) + } + return "OK" + } + return super.parse(message) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt index 0d7c1cf..e0f4a31 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt @@ -6,7 +6,6 @@ import java.util.* object Keyboards { - @Suppress("UNCHECKED_CAST") fun commandsKeyboard(): Keyboard { val bot = Bot.instance val keyboard = Keyboard() From 12d2bb97b756f2c903619815ef1c73f1b8ce7c59 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 06:15:44 +0300 Subject: [PATCH 08/20] refactor: new rcon client --- build.gradle.kts | 8 - .../mefrreex/vkbot/handler/MessageHandler.kt | 48 +++--- .../kotlin/com/mefrreex/vkbot/rcon/Rcon.kt | 23 --- .../com/mefrreex/vkbot/rcon/RconClient.kt | 91 +++++++++++ .../rcon/exception/AuthenticationException.kt | 3 + .../rcon/exception/ConnectionException.kt | 3 + .../mefrreex/vkbot/rcon/packet/RconPacket.kt | 67 ++++++++ .../vkbot/rcon/packet/RconPacketType.kt | 8 + .../kotlin/net/kronos/rkon/core/Rcon.java | 133 --------------- .../net/kronos/rkon/core/RconPacket.java | 152 ------------------ .../rkon/core/ex/AuthenticationException.java | 9 -- .../core/ex/MalformedPacketException.java | 11 -- src/main/resources/messages.yml | 5 +- 13 files changed, 201 insertions(+), 360 deletions(-) delete mode 100644 src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt delete mode 100644 src/main/kotlin/net/kronos/rkon/core/Rcon.java delete mode 100644 src/main/kotlin/net/kronos/rkon/core/RconPacket.java delete mode 100644 src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java delete mode 100644 src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java diff --git a/build.gradle.kts b/build.gradle.kts index bac9460..c783b4d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,12 +43,4 @@ tasks.withType { kotlin { jvmToolchain(17) -} - -sourceSets { - main { - java { - srcDirs("src/main/kotlin") - } - } } \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt index f95c589..bd21761 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt @@ -1,7 +1,8 @@ package com.mefrreex.vkbot.handler import com.mefrreex.vkbot.Bot -import com.mefrreex.vkbot.rcon.Rcon +import com.mefrreex.vkbot.rcon.RconClient +import com.mefrreex.vkbot.rcon.exception.ConnectionException import com.mefrreex.vkbot.utils.Keyboards import com.mefrreex.vkbot.utils.TextFormat import com.vk.api.sdk.client.VkApiClient @@ -10,8 +11,6 @@ import com.vk.api.sdk.events.Events import com.vk.api.sdk.events.longpoll.GroupLongPollApi import com.vk.api.sdk.objects.callback.messages.CallbackMessage import com.vk.api.sdk.objects.messages.Message -import net.kronos.rkon.core.ex.AuthenticationException -import java.io.IOException class MessageHandler( client: VkApiClient, @@ -56,27 +55,32 @@ class MessageHandler( return } + val rcon = RconClient(bot.settings.rconHost, bot.settings.rconPort) + try { - Rcon.command(command, response = { - if (it.length < 4000) { - bot.sendMessage(message.peerId, if (it.isNotBlank()) { - bot.getMessage("command_sent").format(it) - } else { - bot.getMessage("response_null") - }) + rcon.connect(bot.settings.rconPassword) + } catch (e: Exception) { + if (e is ConnectionException) { + bot.sendMessage(message.peerId, bot.getMessage("failed_to_connect")) + bot.logger.error("Unhandled exception when trying to connect to RCON:", e) + } else { + e.printStackTrace() + bot.sendMessage(message.peerId, bot.getMessage("failed_to_authenticate")) + bot.logger.error("Unhandled exception on RCON authentication attempt:", e) + } + } + + rcon.sendCommand(command).let { + if (it.length < 4000) { + bot.sendMessage(message.peerId, if (it.isNotBlank()) { + bot.getMessage("command_sent").format(it) } else { - bot.sendMessage(message.peerId, bot.getMessage("command_response_too_long")) - } - bot.logger.info("Used ${TextFormat.YELLOW}`$command`${TextFormat.RESET} command by user ${message.fromId}") - }) - - } catch (e: IOException) { - bot.sendMessage(message.peerId, bot.getMessage("failed_to_connect")) - bot.logger.error("Unhandled exception when trying to connect to RCON:", e) - - } catch (e: AuthenticationException) { - bot.sendMessage(message.peerId, bot.getMessage("failed_to_authenticate")) - bot.logger.error("Unhandled exception on RCON authentication attempt:", e) + bot.getMessage("response_null") + }) + } else { + bot.sendMessage(message.peerId, bot.getMessage("command_response_too_long")) + } + bot.logger.info("Used ${TextFormat.YELLOW}/$command${TextFormat.RESET} command by user ${message.fromId}") } } } diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt deleted file mode 100644 index 496ccdc..0000000 --- a/src/main/kotlin/com/mefrreex/vkbot/rcon/Rcon.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.mefrreex.vkbot.rcon - -import com.mefrreex.vkbot.Bot -import net.kronos.rkon.core.ex.AuthenticationException -import net.kronos.rkon.core.Rcon as RconManager -import kotlin.jvm.Throws -import java.io.IOException -import java.util.function.Consumer - -object Rcon { - - fun command(name: String, response: Consumer) { - response.accept(command(name)); - } - - @Throws(IOException::class, AuthenticationException::class) - fun command(name: String): String { - val settings = Bot.instance.settings - - val rcon = RconManager(settings.rconHost, settings.rconPort, settings.rconPassword.toByteArray()) - return rcon.command(name.toByteArray(charset("UTF-8"))); - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt new file mode 100644 index 0000000..b619233 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt @@ -0,0 +1,91 @@ +package com.mefrreex.vkbot.rcon + +import com.mefrreex.vkbot.rcon.exception.AuthenticationException +import com.mefrreex.vkbot.rcon.exception.ConnectionException +import com.mefrreex.vkbot.rcon.packet.RconPacket +import com.mefrreex.vkbot.rcon.packet.RconPacketType +import java.io.IOException +import java.net.InetSocketAddress +import java.nio.charset.Charset + +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; + +class RconClient( + host: String, + port: Int, + private val charset: Charset = StandardCharsets.UTF_8 +) { + private val address = InetSocketAddress(host, port) + private var socketChannel: SocketChannel? = null + private var password: String? = null + + /** + * Connect to the server using the provided password + * @param password Password for authentication + */ + fun connect(password: String?) { + this.password = password + this.connect() + } + + /** + * Connect to the server using the stored password + * @throws AuthenticationException If authentication fails + */ + fun connect() { + try { + socketChannel = SocketChannel.open().apply { + socket().setSoTimeout(1500) + connect(address) + } + } catch (e: IOException) { + throw AuthenticationException("Authorization error", e) + } + + val packet = RconPacket(RconPacketType.AUTH_REQUEST) + packet.payload = password!!.toByteArray() + + val response = this.sendPacket(packet) ?: throw ConnectionException("Server is offline") + + if (response.requestId == -1) { + throw AuthenticationException("Wrong password") + } + } + + /** + * Send a command to the server + * @param command Command to send + * @return Server response + */ + fun sendCommand(command: String): String { + val packet = RconPacket(RconPacketType.COMMAND_EXECUTE) + packet.payload = command.toByteArray(charset) + + val response = sendPacket(packet) + return String(response!!.payload, charset) + } + + /** + * Send the packet to the server + * @param packet RconPacket to send + * @param socketChannel SocketChannel to use for sending + * @return A packet sent by the server in response + */ + private fun sendPacket(packet: RconPacket, socketChannel: SocketChannel?): RconPacket? { + return try { + packet.send(socketChannel!!) + } catch (e: IOException) { + null + } + } + + /** + * Send the packet to the socketChannel + * @param packet RconPacket to send + * @return A packet sent by the server in response + */ + fun sendPacket(packet: RconPacket): RconPacket? { + return this.sendPacket(packet, socketChannel) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt new file mode 100644 index 0000000..e4c63ec --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt @@ -0,0 +1,3 @@ +package com.mefrreex.vkbot.rcon.exception + +class AuthenticationException(message: String?, cause: Throwable? = null) : RuntimeException(message, cause) \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt new file mode 100644 index 0000000..246f60a --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt @@ -0,0 +1,3 @@ +package com.mefrreex.vkbot.rcon.exception + +class ConnectionException(message: String?, cause: Throwable? = null) : RuntimeException(message, cause) \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt new file mode 100644 index 0000000..e50e2d3 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt @@ -0,0 +1,67 @@ +package com.mefrreex.vkbot.rcon.packet + +import java.io.DataInputStream +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.channels.SocketChannel +import java.util.* + + +class RconPacket( + val packetType: Int, + val requestId: Int = Random().nextInt() +) { + var payload = ByteArray(1) + + @Throws(IOException::class) + fun send(socketChannel: SocketChannel): RconPacket { + try { + this.write(socketChannel) + } catch (e: IOException) { + e.printStackTrace() + } + + return read(socketChannel) + } + + @Throws(IOException::class) + private fun write(socketChannel: SocketChannel) { + val length = payload.size + 14 + + val buffer = ByteBuffer.allocate(length) + buffer.order(ByteOrder.LITTLE_ENDIAN) + + buffer.putInt(length - 4) + buffer.putInt(requestId) + buffer.putInt(packetType) + buffer.put(payload) + + buffer.put(0.toByte()) + buffer.put(0.toByte()) + + socketChannel.write(ByteBuffer.wrap(buffer.array())) + } + + @Throws(IOException::class) + private fun read(socketChannel: SocketChannel): RconPacket { + val stream = socketChannel.socket().getInputStream() + + val buffer = ByteBuffer.allocate(4 * 3) + stream.read(buffer.array()) + buffer.order(ByteOrder.LITTLE_ENDIAN) + + val length = buffer.getInt() + val requestId = buffer.getInt() + val type = buffer.getInt() + + val payload = ByteArray(length - 10) + val dataStream = DataInputStream(stream) + dataStream.readFully(payload) + dataStream.read(ByteArray(2)) + + val packet = RconPacket(type, requestId) + packet.payload = payload + return packet + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt b/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt new file mode 100644 index 0000000..e48e02a --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt @@ -0,0 +1,8 @@ +package com.mefrreex.vkbot.rcon.packet + +object RconPacketType { + const val AUTH_REQUEST = 3 + const val AUTH_RESPONSE = 2 + const val COMMAND_EXECUTE = 2 + const val COMMAND_RESPONSE = 0 +} diff --git a/src/main/kotlin/net/kronos/rkon/core/Rcon.java b/src/main/kotlin/net/kronos/rkon/core/Rcon.java deleted file mode 100644 index 3d32ea5..0000000 --- a/src/main/kotlin/net/kronos/rkon/core/Rcon.java +++ /dev/null @@ -1,133 +0,0 @@ -package net.kronos.rkon.core; - -import java.io.IOException; -import java.net.Socket; -import java.nio.charset.Charset; -import java.util.Random; - -import net.kronos.rkon.core.ex.AuthenticationException; - -public class Rcon { - - private final Object sync = new Object(); - private final Random rand = new Random(); - - private int requestId; - private Socket socket; - - private Charset charset; - - /** - * Create, connect and authenticate a new Rcon object - * - * @param host Rcon server address - * @param port Rcon server port - * @param password Rcon server password - * - * @throws IOException - * @throws AuthenticationException - */ - public Rcon(String host, int port, byte[] password) throws IOException, AuthenticationException { - // Default charset is utf8 - this.charset = Charset.forName("UTF-8"); - - // Connect to host - this.connect(host, port, password); - } - - /** - * Connect to a rcon server - * - * @param host Rcon server address - * @param port Rcon server port - * @param password Rcon server password - * - * @throws IOException - * @throws AuthenticationException - */ - public void connect(String host, int port, byte[] password) throws IOException, AuthenticationException { - if(host == null || host.trim().isEmpty()) { - throw new IllegalArgumentException("Host can't be null or empty"); - } - - if(port < 1 || port > 65535) { - throw new IllegalArgumentException("Port is out of range"); - } - - // Connect to the rcon server - synchronized(sync) { - // New random request id - this.requestId = rand.nextInt(); - - // We can't reuse a socket, so we need a new one - this.socket = new Socket(host, port); - } - - // Send the auth packet - RconPacket res = this.send(RconPacket.SERVERDATA_AUTH, password); - - // Auth failed - if(res.getRequestId() == -1) { - throw new AuthenticationException("Password rejected by server"); - } - } - - /** - * Disconnect from the current server - * - * @throws IOException - */ - public void disconnect() throws IOException { - synchronized(sync) { - this.socket.close(); - } - } - - /** - * Send a command to the server - * - * @param payload The command to send - * @return The payload of the response - * - * @throws IOException - */ - public String command(String payload) throws IOException { - if(payload == null || payload.trim().isEmpty()) { - throw new IllegalArgumentException("Payload can't be null or empty"); - } - - RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload.getBytes()); - - return new String(response.getPayload(), this.getCharset()); - } - - public String command(byte[] payload) throws IOException { - - RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload); - - return new String(response.getPayload(), this.getCharset()); - } - - private RconPacket send(int type, byte[] payload) throws IOException { - synchronized(sync) { - return RconPacket.send(this, type, payload); - } - } - - public int getRequestId() { - return requestId; - } - - public Socket getSocket() { - return socket; - } - - public Charset getCharset() { - return charset; - } - - public void setCharset(Charset charset) { - this.charset = charset; - } - -} diff --git a/src/main/kotlin/net/kronos/rkon/core/RconPacket.java b/src/main/kotlin/net/kronos/rkon/core/RconPacket.java deleted file mode 100644 index b2deec2..0000000 --- a/src/main/kotlin/net/kronos/rkon/core/RconPacket.java +++ /dev/null @@ -1,152 +0,0 @@ -package net.kronos.rkon.core; - -import java.io.DataInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.SocketException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import net.kronos.rkon.core.ex.MalformedPacketException; - -public class RconPacket { - - public static final int SERVERDATA_EXECCOMMAND = 2; - public static final int SERVERDATA_AUTH = 3; - - private int requestId; - private int type; - private byte[] payload; - - private RconPacket(int requestId, int type, byte[] payload) { - this.requestId = requestId; - this.type = type; - this.payload = payload; - } - - public int getRequestId() { - return requestId; - } - - public int getType() { - return type; - } - - public byte[] getPayload() { - return payload; - } - - /** - * Send a Rcon packet and fetch the response - * - * @param rcon Rcon instance - * @param type The packet type - * @param payload The payload (password, command, etc.) - * @return A RconPacket object containing the response - * - * @throws IOException - * @throws MalformedPacketException - */ - protected static RconPacket send(Rcon rcon, int type, byte[] payload) throws IOException { - try { - RconPacket.write(rcon.getSocket().getOutputStream(), rcon.getRequestId(), type, payload); - } - catch(SocketException se) { - // Close the socket if something happens - rcon.getSocket().close(); - - // Rethrow the exception - throw se; - } - - return RconPacket.read(rcon.getSocket().getInputStream()); - } - - /** - * Write a rcon packet on an outputstream - * - * @param out The OutputStream to write on - * @param requestId The request id - * @param type The packet type - * @param payload The payload - * - * @throws IOException - */ - private static void write(OutputStream out, int requestId, int type, byte[] payload) throws IOException { - int bodyLength = RconPacket.getBodyLength(payload.length); - int packetLength = RconPacket.getPacketLength(bodyLength); - - ByteBuffer buffer = ByteBuffer.allocate(packetLength); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - buffer.putInt(bodyLength); - buffer.putInt(requestId); - buffer.putInt(type); - buffer.put(payload); - - // Null bytes terminators - buffer.put((byte)0); - buffer.put((byte)0); - - // Woosh! - out.write(buffer.array()); - out.flush(); - } - - /** - * Read an incoming rcon packet - * - * @param in The InputStream to read on - * @return The read RconPacket - * - * @throws IOException - * @throws MalformedPacketException - */ - private static RconPacket read(InputStream in) throws IOException { - // Header is 3 4-bytes ints - byte[] header = new byte[4 * 3]; - - // Read the 3 ints - in.read(header); - - try { - // Use a bytebuffer in little endian to read the first 3 ints - ByteBuffer buffer = ByteBuffer.wrap(header); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - int length = buffer.getInt(); - int requestId = buffer.getInt(); - int type = buffer.getInt(); - - // Payload size can be computed now that we have its length - byte[] payload = new byte[length - 4 - 4 - 2]; - - DataInputStream dis = new DataInputStream(in); - - // Read the full payload - dis.readFully(payload); - - // Read the null bytes - dis.read(new byte[2]); - - return new RconPacket(requestId, type, payload); - } - catch(BufferUnderflowException | EOFException e) { - throw new MalformedPacketException("Cannot read the whole packet"); - } - } - - private static int getPacketLength(int bodyLength) { - // 4 bytes for length + x bytes for body length - return 4 + bodyLength; - } - - private static int getBodyLength(int payloadLength) { - // 4 bytes for requestId, 4 bytes for type, x bytes for payload, 2 bytes for two null bytes - return 4 + 4 + payloadLength + 2; - } - -} diff --git a/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java b/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java deleted file mode 100644 index 45ef113..0000000 --- a/src/main/kotlin/net/kronos/rkon/core/ex/AuthenticationException.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.kronos.rkon.core.ex; - -public class AuthenticationException extends Exception { - - public AuthenticationException(String message) { - super(message); - } - -} diff --git a/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java b/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java deleted file mode 100644 index ab2254b..0000000 --- a/src/main/kotlin/net/kronos/rkon/core/ex/MalformedPacketException.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.kronos.rkon.core.ex; - -import java.io.IOException; - -public class MalformedPacketException extends IOException { - - public MalformedPacketException(String message) { - super(message); - } - -} diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index f93df06..6607f40 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -11,5 +11,6 @@ command_blocked: "Данная команда заблокирована." failed_to_connect: "Не удалось подключится к серверу. Возможно он оффлайн." failed_to_authenticate: "Не удалось пройти аутентификацию RCON." -command_sended: "Команда отправлена.\nОтвет сервера: %s" -response_null: "Команда отправлена. Сервер не вернул ответа." \ No newline at end of file +command_sent: "Команда отправлена.\nОтвет сервера: %s" +command_response_too_long: "Ответ сервера слишком длинный" +response_null: "Команда отправлена. Сервер не вернул ответа." From 36950fb992fafda2f02a38d73e2bc14693c3b748 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 07:57:08 +0300 Subject: [PATCH 09/20] refactor: translations --- .../kotlin/com/mefrreex/vkbot/Bootstrap.kt | 10 ++--- src/main/kotlin/com/mefrreex/vkbot/Bot.kt | 26 +++++++----- .../kotlin/com/mefrreex/vkbot/BotSettings.kt | 3 +- .../mefrreex/vkbot/handler/MessageHandler.kt | 25 +++++++----- .../com/mefrreex/vkbot/logger/Logger.kt | 4 -- .../vkbot/translation/TranslationService.kt | 16 ++++++++ .../translation/TranslationServiceImpl.kt | 40 +++++++++++++++++++ .../com/mefrreex/vkbot/utils/ConfigHelper.kt | 2 +- .../com/mefrreex/vkbot/utils/Keyboards.kt | 36 ++++++++--------- .../com/mefrreex/vkbot/utils/TextFormat.kt | 35 ++++++---------- src/main/resources/META-INF/MANIFEST.MF | 3 -- src/main/resources/config.yml | 6 +++ src/main/resources/lang/rus.yml | 14 +++++++ 13 files changed, 141 insertions(+), 79 deletions(-) create mode 100644 src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt delete mode 100644 src/main/resources/META-INF/MANIFEST.MF create mode 100644 src/main/resources/lang/rus.yml diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt index 614544e..fc29fc6 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -5,23 +5,23 @@ import com.mefrreex.vkbot.utils.ConfigHelper import java.io.File -val logger = Logger.instance +val logger = Logger() fun main() { logger.info("Starting the bot...") - val resources = listOf("config.yml", "allow_list.yml", "messages.yml") + val resources = listOf(ConfigHelper.CONFIG, ConfigHelper.ALLOW_LIST) resources.forEach { ConfigHelper.loadConfig(it) if (!File(it).exists()) { ConfigHelper.saveResource(it) - logger.info("Resource $it saved.") + logger.info("Resource $it saved") } } val config = ConfigHelper.getConfig(ConfigHelper.CONFIG) if (config == null) { - logger.error("Failed to start bot. File config.yml not found.") + logger.error("Failed to start bot. File config.yml not found") return } @@ -32,7 +32,7 @@ fun main() { return } - Bot(config.nodes("vk.groupId").asInt(), token) + Bot(config, config.nodes("vk.groupId").asInt(), token) } catch (e: Exception) { logger.error("Failed to start the bot", e) return diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt index 523ceec..1dea1ea 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt @@ -1,7 +1,10 @@ package com.mefrreex.vkbot +import com.mefrreex.config.Config import com.mefrreex.vkbot.handler.MessageHandler import com.mefrreex.vkbot.logger.Logger +import com.mefrreex.vkbot.translation.TranslationService +import com.mefrreex.vkbot.translation.TranslationServiceImpl import com.mefrreex.vkbot.utils.ConfigHelper import com.vk.api.sdk.client.VkApiClient import com.vk.api.sdk.client.actors.GroupActor @@ -9,16 +12,17 @@ import com.vk.api.sdk.httpclient.HttpTransportClient import com.vk.api.sdk.objects.messages.Keyboard import kotlin.random.Random -class Bot(groupId: Int, accessToken: String) { +class Bot(private val config: Config, groupId: Int, accessToken: String) { private val allowList = ConfigHelper.getConfigNotNull(ConfigHelper.ALLOW_LIST) private val messages = ConfigHelper.getConfigNotNull(ConfigHelper.MESSAGES) - val logger = Logger.instance - val settings = BotSettings() + private val vkClient: VkApiClient + private val groupActor: GroupActor - val vkClient: VkApiClient - val groupActor: GroupActor + val logger = Logger() + val settings = BotSettings(config) + val translationService: TranslationService init { instance = this @@ -33,6 +37,8 @@ class Bot(groupId: Int, accessToken: String) { .messageNew(true) .execute() + translationService = TranslationServiceImpl(config) + MessageHandler(vkClient, groupActor, 1, this).run() } @@ -40,10 +46,6 @@ class Bot(groupId: Int, accessToken: String) { return userId in allowList.node("allowed-users").asList() } - fun getMessage(key: String): String { - return messages.node(key).asString() - } - fun sendMessage(userId: Int, message: String, keyboard: Keyboard? = null) { vkClient.messages().send(groupActor) .message(message) @@ -56,6 +58,10 @@ class Bot(groupId: Int, accessToken: String) { } companion object { - lateinit var instance: Bot + private lateinit var instance: Bot + + fun getInstance(): Bot { + return instance + } } } \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt b/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt index dab1c97..89e87b1 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt @@ -1,10 +1,9 @@ package com.mefrreex.vkbot import com.mefrreex.config.Config -import com.mefrreex.vkbot.utils.ConfigHelper data class BotSettings( - private val config: Config = ConfigHelper.getConfigNotNull(ConfigHelper.CONFIG) + private val config: Config ) { val rconHost: String = config.nodes("rcon.host").asString() val rconPort: Int = config.nodes("rcon.port").asInt() diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt index bd21761..f2e718e 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt @@ -3,6 +3,7 @@ package com.mefrreex.vkbot.handler import com.mefrreex.vkbot.Bot import com.mefrreex.vkbot.rcon.RconClient import com.mefrreex.vkbot.rcon.exception.ConnectionException +import com.mefrreex.vkbot.translation.TranslationService import com.mefrreex.vkbot.utils.Keyboards import com.mefrreex.vkbot.utils.TextFormat import com.vk.api.sdk.client.VkApiClient @@ -19,6 +20,8 @@ class MessageHandler( private val bot: Bot ) : GroupLongPollApi(client, actor, waitTime) { + private val translationService = TranslationService.getInstance() + private fun onMessageNew(message: Message) { val text = message.text.lowercase() @@ -26,10 +29,10 @@ class MessageHandler( text == "начать" || text == "start" || text == "rcon" -> { if (!bot.isUserAllowed(message.peerId)) { - bot.sendMessage(message.peerId, bot.getMessage("user_not_whitelisted")) + bot.sendMessage(message.peerId, translationService.translate("generic-user-not-whitelisted")) return } - bot.sendMessage(message.peerId, bot.getMessage("start"), Keyboards.commandsKeyboard()) + bot.sendMessage(message.peerId, translationService.translate("command-start-success"), Keyboards.commandsKeyboard()) } text.startsWith(bot.settings.commandPrefix) -> { @@ -40,7 +43,7 @@ class MessageHandler( when(command) { "getid" -> { - bot.sendMessage(message.peerId, bot.getMessage("user_id").format(message.fromId)) + bot.sendMessage(message.peerId, translationService.translate("command-userid-success", message.fromId)) } else -> { @@ -50,8 +53,8 @@ class MessageHandler( } if (bot.settings.blockedCommands.contains(commandName)) { - bot.sendMessage(message.peerId, bot.getMessage("command_blocked").format(command)) - bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}`$command`${TextFormat.RESET}.") + bot.sendMessage(message.peerId, translationService.translate("command_blocked", command)) + bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}/$command${TextFormat.RESET}.") return } @@ -61,24 +64,24 @@ class MessageHandler( rcon.connect(bot.settings.rconPassword) } catch (e: Exception) { if (e is ConnectionException) { - bot.sendMessage(message.peerId, bot.getMessage("failed_to_connect")) + bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-connect")) bot.logger.error("Unhandled exception when trying to connect to RCON:", e) } else { e.printStackTrace() - bot.sendMessage(message.peerId, bot.getMessage("failed_to_authenticate")) + bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-authenticate")) bot.logger.error("Unhandled exception on RCON authentication attempt:", e) } } rcon.sendCommand(command).let { - if (it.length < 4000) { + if (it.length <= 4000) { bot.sendMessage(message.peerId, if (it.isNotBlank()) { - bot.getMessage("command_sent").format(it) + translationService.translate("rcon-command-sent", it) } else { - bot.getMessage("response_null") + translationService.translate("rcon-response-empty") }) } else { - bot.sendMessage(message.peerId, bot.getMessage("command_response_too_long")) + bot.sendMessage(message.peerId, translationService.translate("rcon-command-response-too-long")) } bot.logger.info("Used ${TextFormat.YELLOW}/$command${TextFormat.RESET} command by user ${message.fromId}") } diff --git a/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt b/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt index 5554ffc..4022b7a 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt @@ -34,8 +34,4 @@ class Logger { println("${TextFormat.CYAN}$date ${TextFormat.WHITE}[$level] $message") throwable?.printStackTrace() } - - companion object { - val instance = Logger() - } } \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt b/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt new file mode 100644 index 0000000..9bc6a8a --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt @@ -0,0 +1,16 @@ +package com.mefrreex.vkbot.translation + +import com.mefrreex.vkbot.Bot + +interface TranslationService { + + fun getLanguage(): String + + fun translate(key: String, vararg replacements: Any?): String + + companion object { + fun getInstance(): TranslationService { + return Bot.getInstance().translationService + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt b/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt new file mode 100644 index 0000000..ab8c01f --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt @@ -0,0 +1,40 @@ +package com.mefrreex.vkbot.translation + +import com.mefrreex.config.Config +import com.mefrreex.config.YamlConfig +import com.mefrreex.vkbot.utils.ConfigHelper +import java.io.File + +class TranslationServiceImpl(config: Config) : TranslationService { + + private val language: String = config.node("language").asString() + private val languages: Map + + init { + ConfigHelper.saveResource("lang/$language.yml") + val languageConfig = YamlConfig(File("lang", "$language.yml")) + this.languages = languageConfig.root().asMap() + } + + /** + * Get selected language + */ + override fun getLanguage(): String { + return language + } + + /** + * Get translation by key + * @param key Message key + * @param replacements Message parameters + */ + override fun translate(key: String, vararg replacements: Any?): String { + var translated = languages[key] ?: key + + for ((i, replacement) in replacements.withIndex()) { + translated = translated.replace("[$i]", replacement.toString()) + } + + return translated + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt index 765facb..f33ba7d 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt @@ -8,7 +8,6 @@ object ConfigHelper { const val CONFIG = "config.yml" const val ALLOW_LIST = "allow_list.yml" - const val MESSAGES = "messages.yml" private val configs: MutableMap = mutableMapOf() @@ -28,6 +27,7 @@ object ConfigHelper { val resourceStream = javaClass.classLoader.getResourceAsStream(target) val outputFile = File(output) if (resourceStream != null && (replace || !outputFile.exists())) { + outputFile.parentFile.mkdirs() outputFile.createNewFile() outputFile.outputStream().use { resourceStream.copyTo(it) } } diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt index e0f4a31..a87693a 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt @@ -2,28 +2,24 @@ package com.mefrreex.vkbot.utils import com.mefrreex.vkbot.Bot import com.vk.api.sdk.objects.messages.* -import java.util.* object Keyboards { - fun commandsKeyboard(): Keyboard { - val bot = Bot.instance - val keyboard = Keyboard() + private val settings = Bot.getInstance().settings - val list: MutableList = ArrayList() - for (command in bot.settings.fastCommands) { - list.add( - KeyboardButton() - .setColor(KeyboardButtonColor.POSITIVE) - .setAction( - KeyboardButtonAction() - .setLabel(bot.settings.commandPrefix + command) - .setType(TemplateActionTypeNames.TEXT) - ) - ) - } - keyboard.buttons = listOf>(list) - keyboard.inline = true - return keyboard + fun commandsKeyboard(): Keyboard { + return Keyboard() + .setInline(true) + .setButtons(listOf( + settings.fastCommands.map { command -> + KeyboardButton() + .setColor(KeyboardButtonColor.POSITIVE) + .setAction( + KeyboardButtonAction() + .setLabel(settings.commandPrefix + command) + .setType(TemplateActionTypeNames.TEXT) + ) + } + )) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt b/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt index 6ec71c6..274d6b5 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt @@ -1,27 +1,16 @@ package com.mefrreex.vkbot.utils object TextFormat { - - val BLACK = "\u001b[0;30m" - val RED = "\u001b[0;31m" - val GREEN = "\u001b[0;92m" - val YELLOW = "\u001b[0;33m" - val BLUE = "\u001b[0;34m" - val MAGENTA = "\u001b[0;35m" - val CYAN = "\u001b[0;36m" - val WHITE = "\u001b[0;37m" - - val BLACK_BACKGROUND = "\u001b[40m" - val RED_BACKGROUND = "\u001b[41m" - val GREEN_BACKGROUND = "\u001b[42m" - val YELLOW_BACKGROUND = "\u001b[43m" - val BLUE_BACKGROUND = "\u001b[44m" - val MAGENTA_BACKGROUND = "\u001b[45m" - val CYAN_BACKGROUND = "\u001b[46m" - val WHITE_BACKGROUND = "\u001b[47m" - - val RESET = "\u001b[0m" - val BOLD = "\u001b[1m" - val UNDERLINE = "\u001b[4m" - val REVERSED = "\u001b[7m" + const val BLACK = "\u001b[0;30m" + const val RED = "\u001b[0;31m" + const val GREEN = "\u001b[0;92m" + const val YELLOW = "\u001b[0;33m" + const val BLUE = "\u001b[0;34m" + const val MAGENTA = "\u001b[0;35m" + const val CYAN = "\u001b[0;36m" + const val WHITE = "\u001b[0;37m" + const val RESET = "\u001b[0m" + const val BOLD = "\u001b[1m" + const val UNDERLINE = "\u001b[4m" + const val REVERSED = "\u001b[7m" } \ No newline at end of file diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF deleted file mode 100644 index fe490c3..0000000 --- a/src/main/resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Main-Class: com.mefrreex.vkbot.BootstrapKt - diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 15b2922..c47c976 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,12 +1,18 @@ +# Доступные языки: eng (English), rus (Русский) +language: "rus" + +# Настройки VK vk: groupId: 123 # Id группы ВК accessToken: "token" # Токен группы ВК +# Настройки RCON rcon: host: "localhost" # Адрес RCON port: 19132 # Порт RCON password: "password" # Пароль RCON +# Настройки команд commands: # Символ перед командой для отправки комманды. Например '/'. # Оставьте пустым если не требуется. diff --git a/src/main/resources/lang/rus.yml b/src/main/resources/lang/rus.yml new file mode 100644 index 0000000..1a2511e --- /dev/null +++ b/src/main/resources/lang/rus.yml @@ -0,0 +1,14 @@ +# Общие сообщения +generic-user-not-whitelisted: "У вас нет разрешения на использование RCON" +generic-command-blocked: "Данная команда заблокирована" + +# Сообщения команд +command-userid-success: "Ваш ID: [0]" +command-start-success: "Введите команду или используйте кнопку ниже." + +# Сообщения RCON +rcon-failed-to-connect: "Не удалось подключиться к серверу" +rcon-failed-to-authenticate: "Не удалось пройти аутентификацию" +rcon-command-sent: "Команда отправлена.\nОтвет сервера: [0]" +rcon-response-empty: "Команда отправлена. Сервер не вернул ответ" +rcon-command-response-too-long: "Ответ сервера слишком длинный" From 74248027d9a41db134b45424e4f51b2524685835 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 08:02:42 +0300 Subject: [PATCH 10/20] feat: english language --- src/main/kotlin/com/mefrreex/vkbot/Bot.kt | 3 +-- .../com/mefrreex/vkbot/handler/MessageHandler.kt | 4 ++-- src/main/resources/lang/eng.yml | 14 ++++++++++++++ src/main/resources/lang/rus.yml | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/lang/eng.yml diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt index 1dea1ea..c3c7c84 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt @@ -12,10 +12,9 @@ import com.vk.api.sdk.httpclient.HttpTransportClient import com.vk.api.sdk.objects.messages.Keyboard import kotlin.random.Random -class Bot(private val config: Config, groupId: Int, accessToken: String) { +class Bot(config: Config, groupId: Int, accessToken: String) { private val allowList = ConfigHelper.getConfigNotNull(ConfigHelper.ALLOW_LIST) - private val messages = ConfigHelper.getConfigNotNull(ConfigHelper.MESSAGES) private val vkClient: VkApiClient private val groupActor: GroupActor diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt index f2e718e..517b72a 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt @@ -53,7 +53,7 @@ class MessageHandler( } if (bot.settings.blockedCommands.contains(commandName)) { - bot.sendMessage(message.peerId, translationService.translate("command_blocked", command)) + bot.sendMessage(message.peerId, translationService.translate("generic-command-blocked", command)) bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}/$command${TextFormat.RESET}.") return } @@ -83,7 +83,7 @@ class MessageHandler( } else { bot.sendMessage(message.peerId, translationService.translate("rcon-command-response-too-long")) } - bot.logger.info("Used ${TextFormat.YELLOW}/$command${TextFormat.RESET} command by user ${message.fromId}") + bot.logger.info("User ${message.fromId} used the command ${TextFormat.YELLOW}/$command${TextFormat.RESET}") } } } diff --git a/src/main/resources/lang/eng.yml b/src/main/resources/lang/eng.yml new file mode 100644 index 0000000..f30d655 --- /dev/null +++ b/src/main/resources/lang/eng.yml @@ -0,0 +1,14 @@ +# Generic messages +generic-user-not-whitelisted: "You do not have permission to use RCON" +generic-command-blocked: "This command is blocked" + +# Command messages +command-userid-success: "Your ID: [0]" +command-start-success: "Type a command or use the button below" + +# RCON Messages +rcon-failed-to-connect: "Failed to connect to the server" +rcon-failed-to-authenticate: "Failed to authenticate" +rcon-command-sent: "Command sent.\nServer response: [0]" +rcon-response-empty: "Command sent. Server didn't return a response" +rcon-command-response-too-long: "Command sent. Server response is too long" diff --git a/src/main/resources/lang/rus.yml b/src/main/resources/lang/rus.yml index 1a2511e..e1e49b7 100644 --- a/src/main/resources/lang/rus.yml +++ b/src/main/resources/lang/rus.yml @@ -4,7 +4,7 @@ generic-command-blocked: "Данная команда заблокирована # Сообщения команд command-userid-success: "Ваш ID: [0]" -command-start-success: "Введите команду или используйте кнопку ниже." +command-start-success: "Введите команду или используйте кнопку ниже" # Сообщения RCON rcon-failed-to-connect: "Не удалось подключиться к серверу" From c929434d8a64ed13225dbf989efe946c417364e0 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 08:49:34 +0300 Subject: [PATCH 11/20] refactor: commands and handlers --- src/main/kotlin/com/mefrreex/vkbot/Bot.kt | 17 +++- .../com/mefrreex/vkbot/command/Command.kt | 7 ++ .../com/mefrreex/vkbot/command/CommandData.kt | 3 + .../mefrreex/vkbot/command/CommandParser.kt | 69 +++++++++++++++ .../mefrreex/vkbot/command/CommandService.kt | 16 ++++ .../vkbot/command/CommandServiceImpl.kt | 26 ++++++ .../mefrreex/vkbot/command/SlashCommand.kt | 13 +++ .../com/mefrreex/vkbot/command/WordCommand.kt | 12 +++ .../vkbot/command/impl/GetIDCommand.kt | 15 ++++ .../vkbot/command/impl/StartCommand.kt | 20 +++++ .../vkbot/handler/CommandMessageHandler.kt | 42 ++++++++++ .../vkbot/handler/DefaultMessageHandler.kt | 70 ++++++++++++++++ .../mefrreex/vkbot/handler/MessageHandler.kt | 83 +------------------ .../vkbot/translation/TranslationService.kt | 8 -- 14 files changed, 311 insertions(+), 90 deletions(-) create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/Command.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt create mode 100644 src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt index c3c7c84..facabfd 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/Bot.kt @@ -1,7 +1,12 @@ package com.mefrreex.vkbot import com.mefrreex.config.Config -import com.mefrreex.vkbot.handler.MessageHandler +import com.mefrreex.vkbot.command.CommandService +import com.mefrreex.vkbot.command.CommandServiceImpl +import com.mefrreex.vkbot.command.impl.GetIDCommand +import com.mefrreex.vkbot.command.impl.StartCommand +import com.mefrreex.vkbot.handler.CommandMessageHandler +import com.mefrreex.vkbot.handler.DefaultMessageHandler import com.mefrreex.vkbot.logger.Logger import com.mefrreex.vkbot.translation.TranslationService import com.mefrreex.vkbot.translation.TranslationServiceImpl @@ -21,7 +26,9 @@ class Bot(config: Config, groupId: Int, accessToken: String) { val logger = Logger() val settings = BotSettings(config) + val translationService: TranslationService + val commandService: CommandService init { instance = this @@ -37,8 +44,14 @@ class Bot(config: Config, groupId: Int, accessToken: String) { .execute() translationService = TranslationServiceImpl(config) + commandService = CommandServiceImpl() + commandService.register( + StartCommand(), + GetIDCommand() + ) - MessageHandler(vkClient, groupActor, 1, this).run() + DefaultMessageHandler(vkClient, groupActor, 1, this).run() + CommandMessageHandler(vkClient, groupActor, 1, this).run() } fun isUserAllowed(userId: Int): Boolean { diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt b/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt new file mode 100644 index 0000000..a176b9b --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt @@ -0,0 +1,7 @@ +package com.mefrreex.vkbot.command + +abstract class Command( + val name: String, + val description: String = "", + val aliases: Array = emptyArray() +) \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt b/src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt new file mode 100644 index 0000000..d997757 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt @@ -0,0 +1,3 @@ +package com.mefrreex.vkbot.command + +class CommandData(val name: String, val args: List) \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt b/src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt new file mode 100644 index 0000000..1978171 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt @@ -0,0 +1,69 @@ +package com.mefrreex.vkbot.command + + +object CommandParser { + + fun canParse(command: String): Boolean { + return command.startsWith("/") + } + + fun parse(command: String?): CommandData? { + var command = command + if (command == null || command.isEmpty()) { + return null + } + + if (command.startsWith("/")) { + command = command.substring(1) + } + + val arguments = parseArguments(command) + if (arguments.isEmpty()) { + return null + } + + val name = arguments[0] + val args = arguments.subList(1, arguments.size) + + return CommandData(name, args) + } + + fun parseArguments(command: String?): List { + if (command == null || command.isEmpty()) { + return emptyList() + } + + val arguments: MutableList = ArrayList() + + var quotes = false + + var currentArgument = StringBuilder() + for (i in 0 until command.length) { + val c = command[i] + if (c == ' ') { + if (quotes) { + currentArgument.append(c) + } else { + if (!currentArgument.isEmpty()) { + arguments.add(currentArgument.toString()) + currentArgument = StringBuilder() + } + } + } else if (c == '"') { + quotes = !quotes + } else { + currentArgument.append(c) + } + } + + if (quotes) { + throw RuntimeException() + } + + if (!currentArgument.isEmpty()) { + arguments.add(currentArgument.toString()) + } + + return arguments + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt b/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt new file mode 100644 index 0000000..760c2e5 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt @@ -0,0 +1,16 @@ +package com.mefrreex.vkbot.command + +import com.vk.api.sdk.objects.messages.Message + +interface CommandService { + + fun getCommands(): Map + + fun getCommand(name: String): Command? + + fun register(vararg commands: Command) { + commands.forEach { register(it)} + } + + fun register(command: Command) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt b/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt new file mode 100644 index 0000000..b111b29 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt @@ -0,0 +1,26 @@ +package com.mefrreex.vkbot.command + +class CommandServiceImpl : CommandService { + val commands = HashMap() + + override fun getCommands(): Map { + return commands + } + + override fun getCommand(name: String): Command? { + return commands[name] + } + + override fun register(vararg commands: Command) { + commands.forEach { register(it)} + } + + override fun register(command: Command) { + commands[command.name.lowercase()] = command + command.aliases.forEach { + if (commands[it.lowercase()] == null) { + commands[it.lowercase()] = command + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt b/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt new file mode 100644 index 0000000..d10cdc0 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt @@ -0,0 +1,13 @@ +package com.mefrreex.vkbot.command + +import com.vk.api.sdk.objects.messages.Message + +abstract class SlashCommand( + name: String, + description: String = "", + aliases: Array = emptyArray() +) : + Command(name, description, aliases) { + + abstract fun execute(message: Message, label: String, args: List) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt b/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt new file mode 100644 index 0000000..3d3082a --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt @@ -0,0 +1,12 @@ +package com.mefrreex.vkbot.command + +import com.vk.api.sdk.objects.messages.Message + +abstract class WordCommand( + name: String, + aliases: Array = emptyArray() +) : + Command(name, "", aliases) +{ + abstract fun execute(message: Message) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt b/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt new file mode 100644 index 0000000..48acda4 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt @@ -0,0 +1,15 @@ +package com.mefrreex.vkbot.command.impl + +import com.mefrreex.vkbot.Bot +import com.mefrreex.vkbot.command.SlashCommand +import com.vk.api.sdk.objects.messages.Message + +class GetIDCommand : SlashCommand("getid") { + + private val bot = Bot.getInstance() + private val translationService = bot.translationService + + override fun execute(message: Message, label: String, args: List) { + bot.sendMessage(message.peerId, translationService.translate("command-userid-success", message.fromId)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt b/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt new file mode 100644 index 0000000..da37f05 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt @@ -0,0 +1,20 @@ +package com.mefrreex.vkbot.command.impl + +import com.mefrreex.vkbot.Bot +import com.mefrreex.vkbot.command.WordCommand +import com.mefrreex.vkbot.utils.Keyboards +import com.vk.api.sdk.objects.messages.Message + +class StartCommand : WordCommand("start", arrayOf("rcon", "начать")) { + + private val bot = Bot.getInstance() + private val translationService = bot.translationService + + override fun execute(message: Message) { + if (!bot.isUserAllowed(message.peerId)) { + bot.sendMessage(message.peerId, translationService.translate("generic-user-not-whitelisted")) + return + } + bot.sendMessage(message.peerId, translationService.translate("command-start-success"), Keyboards.commandsKeyboard()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt new file mode 100644 index 0000000..9423258 --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt @@ -0,0 +1,42 @@ +package com.mefrreex.vkbot.handler + +import com.mefrreex.vkbot.Bot +import com.mefrreex.vkbot.command.CommandParser +import com.mefrreex.vkbot.command.SlashCommand +import com.mefrreex.vkbot.command.WordCommand +import com.vk.api.sdk.client.VkApiClient +import com.vk.api.sdk.client.actors.GroupActor +import com.vk.api.sdk.objects.messages.Message + +class CommandMessageHandler( + client: VkApiClient, + actor: GroupActor, + waitTime: Int, + private val bot: Bot +) : MessageHandler(client, actor, waitTime) { + + private val commandService = bot.commandService + + override fun onMessageNew(message: Message) { + val wordCommand = commandService.getCommand(message.text) + if (wordCommand is WordCommand) { + wordCommand.execute(message) + return + } + + if (!CommandParser.canParse(message.text)) { + return + } + + try { + val data = CommandParser.parse(message.text) + val command = commandService.getCommand(data!!.name) + + if (command is SlashCommand) { + command.execute(message, data.name, data.args) + } + } catch (e: Exception) { + bot.sendMessage(message.peerId, e.message!!) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt new file mode 100644 index 0000000..0faad3f --- /dev/null +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt @@ -0,0 +1,70 @@ +package com.mefrreex.vkbot.handler + +import com.mefrreex.vkbot.Bot +import com.mefrreex.vkbot.rcon.RconClient +import com.mefrreex.vkbot.rcon.exception.ConnectionException +import com.mefrreex.vkbot.utils.TextFormat +import com.vk.api.sdk.client.VkApiClient +import com.vk.api.sdk.client.actors.GroupActor +import com.vk.api.sdk.objects.messages.Message + +class DefaultMessageHandler( + client: VkApiClient, + actor: GroupActor, + waitTime: Int, + private val bot: Bot +) : MessageHandler(client, actor, waitTime) { + + private val translationService = bot.translationService + + override fun onMessageNew(message: Message) { + val text = message.text.lowercase() + + if (text.startsWith(bot.settings.commandPrefix)) { + val command = text.substring(bot.settings.commandPrefix.length, text.length) + val commandName = command.split("\\s+".toRegex())[0].removePrefix("/") + + if (bot.commandService.getCommand(commandName) != null) { + return + } + + if (!bot.isUserAllowed(message.peerId)) { + return + } + + if (bot.settings.blockedCommands.contains(commandName)) { + bot.sendMessage(message.peerId, translationService.translate("generic-command-blocked", command)) + bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}/$command${TextFormat.RESET}.") + return + } + + val rcon = RconClient(bot.settings.rconHost, bot.settings.rconPort) + + try { + rcon.connect(bot.settings.rconPassword) + } catch (e: Exception) { + if (e is ConnectionException) { + bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-connect")) + bot.logger.error("Unhandled exception when trying to connect to RCON:", e) + } else { + e.printStackTrace() + bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-authenticate")) + bot.logger.error("Unhandled exception on RCON authentication attempt:", e) + } + } + + rcon.sendCommand(command).let { + if (it.length <= 4000) { + bot.sendMessage(message.peerId, if (it.isNotBlank()) { + translationService.translate("rcon-command-sent", it) + } else { + translationService.translate("rcon-response-empty") + }) + } else { + bot.sendMessage(message.peerId, translationService.translate("rcon-command-response-too-long")) + } + bot.logger.info("User ${message.fromId} used the command ${TextFormat.YELLOW}/$command${TextFormat.RESET}") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt index 517b72a..cc76fc6 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt @@ -1,11 +1,5 @@ package com.mefrreex.vkbot.handler -import com.mefrreex.vkbot.Bot -import com.mefrreex.vkbot.rcon.RconClient -import com.mefrreex.vkbot.rcon.exception.ConnectionException -import com.mefrreex.vkbot.translation.TranslationService -import com.mefrreex.vkbot.utils.Keyboards -import com.mefrreex.vkbot.utils.TextFormat import com.vk.api.sdk.client.VkApiClient import com.vk.api.sdk.client.actors.GroupActor import com.vk.api.sdk.events.Events @@ -13,83 +7,13 @@ import com.vk.api.sdk.events.longpoll.GroupLongPollApi import com.vk.api.sdk.objects.callback.messages.CallbackMessage import com.vk.api.sdk.objects.messages.Message -class MessageHandler( +abstract class MessageHandler( client: VkApiClient, actor: GroupActor, - waitTime: Int, - private val bot: Bot + waitTime: Int ) : GroupLongPollApi(client, actor, waitTime) { - private val translationService = TranslationService.getInstance() - - private fun onMessageNew(message: Message) { - val text = message.text.lowercase() - - when { - - text == "начать" || text == "start" || text == "rcon" -> { - if (!bot.isUserAllowed(message.peerId)) { - bot.sendMessage(message.peerId, translationService.translate("generic-user-not-whitelisted")) - return - } - bot.sendMessage(message.peerId, translationService.translate("command-start-success"), Keyboards.commandsKeyboard()) - } - - text.startsWith(bot.settings.commandPrefix) -> { - - val command = text.substring(bot.settings.commandPrefix.length, text.length) - val commandName = command.split("\\s+".toRegex())[0].removePrefix("/") - - when(command) { - - "getid" -> { - bot.sendMessage(message.peerId, translationService.translate("command-userid-success", message.fromId)) - } - - else -> { - - if (!bot.isUserAllowed(message.peerId)) { - return - } - - if (bot.settings.blockedCommands.contains(commandName)) { - bot.sendMessage(message.peerId, translationService.translate("generic-command-blocked", command)) - bot.logger.warn("User ${message.peerId} tried to use the blocked command ${TextFormat.RED}/$command${TextFormat.RESET}.") - return - } - - val rcon = RconClient(bot.settings.rconHost, bot.settings.rconPort) - - try { - rcon.connect(bot.settings.rconPassword) - } catch (e: Exception) { - if (e is ConnectionException) { - bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-connect")) - bot.logger.error("Unhandled exception when trying to connect to RCON:", e) - } else { - e.printStackTrace() - bot.sendMessage(message.peerId, translationService.translate("rcon-failed-to-authenticate")) - bot.logger.error("Unhandled exception on RCON authentication attempt:", e) - } - } - - rcon.sendCommand(command).let { - if (it.length <= 4000) { - bot.sendMessage(message.peerId, if (it.isNotBlank()) { - translationService.translate("rcon-command-sent", it) - } else { - translationService.translate("rcon-response-empty") - }) - } else { - bot.sendMessage(message.peerId, translationService.translate("rcon-command-response-too-long")) - } - bot.logger.info("User ${message.fromId} used the command ${TextFormat.YELLOW}/$command${TextFormat.RESET}") - } - } - } - } - } - } + abstract fun onMessageNew(message: Message); override fun parse(message: CallbackMessage): String? { if (message.type == Events.MESSAGE_NEW) { @@ -100,5 +24,4 @@ class MessageHandler( } return super.parse(message) } - } \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt b/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt index 9bc6a8a..0e08701 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt +++ b/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt @@ -1,16 +1,8 @@ package com.mefrreex.vkbot.translation -import com.mefrreex.vkbot.Bot - interface TranslationService { fun getLanguage(): String fun translate(key: String, vararg replacements: Any?): String - - companion object { - fun getInstance(): TranslationService { - return Bot.getInstance().translationService - } - } } \ No newline at end of file From 8e0af94dd71179d155fbf9e5983a394350278017 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 09:10:38 +0300 Subject: [PATCH 12/20] docs: update README --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7b45d1a..730d8f6 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ # Vk-Rcon-Bot A simple Rcon Bot for VK -## Used libraries: -- [vk-java-sdk](https://github.com/VKCOM/vk-java-sdk) -- [snakeyaml](https://github.com/snakeyaml/snakeyaml) -- [rkon-core](https://github.com/kr5ch/rkon-core) +## 🎮 Run +`java -jar VkRconBot-.jar` - -## Build JAR file +## 🛠 Build JAR file - `git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git` - `cd Vk-Rcon-Bot` -- `mvn clean package` +- `gradle build` -## Run -`java -jar VkRconBot-1.4.jar` +## 📄 Used libraries: +- [Vk-Java-Sdk](https://github.com/VKCOM/vk-java-sdk) +- [Configuration](https://github.com/MEFRREEX/Configuration) \ No newline at end of file From ee1d312371c838a694378e48502676957ab54241 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Fri, 3 May 2024 09:25:18 +0300 Subject: [PATCH 13/20] refactor: separation on api and bot --- api/build.gradle.kts | 11 ++++ .../com/mefrreex/vkbot/command/Command.kt | 0 .../mefrreex/vkbot/command/CommandService.kt | 2 - .../mefrreex/vkbot/command/SlashCommand.kt | 0 .../com/mefrreex/vkbot/command/WordCommand.kt | 0 .../vkbot/command/data}/CommandData.kt | 2 +- .../vkbot/command/parser}/CommandParser.kt | 4 +- .../com/mefrreex/vkbot/handler/MessageFix.kt | 0 .../mefrreex/vkbot/handler/MessageHandler.kt | 0 bot/build.gradle.kts | 19 +++++++ .../kotlin/com/mefrreex/vkbot/Bootstrap.kt | 1 - .../main/kotlin/com/mefrreex/vkbot/Bot.kt | 0 .../kotlin/com/mefrreex/vkbot/BotSettings.kt | 0 .../vkbot/command/CommandServiceImpl.kt | 0 .../vkbot/command/impl/GetIDCommand.kt | 0 .../vkbot/command/impl/StartCommand.kt | 0 .../vkbot/handler/CommandMessageHandler.kt | 2 +- .../vkbot/handler/DefaultMessageHandler.kt | 0 .../com/mefrreex/vkbot/logger/Logger.kt | 0 .../com/mefrreex/vkbot/rcon/RconClient.kt | 0 .../rcon/exception/AuthenticationException.kt | 0 .../rcon/exception/ConnectionException.kt | 0 .../mefrreex/vkbot/rcon/packet/RconPacket.kt | 0 .../vkbot/rcon/packet/RconPacketType.kt | 0 .../vkbot/translation/TranslationService.kt | 0 .../translation/TranslationServiceImpl.kt | 0 .../com/mefrreex/vkbot/utils/ConfigHelper.kt | 0 .../com/mefrreex/vkbot/utils/Keyboards.kt | 0 .../com/mefrreex/vkbot/utils/TextFormat.kt | 0 .../src}/main/resources/allow_list.yml | 0 {src => bot/src}/main/resources/config.yml | 52 +++++++++--------- {src => bot/src}/main/resources/lang/eng.yml | 0 {src => bot/src}/main/resources/lang/rus.yml | 0 {src => bot/src}/main/resources/messages.yml | 32 +++++------ build.gradle.kts | 54 +++++++++---------- settings.gradle.kts | 3 ++ 36 files changed, 105 insertions(+), 77 deletions(-) create mode 100644 api/build.gradle.kts rename {src => api/src}/main/kotlin/com/mefrreex/vkbot/command/Command.kt (100%) rename {src => api/src}/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt (85%) rename {src => api/src}/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt (100%) rename {src => api/src}/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt (100%) rename {src/main/kotlin/com/mefrreex/vkbot/command => api/src/main/kotlin/com/mefrreex/vkbot/command/data}/CommandData.kt (60%) rename {src/main/kotlin/com/mefrreex/vkbot/command => api/src/main/kotlin/com/mefrreex/vkbot/command/parser}/CommandParser.kt (94%) rename {src => api/src}/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt (100%) rename {src => api/src}/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt (100%) create mode 100644 bot/build.gradle.kts rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt (99%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/Bot.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/BotSettings.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt (95%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt (100%) rename {src => bot/src}/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt (100%) rename {src => bot/src}/main/resources/allow_list.yml (100%) rename {src => bot/src}/main/resources/config.yml (96%) rename {src => bot/src}/main/resources/lang/eng.yml (100%) rename {src => bot/src}/main/resources/lang/rus.yml (100%) rename {src => bot/src}/main/resources/messages.yml (98%) diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 0000000..c95d2c6 --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + kotlin("jvm") version "1.9.21" +} + +tasks.withType { + archiveFileName.set("Vk-Rcon-Bot-API-${project.version}.jar") +} + +kotlin { + jvmToolchain(17) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/command/Command.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/command/Command.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt similarity index 85% rename from src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt index 760c2e5..97e6ee8 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt +++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/CommandService.kt @@ -1,7 +1,5 @@ package com.mefrreex.vkbot.command -import com.vk.api.sdk.objects.messages.Message - interface CommandService { fun getCommands(): Map diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/command/SlashCommand.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/command/WordCommand.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/data/CommandData.kt similarity index 60% rename from src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/command/data/CommandData.kt index d997757..ec8976a 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/command/CommandData.kt +++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/data/CommandData.kt @@ -1,3 +1,3 @@ -package com.mefrreex.vkbot.command +package com.mefrreex.vkbot.command.data class CommandData(val name: String, val args: List) \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt b/api/src/main/kotlin/com/mefrreex/vkbot/command/parser/CommandParser.kt similarity index 94% rename from src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/command/parser/CommandParser.kt index 1978171..a756bce 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/command/CommandParser.kt +++ b/api/src/main/kotlin/com/mefrreex/vkbot/command/parser/CommandParser.kt @@ -1,4 +1,6 @@ -package com.mefrreex.vkbot.command +package com.mefrreex.vkbot.command.parser + +import com.mefrreex.vkbot.command.data.CommandData object CommandParser { diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt b/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageFix.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt b/api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt rename to api/src/main/kotlin/com/mefrreex/vkbot/handler/MessageHandler.kt diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts new file mode 100644 index 0000000..0459051 --- /dev/null +++ b/bot/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + kotlin("jvm") version "1.9.21" +} + +dependencies { + api("com.github.MEFRREEX:configuration:main-SNAPSHOT") + api(project(":api")) +} + +tasks.withType { + archiveFileName.set("Vk-Rcon-Bot-${project.version}.jar") + manifest { + attributes["Main-Class"] = "com.mefrreex.vkbot.BootstrapKt" + } +} + +kotlin { + jvmToolchain(17) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt similarity index 99% rename from src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt index fc29fc6..603cbbd 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -4,7 +4,6 @@ import com.mefrreex.vkbot.logger.Logger import com.mefrreex.vkbot.utils.ConfigHelper import java.io.File - val logger = Logger() fun main() { diff --git a/src/main/kotlin/com/mefrreex/vkbot/Bot.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/Bot.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/Bot.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/Bot.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/BotSettings.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/command/CommandServiceImpl.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/GetIDCommand.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/command/impl/StartCommand.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt similarity index 95% rename from src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt index 9423258..cbb5351 100644 --- a/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/CommandMessageHandler.kt @@ -1,7 +1,7 @@ package com.mefrreex.vkbot.handler import com.mefrreex.vkbot.Bot -import com.mefrreex.vkbot.command.CommandParser +import com.mefrreex.vkbot.command.parser.CommandParser import com.mefrreex.vkbot.command.SlashCommand import com.mefrreex.vkbot.command.WordCommand import com.vk.api.sdk.client.VkApiClient diff --git a/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/handler/DefaultMessageHandler.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/logger/Logger.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/rcon/RconClient.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/AuthenticationException.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/rcon/exception/ConnectionException.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacket.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/rcon/packet/RconPacketType.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationService.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/translation/TranslationServiceImpl.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/utils/Keyboards.kt diff --git a/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt similarity index 100% rename from src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt rename to bot/src/main/kotlin/com/mefrreex/vkbot/utils/TextFormat.kt diff --git a/src/main/resources/allow_list.yml b/bot/src/main/resources/allow_list.yml similarity index 100% rename from src/main/resources/allow_list.yml rename to bot/src/main/resources/allow_list.yml diff --git a/src/main/resources/config.yml b/bot/src/main/resources/config.yml similarity index 96% rename from src/main/resources/config.yml rename to bot/src/main/resources/config.yml index c47c976..2d6694d 100644 --- a/src/main/resources/config.yml +++ b/bot/src/main/resources/config.yml @@ -1,26 +1,26 @@ -# Доступные языки: eng (English), rus (Русский) -language: "rus" - -# Настройки VK -vk: - groupId: 123 # Id группы ВК - accessToken: "token" # Токен группы ВК - -# Настройки RCON -rcon: - host: "localhost" # Адрес RCON - port: 19132 # Порт RCON - password: "password" # Пароль RCON - -# Настройки команд -commands: - # Символ перед командой для отправки комманды. Например '/'. - # Оставьте пустым если не требуется. - prefix: '/' - - # Быстрые команды - # (Отображаются при вводе "Начать", "Rcon") - fast-commands: ["ver", "status", "stop"] - - # Заблокированные команды - blocked-commands: ["stop"] +# Доступные языки: eng (English), rus (Русский) +language: "rus" + +# Настройки VK +vk: + groupId: 123 # Id группы ВК + accessToken: "token" # Токен группы ВК + +# Настройки RCON +rcon: + host: "localhost" # Адрес RCON + port: 19132 # Порт RCON + password: "password" # Пароль RCON + +# Настройки команд +commands: + # Символ перед командой для отправки комманды. Например '/'. + # Оставьте пустым если не требуется. + prefix: '/' + + # Быстрые команды + # (Отображаются при вводе "Начать", "Rcon") + fast-commands: ["ver", "status", "stop"] + + # Заблокированные команды + blocked-commands: ["stop"] diff --git a/src/main/resources/lang/eng.yml b/bot/src/main/resources/lang/eng.yml similarity index 100% rename from src/main/resources/lang/eng.yml rename to bot/src/main/resources/lang/eng.yml diff --git a/src/main/resources/lang/rus.yml b/bot/src/main/resources/lang/rus.yml similarity index 100% rename from src/main/resources/lang/rus.yml rename to bot/src/main/resources/lang/rus.yml diff --git a/src/main/resources/messages.yml b/bot/src/main/resources/messages.yml similarity index 98% rename from src/main/resources/messages.yml rename to bot/src/main/resources/messages.yml index 6607f40..25baa87 100644 --- a/src/main/resources/messages.yml +++ b/bot/src/main/resources/messages.yml @@ -1,16 +1,16 @@ -user_id: "Ваш ID: %s" - -start: "Введите команду или используйте кнопку ниже." - -rcon: "Введите команду для отправки на сервер." -rcon_with_commands: "Введите команду для отправки на сервер.\n\nБыстрые команды:" - -user_not_whitelisted: "Вас нет в списке пользователей, которые могут использовать RCON." - -command_blocked: "Данная команда заблокирована." -failed_to_connect: "Не удалось подключится к серверу. Возможно он оффлайн." -failed_to_authenticate: "Не удалось пройти аутентификацию RCON." - -command_sent: "Команда отправлена.\nОтвет сервера: %s" -command_response_too_long: "Ответ сервера слишком длинный" -response_null: "Команда отправлена. Сервер не вернул ответа." +user_id: "Ваш ID: %s" + +start: "Введите команду или используйте кнопку ниже." + +rcon: "Введите команду для отправки на сервер." +rcon_with_commands: "Введите команду для отправки на сервер.\n\nБыстрые команды:" + +user_not_whitelisted: "Вас нет в списке пользователей, которые могут использовать RCON." + +command_blocked: "Данная команда заблокирована." +failed_to_connect: "Не удалось подключится к серверу. Возможно он оффлайн." +failed_to_authenticate: "Не удалось пройти аутентификацию RCON." + +command_sent: "Команда отправлена.\nОтвет сервера: %s" +command_response_too_long: "Ответ сервера слишком длинный" +response_null: "Команда отправлена. Сервер не вернул ответа." diff --git a/build.gradle.kts b/build.gradle.kts index c783b4d..b059243 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,46 +1,42 @@ plugins { `java-library` - `maven-publish` - kotlin("jvm") version "1.9.21" id("com.github.johnrengelman.shadow") version "8.1.1" } -group = "com.mefrreex.vkbot" -description = "vkrconbot" -version = "1.5.0" java.sourceCompatibility = JavaVersion.VERSION_17 -repositories { - mavenLocal() - maven("https://repo1.maven.org/maven2") - maven("https://repo.maven.apache.org/maven2/") - maven("https://jitpack.io") -} - -dependencies { - api("com.vk.api:sdk:1.0.14") - api("com.github.MEFRREEX:configuration:main-SNAPSHOT") -} - tasks.build { dependsOn(tasks.shadowJar) } -tasks.withType { - options.encoding = "UTF-8" +allprojects { + group = "com.mefrreex.vkbot" + description = "vkrconbot" + version = "1.5.1" } -tasks.withType { - options.encoding = "UTF-8" -} +subprojects { -tasks.withType { - archiveFileName.set("Vk-Rcon-Bot-${project.version}.jar") - manifest { - attributes["Main-Class"] = "com.mefrreex.vkbot.BootstrapKt" + apply { + plugin("java-library") } -} -kotlin { - jvmToolchain(17) + repositories { + mavenLocal() + maven("https://repo1.maven.org/maven2") + maven("https://repo.maven.apache.org/maven2/") + maven("https://jitpack.io") + } + + dependencies { + api("com.vk.api:sdk:1.0.14") + } + + tasks.withType { + options.encoding = "UTF-8" + } + + tasks.withType { + options.encoding = "UTF-8" + } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a7965bb..f511559 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,3 +3,6 @@ plugins { } rootProject.name = "Vk-Rcon-Bot" + +include("api") +include("bot") From fd2f66c9ae79a8b24090bd308ef068900bc65e65 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 14:50:23 +0300 Subject: [PATCH 14/20] chore: remove unused messages.yml --- bot/src/main/resources/messages.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 bot/src/main/resources/messages.yml diff --git a/bot/src/main/resources/messages.yml b/bot/src/main/resources/messages.yml deleted file mode 100644 index 25baa87..0000000 --- a/bot/src/main/resources/messages.yml +++ /dev/null @@ -1,16 +0,0 @@ -user_id: "Ваш ID: %s" - -start: "Введите команду или используйте кнопку ниже." - -rcon: "Введите команду для отправки на сервер." -rcon_with_commands: "Введите команду для отправки на сервер.\n\nБыстрые команды:" - -user_not_whitelisted: "Вас нет в списке пользователей, которые могут использовать RCON." - -command_blocked: "Данная команда заблокирована." -failed_to_connect: "Не удалось подключится к серверу. Возможно он оффлайн." -failed_to_authenticate: "Не удалось пройти аутентификацию RCON." - -command_sent: "Команда отправлена.\nОтвет сервера: %s" -command_response_too_long: "Ответ сервера слишком длинный" -response_null: "Команда отправлена. Сервер не вернул ответа." From cba73d7920f8cdf21f0d4fc735c8e548e3ff7983 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 14:55:10 +0300 Subject: [PATCH 15/20] docs: update readme --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++++-------- README_ru.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 README_ru.md diff --git a/README.md b/README.md index 730d8f6..68434b4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,66 @@ # Vk-Rcon-Bot -A simple Rcon Bot for VK -## 🎮 Run -`java -jar VkRconBot-.jar` +A simple bot for sending commands to a Minecraft server via RCON through the VK platform. -## 🛠 Build JAR file -- `git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git` -- `cd Vk-Rcon-Bot` -- `gradle build` +## 🚀 Features +- Sends RCON commands to a server directly from VK messages. +- Easy setup and configuration. +- Lightweight and simple to use. -## 📄 Used libraries: -- [Vk-Java-Sdk](https://github.com/VKCOM/vk-java-sdk) -- [Configuration](https://github.com/MEFRREEX/Configuration) \ No newline at end of file +## 🎮 Running the Bot +1. Download the latest release of the bot. +2. Run the following command to start the bot: +```bash + java -jar VkRconBot-.jar +``` + +## ⚙️ Configuration +After the first run of the bot, a configuration file config.yml will be generated. You will need to edit this file before the bot can function properly. +```yaml +# Available languages: eng (English), rus (Русский) +language: "rus" + +# VK settings +vk: + groupId: 123 # VK group Id + accessToken: "token" # VK group token + +# RCON settings +rcon: + host: "localhost" # RCON address + port: 19132 # RCON port + password: "password" # RCON password + +# Command settings +commands: + # A character before the command to send the command. For example '/'. + # Leave blank if not required. + prefix: '/' + + # Fast commands + # (Displayed when “Start”, “Rcon” is entered) + fast-commands: ["ver", "status", "stop"] + + # Blocked commands + blocked-commands: ["stop"] +``` + +## 🛠 Building the JAR File +To build the project from source: +1. Clone the repository: +```bash +git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git +``` +2. Navigate to the project directory: +```bash +cd Vk-Rcon-Bot +``` +3. Build the JAR file using Gradle: +```bash +gradle build +``` + +## 📄 Dependencies +The project uses the following libraries: +- [Vk-Java-Sdk](https://github.com/VKCOM/vk-java-sdk) - for VK API integration. +- [Configuration](https://github.com/MEFRREEX/Configuration) - for managing configuration files. \ No newline at end of file diff --git a/README_ru.md b/README_ru.md new file mode 100644 index 0000000..0f95002 --- /dev/null +++ b/README_ru.md @@ -0,0 +1,67 @@ +# Vk-Rcon-Bot + +Простой бот для отправки команд на сервер через RCON с помощью платформы ВКонтакте. + +## 🚀 Основные возможности +- Отправка команд на сервер через RCON из сообщений ВКонтакте. +- Простая настройка и конфигурация. +- Лёгкий в использовании. + +## 🎮 Запуск бота +1. Скачайте последнюю версию бота. +2. Запустите следующую команду для старта бота: +```bash + java -jar VkRconBot-<Версия>.jar +``` + +## ⚙️ Конфигурация +После первого запуска бота будет сгенерирован файл конфигурации config.yml. Вам необходимо отредактировать этот файл перед дальнейшим использованием бота. + +```yaml +# Доступные языки: eng (English), rus (Русский) +language: "rus" + +# Настройки VK +vk: + groupId: 123 # Id группы ВК + accessToken: "token" # Токен группы ВК + +# Настройки RCON +rcon: + host: "localhost" # Адрес RCON + port: 19132 # Порт RCON + password: "password" # Пароль RCON + +# Настройки команд +commands: + # Символ перед командой для отправки комманды. Например '/'. + # Оставьте пустым если не требуется. + prefix: '/' + + # Быстрые команды + # (Отображаются при вводе "Начать", "Rcon") + fast-commands: ["ver", "status", "stop"] + + # Заблокированные команды + blocked-commands: ["stop"] +``` + +## 🛠 Сборка JAR-файла +Чтобы собрать проект из исходного кода: +1. Клонируйте репозиторий: +```bash +git clone https://github.com/MEFRREEX/Vk-Rcon-Bot.git +``` +2. Перейдите в директорию проекта: +```bash +cd Vk-Rcon-Bot +``` +3. Соберите JAR-файл с помощью Gradle: +```bash +gradle build +``` + +## 📄 Зависимости +Проект использует следующие библиотеки: +- [Vk-Java-Sdk](https://github.com/VKCOM/vk-java-sdk) - для интеграции с API ВКонтакте. +- [Configuration](https://github.com/MEFRREEX/Configuration) - для управления файлами конфигурации. \ No newline at end of file From 1a165a3f159009f151a8593d8086ccd614e8cb70 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 14:58:36 +0300 Subject: [PATCH 16/20] docs: update readme 2 --- README.md | 4 ++++ README_ru.md | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index 68434b4..4702806 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ A simple bot for sending commands to a Minecraft server via RCON through the VK - Sends RCON commands to a server directly from VK messages. - Easy setup and configuration. - Lightweight and simple to use. +- **Multilingual support**: The bot supports multiple languages, with easy customization of all messages. +- **Command prefix**: Ability to specify a custom prefix for commands. +- **Quick commands**: Add predefined quick commands for frequent actions. +- **Command blocking**: Option to block specific unwanted commands from being executed. ## 🎮 Running the Bot 1. Download the latest release of the bot. diff --git a/README_ru.md b/README_ru.md index 0f95002..98e08b2 100644 --- a/README_ru.md +++ b/README_ru.md @@ -6,6 +6,11 @@ - Отправка команд на сервер через RCON из сообщений ВКонтакте. - Простая настройка и конфигурация. - Лёгкий в использовании. +- **Поддержка нескольких языков**: Бот поддерживает несколько языков с простой настройкой всех сообщений. +- **Префикс для команд**: Возможность указать префикс для команд. +- **Быстрые команды**: Возможность добавить заранее настроенные быстрые команды для частых действий. +- **Блокировка команд**: Возможность заблокировать нежелательные для использования команды. + ## 🎮 Запуск бота 1. Скачайте последнюю версию бота. From 0a48f4918955272b95331c22821b39be631f4caf Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 14:59:06 +0300 Subject: [PATCH 17/20] docs: update readme 3 --- README.md | 2 +- README_ru.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4702806..0624224 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A simple bot for sending commands to a Minecraft server via RCON through the VK 1. Download the latest release of the bot. 2. Run the following command to start the bot: ```bash - java -jar VkRconBot-.jar +java -jar VkRconBot-.jar ``` ## ⚙️ Configuration diff --git a/README_ru.md b/README_ru.md index 98e08b2..ad8bc0d 100644 --- a/README_ru.md +++ b/README_ru.md @@ -16,7 +16,7 @@ 1. Скачайте последнюю версию бота. 2. Запустите следующую команду для старта бота: ```bash - java -jar VkRconBot-<Версия>.jar +java -jar VkRconBot-<Версия>.jar ``` ## ⚙️ Конфигурация From 0e0cbb59391a8d8e8866b1243943ce77ace49ac1 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 15:17:00 +0300 Subject: [PATCH 18/20] fix: configs --- bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt | 2 +- bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt index 603cbbd..bba5110 100644 --- a/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -11,11 +11,11 @@ fun main() { val resources = listOf(ConfigHelper.CONFIG, ConfigHelper.ALLOW_LIST) resources.forEach { - ConfigHelper.loadConfig(it) if (!File(it).exists()) { ConfigHelper.saveResource(it) logger.info("Resource $it saved") } + ConfigHelper.loadConfig(it) } val config = ConfigHelper.getConfig(ConfigHelper.CONFIG) diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt index f33ba7d..081f388 100644 --- a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt @@ -27,7 +27,6 @@ object ConfigHelper { val resourceStream = javaClass.classLoader.getResourceAsStream(target) val outputFile = File(output) if (resourceStream != null && (replace || !outputFile.exists())) { - outputFile.parentFile.mkdirs() outputFile.createNewFile() outputFile.outputStream().use { resourceStream.copyTo(it) } } From a244e60a4fea8303ed712d033af8a46804d36802 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 15:24:22 +0300 Subject: [PATCH 19/20] fix: configs --- bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt | 2 +- bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt index 603cbbd..bba5110 100644 --- a/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/Bootstrap.kt @@ -11,11 +11,11 @@ fun main() { val resources = listOf(ConfigHelper.CONFIG, ConfigHelper.ALLOW_LIST) resources.forEach { - ConfigHelper.loadConfig(it) if (!File(it).exists()) { ConfigHelper.saveResource(it) logger.info("Resource $it saved") } + ConfigHelper.loadConfig(it) } val config = ConfigHelper.getConfig(ConfigHelper.CONFIG) diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt index f33ba7d..e7e4d90 100644 --- a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt @@ -27,7 +27,7 @@ object ConfigHelper { val resourceStream = javaClass.classLoader.getResourceAsStream(target) val outputFile = File(output) if (resourceStream != null && (replace || !outputFile.exists())) { - outputFile.parentFile.mkdirs() + outputFile.parentFile?.mkdirs() outputFile.createNewFile() outputFile.outputStream().use { resourceStream.copyTo(it) } } From 6694e5483cec73210bedacf1a41d2df2fefccdf8 Mon Sep 17 00:00:00 2001 From: MEFRREEX Date: Thu, 3 Oct 2024 15:26:07 +0300 Subject: [PATCH 20/20] fix: configs --- bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt index f33ba7d..e7e4d90 100644 --- a/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt +++ b/bot/src/main/kotlin/com/mefrreex/vkbot/utils/ConfigHelper.kt @@ -27,7 +27,7 @@ object ConfigHelper { val resourceStream = javaClass.classLoader.getResourceAsStream(target) val outputFile = File(output) if (resourceStream != null && (replace || !outputFile.exists())) { - outputFile.parentFile.mkdirs() + outputFile.parentFile?.mkdirs() outputFile.createNewFile() outputFile.outputStream().use { resourceStream.copyTo(it) } }