├── .github └── workflows │ ├── maven-publish.yml │ └── maven.yml ├── .gitignore ├── LICENSE ├── LinearBot-bukkit ├── LinearBot-bukkit.iml ├── lib │ └── Residence5.1.0.1.jar ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── linear │ │ └── linearbot │ │ ├── LinearBot.java │ │ ├── bot │ │ └── Bot.java │ │ ├── command │ │ └── Commands.java │ │ ├── config │ │ ├── Args.java │ │ └── Config.java │ │ ├── event │ │ ├── qq │ │ │ ├── QQEvent.java │ │ │ └── WhiteList.java │ │ └── server │ │ │ ├── ConsoleSender.java │ │ │ ├── GDClaimEvent.java │ │ │ ├── QsChatEvent.java │ │ │ ├── QsHikariChatEvent.java │ │ │ ├── ServerEvent.java │ │ │ ├── ServerManager.java │ │ │ └── ServerTps.java │ │ ├── hook │ │ ├── AuthMeHook.java │ │ ├── GriefDefenderHook.java │ │ ├── QuickShopHook.java │ │ └── ResidenceHook.java │ │ ├── metrics │ │ └── Metrics.java │ │ └── tool │ │ └── StringTool.java │ └── resources │ ├── bot.yml │ ├── commands.yml │ ├── config.yml │ ├── plugin.yml │ ├── qqlist.yml │ ├── returns.yml │ └── whitelist.yml ├── LinearBot-velocity ├── LinearBot-velocity.iml ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── linear │ │ └── linearbot │ │ ├── LinearBot.java │ │ ├── bot │ │ └── Bot.java │ │ ├── command │ │ └── Commands.java │ │ ├── config │ │ └── VelocityConfig.java │ │ ├── event │ │ ├── qq │ │ │ └── QQEvent.java │ │ └── server │ │ │ └── ServerEvent.java │ │ ├── internal │ │ └── Config.java │ │ ├── metrics │ │ └── Metrics.java │ │ └── tool │ │ └── StringTool.java │ └── resources │ ├── bot.yml │ ├── config.yml │ └── returns.yml ├── LinearBot.iml ├── README.md └── pom.xml /.github/workflows/maven-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path 3 | 4 | name: Maven Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v3 22 | with: 23 | java-version: '17' 24 | distribution: 'temurin' 25 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 26 | settings-path: ${{ github.workspace }} # location for the settings.xml file 27 | 28 | - name: Build with Maven 29 | run: mvn -B package --file pom.xml 30 | 31 | - name: Publish to GitHub Packages Apache Maven 32 | run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml 33 | env: 34 | GITHUB_TOKEN: ${{ github.token }} 35 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | 2 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 3 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 4 | 5 | # This workflow uses actions that are not certified by GitHub. 6 | # They are provided by a third-party and are governed by 7 | # separate terms of service, privacy policy, and support 8 | # documentation. 9 | 10 | name: Java CI with Maven 11 | 12 | on: 13 | push: 14 | branches: [ "main" ] 15 | pull_request: 16 | branches: [ "main" ] 17 | workflow_dispatch: 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up JDK 18 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: '18' 30 | distribution: 'zulu' 31 | cache: maven 32 | - name: Build with Maven 33 | run: mvn -B package --file pom.xml 34 | 35 | - name: Upload a Build Artifact - LinearBot-bukkit 36 | uses: actions/upload-artifact@v3.1.2 37 | with: 38 | name: "LinearBot-bukkit.jar" 39 | path: LinearBot-bukkit/target/LinearBot-bukkit*.jar 40 | 41 | - name: Upload a Build Artifact - LinearBot-velocity 42 | uses: actions/upload-artifact@v3.1.2 43 | with: 44 | name: "LinearBot-velocity.jar" 45 | path: LinearBot-velocity/target/LinearBot-velocity*.jar 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Eclipse 5 | .project 6 | .classpath 7 | .settings/ 8 | 9 | # Intellij 10 | *.ipr 11 | *.iml 12 | *.iws 13 | .idea/ 14 | 15 | # Maven 16 | target/ 17 | 18 | # Gradle 19 | build 20 | .gradle 21 | 22 | # Log file 23 | *.log 24 | log/ 25 | 26 | # out 27 | **/out/ 28 | 29 | # Mac 30 | .DS_Store 31 | 32 | # others 33 | *.war 34 | *.zip 35 | *.tar 36 | *.tar.gz 37 | *.pid 38 | *.orig 39 | temp/ 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /LinearBot-bukkit/LinearBot-bukkit.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ADVENTURE 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LinearBot-bukkit/lib/Residence5.1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinearBit/LinearBot/e372fd0eb7deca26c754fa5c690560a0c34920a3/LinearBot-bukkit/lib/Residence5.1.0.1.jar -------------------------------------------------------------------------------- /LinearBot-bukkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.linear 8 | LinearBot 9 | 1.2.2 10 | 11 | 12 | LinearBot-bukkit 13 | 14 | 15 | 11 16 | 11 17 | UTF-8 18 | 19 | 20 | 21 | 22 | jitpack.io 23 | https://jitpack.io 24 | 25 | 26 | placeholderapi 27 | https://repo.extendedclip.com/content/repositories/placeholderapi/ 28 | 29 | 30 | purpur 31 | https://repo.purpurmc.org/snapshots 32 | 33 | 34 | codemc-repo 35 | https://repo.codemc.io/repository/maven-public/ 36 | 37 | 38 | quickshop-repo 39 | https://repo.codemc.io/repository/maven-public/ 40 | 41 | 42 | griefdefender 43 | https://repo.glaremasters.me/repository/bloodshot 44 | 45 | 55 | 56 | 57 | 58 | 59 | org.purpurmc.purpur 60 | purpur-api 61 | 1.16.5-R0.1-SNAPSHOT 62 | provided 63 | 64 | 65 | me.clip 66 | placeholderapi 67 | 2.10.3 68 | provided 69 | 70 | 71 | fr.xephi 72 | authme 73 | 5.6.0-SNAPSHOT 74 | provided 75 | 76 | 77 | org.maxgamer 78 | QuickShop 79 | 5.1.2.5 80 | provided 81 | 82 | 83 | com.ghostchu 84 | quickshop-bukkit 85 | 6.0.0.8 86 | provided 87 | shaded 88 | 89 | 90 | com.griefdefender 91 | api 92 | 2.1.0-SNAPSHOT 93 | provided 94 | 95 | 96 | com.bekvon.bukkit.residence 97 | Residence 98 | 5.1.0.1 99 | system 100 | ${project.basedir}/lib/Residence5.1.0.1.jar 101 | 102 | 103 | org.xerial 104 | sqlite-jdbc 105 | 3.43.0.0 106 | 107 | 108 | mysql 109 | mysql-connector-java 110 | 8.0.33 111 | 112 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/LinearBot.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.command.CommandSender; 5 | import org.bukkit.event.Listener; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.linear.linearbot.bot.Bot; 8 | import org.linear.linearbot.command.Commands; 9 | import org.linear.linearbot.config.Config; 10 | import org.linear.linearbot.event.qq.QQEvent; 11 | import org.linear.linearbot.event.server.QsChatEvent; 12 | import org.linear.linearbot.event.server.QsHikariChatEvent; 13 | import org.linear.linearbot.event.server.ServerEvent; 14 | import org.linear.linearbot.hook.AuthMeHook; 15 | import org.linear.linearbot.hook.GriefDefenderHook; 16 | import org.linear.linearbot.hook.QuickShopHook; 17 | import org.linear.linearbot.hook.ResidenceHook; 18 | import org.linear.linearbot.metrics.Metrics; 19 | import org.linear.linearbot.event.qq.WhiteList; 20 | 21 | import java.sql.SQLException; 22 | import java.util.List; 23 | 24 | 25 | public final class LinearBot extends JavaPlugin implements Listener{ 26 | 27 | public static LinearBot INSTANCE; 28 | 29 | @Override 30 | public void onEnable() { 31 | 32 | INSTANCE = this; 33 | 34 | 35 | Config.createConfig(); 36 | 37 | Bukkit.getPluginManager().registerEvents(this, this); 38 | AuthMeHook.hookAuthme(); 39 | ResidenceHook.hookRes(); 40 | QuickShopHook.hookQuickShop(); 41 | GriefDefenderHook.hookGriefDefender(); 42 | getLogger().info("关联插件连接完毕"); 43 | Bukkit.getPluginManager().registerEvents(new ServerEvent(), this); 44 | if (QuickShopHook.hasQs) Bukkit.getPluginManager().registerEvents(new QsChatEvent(),this); 45 | if (QuickShopHook.hasQsHikari) Bukkit.getPluginManager().registerEvents(new QsHikariChatEvent(),this); 46 | getLogger().info("服务器事件监听器注册完毕"); 47 | Bukkit.getPluginManager().registerEvents(new QQEvent(), this); 48 | getLogger().info("QQ事件监听器注册完毕"); 49 | Bukkit.getServer().getPluginCommand("linearbot").setExecutor(new Commands()); 50 | getLogger().info("命令注册完毕"); 51 | try { 52 | WhiteList.initializeSQLite(); 53 | getLogger().info("数据库初始化成功"); 54 | } catch (SQLException | ClassNotFoundException e) { 55 | throw new RuntimeException(e); 56 | } 57 | 58 | // All you have to do is adding the following two lines in your onEnable method. 59 | // You can find the plugin ids of your plugins on the page https://bstats.org/what-is-my-plugin-id 60 | int pluginId = 17137; // <-- Replace with the id of your plugin! 61 | Metrics metrics = new Metrics(this, pluginId); 62 | 63 | getLogger().info( "LinearBot已启动"); 64 | /*List groups = Config.getGroupQQ(); 65 | for (long groupID : groups) { 66 | Bot.sendMsg("插件已启动", groupID); 67 | }*/ 68 | runAfterDone(); 69 | } 70 | 71 | @Override 72 | public void onDisable() { 73 | 74 | try { 75 | WhiteList.closeSQLite(); 76 | getLogger().info("数据库已关闭"); 77 | } catch (SQLException e) { 78 | throw new RuntimeException(e); 79 | } 80 | 81 | getLogger().info("LinearBot已关闭"); 82 | List groups = Config.getGroupQQs(); 83 | for (long groupID : groups) { 84 | Bot.sendMsg("LinearBot已关闭", groupID); 85 | } 86 | } 87 | 88 | public static void say(String s) { 89 | CommandSender sender = Bukkit.getConsoleSender(); 90 | sender.sendMessage(s); 91 | } 92 | 93 | public void runAfterDone() { 94 | Runnable thread = () -> { 95 | List groups = Config.getGroupQQs(); 96 | for (long groupID : groups) { 97 | Bot.sendMsg("LinearBot已启动", groupID); 98 | } 99 | }; 100 | INSTANCE.getServer().getScheduler().runTaskLaterAsynchronously(INSTANCE, thread, 200); 101 | } 102 | 103 | } 104 | 105 | 106 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/bot/Bot.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.bot; 2 | 3 | import org.linear.linearbot.config.Config; 4 | import me.dreamvoid.miraimc.api.MiraiBot; 5 | 6 | public class Bot { 7 | public static void sendMsg(String msg,long groupID){ 8 | MiraiBot.getBot(Config.getBotQQ()).getGroup(groupID).sendMessageMirai(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/command/Commands.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.command; 2 | 3 | import org.linear.linearbot.config.Config; 4 | 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | 9 | public class Commands implements CommandExecutor{ 10 | 11 | @Override 12 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 13 | 14 | if (args.length == 0) { 15 | sender.sendMessage("请使用/lb help查看命令使用方法"); 16 | return true; 17 | } 18 | 19 | if(args.length != 0) { 20 | switch (args[0]) { 21 | /* 22 | case "bind": 23 | if (args.length != 2) return true; 24 | if (!(sender instanceof Player)) { 25 | sender.sendMessage(ConfigManager.getMessage_Server("ArgsError")); 26 | return true; 27 | } 28 | Player player = (Player) sender; 29 | try { 30 | long qq = Long.parseLong(args[1]); 31 | if (ConfigManager.getBindUser().containsKey(qq)) { 32 | player.sendMessage(ConfigManager.getMessage_Server("QQReapt")); 33 | return true; 34 | } 35 | player.sendMessage(ConfigManager.getMessage_Server("ChangeBind") 36 | .replaceAll("%qq%", String.valueOf(qq))); 37 | for (long group : ConfigManager.getGroups()) { 38 | Bot.getMiraiBot() 39 | .getGroup(group) 40 | .sendMessage(ConfigManager.getMessage_QQ("ChangeBind") 41 | .replaceAll("%qq%", String.valueOf(qq)) 42 | .replaceAll("%player%", player.getName())); 43 | } 44 | PlayerData.changeBind(player.getName(), qq); 45 | return true; 46 | } catch (Exception ex) { 47 | ex.printStackTrace(); 48 | player.sendMessage(ConfigManager.getMessage_Server("ArgsError")); 49 | } 50 | break;*/ 51 | case "reload": 52 | if (args.length != 1) return true; 53 | Config.loadConfig(); 54 | sender.sendMessage("LinearBot配置文件已重载"); 55 | break; 56 | case "help": 57 | if (args.length != 1) return true; 58 | sender.sendMessage("§6LinearBot 机器人帮助菜单"); 59 | sender.sendMessage("§6/lb reload :§f重载插件"); 60 | sender.sendMessage("§6/lb help :§f获取插件帮助"); 61 | break; 62 | default: 63 | if (args.length != 1) return true; 64 | sender.sendMessage("错误的指令用法,请使用/lb help查看命令使用方法"); 65 | break; 66 | } 67 | } 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/config/Args.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.config; 2 | 3 | public class Args { 4 | 5 | public static int ForwardingMode(){ 6 | return Config.getConfigYaml().getInt("Forwarding.mode"); 7 | } 8 | 9 | public static int WhitelistMode(){ 10 | return Config.getConfigYaml().getInt("Whitelist.mode"); 11 | } 12 | 13 | public static String ForwardingPrefix(){ 14 | return Config.getConfigYaml().getString("Forwarding.prefix"); 15 | } 16 | 17 | public static String Prefix(){ 18 | return Config.getConfigYaml().getString("Prefix"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/config/Config.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.config; 2 | 3 | import org.linear.linearbot.LinearBot; 4 | import org.bukkit.configuration.file.YamlConfiguration; 5 | 6 | import java.io.File; 7 | import java.util.List; 8 | 9 | public class Config { 10 | private static final LinearBot INSTANCE = LinearBot.INSTANCE; 11 | private static final File botFile = new File(INSTANCE.getDataFolder(), "bot.yml"); 12 | private static final File configFile = new File(INSTANCE.getDataFolder(), "config.yml"); 13 | private static final File returnsFile = new File(INSTANCE.getDataFolder(), "returns.yml"); 14 | private static final File commandsFile = new File(INSTANCE.getDataFolder(), "commands.yml"); 15 | private static YamlConfiguration bot; 16 | private static YamlConfiguration config; 17 | private static YamlConfiguration returns; 18 | private static YamlConfiguration commands; 19 | 20 | public static void createConfig(){ 21 | File[] allFile = {botFile,configFile,returnsFile,commandsFile}; 22 | for (File file : allFile) { 23 | if (!file.exists()) { 24 | INSTANCE.saveResource(file.getName(), true); 25 | } 26 | } 27 | loadConfig(); 28 | if (!Config.getBotYamlVersion().equals("1.1")){ 29 | INSTANCE.saveResource(botFile.getName(), true); 30 | } 31 | if (!Config.getConfigYamlVersion().equals("1.2.2")){ 32 | INSTANCE.saveResource(configFile.getName(), true); 33 | } 34 | if (!Config.getCommandsYamlVersion().equals("1.2.2")){ 35 | INSTANCE.saveResource(commandsFile.getName(), true); 36 | } 37 | if (!Config.getReturnsYamlVersion().equals("1.2")){ 38 | INSTANCE.saveResource(configFile.getName(), true); 39 | } 40 | } 41 | 42 | public static String getBotYamlVersion(){ 43 | return getBotYaml().getString("Ver"); 44 | } 45 | 46 | public static String getConfigYamlVersion(){ 47 | return getConfigYaml().getString("Ver"); 48 | } 49 | 50 | public static String getCommandsYamlVersion(){ 51 | return getCommandsYaml().getString("Ver"); 52 | } 53 | 54 | public static String getReturnsYamlVersion(){ 55 | return getReturnsYaml().getString("Ver"); 56 | } 57 | 58 | 59 | 60 | public static void loadConfig(){ 61 | bot = YamlConfiguration.loadConfiguration(botFile); 62 | config = YamlConfiguration.loadConfiguration(configFile); 63 | returns = YamlConfiguration.loadConfiguration(returnsFile); 64 | commands = YamlConfiguration.loadConfiguration(commandsFile); 65 | } 66 | 67 | public static YamlConfiguration getBotYaml(){ 68 | return bot; 69 | } 70 | 71 | public static YamlConfiguration getConfigYaml() { 72 | return config; 73 | } 74 | 75 | public static YamlConfiguration getReturnsYaml() {return returns;} 76 | 77 | public static YamlConfiguration getCommandsYaml() {return commands;} 78 | 79 | 80 | 81 | public static long getBotQQ() { 82 | return getBotYaml().getLong("Bot.QQ"); 83 | } 84 | 85 | public static List getGroupQQs(){ 86 | return getBotYaml().getLongList("Groups"); 87 | } 88 | 89 | public static List getAdmins() { 90 | return Config.getBotYaml().getLongList("Admins"); 91 | } 92 | 93 | public static boolean DieReport(){ 94 | return getConfigYaml().getBoolean("DieReport"); 95 | } 96 | 97 | public static boolean Forwarding(){ 98 | return getConfigYaml().getBoolean("Forwarding.enable"); 99 | } 100 | 101 | public static boolean WhiteList(){ 102 | return getConfigYaml().getBoolean("WhiteList.enable"); 103 | } 104 | 105 | public static boolean JoinAndLeave(){ 106 | return getConfigYaml().getBoolean("JoinAndLeave"); 107 | } 108 | 109 | public static boolean CMD(){ 110 | return getConfigYaml().getBoolean("CMD"); 111 | } 112 | 113 | public static boolean Online(){ 114 | return getConfigYaml().getBoolean("Online"); 115 | } 116 | 117 | public static boolean TPS() {return getConfigYaml().getBoolean("TPS");} 118 | 119 | public static boolean SDC() {return getConfigYaml().getBoolean("SDC");} 120 | 121 | public static boolean SDR() {return getConfigYaml().getBoolean("SDR");} 122 | 123 | 124 | 125 | 126 | } 127 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/qq/QQEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.qq; 2 | 3 | import me.dreamvoid.miraimc.api.MiraiBot; 4 | import me.dreamvoid.miraimc.api.MiraiMC; 5 | import me.dreamvoid.miraimc.bukkit.event.group.member.MiraiMemberLeaveEvent; 6 | import me.dreamvoid.miraimc.bukkit.event.message.passive.MiraiFriendMessageEvent; 7 | import me.dreamvoid.miraimc.bukkit.event.message.passive.MiraiGroupMessageEvent; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.configuration.file.YamlConfiguration; 10 | import org.bukkit.event.EventHandler; 11 | import org.bukkit.event.Listener; 12 | import org.bukkit.scheduler.BukkitRunnable; 13 | import org.linear.linearbot.LinearBot; 14 | import org.linear.linearbot.bot.Bot; 15 | import org.linear.linearbot.config.Config; 16 | import org.linear.linearbot.event.server.ServerManager; 17 | import org.linear.linearbot.event.server.ServerTps; 18 | import org.linear.linearbot.tool.StringTool; 19 | import org.linear.linearbot.config.Args; 20 | 21 | import java.io.*; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.UUID; 25 | import java.util.regex.Matcher; 26 | import java.util.regex.Pattern; 27 | 28 | 29 | public class QQEvent implements Listener { 30 | 31 | String Prefix = Args.Prefix(); 32 | 33 | @EventHandler 34 | public void onFriendMessageReceive(MiraiFriendMessageEvent e){ 35 | 36 | 37 | if(e.getMessage().equals(Prefix+"在线人数")) { 38 | if(!Config.Online()){ 39 | return; 40 | } 41 | MiraiBot.getBot(e.getBotID()).getFriend(e.getSenderID()).sendMessage("当前在线:" + Bukkit.getServer().getOnlinePlayers()+"("+Bukkit.getServer().getOnlinePlayers().size()+"人)"); 42 | return; 43 | } 44 | 45 | if(Config.getAdmins().contains(e.getSenderID())) { 46 | 47 | Pattern pattern; 48 | Matcher matcher; 49 | 50 | pattern = Pattern.compile(Prefix+"cmd .*"); 51 | matcher = pattern.matcher(e.getMessage()); 52 | if (matcher.find()) { 53 | if(!Config.CMD()){ 54 | return; 55 | } 56 | String cmd = Pattern.compile(Prefix+"cmd .*").matcher(e.getMessage()).group().replace(Prefix+"cmd ", ""); 57 | ServerManager.sendCmd(cmd,e.getSenderID(),false); 58 | Bot.sendMsg("已发送指令至服务器",e.getSenderID()); 59 | } 60 | 61 | return; 62 | } 63 | 64 | Bukkit.broadcastMessage("§6"+"[私聊消息]"+"§a"+e.getSenderName()+"§f"+":"+e.getMessage()); 65 | } 66 | 67 | @EventHandler 68 | public void onGroupMessageReceive(MiraiGroupMessageEvent e){ 69 | 70 | Pattern pattern; 71 | Matcher matcher; 72 | 73 | String msg = e.getMessage(); 74 | long groupID= e.getGroupID(); 75 | long senderID = e.getSenderID(); 76 | String groupName = e.getGroup().getName(); 77 | 78 | pattern = Pattern.compile(" messages = new LinkedList<>(); 94 | StringBuilder stringBuilder = new StringBuilder(); 95 | messages.add("成员命令:"); 96 | messages.add(Prefix+"在线人数 查看服务器当前在线人数"); 97 | messages.add(Prefix+"tps 查看服务器当前tps"); 98 | messages.add(Prefix+"申请白名单 为自己申请白名单"); 99 | messages.add(Prefix+"删除白名单 删除自己的白名单"); 100 | messages.add("管理命令:"); 101 | messages.add(Prefix+"cmd 向服务器发送命令"); 102 | messages.add(Prefix+"删除白名单 删除指定游戏id的白名单"); 103 | for (String message : messages) { 104 | if (messages.get(messages.size() - 1).equalsIgnoreCase(message)) { 105 | stringBuilder.append(message.replaceAll("§\\S", "")); 106 | } else { 107 | stringBuilder.append(message.replaceAll("§\\S", "")).append("\n"); 108 | } 109 | } 110 | Bot.sendMsg(stringBuilder.toString(),groupID); 111 | } 112 | 113 | if(msg.equals(Prefix+"在线人数")) { 114 | if(!Config.Online()){ 115 | return; 116 | } 117 | Bot.sendMsg("当前在线:" + "("+Bukkit.getServer().getOnlinePlayers().size()+"人)"+ServerManager.listOnlinePlayer(),groupID); 118 | return; 119 | } 120 | 121 | if(msg.equals(Prefix+"tps")) { 122 | if(!Config.TPS()){ 123 | return; 124 | } 125 | ServerTps st = new ServerTps(); 126 | Bot.sendMsg("当前tps:" + st.getTps() + "\n" + "当前MSPT:" + st.getMSPT(),groupID); 127 | return; 128 | } 129 | 130 | pattern = Pattern.compile(Prefix + "申请白名单 .*"); 131 | matcher = pattern.matcher(msg); 132 | if (matcher.find()) { 133 | if (!Config.WhiteList()) { 134 | return; 135 | } 136 | String PlayerName = matcher.group().replace(Prefix + "申请白名单 ", ""); 137 | if (PlayerName.isEmpty()) { 138 | Bot.sendMsg("id不能为空", groupID); 139 | return; 140 | } 141 | new BukkitRunnable(){ 142 | @Override 143 | public void run(){ 144 | if ((WhiteList.getBind(senderID) != null) || (WhiteList.getBind(PlayerName) != 0L)) { 145 | Bot.sendMsg("玩家已存在", groupID); 146 | return; 147 | } 148 | WhiteList.addBind(PlayerName, senderID); 149 | Bot.sendMsg("成功申请白名单", groupID); 150 | } 151 | }.runTask(LinearBot.INSTANCE); 152 | return; 153 | } 154 | 155 | pattern = Pattern.compile(Prefix+".*"); 156 | matcher = pattern.matcher(msg); 157 | if(matcher.find()){ 158 | if (!Config.SDC()){ 159 | return; 160 | } 161 | String scmd = matcher.group().replace(Prefix+"", ""); 162 | String gcmd = Config.getCommandsYaml().getString("User."+scmd); 163 | if(gcmd!=null) { 164 | ServerManager.sendCmd(gcmd,groupID,false); 165 | return; 166 | } 167 | } 168 | 169 | if (Config.SDR()){ 170 | String back = Config.getReturnsYaml().getString(msg); 171 | if(back!=null){ 172 | Bot.sendMsg(back,groupID); 173 | return; 174 | } 175 | } 176 | 177 | if(Config.getAdmins().contains(senderID)) { 178 | 179 | pattern = Pattern.compile(Prefix+"cmd .*"); 180 | matcher = pattern.matcher(msg); 181 | if (matcher.find()) { 182 | if(!Config.CMD()){ 183 | return; 184 | } 185 | String cmd = matcher.group().replace(Prefix+"cmd ", ""); 186 | ServerManager.sendCmd(cmd,groupID,true); 187 | return; 188 | } 189 | 190 | pattern = Pattern.compile(Prefix+".*"); 191 | matcher = pattern.matcher(msg); 192 | if(matcher.find()){ 193 | if (!Config.SDC()){ 194 | return; 195 | } 196 | String scmd = matcher.group().replace(Prefix+"", ""); 197 | String gcmd = Config.getCommandsYaml().getString("Admin."+scmd); 198 | if(gcmd!=null) { 199 | ServerManager.sendCmd(gcmd,groupID,false); 200 | return; 201 | } 202 | } 203 | 204 | pattern = Pattern.compile(Prefix + "删除白名单 .*"); 205 | matcher = pattern.matcher(msg); 206 | if (matcher.find()) { 207 | if (!Config.WhiteList()) { 208 | return; 209 | } 210 | String name = matcher.group().replace(Prefix + "删除白名单 ", ""); 211 | if (name.isEmpty()) { 212 | Bot.sendMsg("id不能为空", groupID); 213 | return; 214 | } 215 | new BukkitRunnable(){ 216 | @Override 217 | public void run(){ 218 | long nameForId = WhiteList.getBind(name); 219 | if (nameForId == 0L) { 220 | Bot.sendMsg("尚未申请白名单", groupID); 221 | return; 222 | } 223 | WhiteList.removeBind(name); 224 | Bot.sendMsg("成功移出白名单", groupID); 225 | } 226 | }.runTask(LinearBot.INSTANCE); 227 | return; 228 | } 229 | 230 | } 231 | 232 | pattern = Pattern.compile(Prefix + "删除白名单 .*"); 233 | matcher = pattern.matcher(msg); 234 | if (matcher.find()) { 235 | if (!Config.WhiteList()) { 236 | return; 237 | } 238 | String name = matcher.group().replace(Prefix + "删除白名单 ", ""); 239 | new BukkitRunnable(){ 240 | @Override 241 | public void run(){ 242 | String idForName = WhiteList.getBind(senderID); 243 | if (idForName == null || idForName.isEmpty()) { 244 | Bot.sendMsg("您尚未申请白名单", groupID); 245 | return; 246 | } 247 | if (name.isEmpty()) { 248 | Bot.sendMsg("id不能为空", groupID); 249 | return; 250 | } 251 | if (!idForName.equals(name)) { 252 | Bot.sendMsg("你无权这样做", groupID); 253 | return; 254 | } 255 | WhiteList.removeBind(name); 256 | Bot.sendMsg("成功移出白名单", groupID); 257 | } 258 | }.runTask(LinearBot.INSTANCE); 259 | return; 260 | } 261 | 262 | if (!Config.Forwarding()){ 263 | return; 264 | } 265 | 266 | if (Args.ForwardingMode()==1){ 267 | pattern = Pattern.compile(Args.ForwardingPrefix()+".*"); 268 | matcher = pattern.matcher(msg); 269 | if(!matcher.find()){ 270 | return; 271 | } 272 | String name = StringTool.filterColor(e.getSenderName()); 273 | String smsg = StringTool.filterColor(msg); 274 | Bukkit.broadcastMessage("§6" + "[" + groupName + "]" + "§a" + name + "§f" + ":" + smsg); 275 | } 276 | 277 | if(Config.getGroupQQs().contains(groupID)) { 278 | String name = StringTool.filterColor(e.getSenderName()); 279 | String smsg = StringTool.filterColor(msg); 280 | Bukkit.broadcastMessage("§6" + "[" + groupName + "]" + "§a" + name + "§f" + ":" + smsg); 281 | } 282 | 283 | } 284 | 285 | @EventHandler 286 | public void MemberLeaveEvent(MiraiMemberLeaveEvent event){ 287 | long targetID = event.getTargetID(); 288 | String id = WhiteList.getBind(targetID); 289 | if(id == null){ 290 | return; 291 | } 292 | WhiteList.removeBind(targetID); 293 | } 294 | 295 | 296 | } 297 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/qq/WhiteList.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.qq; 2 | 3 | import org.linear.linearbot.LinearBot; 4 | 5 | import java.io.File; 6 | import java.sql.*; 7 | 8 | public class WhiteList { 9 | 10 | private static final LinearBot INSTANCE = LinearBot.INSTANCE; 11 | 12 | public static Connection connection; 13 | 14 | public static void initializeSQLite() throws SQLException, ClassNotFoundException{ 15 | Class.forName("org.sqlite.JDBC"); 16 | connection = DriverManager.getConnection("jdbc:sqlite:" + new File(INSTANCE.getDataFolder(),"database.db").getPath()); 17 | } 18 | 19 | public static void closeSQLite() throws SQLException { 20 | connection.close(); 21 | } 22 | 23 | 24 | public static void addBind(String id, long qq) { 25 | String createTable = "CREATE TABLE IF NOT EXISTS whitelist (id TINYTEXT NOT NULL, qq long NOT NULL);"; 26 | String selectid = "SELECT * FROM whitelist WHERE id='" + id + "' LIMIT 1"; 27 | String selectqq = "SELECT * FROM whitelist WHERE qq=" + qq + " LIMIT 1"; 28 | String updateid = "UPDATE whitelist SET id='" + id + "' WHERE qq=" + qq + ";"; 29 | String updateqq = "UPDATE whitelist SET qq=" + qq + " WHERE id='" + id + "';"; 30 | String insert = "insert into whitelist values('" + id + "', " + qq + ");"; 31 | 32 | try { 33 | Statement statement = connection.createStatement(); 34 | Statement statement1 = connection.createStatement(); 35 | statement.executeUpdate(createTable); 36 | 37 | ResultSet resultSetid = statement.executeQuery(selectid); 38 | ResultSet resultSetqq = statement1.executeQuery(selectqq); 39 | 40 | if (!resultSetid.isBeforeFirst() && resultSetqq.isBeforeFirst()) { 41 | statement.executeUpdate(updateid); 42 | } else if (resultSetid.isBeforeFirst() && !resultSetqq.isBeforeFirst()) { 43 | statement.executeUpdate(updateqq); 44 | } else if (!resultSetid.isBeforeFirst() && !resultSetqq.isBeforeFirst()) { 45 | statement.executeUpdate(insert); 46 | } 47 | 48 | resultSetid.close(); 49 | resultSetqq.close(); 50 | statement.close(); 51 | statement1.close(); 52 | } catch (SQLException e) { 53 | throw new RuntimeException(e); 54 | } 55 | } 56 | 57 | 58 | public static void removeBind(String id) { 59 | String createTable = "CREATE TABLE IF NOT EXISTS whitelist (id TINYTEXT NOT NULL, qq long NOT NULL);"; 60 | String select = "SELECT * FROM whitelist WHERE id='" + id + "' LIMIT 1;"; 61 | String delete = "DELETE FROM whitelist WHERE id='" + id + "';"; 62 | 63 | try { 64 | Statement statement = connection.createStatement(); 65 | statement.executeUpdate(createTable); 66 | 67 | ResultSet resultSet = statement.executeQuery(select); 68 | 69 | if (resultSet.isBeforeFirst()) { 70 | resultSet.next(); 71 | statement.executeUpdate(delete); 72 | } 73 | resultSet.close(); 74 | statement.close(); 75 | } catch (SQLException e) { 76 | throw new RuntimeException(e); 77 | } 78 | } 79 | 80 | public static void removeBind(long qq) { 81 | String createTable = "CREATE TABLE IF NOT EXISTS whitelist (id TINYTEXT NOT NULL, qq long NOT NULL);"; 82 | String select = "SELECT * FROM whitelist WHERE qq=" + qq + " LIMIT 1;"; 83 | String delete = "DELETE FROM whitelist WHERE qq=" + qq+";"; 84 | 85 | try { 86 | Statement statement = connection.createStatement(); 87 | statement.executeUpdate(createTable); 88 | 89 | // 如果没有找到记录为false,找到就是true 90 | ResultSet resultSet = statement.executeQuery(select); 91 | 92 | if (resultSet.isBeforeFirst()) { 93 | resultSet.next(); 94 | statement.executeUpdate(delete); 95 | } 96 | resultSet.close(); 97 | statement.close(); 98 | } catch (SQLException e) { 99 | throw new RuntimeException(e); 100 | } 101 | } 102 | 103 | public static long getBind(String id){ 104 | long qq = 0L; 105 | 106 | String createTable = "CREATE TABLE IF NOT EXISTS whitelist (id TINYTEXT NOT NULL, qq long NOT NULL);"; 107 | String select = "SELECT * FROM whitelist WHERE id='" + id + "' LIMIT 1;"; 108 | 109 | try { 110 | Statement statement = connection.createStatement(); 111 | statement.executeUpdate(createTable); 112 | 113 | ResultSet resultSet = statement.executeQuery(select); 114 | 115 | if (resultSet.isBeforeFirst()) { 116 | resultSet.next(); 117 | qq = resultSet.getLong("qq"); 118 | } 119 | resultSet.close(); 120 | statement.close(); 121 | } catch (SQLException e) { 122 | throw new RuntimeException(e); 123 | } 124 | return qq; 125 | } 126 | 127 | public static String getBind(long qq) { 128 | String id = null; 129 | 130 | String createTable = "CREATE TABLE IF NOT EXISTS whitelist (id TINYTEXT NOT NULL, qq long NOT NULL);"; 131 | String select = "SELECT * FROM whitelist WHERE qq=" + qq + " LIMIT 1;"; 132 | 133 | try { 134 | Statement statement = connection.createStatement(); 135 | statement.executeUpdate(createTable); 136 | 137 | ResultSet resultSet = statement.executeQuery(select); 138 | 139 | if (resultSet.isBeforeFirst()) { 140 | resultSet.next(); 141 | id = resultSet.getString("id"); 142 | } 143 | resultSet.close(); 144 | statement.close(); 145 | } catch (SQLException e) { 146 | throw new RuntimeException(e); 147 | } 148 | return id; 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ConsoleSender.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Server; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.command.ConsoleCommandSender; 8 | import org.bukkit.conversations.Conversation; 9 | import org.bukkit.conversations.ConversationAbandonedEvent; 10 | import org.bukkit.permissions.Permission; 11 | import org.bukkit.permissions.PermissionAttachment; 12 | import org.bukkit.permissions.PermissionAttachmentInfo; 13 | import org.bukkit.plugin.Plugin; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.Set; 18 | import java.util.UUID; 19 | 20 | public class ConsoleSender implements ConsoleCommandSender { 21 | 22 | private final Server server = Bukkit.getServer(); 23 | 24 | public Server getServer() { 25 | return this.server; 26 | } 27 | 28 | public String getName() { 29 | return "CONSOLE"; 30 | } 31 | 32 | public void sendMessage(String message) { 33 | ServerManager.msgList.add(message); 34 | } 35 | 36 | public void sendMessage(String[] messages) { 37 | for (String msg : messages) 38 | sendMessage(msg); 39 | } 40 | 41 | public void sendMessage(@Nullable UUID sender, @NotNull String message) { 42 | ServerManager.msgList.add(message); 43 | } 44 | 45 | public void sendMessage(@Nullable UUID sender, @NotNull String[] messages) { 46 | for (String msg : messages) 47 | sendMessage(null, messages); 48 | } 49 | 50 | public boolean isPermissionSet(String s) { 51 | return false; 52 | } 53 | 54 | public boolean isPermissionSet(Permission permission) { 55 | return false; 56 | } 57 | 58 | public boolean hasPermission(String s) { 59 | return true; 60 | } 61 | 62 | public boolean hasPermission(Permission permission) { 63 | return true; 64 | } 65 | 66 | public boolean isOp() { 67 | return true; 68 | } 69 | 70 | public void setOp(boolean b) { 71 | throw new UnsupportedOperationException(); 72 | } 73 | 74 | public CommandSender.Spigot spigot() { 75 | throw new UnsupportedOperationException(); 76 | } 77 | 78 | public boolean isConversing() { 79 | throw new UnsupportedOperationException(); 80 | } 81 | 82 | public void acceptConversationInput(String s) { 83 | ServerManager.msgList.add(s); 84 | throw new UnsupportedOperationException(); 85 | } 86 | 87 | public boolean beginConversation(Conversation conversation) { 88 | throw new UnsupportedOperationException(); 89 | } 90 | 91 | public void abandonConversation(Conversation conversation) { 92 | throw new UnsupportedOperationException(); 93 | } 94 | 95 | public void abandonConversation(Conversation conversation, ConversationAbandonedEvent conversationAbandonedEvent) { 96 | throw new UnsupportedOperationException(); 97 | } 98 | 99 | public void sendRawMessage(String s) { 100 | } 101 | 102 | public void sendRawMessage(@Nullable UUID sender, @NotNull String message) { 103 | } 104 | 105 | public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) { 106 | ServerManager.msgList.add(s); 107 | throw new UnsupportedOperationException(); 108 | } 109 | 110 | public PermissionAttachment addAttachment(Plugin plugin) { 111 | throw new UnsupportedOperationException(); 112 | } 113 | 114 | public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) { 115 | throw new UnsupportedOperationException(); 116 | } 117 | 118 | public PermissionAttachment addAttachment(Plugin plugin, int i) { 119 | throw new UnsupportedOperationException(); 120 | } 121 | 122 | public void removeAttachment(PermissionAttachment permissionAttachment) { 123 | throw new UnsupportedOperationException(); 124 | } 125 | 126 | public void recalculatePermissions() { 127 | throw new UnsupportedOperationException(); 128 | } 129 | 130 | public Set getEffectivePermissions() { 131 | throw new UnsupportedOperationException(); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/GDClaimEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import com.griefdefender.api.event.ClaimEvent; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.EventPriority; 6 | import org.bukkit.event.Listener; 7 | 8 | public class GDClaimEvent implements Listener { 9 | 10 | 11 | private static String gdMessage; 12 | 13 | @EventHandler(priority = EventPriority.HIGH) 14 | public void onGDClaim (ClaimEvent e){ 15 | setGDMessage(e.getMessage().toString()); 16 | } 17 | 18 | public static String getGDMessage() {return gdMessage;} 19 | private static void setGDMessage(String s) {gdMessage = s;} 20 | } 21 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/QsChatEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import com.griefdefender.api.event.ClaimEvent; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | 9 | public class QsChatEvent implements Listener { 10 | 11 | private static String qsMessage; 12 | private static Player qsSender; 13 | 14 | @EventHandler(priority = EventPriority.HIGH) 15 | public void onQSChat (org.maxgamer.quickshop.api.event.QSHandleChatEvent e){ 16 | setQsMessage(e.getMessage()); 17 | setQsSender(e.getSender()); 18 | } 19 | 20 | public static String getQsMessage() {return qsMessage;} 21 | public static Player getQsSender() {return qsSender;} 22 | private static void setQsMessage(String s) {qsMessage = s;} 23 | private static void setQsSender(Player p) {qsSender = p;} 24 | } 25 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/QsHikariChatEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.EventPriority; 6 | import org.bukkit.event.Listener; 7 | 8 | public class QsHikariChatEvent implements Listener { 9 | 10 | private static String qsMessage; 11 | private static Player qsSender; 12 | 13 | @EventHandler(priority = EventPriority.HIGH) 14 | public void onQSChat (com.ghostchu.quickshop.api.event.QSHandleChatEvent e){ 15 | setQsMessage(e.getMessage()); 16 | setQsSender((Player) e.getSender()); 17 | } 18 | 19 | 20 | public static String getQsMessage() {return qsMessage;} 21 | public static Player getQsSender() {return qsSender;} 22 | private static void setQsMessage(String s) {qsMessage = s;} 23 | private static void setQsSender(Player p) {qsSender = p;} 24 | } 25 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.configuration.file.YamlConfiguration; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.EventPriority; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.event.entity.PlayerDeathEvent; 10 | import org.bukkit.event.player.AsyncPlayerChatEvent; 11 | import org.bukkit.event.player.AsyncPlayerPreLoginEvent; 12 | import org.bukkit.event.player.PlayerJoinEvent; 13 | import org.bukkit.event.player.PlayerQuitEvent; 14 | import org.linear.linearbot.bot.Bot; 15 | import org.linear.linearbot.config.Args; 16 | import org.linear.linearbot.config.Config; 17 | import org.linear.linearbot.event.qq.WhiteList; 18 | import org.linear.linearbot.hook.AuthMeHook; 19 | import org.linear.linearbot.hook.GriefDefenderHook; 20 | import org.linear.linearbot.hook.QuickShopHook; 21 | import org.linear.linearbot.hook.ResidenceHook; 22 | import org.linear.linearbot.tool.StringTool; 23 | 24 | import java.util.List; 25 | 26 | import static org.linear.linearbot.hook.ResidenceHook.resChatApi; 27 | 28 | public class ServerEvent implements Listener{ 29 | 30 | @EventHandler(priority = EventPriority.LOWEST) 31 | public void onChat(AsyncPlayerChatEvent event) { 32 | if (!Config.Forwarding()){ 33 | return; 34 | } 35 | String name = StringTool.filterColor(event.getPlayer().getDisplayName()); 36 | String message = StringTool.filterColor(event.getMessage()); 37 | if (AuthMeHook.hasAuthMe) {if (!AuthMeHook.authMeApi.isAuthenticated(event.getPlayer())) {return;} } 38 | if (ResidenceHook.hasRes) {if (resChatApi.getPlayerChannel(event.getPlayer().getName()) != null) {return;}} 39 | if (QuickShopHook.hasQs) {if (event.getPlayer() == QsChatEvent.getQsSender() && event.getMessage() == QsChatEvent.getQsMessage()) {return;}} 40 | if (QuickShopHook.hasQsHikari) {if (event.getPlayer() == QsHikariChatEvent.getQsSender() && event.getMessage() == QsHikariChatEvent.getQsMessage()) {return;}} 41 | if (GriefDefenderHook.hasGriefDefender) {if (GDClaimEvent.getGDMessage() == event.getMessage()){return;}} 42 | List groups = Config.getGroupQQs(); 43 | for (long groupID : groups){ 44 | Bot.sendMsg("[服务器]"+name+":"+message,groupID); 45 | } 46 | } 47 | 48 | @EventHandler(priority = EventPriority.MONITOR) 49 | public void onPreLogin(AsyncPlayerPreLoginEvent event){ 50 | String name = event.getName(); 51 | 52 | if(Config.WhiteList()){ 53 | if (WhiteList.getBind(name)==0) { 54 | event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_WHITELIST, Config.getConfigYaml().getString("Whitelist.kickMsg")); 55 | List groups = Config.getGroupQQs(); 56 | for (long groupID : groups) { 57 | Bot.sendMsg("玩家" + name + "因为未在白名单中被踢出", groupID); 58 | } 59 | return; 60 | } 61 | event.allow(); 62 | } 63 | } 64 | 65 | @EventHandler 66 | public void onJoin(PlayerJoinEvent event){ 67 | 68 | Player player = event.getPlayer(); 69 | 70 | String name = StringTool.filterColor(player.getDisplayName()); 71 | 72 | String realName = StringTool.filterColor(player.getName()); 73 | 74 | if (!Config.JoinAndLeave()){ 75 | return; 76 | } 77 | List groups = Config.getGroupQQs(); 78 | for (long groupID : groups){ 79 | Bot.sendMsg("玩家"+name+"加入游戏",groupID); 80 | } 81 | 82 | } 83 | 84 | @EventHandler 85 | public void onQuit(PlayerQuitEvent event){ 86 | 87 | String name = StringTool.filterColor(event.getPlayer().getDisplayName()); 88 | 89 | if (!Config.JoinAndLeave()){ 90 | return; 91 | } 92 | List groups = Config.getGroupQQs(); 93 | for (long groupID : groups){ 94 | Bot.sendMsg("玩家"+name+"退出游戏",groupID); 95 | } 96 | } 97 | 98 | @EventHandler 99 | public void onPlayerDeath(PlayerDeathEvent event){ 100 | if(!Config.DieReport()){ 101 | return; 102 | } 103 | Player player=event.getEntity(); 104 | String name= player.getName(); 105 | Location location=player.getLocation(); 106 | int x= (int) location.getX(); 107 | int y= (int) location.getY(); 108 | int z= (int) location.getZ(); 109 | String msg = "死在了"+location.getWorld().getName()+"世界"+"("+x+","+y+","+z+")"; 110 | ServerManager.sendCmd("msg "+name+" "+msg,0,false); 111 | List groups = Config.getGroupQQs(); 112 | for (long groupID : groups){ 113 | Bot.sendMsg("玩家"+name+msg,groupID); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerManager.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.command.CommandSender; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | import org.linear.linearbot.LinearBot; 8 | import org.linear.linearbot.bot.Bot; 9 | import org.linear.linearbot.config.Config; 10 | 11 | import java.util.Arrays; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | public class ServerManager { 16 | 17 | public static String listOnlinePlayer() { 18 | List onlinePlayer = new LinkedList<>(); 19 | for (Player p : Bukkit.getOnlinePlayers()) { 20 | onlinePlayer.add(p.getName()); 21 | } 22 | return Arrays.toString(onlinePlayer.toArray()).replace("\\[|\\]", ""); 23 | } 24 | 25 | public static List msgList = new LinkedList<>(); 26 | 27 | public static void sendCmd(String cmd,long groupID,boolean disp) { 28 | if(!Config.CMD()){ 29 | return; 30 | } 31 | 32 | CommandSender commandSender = new ConsoleSender(); 33 | new BukkitRunnable(){ 34 | @Override 35 | public void run(){ 36 | msgList.clear(); 37 | Bukkit.dispatchCommand(commandSender, cmd); 38 | } 39 | }.runTask(LinearBot.INSTANCE); 40 | Bukkit.getScheduler().runTaskLaterAsynchronously(LinearBot.INSTANCE, () -> { 41 | StringBuilder stringBuilder = new StringBuilder(); 42 | if (msgList.size() == 0) { 43 | msgList.add("无返回值"); 44 | } 45 | for (String msg : msgList) { 46 | if (msgList.get(msgList.size() - 1).equalsIgnoreCase(msg)) { 47 | stringBuilder.append(msg.replaceAll("§\\S", "")); 48 | } else { 49 | stringBuilder.append(msg.replaceAll("§\\S", "")).append("\n"); 50 | } 51 | } 52 | if(!disp){ 53 | msgList.clear(); 54 | return; 55 | } 56 | if(stringBuilder.toString().length()<=5000){ 57 | Bot.sendMsg(stringBuilder.toString(),groupID); 58 | return; 59 | } 60 | Bot.sendMsg("返回值过长",groupID); 61 | msgList.clear(); 62 | }, 4L); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerTps.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import org.bukkit.Bukkit; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.text.NumberFormat; 8 | import java.util.Locale; 9 | import java.util.function.Supplier; 10 | 11 | public class ServerTps { 12 | 13 | /** Number formatter for 2 decimal places */ 14 | private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.CHINA); 15 | 16 | /** NMS server to get TPS from on spigot */ 17 | private Object server; 18 | 19 | /** TPS field*/ 20 | private Field recentTps; 21 | 22 | /** Detection for presence of Paper's TPS getter */ 23 | private Method paperTps; 24 | 25 | /** Detection for presence of Paper's MSPT getter */ 26 | private Method paperMspt; 27 | 28 | private Supplier TPS; 29 | 30 | private String MSPT = "none"; 31 | 32 | public ServerTps() { 33 | try { 34 | server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer()); 35 | recentTps = server.getClass().getField("recentTps"); 36 | } catch (ReflectiveOperationException e) { 37 | //not spigot 38 | } 39 | try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {} 40 | try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {} 41 | TPS = () -> { 42 | if (paperTps != null) { 43 | return formatTPS(Bukkit.getTPS()[0]); 44 | } else if (recentTps != null) { 45 | try { 46 | return formatTPS(((double[]) recentTps.get(server))[0]); 47 | } catch (IllegalAccessException e) { 48 | return String.valueOf(-1); 49 | } 50 | } else { 51 | return String.valueOf(-1); 52 | } 53 | }; 54 | if (paperMspt != null) { 55 | MSPT = numberFormat.format(Bukkit.getAverageTickTime()); 56 | } 57 | } 58 | 59 | private String formatTPS(double tps) { 60 | return numberFormat.format(Math.min(20, tps)); 61 | } 62 | 63 | public Object getTps() {return TPS.get();} 64 | 65 | public String getMSPT() {return MSPT;} 66 | 67 | } 68 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/AuthMeHook.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.hook; 2 | 3 | import fr.xephi.authme.api.v3.AuthMeApi; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.plugin.Plugin; 6 | import org.linear.linearbot.LinearBot; 7 | 8 | public class AuthMeHook { 9 | 10 | public static Boolean hasAuthMe; 11 | public static AuthMeApi authMeApi; 12 | 13 | public static void hookAuthme() { 14 | 15 | Plugin authMe = Bukkit.getPluginManager().getPlugin("AuthMe"); 16 | try { 17 | if (authMe != null) { 18 | hasAuthMe = true; 19 | authMeApi = AuthMeApi.getInstance(); 20 | LinearBot.INSTANCE.getLogger().info("AuthMe 关联成功"); 21 | }else{ 22 | hasAuthMe = false; 23 | LinearBot.INSTANCE.getLogger().info("AuthMe 关联失败"); 24 | } 25 | } catch (Throwable e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/GriefDefenderHook.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.hook; 2 | 3 | import com.griefdefender.api.Core; 4 | import com.griefdefender.api.GriefDefender; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.plugin.Plugin; 7 | import org.linear.linearbot.LinearBot; 8 | 9 | public class GriefDefenderHook { 10 | 11 | 12 | public static Boolean hasGriefDefender; 13 | 14 | public static void hookGriefDefender() { 15 | 16 | Plugin authMe = Bukkit.getPluginManager().getPlugin("GriefDefender"); 17 | try { 18 | if (authMe != null) { 19 | hasGriefDefender = true; 20 | LinearBot.INSTANCE.getLogger().info("GriefDefender 关联成功"); 21 | }else{ 22 | hasGriefDefender = false; 23 | LinearBot.INSTANCE.getLogger().info("GriefDefender 关联失败"); 24 | } 25 | } catch (Throwable e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/QuickShopHook.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.hook; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.plugin.Plugin; 5 | import org.linear.linearbot.LinearBot; 6 | 7 | public class QuickShopHook { 8 | 9 | public static Boolean hasQs; 10 | 11 | public static Boolean hasQsHikari; 12 | 13 | public static void hookQuickShop() { 14 | 15 | Plugin quickShop = Bukkit.getPluginManager().getPlugin("QuickShop"); 16 | try { 17 | if (quickShop != null) { 18 | hasQs = true; 19 | hasQsHikari = false; 20 | LinearBot.INSTANCE.getLogger().info("QuickShop-Reremake 关联成功"); 21 | }else{ 22 | hasQs = false; 23 | Plugin quickShopHikari = Bukkit.getPluginManager().getPlugin("QuickShop-Hikari"); 24 | try { 25 | if (quickShopHikari != null) { 26 | hasQsHikari = true; 27 | LinearBot.INSTANCE.getLogger().info("QuickShop-Hikari 关联成功"); 28 | }else{ 29 | hasQsHikari = false; 30 | LinearBot.INSTANCE.getLogger().info("QuickShop 关联失败"); 31 | } 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } catch (Throwable e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/ResidenceHook.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.hook; 2 | 3 | import com.bekvon.bukkit.residence.Residence; 4 | import com.bekvon.bukkit.residence.chat.ChatManager; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.plugin.Plugin; 7 | import org.linear.linearbot.LinearBot; 8 | 9 | public class ResidenceHook { 10 | 11 | public static Boolean hasRes; 12 | 13 | public static ChatManager resChatApi; 14 | 15 | public static void hookRes() { 16 | 17 | Plugin residence = Bukkit.getPluginManager().getPlugin("Residence"); 18 | try { 19 | if (residence != null) { 20 | hasRes = true; 21 | resChatApi = Residence.getInstance().getChatManager(); 22 | LinearBot.INSTANCE.getLogger().info("Residence 关联成功"); 23 | }else{ 24 | hasRes = false; 25 | LinearBot.INSTANCE.getLogger().info("Residence 关联失败"); 26 | } 27 | } catch (Throwable e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/metrics/Metrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Metrics class was auto-generated and can be copied into your project if you are 3 | * not using a build tool like Gradle or Maven for dependency management. 4 | * 5 | * IMPORTANT: You are not allowed to modify this class, except changing the package. 6 | * 7 | * Unallowed modifications include but are not limited to: 8 | * - Remove the option for users to opt-out 9 | * - Change the frequency for data submission 10 | * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) 11 | * - Reformat the code (if you use a linter, add an exception) 12 | * 13 | * Violations will result in a ban of your plugin and account from bStats. 14 | */ 15 | package org.linear.linearbot.metrics; 16 | 17 | import org.bukkit.Bukkit; 18 | import org.bukkit.configuration.file.YamlConfiguration; 19 | import org.bukkit.entity.Player; 20 | import org.bukkit.plugin.Plugin; 21 | import org.bukkit.plugin.java.JavaPlugin; 22 | 23 | import javax.net.ssl.HttpsURLConnection; 24 | import java.io.*; 25 | import java.lang.reflect.Method; 26 | import java.net.URL; 27 | import java.nio.charset.StandardCharsets; 28 | import java.util.*; 29 | import java.util.concurrent.Callable; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.ScheduledExecutorService; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.function.BiConsumer; 34 | import java.util.function.Consumer; 35 | import java.util.function.Supplier; 36 | import java.util.logging.Level; 37 | import java.util.stream.Collectors; 38 | import java.util.zip.GZIPOutputStream; 39 | 40 | public class Metrics { 41 | 42 | private final Plugin plugin; 43 | 44 | private final MetricsBase metricsBase; 45 | 46 | /** 47 | * Creates a new Metrics instance. 48 | * 49 | * @param plugin Your plugin instance. 50 | * @param serviceId The id of the service. It can be found at What is my plugin id? 52 | */ 53 | public Metrics(JavaPlugin plugin, int serviceId) { 54 | this.plugin = plugin; 55 | // Get the config file 56 | File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); 57 | File configFile = new File(bStatsFolder, "config.yml"); 58 | YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); 59 | if (!config.isSet("serverUuid")) { 60 | config.addDefault("enabled", true); 61 | config.addDefault("serverUuid", UUID.randomUUID().toString()); 62 | config.addDefault("logFailedRequests", false); 63 | config.addDefault("logSentData", false); 64 | config.addDefault("logResponseStatusText", false); 65 | // Inform the server owners about bStats 66 | config 67 | .options() 68 | .header( 69 | "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" 70 | + "many people use their plugin and their total player count. It's recommended to keep bStats\n" 71 | + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" 72 | + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" 73 | + "anonymous.") 74 | .copyDefaults(true); 75 | try { 76 | config.save(configFile); 77 | } catch (IOException ignored) { 78 | } 79 | } 80 | // Load the data 81 | boolean enabled = config.getBoolean("enabled", true); 82 | String serverUUID = config.getString("serverUuid"); 83 | boolean logErrors = config.getBoolean("logFailedRequests", false); 84 | boolean logSentData = config.getBoolean("logSentData", false); 85 | boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); 86 | metricsBase = 87 | new MetricsBase( 88 | "bukkit", 89 | serverUUID, 90 | serviceId, 91 | enabled, 92 | this::appendPlatformData, 93 | this::appendServiceData, 94 | submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), 95 | plugin::isEnabled, 96 | (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), 97 | (message) -> this.plugin.getLogger().log(Level.INFO, message), 98 | logErrors, 99 | logSentData, 100 | logResponseStatusText); 101 | } 102 | 103 | /** 104 | * Adds a custom chart. 105 | * 106 | * @param chart The chart to add. 107 | */ 108 | public void addCustomChart(CustomChart chart) { 109 | metricsBase.addCustomChart(chart); 110 | } 111 | 112 | private void appendPlatformData(JsonObjectBuilder builder) { 113 | builder.appendField("playerAmount", getPlayerAmount()); 114 | builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); 115 | builder.appendField("bukkitVersion", Bukkit.getVersion()); 116 | builder.appendField("bukkitName", Bukkit.getName()); 117 | builder.appendField("javaVersion", System.getProperty("java.version")); 118 | builder.appendField("osName", System.getProperty("os.name")); 119 | builder.appendField("osArch", System.getProperty("os.arch")); 120 | builder.appendField("osVersion", System.getProperty("os.version")); 121 | builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); 122 | } 123 | 124 | private void appendServiceData(JsonObjectBuilder builder) { 125 | builder.appendField("pluginVersion", plugin.getDescription().getVersion()); 126 | } 127 | 128 | private int getPlayerAmount() { 129 | try { 130 | // Around MC 1.8 the return type was changed from an array to a collection, 131 | // This fixes java.lang.NoSuchMethodError: 132 | // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; 133 | Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); 134 | return onlinePlayersMethod.getReturnType().equals(Collection.class) 135 | ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() 136 | : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; 137 | } catch (Exception e) { 138 | // Just use the new method if the reflection failed 139 | return Bukkit.getOnlinePlayers().size(); 140 | } 141 | } 142 | 143 | public static class MetricsBase { 144 | 145 | /** The version of the Metrics class. */ 146 | public static final String METRICS_VERSION = "3.0.0"; 147 | 148 | private static final ScheduledExecutorService scheduler = 149 | Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); 150 | 151 | private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; 152 | 153 | private final String platform; 154 | 155 | private final String serverUuid; 156 | 157 | private final int serviceId; 158 | 159 | private final Consumer appendPlatformDataConsumer; 160 | 161 | private final Consumer appendServiceDataConsumer; 162 | 163 | private final Consumer submitTaskConsumer; 164 | 165 | private final Supplier checkServiceEnabledSupplier; 166 | 167 | private final BiConsumer errorLogger; 168 | 169 | private final Consumer infoLogger; 170 | 171 | private final boolean logErrors; 172 | 173 | private final boolean logSentData; 174 | 175 | private final boolean logResponseStatusText; 176 | 177 | private final Set customCharts = new HashSet<>(); 178 | 179 | private final boolean enabled; 180 | 181 | /** 182 | * Creates a new MetricsBase class instance. 183 | * 184 | * @param platform The platform of the service. 185 | * @param serviceId The id of the service. 186 | * @param serverUuid The server uuid. 187 | * @param enabled Whether or not data sending is enabled. 188 | * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 189 | * appends all platform-specific data. 190 | * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 191 | * appends all service-specific data. 192 | * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be 193 | * used to delegate the data collection to a another thread to prevent errors caused by 194 | * concurrency. Can be {@code null}. 195 | * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. 196 | * @param errorLogger A consumer that accepts log message and an error. 197 | * @param infoLogger A consumer that accepts info log messages. 198 | * @param logErrors Whether or not errors should be logged. 199 | * @param logSentData Whether or not the sent data should be logged. 200 | * @param logResponseStatusText Whether or not the response status text should be logged. 201 | */ 202 | public MetricsBase( 203 | String platform, 204 | String serverUuid, 205 | int serviceId, 206 | boolean enabled, 207 | Consumer appendPlatformDataConsumer, 208 | Consumer appendServiceDataConsumer, 209 | Consumer submitTaskConsumer, 210 | Supplier checkServiceEnabledSupplier, 211 | BiConsumer errorLogger, 212 | Consumer infoLogger, 213 | boolean logErrors, 214 | boolean logSentData, 215 | boolean logResponseStatusText) { 216 | this.platform = platform; 217 | this.serverUuid = serverUuid; 218 | this.serviceId = serviceId; 219 | this.enabled = enabled; 220 | this.appendPlatformDataConsumer = appendPlatformDataConsumer; 221 | this.appendServiceDataConsumer = appendServiceDataConsumer; 222 | this.submitTaskConsumer = submitTaskConsumer; 223 | this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; 224 | this.errorLogger = errorLogger; 225 | this.infoLogger = infoLogger; 226 | this.logErrors = logErrors; 227 | this.logSentData = logSentData; 228 | this.logResponseStatusText = logResponseStatusText; 229 | checkRelocation(); 230 | if (enabled) { 231 | // WARNING: Removing the option to opt-out will get your plugin banned from bStats 232 | startSubmitting(); 233 | } 234 | } 235 | 236 | public void addCustomChart(CustomChart chart) { 237 | this.customCharts.add(chart); 238 | } 239 | 240 | private void startSubmitting() { 241 | final Runnable submitTask = 242 | () -> { 243 | if (!enabled || !checkServiceEnabledSupplier.get()) { 244 | // Submitting data or service is disabled 245 | scheduler.shutdown(); 246 | return; 247 | } 248 | if (submitTaskConsumer != null) { 249 | submitTaskConsumer.accept(this::submitData); 250 | } else { 251 | this.submitData(); 252 | } 253 | }; 254 | // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution 255 | // of requests on the 256 | // bStats backend. To circumvent this problem, we introduce some randomness into the initial 257 | // and second delay. 258 | // WARNING: You must not modify and part of this Metrics class, including the submit delay or 259 | // frequency! 260 | // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! 261 | long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); 262 | long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); 263 | scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); 264 | scheduler.scheduleAtFixedRate( 265 | submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); 266 | } 267 | 268 | private void submitData() { 269 | final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); 270 | appendPlatformDataConsumer.accept(baseJsonBuilder); 271 | final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); 272 | appendServiceDataConsumer.accept(serviceJsonBuilder); 273 | JsonObjectBuilder.JsonObject[] chartData = 274 | customCharts.stream() 275 | .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) 276 | .filter(Objects::nonNull) 277 | .toArray(JsonObjectBuilder.JsonObject[]::new); 278 | serviceJsonBuilder.appendField("id", serviceId); 279 | serviceJsonBuilder.appendField("customCharts", chartData); 280 | baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); 281 | baseJsonBuilder.appendField("serverUUID", serverUuid); 282 | baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); 283 | JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); 284 | scheduler.execute( 285 | () -> { 286 | try { 287 | // Send the data 288 | sendData(data); 289 | } catch (Exception e) { 290 | // Something went wrong! :( 291 | if (logErrors) { 292 | errorLogger.accept("Could not submit bStats metrics data", e); 293 | } 294 | } 295 | }); 296 | } 297 | 298 | private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { 299 | if (logSentData) { 300 | infoLogger.accept("Sent bStats metrics data: " + data.toString()); 301 | } 302 | String url = String.format(REPORT_URL, platform); 303 | HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); 304 | // Compress the data to save bandwidth 305 | byte[] compressedData = compress(data.toString()); 306 | connection.setRequestMethod("POST"); 307 | connection.addRequestProperty("Accept", "application/json"); 308 | connection.addRequestProperty("Connection", "close"); 309 | connection.addRequestProperty("Content-Encoding", "gzip"); 310 | connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); 311 | connection.setRequestProperty("Content-Type", "application/json"); 312 | connection.setRequestProperty("User-Agent", "Metrics-Service/1"); 313 | connection.setDoOutput(true); 314 | try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { 315 | outputStream.write(compressedData); 316 | } 317 | StringBuilder builder = new StringBuilder(); 318 | try (BufferedReader bufferedReader = 319 | new BufferedReader(new InputStreamReader(connection.getInputStream()))) { 320 | String line; 321 | while ((line = bufferedReader.readLine()) != null) { 322 | builder.append(line); 323 | } 324 | } 325 | if (logResponseStatusText) { 326 | infoLogger.accept("Sent data to bStats and received response: " + builder); 327 | } 328 | } 329 | 330 | /** Checks that the class was properly relocated. */ 331 | private void checkRelocation() { 332 | // You can use the property to disable the check in your test environment 333 | if (System.getProperty("bstats.relocatecheck") == null 334 | || !System.getProperty("bstats.relocatecheck").equals("false")) { 335 | // Maven's Relocate is clever and changes strings, too. So we have to use this little 336 | // "trick" ... :D 337 | final String defaultPackage = 338 | new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); 339 | final String examplePackage = 340 | new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); 341 | // We want to make sure no one just copy & pastes the example and uses the wrong package 342 | // names 343 | if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) 344 | || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { 345 | throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); 346 | } 347 | } 348 | } 349 | 350 | /** 351 | * Gzips the given string. 352 | * 353 | * @param str The string to gzip. 354 | * @return The gzipped string. 355 | */ 356 | private static byte[] compress(final String str) throws IOException { 357 | if (str == null) { 358 | return null; 359 | } 360 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 361 | try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { 362 | gzip.write(str.getBytes(StandardCharsets.UTF_8)); 363 | } 364 | return outputStream.toByteArray(); 365 | } 366 | } 367 | 368 | public static class DrilldownPie extends CustomChart { 369 | 370 | private final Callable>> callable; 371 | 372 | /** 373 | * Class constructor. 374 | * 375 | * @param chartId The id of the chart. 376 | * @param callable The callable which is used to request the chart data. 377 | */ 378 | public DrilldownPie(String chartId, Callable>> callable) { 379 | super(chartId); 380 | this.callable = callable; 381 | } 382 | 383 | @Override 384 | public JsonObjectBuilder.JsonObject getChartData() throws Exception { 385 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 386 | Map> map = callable.call(); 387 | if (map == null || map.isEmpty()) { 388 | // Null = skip the chart 389 | return null; 390 | } 391 | boolean reallyAllSkipped = true; 392 | for (Map.Entry> entryValues : map.entrySet()) { 393 | JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); 394 | boolean allSkipped = true; 395 | for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { 396 | valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); 397 | allSkipped = false; 398 | } 399 | if (!allSkipped) { 400 | reallyAllSkipped = false; 401 | valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); 402 | } 403 | } 404 | if (reallyAllSkipped) { 405 | // Null = skip the chart 406 | return null; 407 | } 408 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 409 | } 410 | } 411 | 412 | public static class AdvancedPie extends CustomChart { 413 | 414 | private final Callable> callable; 415 | 416 | /** 417 | * Class constructor. 418 | * 419 | * @param chartId The id of the chart. 420 | * @param callable The callable which is used to request the chart data. 421 | */ 422 | public AdvancedPie(String chartId, Callable> callable) { 423 | super(chartId); 424 | this.callable = callable; 425 | } 426 | 427 | @Override 428 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 429 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 430 | Map map = callable.call(); 431 | if (map == null || map.isEmpty()) { 432 | // Null = skip the chart 433 | return null; 434 | } 435 | boolean allSkipped = true; 436 | for (Map.Entry entry : map.entrySet()) { 437 | if (entry.getValue() == 0) { 438 | // Skip this invalid 439 | continue; 440 | } 441 | allSkipped = false; 442 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 443 | } 444 | if (allSkipped) { 445 | // Null = skip the chart 446 | return null; 447 | } 448 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 449 | } 450 | } 451 | 452 | public static class MultiLineChart extends CustomChart { 453 | 454 | private final Callable> callable; 455 | 456 | /** 457 | * Class constructor. 458 | * 459 | * @param chartId The id of the chart. 460 | * @param callable The callable which is used to request the chart data. 461 | */ 462 | public MultiLineChart(String chartId, Callable> callable) { 463 | super(chartId); 464 | this.callable = callable; 465 | } 466 | 467 | @Override 468 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 469 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 470 | Map map = callable.call(); 471 | if (map == null || map.isEmpty()) { 472 | // Null = skip the chart 473 | return null; 474 | } 475 | boolean allSkipped = true; 476 | for (Map.Entry entry : map.entrySet()) { 477 | if (entry.getValue() == 0) { 478 | // Skip this invalid 479 | continue; 480 | } 481 | allSkipped = false; 482 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 483 | } 484 | if (allSkipped) { 485 | // Null = skip the chart 486 | return null; 487 | } 488 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 489 | } 490 | } 491 | 492 | public static class SimpleBarChart extends CustomChart { 493 | 494 | private final Callable> callable; 495 | 496 | /** 497 | * Class constructor. 498 | * 499 | * @param chartId The id of the chart. 500 | * @param callable The callable which is used to request the chart data. 501 | */ 502 | public SimpleBarChart(String chartId, Callable> callable) { 503 | super(chartId); 504 | this.callable = callable; 505 | } 506 | 507 | @Override 508 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 509 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 510 | Map map = callable.call(); 511 | if (map == null || map.isEmpty()) { 512 | // Null = skip the chart 513 | return null; 514 | } 515 | for (Map.Entry entry : map.entrySet()) { 516 | valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); 517 | } 518 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 519 | } 520 | } 521 | 522 | public abstract static class CustomChart { 523 | 524 | private final String chartId; 525 | 526 | protected CustomChart(String chartId) { 527 | if (chartId == null) { 528 | throw new IllegalArgumentException("chartId must not be null"); 529 | } 530 | this.chartId = chartId; 531 | } 532 | 533 | public JsonObjectBuilder.JsonObject getRequestJsonObject( 534 | BiConsumer errorLogger, boolean logErrors) { 535 | JsonObjectBuilder builder = new JsonObjectBuilder(); 536 | builder.appendField("chartId", chartId); 537 | try { 538 | JsonObjectBuilder.JsonObject data = getChartData(); 539 | if (data == null) { 540 | // If the data is null we don't send the chart. 541 | return null; 542 | } 543 | builder.appendField("data", data); 544 | } catch (Throwable t) { 545 | if (logErrors) { 546 | errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); 547 | } 548 | return null; 549 | } 550 | return builder.build(); 551 | } 552 | 553 | protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; 554 | } 555 | 556 | public static class SimplePie extends CustomChart { 557 | 558 | private final Callable callable; 559 | 560 | /** 561 | * Class constructor. 562 | * 563 | * @param chartId The id of the chart. 564 | * @param callable The callable which is used to request the chart data. 565 | */ 566 | public SimplePie(String chartId, Callable callable) { 567 | super(chartId); 568 | this.callable = callable; 569 | } 570 | 571 | @Override 572 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 573 | String value = callable.call(); 574 | if (value == null || value.isEmpty()) { 575 | // Null = skip the chart 576 | return null; 577 | } 578 | return new JsonObjectBuilder().appendField("value", value).build(); 579 | } 580 | } 581 | 582 | public static class AdvancedBarChart extends CustomChart { 583 | 584 | private final Callable> callable; 585 | 586 | /** 587 | * Class constructor. 588 | * 589 | * @param chartId The id of the chart. 590 | * @param callable The callable which is used to request the chart data. 591 | */ 592 | public AdvancedBarChart(String chartId, Callable> callable) { 593 | super(chartId); 594 | this.callable = callable; 595 | } 596 | 597 | @Override 598 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 599 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 600 | Map map = callable.call(); 601 | if (map == null || map.isEmpty()) { 602 | // Null = skip the chart 603 | return null; 604 | } 605 | boolean allSkipped = true; 606 | for (Map.Entry entry : map.entrySet()) { 607 | if (entry.getValue().length == 0) { 608 | // Skip this invalid 609 | continue; 610 | } 611 | allSkipped = false; 612 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 613 | } 614 | if (allSkipped) { 615 | // Null = skip the chart 616 | return null; 617 | } 618 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 619 | } 620 | } 621 | 622 | public static class SingleLineChart extends CustomChart { 623 | 624 | private final Callable callable; 625 | 626 | /** 627 | * Class constructor. 628 | * 629 | * @param chartId The id of the chart. 630 | * @param callable The callable which is used to request the chart data. 631 | */ 632 | public SingleLineChart(String chartId, Callable callable) { 633 | super(chartId); 634 | this.callable = callable; 635 | } 636 | 637 | @Override 638 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 639 | int value = callable.call(); 640 | if (value == 0) { 641 | // Null = skip the chart 642 | return null; 643 | } 644 | return new JsonObjectBuilder().appendField("value", value).build(); 645 | } 646 | } 647 | 648 | /** 649 | * An extremely simple JSON builder. 650 | * 651 | *

While this class is neither feature-rich nor the most performant one, it's sufficient enough 652 | * for its use-case. 653 | */ 654 | public static class JsonObjectBuilder { 655 | 656 | private StringBuilder builder = new StringBuilder(); 657 | 658 | private boolean hasAtLeastOneField = false; 659 | 660 | public JsonObjectBuilder() { 661 | builder.append("{"); 662 | } 663 | 664 | /** 665 | * Appends a null field to the JSON. 666 | * 667 | * @param key The key of the field. 668 | * @return A reference to this object. 669 | */ 670 | public JsonObjectBuilder appendNull(String key) { 671 | appendFieldUnescaped(key, "null"); 672 | return this; 673 | } 674 | 675 | /** 676 | * Appends a string field to the JSON. 677 | * 678 | * @param key The key of the field. 679 | * @param value The value of the field. 680 | * @return A reference to this object. 681 | */ 682 | public JsonObjectBuilder appendField(String key, String value) { 683 | if (value == null) { 684 | throw new IllegalArgumentException("JSON value must not be null"); 685 | } 686 | appendFieldUnescaped(key, "\"" + escape(value) + "\""); 687 | return this; 688 | } 689 | 690 | /** 691 | * Appends an integer field to the JSON. 692 | * 693 | * @param key The key of the field. 694 | * @param value The value of the field. 695 | * @return A reference to this object. 696 | */ 697 | public JsonObjectBuilder appendField(String key, int value) { 698 | appendFieldUnescaped(key, String.valueOf(value)); 699 | return this; 700 | } 701 | 702 | /** 703 | * Appends an object to the JSON. 704 | * 705 | * @param key The key of the field. 706 | * @param object The object. 707 | * @return A reference to this object. 708 | */ 709 | public JsonObjectBuilder appendField(String key, JsonObject object) { 710 | if (object == null) { 711 | throw new IllegalArgumentException("JSON object must not be null"); 712 | } 713 | appendFieldUnescaped(key, object.toString()); 714 | return this; 715 | } 716 | 717 | /** 718 | * Appends a string array to the JSON. 719 | * 720 | * @param key The key of the field. 721 | * @param values The string array. 722 | * @return A reference to this object. 723 | */ 724 | public JsonObjectBuilder appendField(String key, String[] values) { 725 | if (values == null) { 726 | throw new IllegalArgumentException("JSON values must not be null"); 727 | } 728 | String escapedValues = 729 | Arrays.stream(values) 730 | .map(value -> "\"" + escape(value) + "\"") 731 | .collect(Collectors.joining(",")); 732 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 733 | return this; 734 | } 735 | 736 | /** 737 | * Appends an integer array to the JSON. 738 | * 739 | * @param key The key of the field. 740 | * @param values The integer array. 741 | * @return A reference to this object. 742 | */ 743 | public JsonObjectBuilder appendField(String key, int[] values) { 744 | if (values == null) { 745 | throw new IllegalArgumentException("JSON values must not be null"); 746 | } 747 | String escapedValues = 748 | Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); 749 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 750 | return this; 751 | } 752 | 753 | /** 754 | * Appends an object array to the JSON. 755 | * 756 | * @param key The key of the field. 757 | * @param values The integer array. 758 | * @return A reference to this object. 759 | */ 760 | public JsonObjectBuilder appendField(String key, JsonObject[] values) { 761 | if (values == null) { 762 | throw new IllegalArgumentException("JSON values must not be null"); 763 | } 764 | String escapedValues = 765 | Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); 766 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 767 | return this; 768 | } 769 | 770 | /** 771 | * Appends a field to the object. 772 | * 773 | * @param key The key of the field. 774 | * @param escapedValue The escaped value of the field. 775 | */ 776 | private void appendFieldUnescaped(String key, String escapedValue) { 777 | if (builder == null) { 778 | throw new IllegalStateException("JSON has already been built"); 779 | } 780 | if (key == null) { 781 | throw new IllegalArgumentException("JSON key must not be null"); 782 | } 783 | if (hasAtLeastOneField) { 784 | builder.append(","); 785 | } 786 | builder.append("\"").append(escape(key)).append("\":").append(escapedValue); 787 | hasAtLeastOneField = true; 788 | } 789 | 790 | /** 791 | * Builds the JSON string and invalidates this builder. 792 | * 793 | * @return The built JSON string. 794 | */ 795 | public JsonObject build() { 796 | if (builder == null) { 797 | throw new IllegalStateException("JSON has already been built"); 798 | } 799 | JsonObject object = new JsonObject(builder.append("}").toString()); 800 | builder = null; 801 | return object; 802 | } 803 | 804 | /** 805 | * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. 806 | * 807 | *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. 808 | * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). 809 | * 810 | * @param value The value to escape. 811 | * @return The escaped value. 812 | */ 813 | private static String escape(String value) { 814 | final StringBuilder builder = new StringBuilder(); 815 | for (int i = 0; i < value.length(); i++) { 816 | char c = value.charAt(i); 817 | if (c == '"') { 818 | builder.append("\\\""); 819 | } else if (c == '\\') { 820 | builder.append("\\\\"); 821 | } else if (c <= '\u000F') { 822 | builder.append("\\u000").append(Integer.toHexString(c)); 823 | } else if (c <= '\u001F') { 824 | builder.append("\\u00").append(Integer.toHexString(c)); 825 | } else { 826 | builder.append(c); 827 | } 828 | } 829 | return builder.toString(); 830 | } 831 | 832 | /** 833 | * A super simple representation of a JSON object. 834 | * 835 | *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not 836 | * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, 837 | * JsonObject)}. 838 | */ 839 | public static class JsonObject { 840 | 841 | private final String value; 842 | 843 | private JsonObject(String value) { 844 | this.value = value; 845 | } 846 | 847 | @Override 848 | public String toString() { 849 | return value; 850 | } 851 | } 852 | } 853 | } 854 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/java/org/linear/linearbot/tool/StringTool.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.tool; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class StringTool { 7 | 8 | public static String filterColor(String text){ 9 | 10 | String regEx = "§[0-9a-zA-Z]"; 11 | Pattern p = Pattern.compile(regEx); 12 | Matcher matcher = p.matcher(text); 13 | return matcher.replaceAll("").trim(); 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/bot.yml: -------------------------------------------------------------------------------- 1 | #Bot配置 2 | Ver: '1.1' 3 | 4 | Bot: 5 | QQ: 111 6 | #机器人的qq号,一个长整形 7 | 8 | Groups: 9 | - 111 10 | #启用消息转发功能的群聊qq,也是一个长整形 11 | 12 | Admins: 13 | - 111 14 | #管理员的qq,还是一个长整形 15 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/commands.yml: -------------------------------------------------------------------------------- 1 | #自定义命令配置 2 | Ver: '1.2.2' 3 | 4 | #Admin中的命令只能管理员使用 5 | Admin: 6 | 自定义群聊命令: 要执行的游戏命令 7 | 8 | #User中的命令管理员和用户都能使用 9 | User: 10 | 自定义群聊命令: 要执行的游戏命令 -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | #机器人功能设置 2 | Ver: "1.2.2" 3 | 4 | #QQ群聊命令前缀 5 | Prefix: "/" 6 | 7 | #消息转发 8 | Forwarding: 9 | enable: true 10 | mode: 0 #mode设为0即无条件转发消息,设为1则消息前有前缀才会转发消息 11 | prefix: "#" 12 | 13 | #死亡报告 14 | DieReport: false 15 | 16 | #白名单 17 | WhiteList: 18 | enable: false 19 | kickMsg: "请加入qq群:xxx申请白名单" 20 | 21 | #执行命令功能 22 | CMD: true 23 | 24 | #进出提示 25 | JoinAndLeave: true 26 | 27 | #在线玩家查询 28 | Online: true 29 | 30 | #tps查询 31 | TPS: true 32 | 33 | #自定义命令 34 | SDC: true 35 | 36 | #自定义回复 37 | SDR: true -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: LinearBot 2 | version: '${project.version}' 3 | main: org.linear.linearbot.LinearBot 4 | api-version: 1.16 5 | prefix: LinearBot 6 | authors: [ Linear,RegadPole ] 7 | depend: [ MiraiMC ] 8 | softdepend: 9 | - AuthMe 10 | - QuickShop 11 | - Residence 12 | 13 | commands: 14 | linearbot: 15 | aliases: [lb,LinearBot] 16 | description: LinearBot基本命令. 17 | usage: / 18 | permission: linearbot.command 19 | permission-message: 你不能执行它. -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/qqlist.yml: -------------------------------------------------------------------------------- 1 | #qq号对应的游戏id 2 | Ver: "1.2.2" -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/returns.yml: -------------------------------------------------------------------------------- 1 | #自定义回复配置 2 | Ver: '1.2' 3 | 4 | 自定义回复的关键词: 自定义回复的内容 5 | -------------------------------------------------------------------------------- /LinearBot-bukkit/src/main/resources/whitelist.yml: -------------------------------------------------------------------------------- 1 | #白名单文件 2 | Ver: "1.2.2" -------------------------------------------------------------------------------- /LinearBot-velocity/LinearBot-velocity.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | VELOCITY 8 | ADVENTURE 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LinearBot-velocity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.linear 8 | LinearBot 9 | 1.2.2 10 | 11 | 12 | LinearBot-velocity 13 | 14 | 15 | 18 16 | 18 17 | UTF-8 18 | 19 | 20 | 21 | 22 | papermc 23 | https://repo.papermc.io/repository/maven-public/ 24 | 25 | 26 | 27 | 28 | 29 | com.velocitypowered 30 | velocity-api 31 | 3.1.1 32 | provided 33 | 34 | 35 | org.yaml 36 | snakeyaml 37 | 2.0 38 | compile 39 | 40 | 41 | com.alibaba 42 | fastjson 43 | 2.0.32 44 | compile 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/LinearBot.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot; 2 | 3 | import com.google.inject.Inject; 4 | import com.velocitypowered.api.command.CommandManager; 5 | import com.velocitypowered.api.command.CommandMeta; 6 | import com.velocitypowered.api.event.PostOrder; 7 | import com.velocitypowered.api.event.Subscribe; 8 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 9 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 10 | import com.velocitypowered.api.plugin.Dependency; 11 | import com.velocitypowered.api.plugin.Plugin; 12 | import com.velocitypowered.api.plugin.PluginContainer; 13 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 14 | import com.velocitypowered.api.proxy.ProxyServer; 15 | import com.velocitypowered.api.proxy.server.RegisteredServer; 16 | import org.linear.linearbot.bot.Bot; 17 | import org.linear.linearbot.command.Commands; 18 | import org.linear.linearbot.config.VelocityConfig; 19 | import org.linear.linearbot.event.qq.QQEvent; 20 | import org.linear.linearbot.event.server.ServerEvent; 21 | import org.linear.linearbot.internal.Config; 22 | import org.linear.linearbot.metrics.Metrics; 23 | import org.slf4j.Logger; 24 | 25 | import java.io.File; 26 | import java.nio.file.Path; 27 | import java.util.Collection; 28 | import java.util.List; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | @Plugin(id = "linearbot", name = "LinearBot", version = "1.2.1", 32 | url = "https://github.com/LinearBit/LinearBot", description = "A plugin for MiraiMC!", authors = {"Linear,RegadPole"}, dependencies = {@Dependency(id = "miraimc")}) 33 | public class LinearBot { 34 | 35 | private final ProxyServer server; 36 | private final Logger logger; 37 | private final Path dataDirectory; 38 | private PluginContainer pluginContainer; 39 | private final Metrics.Factory metricsFactory; 40 | public VelocityConfig vconf; 41 | 42 | @Inject 43 | public LinearBot(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { 44 | this.server = server; 45 | this.logger = logger; 46 | this.dataDirectory = dataDirectory; 47 | this.metricsFactory = metricsFactory; 48 | 49 | logger.info("It's a plugin for MiraiMC!"); 50 | } 51 | 52 | @Subscribe 53 | public void onProxyInitialization(ProxyInitializeEvent event) { 54 | // Do some operation demanding access to the Velocity API here. 55 | // For instance, we could register an event: 56 | try { 57 | vconf = new VelocityConfig(this); 58 | vconf.loadConfig(); 59 | logger.info("配置文件获取成功"); 60 | }catch (Exception e) { 61 | getLogger().warn("An error occurred while loading plugin."); 62 | e.printStackTrace(); 63 | } 64 | 65 | server.getEventManager().register(this, new ServerEvent()); 66 | logger.info("服务器事件监听器注册成功"); 67 | server.getEventManager().register(this, new QQEvent(this)); 68 | logger.info("QQ事件监听器注册成功"); 69 | CommandManager manager = server.getCommandManager(); 70 | CommandMeta linearbot = manager.metaBuilder("linearbot").aliases("lb", "LinearBot").build(); 71 | manager.register(linearbot, new Commands(this)); 72 | logger.info("插件命令监听器注册成功"); 73 | 74 | // All you have to do is adding the following two lines in your onProxyInitialization method. 75 | // You can find the plugin ids of your plugins on the page https://bstats.org/what-is-my-plugin-id 76 | int pluginId = 17478; 77 | Metrics metrics = metricsFactory.make(this, pluginId); 78 | metrics.addCustomChart(new Metrics.SimplePie("chart_id", () -> "value")); 79 | logger.info("LinearBot 已启动"); 80 | 81 | runAfterDone(); 82 | } 83 | 84 | @Subscribe(order = PostOrder.FIRST) 85 | public void onProxyShutdown(ProxyShutdownEvent event) { 86 | List groups = Config.bot.Groups; 87 | for (long groupID : groups) { 88 | Bot.sendMsg("LinearBot已关闭", groupID); 89 | } 90 | 91 | getLogger().info("LinearBot已关闭"); 92 | } 93 | 94 | public Logger getLogger() { 95 | return logger; 96 | } 97 | 98 | public File getDataFolder() { 99 | return dataDirectory.toFile(); 100 | } 101 | public ProxyServer getServer() { 102 | return server; 103 | } 104 | 105 | public PluginContainer getPluginContainer(){ 106 | return pluginContainer; 107 | } 108 | 109 | public void runAfterDone() { 110 | this.getServer().getScheduler().buildTask(this, () -> { 111 | for (long groupID : Config.bot.Groups) { 112 | Bot.sendMsg("LinearBot已启动", groupID); 113 | } 114 | }).delay(10L, TimeUnit.SECONDS).schedule(); 115 | 116 | } 117 | } -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/bot/Bot.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.bot; 2 | 3 | import me.dreamvoid.miraimc.api.MiraiBot; 4 | import org.linear.linearbot.internal.Config; 5 | 6 | public class Bot { 7 | public static void sendMsg(String msg,long groupID){ 8 | MiraiBot.getBot(Config.bot.Bot.QQ).getGroup(groupID).sendMessageMirai(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/command/Commands.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.command; 2 | 3 | import com.velocitypowered.api.command.CommandSource; 4 | import com.velocitypowered.api.command.SimpleCommand; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.format.NamedTextColor; 7 | import net.kyori.adventure.text.format.TextColor; 8 | import org.linear.linearbot.LinearBot; 9 | import org.linear.linearbot.config.VelocityConfig; 10 | 11 | import java.io.IOException; 12 | import java.util.List; 13 | import java.util.concurrent.CompletableFuture; 14 | 15 | public class Commands implements SimpleCommand { 16 | 17 | private final LinearBot plugin; 18 | 19 | public Commands(LinearBot plugin){ 20 | this.plugin = plugin; 21 | } 22 | 23 | @Override 24 | public void execute(Invocation invocation) { 25 | CommandSource source = invocation.source(); 26 | // Get the arguments after the command alias 27 | String[] args = invocation.arguments(); 28 | 29 | if (args.length == 0) { 30 | source.sendMessage(Component.text("请使用/lb help查看帮助")); 31 | return; 32 | } else if (args.length == 1) { 33 | switch (args[0].toLowerCase()) { 34 | case "reload": { 35 | if (source.hasPermission("linearbot.command")) { 36 | try { 37 | plugin.vconf = new VelocityConfig(plugin); 38 | plugin.vconf.loadConfig(); 39 | source.sendMessage(Component.text("配置文件已重新加载")); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | source.sendMessage(Component.text("配置文件加载时出错").color(NamedTextColor.RED)); 43 | } 44 | }else source.sendMessage(Component.text("你没有权限执行此命令")); 45 | break; 46 | } 47 | case "help": { 48 | source.sendMessage(Component.text("§6LinearBot 机器人帮助菜单")); 49 | source.sendMessage(Component.text("§6/lb reload :§f重载插件")); 50 | source.sendMessage(Component.text("§6/lb help :§f获取插件帮助")); 51 | break; 52 | } 53 | default: { 54 | source.sendMessage(Component.text("请使用/lb help查看帮助")); 55 | } 56 | } 57 | }else { 58 | source.sendMessage(Component.text("请使用/lb help查看帮助")); 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/config/VelocityConfig.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.config; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import org.linear.linearbot.LinearBot; 5 | import org.yaml.snakeyaml.Yaml; 6 | 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.nio.file.Files; 12 | import java.nio.file.StandardCopyOption; 13 | import java.util.*; 14 | 15 | import static org.linear.linearbot.internal.Config.*; 16 | 17 | public class VelocityConfig { 18 | private static VelocityConfig Instance; 19 | private final LinearBot plugin; 20 | private Map returnsObj; 21 | 22 | public VelocityConfig(LinearBot plugin){ 23 | Instance = this; 24 | this.plugin = plugin; 25 | PluginDir = plugin.getDataFolder(); 26 | } 27 | 28 | public void loadConfig() throws IOException { 29 | File botFile = new File(plugin.getDataFolder(), "bot.yml"); 30 | File configFile = new File(plugin.getDataFolder(), "config.yml"); 31 | File returnsFile = new File(plugin.getDataFolder(), "returns.yml"); 32 | 33 | if(!PluginDir.exists() && !PluginDir.mkdirs()) throw new RuntimeException("Failed to create data folder!"); 34 | File[] allFile = {botFile,configFile,returnsFile}; 35 | for (File file : allFile) { 36 | if (!file.exists()) { 37 | try (InputStream is = plugin.getClass().getResourceAsStream("/" + file.getName())) { 38 | assert is != null; 39 | Files.copy(is, file.toPath()); 40 | } 41 | } 42 | } 43 | 44 | InputStream botIs = new FileInputStream(botFile); 45 | InputStream configIs = new FileInputStream(configFile); 46 | InputStream returnsIs = new FileInputStream(returnsFile); 47 | 48 | Yaml yaml = new Yaml(); 49 | 50 | Map botObj = yaml.load(botIs); 51 | Map configObj = yaml.load(configIs); 52 | Map returnsObj = yaml.load(returnsIs); 53 | this.returnsObj = returnsObj; 54 | 55 | bot.Ver = !Objects.isNull(botObj.get("Ver")) ? String.valueOf(botObj.get("Ver")) : "1.0"; 56 | Map botMap = !Objects.isNull(botObj.get("Bot")) ? (Map) botObj.get("Bot") : new HashMap<>(); 57 | bot.Bot.QQ = !Objects.isNull(botMap.get("QQ")) ? Long.parseLong(String.valueOf(botMap.get("QQ"))) : 111; 58 | bot.Groups = !Objects.isNull(botObj.get("Groups")) ? JSONArray.parseArray(botObj.get("Groups").toString(), Long.class) : new ArrayList<>(); 59 | bot.Admins = !Objects.isNull(botObj.get("Admins")) ? JSONArray.parseArray(botObj.get("Admins").toString(), Long.class) : new ArrayList<>(); 60 | 61 | config.Ver = !Objects.isNull(configObj.get("Ver")) ? String.valueOf(configObj.get("Ver")) : "1.0"; 62 | config.Forwarding = !Objects.isNull(configObj.get("Forwarding")) ? Boolean.parseBoolean(String.valueOf(configObj.get("Forwarding"))) : false; 63 | config.JoinAndLeave = !Objects.isNull(configObj.get("JoinAndLeave")) ? Boolean.parseBoolean(String.valueOf(configObj.get("JoinAndLeave"))) : false; 64 | config.Online = !Objects.isNull(configObj.get("Online")) ? Boolean.parseBoolean(String.valueOf(configObj.get("Online"))) : false; 65 | config.SDR = !Objects.isNull(configObj.get("SDR")) ? Boolean.parseBoolean(String.valueOf(configObj.get("SDR"))) : false; 66 | 67 | returns.Ver = !Objects.isNull(returnsObj.get("Ver")) ? String.valueOf(returnsObj.get("Ver")) : "1.0"; 68 | 69 | if (!"1.1".equals(bot.Ver)){ 70 | try (InputStream is = plugin.getClass().getResourceAsStream("/" + botFile.getName())) { 71 | botIs.close(); 72 | assert is != null; 73 | Files.copy(is, botFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 74 | Instance.loadConfig(); 75 | } 76 | } 77 | if (!config.Ver.equals("1.2")){ 78 | try (InputStream is = plugin.getClass().getResourceAsStream("/" + configFile.getName())) { 79 | configIs.close(); 80 | assert is != null; 81 | Files.copy(is, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 82 | Instance.loadConfig(); 83 | } 84 | } 85 | if (!returns.Ver.equals("1.2")){ 86 | try (InputStream is = plugin.getClass().getResourceAsStream("/" + returnsFile.getName())) { 87 | returnsIs.close(); 88 | assert is != null; 89 | Files.copy(is, returnsFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 90 | Instance.loadConfig(); 91 | } 92 | } 93 | } 94 | 95 | public Map getReturnsObj() { 96 | return returnsObj; 97 | } 98 | 99 | public static void reloadConfig() throws IOException { 100 | Instance.loadConfig(); 101 | } 102 | 103 | /* public static YamlConfiguration getBotYaml(){ 104 | return bot; 105 | } 106 | 107 | public static YamlConfiguration getConfigYaml() { 108 | return config; 109 | } 110 | 111 | public static YamlConfiguration getReturnsYaml() {return returns;} 112 | 113 | public static YamlConfiguration getCommandsYaml() {return commands;}*/ 114 | 115 | } 116 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/event/qq/QQEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.qq; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.proxy.Player; 5 | import me.dreamvoid.miraimc.api.MiraiBot; 6 | import me.dreamvoid.miraimc.velocity.event.message.passive.MiraiFriendMessageEvent; 7 | import me.dreamvoid.miraimc.velocity.event.message.passive.MiraiGroupMessageEvent; 8 | import net.kyori.adventure.text.Component; 9 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 10 | import org.linear.linearbot.LinearBot; 11 | import org.linear.linearbot.bot.Bot; 12 | import org.linear.linearbot.config.VelocityConfig; 13 | import org.linear.linearbot.internal.Config; 14 | import org.linear.linearbot.tool.StringTool; 15 | 16 | import java.util.*; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | public class QQEvent { 21 | 22 | public static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder().build(); 23 | 24 | private final LinearBot plugin; 25 | private VelocityConfig velocityConfig; 26 | 27 | public QQEvent(LinearBot plugin) { 28 | this.plugin = plugin; 29 | } 30 | @Subscribe 31 | public void onFriendMessageReceive(MiraiFriendMessageEvent e){ 32 | 33 | if(e.getMessage().equals("/在线人数")) { 34 | if(!Config.config.Online){ 35 | return; 36 | } 37 | List pname = new ArrayList<>(); 38 | for (Player player : plugin.getServer().getAllPlayers()) { 39 | pname.add(player.getUsername()); 40 | } 41 | MiraiBot.getBot(e.getBotID()).getFriend(e.getSenderID()).sendMessage("当前在线:" + "("+plugin.getServer().getAllPlayers().size()+"人)"+pname); 42 | return; 43 | } 44 | 45 | plugin.getServer().sendMessage(Component.text("§6"+"[私聊消息]"+"§a"+e.getSenderName()+"§f"+":"+e.getMessage())); 46 | } 47 | 48 | @Subscribe 49 | public void onGroupMessageReceive(MiraiGroupMessageEvent e){ 50 | 51 | Pattern pattern; 52 | Matcher matcher; 53 | 54 | String msg = e.getMessage(); 55 | long groupID= e.getGroupID(); 56 | long senderID = e.getSenderID(); 57 | 58 | pattern = Pattern.compile(" pname = new ArrayList<>(); 75 | for (Player player : plugin.getServer().getAllPlayers()) { 76 | pname.add(player.getUsername()); 77 | } 78 | Bot.sendMsg("当前在线:" + "("+plugin.getServer().getAllPlayers().size()+"人)"+pname,groupID); 79 | return; 80 | } 81 | 82 | if(Config.bot.Groups.contains(groupID)) { 83 | if (!Config.config.Forwarding){ 84 | return; 85 | } 86 | String name = StringTool.filterColor(e.getSenderName()); 87 | String smsg = StringTool.filterColor(msg); 88 | String message = "§6" + "[" + e.getGroupName() + "]" + "§a" + name + "§f" + ":" + smsg; 89 | plugin.getServer().getAllServers().forEach(server -> {server.sendMessage(SERIALIZER.deserialize(message));}); 90 | } 91 | 92 | if (Config.config.SDR){ 93 | if (plugin.vconf.getReturnsObj().get(msg) == null) return; 94 | String back = String.valueOf(plugin.vconf.getReturnsObj().get(msg)); 95 | if(back!=null){ 96 | Bot.sendMsg(back,groupID); 97 | } 98 | } 99 | 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/event/server/ServerEvent.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.event.server; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.player.KickedFromServerEvent; 5 | import com.velocitypowered.api.event.player.PlayerChatEvent; 6 | import com.velocitypowered.api.event.player.ServerConnectedEvent; 7 | import org.linear.linearbot.bot.Bot; 8 | import org.linear.linearbot.internal.Config; 9 | import org.linear.linearbot.tool.StringTool; 10 | 11 | import java.util.List; 12 | 13 | public class ServerEvent { 14 | @Subscribe 15 | public void onPlayerChat(PlayerChatEvent event) { 16 | if (!Config.config.Forwarding){ 17 | return; 18 | } 19 | String name = StringTool.filterColor(event.getPlayer().getUsername()); 20 | String message = StringTool.filterColor(event.getMessage()); 21 | List groups = Config.bot.Groups; 22 | for (long groupID : groups){ 23 | Bot.sendMsg("[" + event.getPlayer().getCurrentServer().get().getServer().getServerInfo().getName() + "]" + name+":"+message,groupID); 24 | } 25 | } 26 | 27 | @Subscribe 28 | public void onJoin(ServerConnectedEvent event){ 29 | 30 | String name = StringTool.filterColor(event.getPlayer().getUsername()); 31 | 32 | if (!Config.config.JoinAndLeave){ 33 | return; 34 | } 35 | List groups = Config.bot.Groups; 36 | for (long groupID : groups){ 37 | Bot.sendMsg("玩家"+name+"加入服务器",groupID); 38 | } 39 | 40 | } 41 | 42 | @Subscribe 43 | public void onQuit(KickedFromServerEvent event){ 44 | 45 | String name = StringTool.filterColor(event.getPlayer().getUsername()); 46 | 47 | if (!Config.config.JoinAndLeave){ 48 | return; 49 | } 50 | List groups = Config.bot.Groups; 51 | for (long groupID : groups){ 52 | Bot.sendMsg("玩家"+name+"退出服务器",groupID); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/internal/Config.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.internal; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | public class Config { 7 | public static File PluginDir; 8 | 9 | public static class bot{ // bot 10 | public static String Ver; // version 11 | 12 | public static class Bot{ 13 | public static long QQ; // bot's QQ 14 | } 15 | public static List Groups; // group's number 16 | public static List Admins; // admin's QQ 17 | } 18 | 19 | public static class config{ // bot 20 | public static String Ver; // version 21 | public static boolean Forwarding; // 消息转发 22 | public static boolean JoinAndLeave; // 进出游戏 23 | public static boolean Online; // 在线人数 24 | public static boolean SDR; // 自定义回复 25 | 26 | } 27 | 28 | public static class returns{ // commands 29 | public static String Ver; // version 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/metrics/Metrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Metrics class was auto-generated and can be copied into your project if you are 3 | * not using a build tool like Gradle or Maven for dependency management. 4 | * 5 | * IMPORTANT: You are not allowed to modify this class, except changing the package. 6 | * 7 | * Unallowed modifications include but are not limited to: 8 | * - Remove the option for users to opt-out 9 | * - Change the frequency for data submission 10 | * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) 11 | * - Reformat the code (if you use a linter, add an exception) 12 | * 13 | * Violations will result in a ban of your plugin and account from bStats. 14 | */ 15 | package org.linear.linearbot.metrics; 16 | 17 | import com.google.inject.Inject; 18 | import com.velocitypowered.api.plugin.PluginContainer; 19 | import com.velocitypowered.api.plugin.PluginDescription; 20 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 21 | import com.velocitypowered.api.proxy.ProxyServer; 22 | import java.io.BufferedReader; 23 | import java.io.BufferedWriter; 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.DataOutputStream; 26 | import java.io.File; 27 | import java.io.FileReader; 28 | import java.io.FileWriter; 29 | import java.io.IOException; 30 | import java.io.InputStreamReader; 31 | import java.net.URL; 32 | import java.nio.charset.StandardCharsets; 33 | import java.nio.file.Path; 34 | import java.util.ArrayList; 35 | import java.util.Arrays; 36 | import java.util.HashSet; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Objects; 40 | import java.util.Optional; 41 | import java.util.Set; 42 | import java.util.UUID; 43 | import java.util.concurrent.Callable; 44 | import java.util.concurrent.Executors; 45 | import java.util.concurrent.ScheduledExecutorService; 46 | import java.util.concurrent.TimeUnit; 47 | import java.util.function.BiConsumer; 48 | import java.util.function.Consumer; 49 | import java.util.function.Supplier; 50 | import java.util.regex.Pattern; 51 | import java.util.stream.Collectors; 52 | import java.util.zip.GZIPOutputStream; 53 | import javax.net.ssl.HttpsURLConnection; 54 | import org.slf4j.Logger; 55 | 56 | public class Metrics { 57 | 58 | /** A factory to create new Metrics classes. */ 59 | public static class Factory { 60 | 61 | private final ProxyServer server; 62 | 63 | private final Logger logger; 64 | 65 | private final Path dataDirectory; 66 | 67 | // The constructor is not meant to be called by the user. 68 | // The instance is created using Dependency Injection 69 | @Inject 70 | private Factory(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 71 | this.server = server; 72 | this.logger = logger; 73 | this.dataDirectory = dataDirectory; 74 | } 75 | 76 | /** 77 | * Creates a new Metrics class. 78 | * 79 | * @param plugin The plugin instance. 80 | * @param serviceId The id of the service. It can be found at What is my plugin id? 82 | *

Not to be confused with Velocity's {@link PluginDescription#getId()} method! 83 | * @return A Metrics instance that can be used to register custom charts. 84 | *

The return value can be ignored, when you do not want to register custom charts. 85 | */ 86 | public Metrics make(Object plugin, int serviceId) { 87 | return new Metrics(plugin, server, logger, dataDirectory, serviceId); 88 | } 89 | } 90 | 91 | private final PluginContainer pluginContainer; 92 | 93 | private final ProxyServer server; 94 | 95 | private MetricsBase metricsBase; 96 | 97 | private Metrics( 98 | Object plugin, ProxyServer server, Logger logger, Path dataDirectory, int serviceId) { 99 | pluginContainer = 100 | server 101 | .getPluginManager() 102 | .fromInstance(plugin) 103 | .orElseThrow( 104 | () -> new IllegalArgumentException("The provided instance is not a plugin")); 105 | this.server = server; 106 | File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile(); 107 | MetricsConfig config; 108 | try { 109 | config = new MetricsConfig(configFile, true); 110 | } catch (IOException e) { 111 | logger.error("Failed to create bStats config", e); 112 | return; 113 | } 114 | metricsBase = 115 | new MetricsBase( 116 | "velocity", 117 | config.getServerUUID(), 118 | serviceId, 119 | config.isEnabled(), 120 | this::appendPlatformData, 121 | this::appendServiceData, 122 | task -> server.getScheduler().buildTask(plugin, task).schedule(), 123 | () -> true, 124 | logger::warn, 125 | logger::info, 126 | config.isLogErrorsEnabled(), 127 | config.isLogSentDataEnabled(), 128 | config.isLogResponseStatusTextEnabled()); 129 | if (!config.didExistBefore()) { 130 | // Send an info message when the bStats config file gets created for the first time 131 | logger.info( 132 | "Velocity and some of its plugins collect metrics and send them to bStats (https://bStats.org)."); 133 | logger.info( 134 | "bStats collects some basic information for plugin authors, like how many people use"); 135 | logger.info( 136 | "their plugin and their total player count. It's recommend to keep bStats enabled, but"); 137 | logger.info( 138 | "if you're not comfortable with this, you can opt-out by editing the config.txt file in"); 139 | logger.info("the '/plugins/bStats/' folder and setting enabled to false."); 140 | } 141 | } 142 | 143 | /** 144 | * Adds a custom chart. 145 | * 146 | * @param chart The chart to add. 147 | */ 148 | public void addCustomChart(CustomChart chart) { 149 | if (metricsBase != null) { 150 | metricsBase.addCustomChart(chart); 151 | } 152 | } 153 | 154 | private void appendPlatformData(JsonObjectBuilder builder) { 155 | builder.appendField("playerAmount", server.getPlayerCount()); 156 | builder.appendField("managedServers", server.getAllServers().size()); 157 | builder.appendField("onlineMode", server.getConfiguration().isOnlineMode() ? 1 : 0); 158 | builder.appendField("velocityVersionVersion", server.getVersion().getVersion()); 159 | builder.appendField("velocityVersionName", server.getVersion().getName()); 160 | builder.appendField("velocityVersionVendor", server.getVersion().getVendor()); 161 | builder.appendField("javaVersion", System.getProperty("java.version")); 162 | builder.appendField("osName", System.getProperty("os.name")); 163 | builder.appendField("osArch", System.getProperty("os.arch")); 164 | builder.appendField("osVersion", System.getProperty("os.version")); 165 | builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); 166 | } 167 | 168 | private void appendServiceData(JsonObjectBuilder builder) { 169 | builder.appendField( 170 | "pluginVersion", pluginContainer.getDescription().getVersion().orElse("unknown")); 171 | } 172 | 173 | public static class MetricsBase { 174 | 175 | /** The version of the Metrics class. */ 176 | public static final String METRICS_VERSION = "3.0.0"; 177 | 178 | private static final ScheduledExecutorService scheduler = 179 | Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); 180 | 181 | private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; 182 | 183 | private final String platform; 184 | 185 | private final String serverUuid; 186 | 187 | private final int serviceId; 188 | 189 | private final Consumer appendPlatformDataConsumer; 190 | 191 | private final Consumer appendServiceDataConsumer; 192 | 193 | private final Consumer submitTaskConsumer; 194 | 195 | private final Supplier checkServiceEnabledSupplier; 196 | 197 | private final BiConsumer errorLogger; 198 | 199 | private final Consumer infoLogger; 200 | 201 | private final boolean logErrors; 202 | 203 | private final boolean logSentData; 204 | 205 | private final boolean logResponseStatusText; 206 | 207 | private final Set customCharts = new HashSet<>(); 208 | 209 | private final boolean enabled; 210 | 211 | /** 212 | * Creates a new MetricsBase class instance. 213 | * 214 | * @param platform The platform of the service. 215 | * @param serviceId The id of the service. 216 | * @param serverUuid The server uuid. 217 | * @param enabled Whether or not data sending is enabled. 218 | * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 219 | * appends all platform-specific data. 220 | * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 221 | * appends all service-specific data. 222 | * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be 223 | * used to delegate the data collection to a another thread to prevent errors caused by 224 | * concurrency. Can be {@code null}. 225 | * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. 226 | * @param errorLogger A consumer that accepts log message and an error. 227 | * @param infoLogger A consumer that accepts info log messages. 228 | * @param logErrors Whether or not errors should be logged. 229 | * @param logSentData Whether or not the sent data should be logged. 230 | * @param logResponseStatusText Whether or not the response status text should be logged. 231 | */ 232 | public MetricsBase( 233 | String platform, 234 | String serverUuid, 235 | int serviceId, 236 | boolean enabled, 237 | Consumer appendPlatformDataConsumer, 238 | Consumer appendServiceDataConsumer, 239 | Consumer submitTaskConsumer, 240 | Supplier checkServiceEnabledSupplier, 241 | BiConsumer errorLogger, 242 | Consumer infoLogger, 243 | boolean logErrors, 244 | boolean logSentData, 245 | boolean logResponseStatusText) { 246 | this.platform = platform; 247 | this.serverUuid = serverUuid; 248 | this.serviceId = serviceId; 249 | this.enabled = enabled; 250 | this.appendPlatformDataConsumer = appendPlatformDataConsumer; 251 | this.appendServiceDataConsumer = appendServiceDataConsumer; 252 | this.submitTaskConsumer = submitTaskConsumer; 253 | this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; 254 | this.errorLogger = errorLogger; 255 | this.infoLogger = infoLogger; 256 | this.logErrors = logErrors; 257 | this.logSentData = logSentData; 258 | this.logResponseStatusText = logResponseStatusText; 259 | checkRelocation(); 260 | if (enabled) { 261 | // WARNING: Removing the option to opt-out will get your plugin banned from bStats 262 | startSubmitting(); 263 | } 264 | } 265 | 266 | public void addCustomChart(CustomChart chart) { 267 | this.customCharts.add(chart); 268 | } 269 | 270 | private void startSubmitting() { 271 | final Runnable submitTask = 272 | () -> { 273 | if (!enabled || !checkServiceEnabledSupplier.get()) { 274 | // Submitting data or service is disabled 275 | scheduler.shutdown(); 276 | return; 277 | } 278 | if (submitTaskConsumer != null) { 279 | submitTaskConsumer.accept(this::submitData); 280 | } else { 281 | this.submitData(); 282 | } 283 | }; 284 | // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution 285 | // of requests on the 286 | // bStats backend. To circumvent this problem, we introduce some randomness into the initial 287 | // and second delay. 288 | // WARNING: You must not modify and part of this Metrics class, including the submit delay or 289 | // frequency! 290 | // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! 291 | long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); 292 | long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); 293 | scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); 294 | scheduler.scheduleAtFixedRate( 295 | submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); 296 | } 297 | 298 | private void submitData() { 299 | final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); 300 | appendPlatformDataConsumer.accept(baseJsonBuilder); 301 | final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); 302 | appendServiceDataConsumer.accept(serviceJsonBuilder); 303 | JsonObjectBuilder.JsonObject[] chartData = 304 | customCharts.stream() 305 | .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) 306 | .filter(Objects::nonNull) 307 | .toArray(JsonObjectBuilder.JsonObject[]::new); 308 | serviceJsonBuilder.appendField("id", serviceId); 309 | serviceJsonBuilder.appendField("customCharts", chartData); 310 | baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); 311 | baseJsonBuilder.appendField("serverUUID", serverUuid); 312 | baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); 313 | JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); 314 | scheduler.execute( 315 | () -> { 316 | try { 317 | // Send the data 318 | sendData(data); 319 | } catch (Exception e) { 320 | // Something went wrong! :( 321 | if (logErrors) { 322 | errorLogger.accept("Could not submit bStats metrics data", e); 323 | } 324 | } 325 | }); 326 | } 327 | 328 | private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { 329 | if (logSentData) { 330 | infoLogger.accept("Sent bStats metrics data: " + data.toString()); 331 | } 332 | String url = String.format(REPORT_URL, platform); 333 | HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); 334 | // Compress the data to save bandwidth 335 | byte[] compressedData = compress(data.toString()); 336 | connection.setRequestMethod("POST"); 337 | connection.addRequestProperty("Accept", "application/json"); 338 | connection.addRequestProperty("Connection", "close"); 339 | connection.addRequestProperty("Content-Encoding", "gzip"); 340 | connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); 341 | connection.setRequestProperty("Content-Type", "application/json"); 342 | connection.setRequestProperty("User-Agent", "Metrics-Service/1"); 343 | connection.setDoOutput(true); 344 | try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { 345 | outputStream.write(compressedData); 346 | } 347 | StringBuilder builder = new StringBuilder(); 348 | try (BufferedReader bufferedReader = 349 | new BufferedReader(new InputStreamReader(connection.getInputStream()))) { 350 | String line; 351 | while ((line = bufferedReader.readLine()) != null) { 352 | builder.append(line); 353 | } 354 | } 355 | if (logResponseStatusText) { 356 | infoLogger.accept("Sent data to bStats and received response: " + builder); 357 | } 358 | } 359 | 360 | /** Checks that the class was properly relocated. */ 361 | private void checkRelocation() { 362 | // You can use the property to disable the check in your test environment 363 | if (System.getProperty("bstats.relocatecheck") == null 364 | || !System.getProperty("bstats.relocatecheck").equals("false")) { 365 | // Maven's Relocate is clever and changes strings, too. So we have to use this little 366 | // "trick" ... :D 367 | final String defaultPackage = 368 | new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); 369 | final String examplePackage = 370 | new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); 371 | // We want to make sure no one just copy & pastes the example and uses the wrong package 372 | // names 373 | if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) 374 | || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { 375 | throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); 376 | } 377 | } 378 | } 379 | 380 | /** 381 | * Gzips the given string. 382 | * 383 | * @param str The string to gzip. 384 | * @return The gzipped string. 385 | */ 386 | private static byte[] compress(final String str) throws IOException { 387 | if (str == null) { 388 | return null; 389 | } 390 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 391 | try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { 392 | gzip.write(str.getBytes(StandardCharsets.UTF_8)); 393 | } 394 | return outputStream.toByteArray(); 395 | } 396 | } 397 | 398 | public static class DrilldownPie extends CustomChart { 399 | 400 | private final Callable>> callable; 401 | 402 | /** 403 | * Class constructor. 404 | * 405 | * @param chartId The id of the chart. 406 | * @param callable The callable which is used to request the chart data. 407 | */ 408 | public DrilldownPie(String chartId, Callable>> callable) { 409 | super(chartId); 410 | this.callable = callable; 411 | } 412 | 413 | @Override 414 | public JsonObjectBuilder.JsonObject getChartData() throws Exception { 415 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 416 | Map> map = callable.call(); 417 | if (map == null || map.isEmpty()) { 418 | // Null = skip the chart 419 | return null; 420 | } 421 | boolean reallyAllSkipped = true; 422 | for (Map.Entry> entryValues : map.entrySet()) { 423 | JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); 424 | boolean allSkipped = true; 425 | for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { 426 | valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); 427 | allSkipped = false; 428 | } 429 | if (!allSkipped) { 430 | reallyAllSkipped = false; 431 | valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); 432 | } 433 | } 434 | if (reallyAllSkipped) { 435 | // Null = skip the chart 436 | return null; 437 | } 438 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 439 | } 440 | } 441 | 442 | public static class AdvancedPie extends CustomChart { 443 | 444 | private final Callable> callable; 445 | 446 | /** 447 | * Class constructor. 448 | * 449 | * @param chartId The id of the chart. 450 | * @param callable The callable which is used to request the chart data. 451 | */ 452 | public AdvancedPie(String chartId, Callable> callable) { 453 | super(chartId); 454 | this.callable = callable; 455 | } 456 | 457 | @Override 458 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 459 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 460 | Map map = callable.call(); 461 | if (map == null || map.isEmpty()) { 462 | // Null = skip the chart 463 | return null; 464 | } 465 | boolean allSkipped = true; 466 | for (Map.Entry entry : map.entrySet()) { 467 | if (entry.getValue() == 0) { 468 | // Skip this invalid 469 | continue; 470 | } 471 | allSkipped = false; 472 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 473 | } 474 | if (allSkipped) { 475 | // Null = skip the chart 476 | return null; 477 | } 478 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 479 | } 480 | } 481 | 482 | public static class MultiLineChart extends CustomChart { 483 | 484 | private final Callable> callable; 485 | 486 | /** 487 | * Class constructor. 488 | * 489 | * @param chartId The id of the chart. 490 | * @param callable The callable which is used to request the chart data. 491 | */ 492 | public MultiLineChart(String chartId, Callable> callable) { 493 | super(chartId); 494 | this.callable = callable; 495 | } 496 | 497 | @Override 498 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 499 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 500 | Map map = callable.call(); 501 | if (map == null || map.isEmpty()) { 502 | // Null = skip the chart 503 | return null; 504 | } 505 | boolean allSkipped = true; 506 | for (Map.Entry entry : map.entrySet()) { 507 | if (entry.getValue() == 0) { 508 | // Skip this invalid 509 | continue; 510 | } 511 | allSkipped = false; 512 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 513 | } 514 | if (allSkipped) { 515 | // Null = skip the chart 516 | return null; 517 | } 518 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 519 | } 520 | } 521 | 522 | public static class SimpleBarChart extends CustomChart { 523 | 524 | private final Callable> callable; 525 | 526 | /** 527 | * Class constructor. 528 | * 529 | * @param chartId The id of the chart. 530 | * @param callable The callable which is used to request the chart data. 531 | */ 532 | public SimpleBarChart(String chartId, Callable> callable) { 533 | super(chartId); 534 | this.callable = callable; 535 | } 536 | 537 | @Override 538 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 539 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 540 | Map map = callable.call(); 541 | if (map == null || map.isEmpty()) { 542 | // Null = skip the chart 543 | return null; 544 | } 545 | for (Map.Entry entry : map.entrySet()) { 546 | valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); 547 | } 548 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 549 | } 550 | } 551 | 552 | public abstract static class CustomChart { 553 | 554 | private final String chartId; 555 | 556 | protected CustomChart(String chartId) { 557 | if (chartId == null) { 558 | throw new IllegalArgumentException("chartId must not be null"); 559 | } 560 | this.chartId = chartId; 561 | } 562 | 563 | public JsonObjectBuilder.JsonObject getRequestJsonObject( 564 | BiConsumer errorLogger, boolean logErrors) { 565 | JsonObjectBuilder builder = new JsonObjectBuilder(); 566 | builder.appendField("chartId", chartId); 567 | try { 568 | JsonObjectBuilder.JsonObject data = getChartData(); 569 | if (data == null) { 570 | // If the data is null we don't send the chart. 571 | return null; 572 | } 573 | builder.appendField("data", data); 574 | } catch (Throwable t) { 575 | if (logErrors) { 576 | errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); 577 | } 578 | return null; 579 | } 580 | return builder.build(); 581 | } 582 | 583 | protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; 584 | } 585 | 586 | public static class SimplePie extends CustomChart { 587 | 588 | private final Callable callable; 589 | 590 | /** 591 | * Class constructor. 592 | * 593 | * @param chartId The id of the chart. 594 | * @param callable The callable which is used to request the chart data. 595 | */ 596 | public SimplePie(String chartId, Callable callable) { 597 | super(chartId); 598 | this.callable = callable; 599 | } 600 | 601 | @Override 602 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 603 | String value = callable.call(); 604 | if (value == null || value.isEmpty()) { 605 | // Null = skip the chart 606 | return null; 607 | } 608 | return new JsonObjectBuilder().appendField("value", value).build(); 609 | } 610 | } 611 | 612 | public static class AdvancedBarChart extends CustomChart { 613 | 614 | private final Callable> callable; 615 | 616 | /** 617 | * Class constructor. 618 | * 619 | * @param chartId The id of the chart. 620 | * @param callable The callable which is used to request the chart data. 621 | */ 622 | public AdvancedBarChart(String chartId, Callable> callable) { 623 | super(chartId); 624 | this.callable = callable; 625 | } 626 | 627 | @Override 628 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 629 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 630 | Map map = callable.call(); 631 | if (map == null || map.isEmpty()) { 632 | // Null = skip the chart 633 | return null; 634 | } 635 | boolean allSkipped = true; 636 | for (Map.Entry entry : map.entrySet()) { 637 | if (entry.getValue().length == 0) { 638 | // Skip this invalid 639 | continue; 640 | } 641 | allSkipped = false; 642 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 643 | } 644 | if (allSkipped) { 645 | // Null = skip the chart 646 | return null; 647 | } 648 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 649 | } 650 | } 651 | 652 | public static class SingleLineChart extends CustomChart { 653 | 654 | private final Callable callable; 655 | 656 | /** 657 | * Class constructor. 658 | * 659 | * @param chartId The id of the chart. 660 | * @param callable The callable which is used to request the chart data. 661 | */ 662 | public SingleLineChart(String chartId, Callable callable) { 663 | super(chartId); 664 | this.callable = callable; 665 | } 666 | 667 | @Override 668 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 669 | int value = callable.call(); 670 | if (value == 0) { 671 | // Null = skip the chart 672 | return null; 673 | } 674 | return new JsonObjectBuilder().appendField("value", value).build(); 675 | } 676 | } 677 | 678 | /** 679 | * An extremely simple JSON builder. 680 | * 681 | *

While this class is neither feature-rich nor the most performant one, it's sufficient enough 682 | * for its use-case. 683 | */ 684 | public static class JsonObjectBuilder { 685 | 686 | private StringBuilder builder = new StringBuilder(); 687 | 688 | private boolean hasAtLeastOneField = false; 689 | 690 | public JsonObjectBuilder() { 691 | builder.append("{"); 692 | } 693 | 694 | /** 695 | * Appends a null field to the JSON. 696 | * 697 | * @param key The key of the field. 698 | * @return A reference to this object. 699 | */ 700 | public JsonObjectBuilder appendNull(String key) { 701 | appendFieldUnescaped(key, "null"); 702 | return this; 703 | } 704 | 705 | /** 706 | * Appends a string field to the JSON. 707 | * 708 | * @param key The key of the field. 709 | * @param value The value of the field. 710 | * @return A reference to this object. 711 | */ 712 | public JsonObjectBuilder appendField(String key, String value) { 713 | if (value == null) { 714 | throw new IllegalArgumentException("JSON value must not be null"); 715 | } 716 | appendFieldUnescaped(key, "\"" + escape(value) + "\""); 717 | return this; 718 | } 719 | 720 | /** 721 | * Appends an integer field to the JSON. 722 | * 723 | * @param key The key of the field. 724 | * @param value The value of the field. 725 | * @return A reference to this object. 726 | */ 727 | public JsonObjectBuilder appendField(String key, int value) { 728 | appendFieldUnescaped(key, String.valueOf(value)); 729 | return this; 730 | } 731 | 732 | /** 733 | * Appends an object to the JSON. 734 | * 735 | * @param key The key of the field. 736 | * @param object The object. 737 | * @return A reference to this object. 738 | */ 739 | public JsonObjectBuilder appendField(String key, JsonObject object) { 740 | if (object == null) { 741 | throw new IllegalArgumentException("JSON object must not be null"); 742 | } 743 | appendFieldUnescaped(key, object.toString()); 744 | return this; 745 | } 746 | 747 | /** 748 | * Appends a string array to the JSON. 749 | * 750 | * @param key The key of the field. 751 | * @param values The string array. 752 | * @return A reference to this object. 753 | */ 754 | public JsonObjectBuilder appendField(String key, String[] values) { 755 | if (values == null) { 756 | throw new IllegalArgumentException("JSON values must not be null"); 757 | } 758 | String escapedValues = 759 | Arrays.stream(values) 760 | .map(value -> "\"" + escape(value) + "\"") 761 | .collect(Collectors.joining(",")); 762 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 763 | return this; 764 | } 765 | 766 | /** 767 | * Appends an integer array to the JSON. 768 | * 769 | * @param key The key of the field. 770 | * @param values The integer array. 771 | * @return A reference to this object. 772 | */ 773 | public JsonObjectBuilder appendField(String key, int[] values) { 774 | if (values == null) { 775 | throw new IllegalArgumentException("JSON values must not be null"); 776 | } 777 | String escapedValues = 778 | Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); 779 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 780 | return this; 781 | } 782 | 783 | /** 784 | * Appends an object array to the JSON. 785 | * 786 | * @param key The key of the field. 787 | * @param values The integer array. 788 | * @return A reference to this object. 789 | */ 790 | public JsonObjectBuilder appendField(String key, JsonObject[] values) { 791 | if (values == null) { 792 | throw new IllegalArgumentException("JSON values must not be null"); 793 | } 794 | String escapedValues = 795 | Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); 796 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 797 | return this; 798 | } 799 | 800 | /** 801 | * Appends a field to the object. 802 | * 803 | * @param key The key of the field. 804 | * @param escapedValue The escaped value of the field. 805 | */ 806 | private void appendFieldUnescaped(String key, String escapedValue) { 807 | if (builder == null) { 808 | throw new IllegalStateException("JSON has already been built"); 809 | } 810 | if (key == null) { 811 | throw new IllegalArgumentException("JSON key must not be null"); 812 | } 813 | if (hasAtLeastOneField) { 814 | builder.append(","); 815 | } 816 | builder.append("\"").append(escape(key)).append("\":").append(escapedValue); 817 | hasAtLeastOneField = true; 818 | } 819 | 820 | /** 821 | * Builds the JSON string and invalidates this builder. 822 | * 823 | * @return The built JSON string. 824 | */ 825 | public JsonObject build() { 826 | if (builder == null) { 827 | throw new IllegalStateException("JSON has already been built"); 828 | } 829 | JsonObject object = new JsonObject(builder.append("}").toString()); 830 | builder = null; 831 | return object; 832 | } 833 | 834 | /** 835 | * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. 836 | * 837 | *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. 838 | * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). 839 | * 840 | * @param value The value to escape. 841 | * @return The escaped value. 842 | */ 843 | private static String escape(String value) { 844 | final StringBuilder builder = new StringBuilder(); 845 | for (int i = 0; i < value.length(); i++) { 846 | char c = value.charAt(i); 847 | if (c == '"') { 848 | builder.append("\\\""); 849 | } else if (c == '\\') { 850 | builder.append("\\\\"); 851 | } else if (c <= '\u000F') { 852 | builder.append("\\u000").append(Integer.toHexString(c)); 853 | } else if (c <= '\u001F') { 854 | builder.append("\\u00").append(Integer.toHexString(c)); 855 | } else { 856 | builder.append(c); 857 | } 858 | } 859 | return builder.toString(); 860 | } 861 | 862 | /** 863 | * A super simple representation of a JSON object. 864 | * 865 | *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not 866 | * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, 867 | * JsonObject)}. 868 | */ 869 | public static class JsonObject { 870 | 871 | private final String value; 872 | 873 | private JsonObject(String value) { 874 | this.value = value; 875 | } 876 | 877 | @Override 878 | public String toString() { 879 | return value; 880 | } 881 | } 882 | } 883 | 884 | /** 885 | * A simple config for bStats. 886 | * 887 | *

This class is not used by every platform. 888 | */ 889 | public static class MetricsConfig { 890 | 891 | private final File file; 892 | 893 | private final boolean defaultEnabled; 894 | 895 | private String serverUUID; 896 | 897 | private boolean enabled; 898 | 899 | private boolean logErrors; 900 | 901 | private boolean logSentData; 902 | 903 | private boolean logResponseStatusText; 904 | 905 | private boolean didExistBefore = true; 906 | 907 | public MetricsConfig(File file, boolean defaultEnabled) throws IOException { 908 | this.file = file; 909 | this.defaultEnabled = defaultEnabled; 910 | setupConfig(); 911 | } 912 | 913 | public String getServerUUID() { 914 | return serverUUID; 915 | } 916 | 917 | public boolean isEnabled() { 918 | return enabled; 919 | } 920 | 921 | public boolean isLogErrorsEnabled() { 922 | return logErrors; 923 | } 924 | 925 | public boolean isLogSentDataEnabled() { 926 | return logSentData; 927 | } 928 | 929 | public boolean isLogResponseStatusTextEnabled() { 930 | return logResponseStatusText; 931 | } 932 | 933 | /** 934 | * Checks whether the config file did exist before or not. 935 | * 936 | * @return If the config did exist before. 937 | */ 938 | public boolean didExistBefore() { 939 | return didExistBefore; 940 | } 941 | 942 | /** Creates the config file if it does not exist and read its content. */ 943 | private void setupConfig() throws IOException { 944 | if (!file.exists()) { 945 | // Looks like it's the first time we create it (or someone deleted it). 946 | didExistBefore = false; 947 | writeConfig(); 948 | } 949 | readConfig(); 950 | if (serverUUID == null) { 951 | // Found a malformed config file with no UUID. Let's recreate it. 952 | writeConfig(); 953 | readConfig(); 954 | } 955 | } 956 | 957 | /** Creates a config file with teh default content. */ 958 | private void writeConfig() throws IOException { 959 | List configContent = new ArrayList<>(); 960 | configContent.add( 961 | "# bStats (https://bStats.org) collects some basic information for plugin authors, like"); 962 | configContent.add( 963 | "# how many people use their plugin and their total player count. It's recommended to keep"); 964 | configContent.add( 965 | "# bStats enabled, but if you're not comfortable with this, you can turn this setting off."); 966 | configContent.add( 967 | "# There is no performance penalty associated with having metrics enabled, and data sent to"); 968 | configContent.add("# bStats is fully anonymous."); 969 | configContent.add("enabled=" + defaultEnabled); 970 | configContent.add("server-uuid=" + UUID.randomUUID().toString()); 971 | configContent.add("log-errors=false"); 972 | configContent.add("log-sent-data=false"); 973 | configContent.add("log-response-status-text=false"); 974 | writeFile(file, configContent); 975 | } 976 | 977 | /** Reads the content of the config file. */ 978 | private void readConfig() throws IOException { 979 | List lines = readFile(file); 980 | if (lines == null) { 981 | throw new AssertionError("Content of newly created file is null"); 982 | } 983 | enabled = getConfigValue("enabled", lines).map("true"::equals).orElse(true); 984 | serverUUID = getConfigValue("server-uuid", lines).orElse(null); 985 | logErrors = getConfigValue("log-errors", lines).map("true"::equals).orElse(false); 986 | logSentData = getConfigValue("log-sent-data", lines).map("true"::equals).orElse(false); 987 | logResponseStatusText = 988 | getConfigValue("log-response-status-text", lines).map("true"::equals).orElse(false); 989 | } 990 | 991 | /** 992 | * Gets a config setting from the given list of lines of the file. 993 | * 994 | * @param key The key for the setting. 995 | * @param lines The lines of the file. 996 | * @return The value of the setting. 997 | */ 998 | private Optional getConfigValue(String key, List lines) { 999 | return lines.stream() 1000 | .filter(line -> line.startsWith(key + "=")) 1001 | .map(line -> line.replaceFirst(Pattern.quote(key + "="), "")) 1002 | .findFirst(); 1003 | } 1004 | 1005 | /** 1006 | * Reads the text content of the given file. 1007 | * 1008 | * @param file The file to read. 1009 | * @return The lines of the given file. 1010 | */ 1011 | private List readFile(File file) throws IOException { 1012 | if (!file.exists()) { 1013 | return null; 1014 | } 1015 | try (FileReader fileReader = new FileReader(file); 1016 | BufferedReader bufferedReader = new BufferedReader(fileReader)) { 1017 | return bufferedReader.lines().collect(Collectors.toList()); 1018 | } 1019 | } 1020 | 1021 | /** 1022 | * Writes the given lines to the given file. 1023 | * 1024 | * @param file The file to write to. 1025 | * @param lines The lines to write. 1026 | */ 1027 | private void writeFile(File file, List lines) throws IOException { 1028 | if (!file.exists()) { 1029 | file.getParentFile().mkdirs(); 1030 | file.createNewFile(); 1031 | } 1032 | try (FileWriter fileWriter = new FileWriter(file); 1033 | BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { 1034 | for (String line : lines) { 1035 | bufferedWriter.write(line); 1036 | bufferedWriter.newLine(); 1037 | } 1038 | } 1039 | } 1040 | } 1041 | } -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/java/org/linear/linearbot/tool/StringTool.java: -------------------------------------------------------------------------------- 1 | package org.linear.linearbot.tool; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class StringTool { 7 | 8 | public static String filterColor(String text){ 9 | 10 | String regEx = "§[0-9a-zA-Z]"; 11 | Pattern p = Pattern.compile(regEx); 12 | Matcher matcher = p.matcher(text); 13 | return matcher.replaceAll("").trim(); 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/resources/bot.yml: -------------------------------------------------------------------------------- 1 | #Bot配置 2 | Ver: '1.1' 3 | 4 | Bot: 5 | QQ: 111 6 | #机器人的qq号,一个长整形 7 | 8 | Groups: 9 | - 111 10 | #启用消息转发功能的群聊qq,也是一个长整形 11 | 12 | Admins: 13 | - 111 14 | #管理员的qq,还是一个长整形 15 | -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | #机器人功能设置 2 | Ver: '1.2' 3 | 4 | #消息转发 5 | Forwarding: true 6 | 7 | #进出提示 8 | JoinAndLeave: true 9 | 10 | #在线玩家查询 11 | Online: true 12 | 13 | #自定义回复 14 | SDR: true -------------------------------------------------------------------------------- /LinearBot-velocity/src/main/resources/returns.yml: -------------------------------------------------------------------------------- 1 | #自定义回复配置 2 | Ver: '1.2' 3 | 4 | 自定义回复的关键词: 自定义回复的内容 5 | -------------------------------------------------------------------------------- /LinearBot.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | ADVENTURE 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinearBot 2 | 适用Minecraft Java版本:1.16+(需v1.2及以上插件) 3 | 4 | 使用本插件需要前置插件[MiraiMC](https://github.com/DreamVoid/MiraiMC/) 5 | 6 | 本项目使用GNU AGPL3.0协议开源 7 | 8 | 9 | ## bStats 10 | 11 | ![https://bstats.org/plugin/bukkit/LinearBot/17137](https://bstats.org/signatures/bukkit/LinearBot.svg) 12 | 13 | 14 | ## 安全状态 15 | 16 | [![Security Status](https://www.murphysec.com/platform3/v31/badge/1676956296301068288.svg)](https://www.murphysec.com/console/report/1653013114790641664/1676956296301068288) 17 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.linear 8 | LinearBot 9 | 1.2.2 10 | pom 11 | 12 | LinearBot 13 | 14 | LinearBot-velocity 15 | LinearBot-bukkit 16 | 17 | 18 | 19 | 1.8 20 | UTF-8 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-compiler-plugin 28 | 3.8.1 29 | 30 | ${java.version} 31 | ${java.version} 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-shade-plugin 37 | 3.2.4 38 | 39 | 40 | package 41 | 42 | shade 43 | 44 | 45 | false 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | src/main/resources 54 | true 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | 72 | 73 | 74 | io.github.dreamvoid 75 | MiraiMC-Integration 76 | 1.8.3 77 | provided 78 | 79 | 80 | 87 | 88 | 89 | 90 | 91 | 92 | --------------------------------------------------------------------------------