From 430653a4abbb693eec3de1032b22f3076c0e6ae7 Mon Sep 17 00:00:00 2001 From: Colbster937 Date: Fri, 9 Jan 2026 18:07:40 -0600 Subject: [PATCH] 2.0 --- .github/workflows/build.yml | 76 -- .github/workflows/gradle.yml | 69 ++ .gitignore | 16 +- LICENSE.md | 661 ++++++++++++++++++ README.md | 45 +- build.gradle.kts | 189 +++-- gradle.properties | 3 + icon.png | Bin 0 -> 15408 bytes .../originblacklist/base/Base.java | 316 --------- .../originblacklist/base/Command.java | 173 ----- .../originblacklist/base/ConfigManager.java | 140 ---- .../originblacklist/base/IPBlacklist.java | 41 -- .../originblacklist/bukkit/CommandBukkit.java | 37 - .../bukkit/OriginBlacklistBukkit.java | 55 -- .../originblacklist/bungee/CommandBungee.java | 33 - .../bungee/OriginBlacklistBungee.java | 54 -- .../velocity/CommandVelocity.java | 49 -- .../velocity/OriginBlacklistVelocity.java | 59 -- .../originblacklist/base/OriginBlacklist.java | 213 ++++++ .../base/command/CommandContext.java | 8 + .../base/command/ICommand.java | 10 + .../base/command/OriginBlacklistCommand.java | 76 ++ .../base/config/OriginBlacklistConfig.java | 257 +++++++ .../base/enums/EnumBlacklistType.java | 39 ++ .../base/enums/EnumConnectionType.java | 6 + .../base/enums/EnumLogLevel.java | 8 + .../base/events/OriginBlacklistEvent.java | 36 + .../events/OriginBlacklistLoginEvent.java | 18 + .../base/events/OriginBlacklistMOTDEvent.java | 18 + .../originblacklist/base/util/ChatFormat.java | 29 + .../base/util/IOriginBlacklistPlugin.java | 22 + .../util/IncompatibleDependencyException.java | 14 + .../originblacklist/base/util/OPlayer.java | 100 +++ .../base/util/UpdateChecker.java | 47 ++ .../bukkit/OriginBlacklistBukkit.java | 235 +++++++ .../bukkit/command/BKTCommandContext.java | 36 + .../command/OriginBlacklistCommandBukkit.java | 26 + .../bungee/OriginBlacklistBungee.java | 200 ++++++ .../bungee/command/BNGCommandContext.java | 37 + .../command/OriginBlacklistCommandBungee.java | 30 + .../velocity/OriginBlacklistVelocity.java | 226 ++++++ .../OriginBlacklistCommandVelocity.java | 24 + .../velocity/command/VCommandContext.java | 35 + .../{server-blocked.png => blacklisted.png} | Bin src/main/resources/bungee.yml | 17 +- src/main/resources/config.yml | 69 -- src/main/resources/plugin.yml | 17 +- src/main/resources/velocity-plugin.json | 22 +- 48 files changed, 2690 insertions(+), 1201 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/gradle.yml create mode 100644 LICENSE.md create mode 100644 gradle.properties create mode 100644 icon.png delete mode 100644 src/main/java/dev/colbster937/originblacklist/base/Base.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/base/Command.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/base/ConfigManager.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/base/IPBlacklist.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/bukkit/CommandBukkit.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/bukkit/OriginBlacklistBukkit.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/bungee/CommandBungee.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/bungee/OriginBlacklistBungee.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/velocity/CommandVelocity.java delete mode 100644 src/main/java/dev/colbster937/originblacklist/velocity/OriginBlacklistVelocity.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/OriginBlacklist.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/command/CommandContext.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/command/ICommand.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/command/OriginBlacklistCommand.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/config/OriginBlacklistConfig.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/enums/EnumBlacklistType.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/enums/EnumConnectionType.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/enums/EnumLogLevel.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistEvent.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistLoginEvent.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistMOTDEvent.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/util/ChatFormat.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/util/IOriginBlacklistPlugin.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/util/IncompatibleDependencyException.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/util/OPlayer.java create mode 100644 src/main/java/xyz/webmc/originblacklist/base/util/UpdateChecker.java create mode 100644 src/main/java/xyz/webmc/originblacklist/bukkit/OriginBlacklistBukkit.java create mode 100644 src/main/java/xyz/webmc/originblacklist/bukkit/command/BKTCommandContext.java create mode 100644 src/main/java/xyz/webmc/originblacklist/bukkit/command/OriginBlacklistCommandBukkit.java create mode 100644 src/main/java/xyz/webmc/originblacklist/bungee/OriginBlacklistBungee.java create mode 100644 src/main/java/xyz/webmc/originblacklist/bungee/command/BNGCommandContext.java create mode 100644 src/main/java/xyz/webmc/originblacklist/bungee/command/OriginBlacklistCommandBungee.java create mode 100644 src/main/java/xyz/webmc/originblacklist/velocity/OriginBlacklistVelocity.java create mode 100644 src/main/java/xyz/webmc/originblacklist/velocity/command/OriginBlacklistCommandVelocity.java create mode 100644 src/main/java/xyz/webmc/originblacklist/velocity/command/VCommandContext.java rename src/main/resources/{server-blocked.png => blacklisted.png} (100%) delete mode 100644 src/main/resources/config.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index be9a31a..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Build Plugin - -on: - push: - workflow_dispatch: - -permissions: - contents: write - -jobs: - build: - if: ${{ github.ref == 'refs/heads/main' }} - concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - name: Build Jar - run: | - gradle wrapper - ./gradlew clean shadowJar - - - name: Publish Jar - uses: actions/upload-artifact@v4 - with: - name: OriginBlacklist - path: build/libs/OriginBlacklist.jar - - - name: Extract Version - id: version - run: | - VERSION="$(./gradlew -q properties | sed -n 's/^version: //p' | head -n 1)" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - - name: Remove Existing Release - run: | - gh release delete v${{ steps.version.outputs.version }} -y || true - git push origin :refs/tags/v${{ steps.version.outputs.version }} || true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create Release - id: create_release - uses: softprops/action-gh-release@v1 - with: - tag_name: v${{ steps.version.outputs.version }} - name: ${{ steps.version.outputs.version }} - draft: false - prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./build/libs/OriginBlacklist.jar - asset_name: OriginBlacklist_${{ steps.version.outputs.version }}.jar - asset_content_type: application/java-archive - - - uses: eregon/publish-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - release_id: ${{ steps.create_release.outputs.id }} \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..894161e --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,69 @@ +name: Build Plugin + +on: + workflow_dispatch: + push: + branches: [ main ] + +permissions: + contents: write + actions: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - uses: gradle/actions/setup-gradle@v4 + + - run: | + gradle wrapper + chmod +x ./gradlew + ./gradlew shadowJar + + - id: vars + run: | + echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + ./gradlew -q printVars | sed 's/ = /=/g' >> "$GITHUB_OUTPUT" + + - id: ghck + run: | + CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${{ github.repository }}/releases/tags/v${{ steps.vars.outputs.VERS }}") + if [ "$CODE" = "200" ]; then + echo "EXISTS=true" >> "$GITHUB_OUTPUT" + else + echo "EXISTS=false" >> "$GITHUB_OUTPUT" + fi + + - run: | + mkdir -p dist + cp "./build/libs/${{ steps.vars.outputs.AFCT }}" ./dist + + - uses: actions/upload-artifact@v4 + with: + path: dist/${{ steps.vars.outputs.AFCT }} + name: ${{ steps.vars.outputs.AFCT }} + + - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ format('v{0}{1}', steps.vars.outputs.VERS, steps.ghck.outputs.EXISTS == 'true' && format('+{0}', steps.vars.outputs.COMMIT_HASH) || '') }} + name: ${{ format('{0}{1}', steps.ghck.outputs.EXISTS == 'true' && 'Snapshot ' || 'v', steps.ghck.outputs.EXISTS == 'true' && steps.vars.outputs.COMMIT_HASH || steps.vars.outputs.VERS) }} + files: dist/${{ steps.vars.outputs.AFCT }} + prerelease: ${{ steps.ghck.outputs.EXISTS == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index ae55814..583b97b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ -**/.DS_Store -.idea -.gradle -gradle -build -run -bin -gradlew -gradlew.bat \ No newline at end of file +/.gradle/ +/.vscode/ +/gradle/ +/build/ +/run/ +/gradlew +/gradlew.bat \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 4d9eff9..66b2945 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,28 @@ -# OriginBlacklist + -basically just a reimplementation of originblacklist but for eaglerxserver +

Features

-> [!WARNING] -> **Velocity is the main platform I'm developing this for, bungee & bukkit will still work but will probably have some bugs and will receive less support!** +- [x] Origin based blacklisting +- [x] Client brand based blacklisting +- [x] Username based blacklisting +- [x] IP based blacklisting +- [x] Kick message customization +- [x] Blacklist MOTD customization +- [x] MiniMessage and legacy formattings supported +- [x] Plugin update checker +- [ ] Send blacklist logs to a webhook +- [ ] Subscribe to an auto-updating blacklist +- [ ] Reverse blacklist (whitelist) -### Features -- [x] Origin Blacklisting -- [x] Brand Blacklisting -- [x] Username blacklisting -- [x] IP blacklisting -- [x] Custom kick message -- [x] Custom blacklist MOTD -- [x] MiniMessage formatting for messages -- [x] Velocity, Bungee, and Bukkit support -- [x] Send blacklists to a discord webhook -- [x] Simple blacklist command -- [ ] Blacklist subscription URLs -- [ ] Blacklist -> Whitelist -- [ ] Update system +

Download

+The latest release can be found at https://github.com/WebMCDevelopment/originblacklist/releases/tag/v1.1.3 -### Download -**[https://github.com/colbster937/originblacklist/releases/latest/](https://github.com/colbster937/originblacklist/releases/latest/)** - -### Building -``` -$ git clone https://github.com/colbster937/originblacklist.git +

Building

+```sh +$ git clone https://github.com/WebMCDevelopment/originblacklist $ cd originblacklist $ gradle wrapper $ ./gradlew shadowJar diff --git a/build.gradle.kts b/build.gradle.kts index 3b30e90..2b0f607 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,48 @@ +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.language.jvm.tasks.ProcessResources +import xyz.jpenilla.runpaper.task.RunServer +import xyz.jpenilla.runwaterfall.task.RunWaterfall +import xyz.jpenilla.runvelocity.task.RunVelocity + + + + +val PLUGIN_NAME = "OriginBlacklist" +val PLUGIN_IDEN = "originblacklist" +val PLUGIN_DOMN = "xyz.webmc" +val PLUGIN_DESC = "An eaglercraft client blacklist plugin." +val PLUGIN_VERS = "2.0.0" +val PLUGIN_SITE = "https://github.com/WebMCDevelopment/$PLUGIN_IDEN" +val PLUGIN_DEPA = listOf("EaglercraftXServer") +val PLUGIN_DEPB = listOf("EaglercraftXServer") +val PLUGIN_DEPC = listOf("eaglerxserver") +val PLUGIN_SDPA = listOf("PlaceholderAPI") +val PLUGIN_SDPB = listOf("PAPIProxyBridge") +val PLUGIN_SDPC = listOf("papiproxybridge") +val PLUGIN_PROV = emptyList() +val PLUGIN_ATHR = listOf("Colbster937") +val PLUGIN_CTBR = emptyList() + + + + +val PLUGIN_DEPA_J = getJSONObj(PLUGIN_DEPA) +val PLUGIN_DEPB_J = getJSONObj(PLUGIN_DEPB) +val PLUGIN_DEPC_J = getJSONObjMerge(PLUGIN_DEPC, PLUGIN_SDPC) +val PLUGIN_SDPA_J = getJSONObj(PLUGIN_SDPA) +val PLUGIN_SDPB_J = getJSONObj(PLUGIN_SDPB) +val PLUGIN_PROV_J = getJSONObj(PLUGIN_PROV) +val PLUGIN_ATHR_J = getJSONObj(PLUGIN_ATHR) +val PLUGIN_CTBR_J = getJSONObj(PLUGIN_CTBR) + plugins { id("java") - id("eclipse") - id("org.jetbrains.gradle.plugin.idea-ext") version "1.1.8" id("com.gradleup.shadow") version "9.3.0" - id("xyz.jpenilla.run-velocity") version "2.3.1" + id("xyz.jpenilla.run-paper") version "3.0.2" + id("xyz.jpenilla.run-waterfall") version "3.0.2" + id("xyz.jpenilla.run-velocity") version "3.0.2" } -group = "dev.colbster937" -version = "1.1.3" -description = "A reimplementation of OriginBlacklist for EaglerXServer" - -val targetJavaVersion = 17 - repositories { mavenCentral() maven("https://repo.papermc.io/repository/maven-public/") @@ -20,51 +51,131 @@ repositories { maven("https://repo.md-5.net/content/repositories/releases/") maven("https://repo.aikar.co/nexus/content/groups/aikar/") maven("https://repo.lax1dude.net/repository/releases/") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") + maven("https://repo.william278.net/releases/") } dependencies { compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") compileOnly("org.bukkit:bukkit:1.8-R0.1-SNAPSHOT") - compileOnly("net.md-5:bungeecord-api:1.8-SNAPSHOT") + compileOnly("net.md-5:bungeecord-api:1.21-R0.5-SNAPSHOT") compileOnly("net.lax1dude.eaglercraft.backend:api-velocity:1.0.0") compileOnly("net.lax1dude.eaglercraft.backend:api-bungee:1.0.0") compileOnly("net.lax1dude.eaglercraft.backend:api-bukkit:1.0.0") - implementation("org.yaml:snakeyaml:2.2") - implementation("net.kyori:adventure-text-serializer-legacy:4.20.0") - implementation("net.kyori:adventure-text-minimessage:4.20.0") - implementation("com.github.seancfoley:ipaddress:5.3.4") - annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") + compileOnly("me.clip:placeholderapi:2.11.7") + compileOnly("net.william278:papiproxybridge:1.8.4") + implementation("org.semver4j:semver4j:6.0.0") + implementation("de.marhali:json5-java:3.0.0") + implementation("net.kyori:adventure-text-minimessage:4.26.1") + implementation("net.kyori:adventure-text-serializer-legacy:4.26.1") + implementation("com.github.seancfoley:ipaddress:5.5.1") + implementation("org.bstats:bstats-bukkit:3.1.0") + implementation("org.bstats:bstats-bungeecord:3.1.0") + implementation("org.bstats:bstats-velocity:3.1.0") } -tasks { - named("runVelocity") { - velocityVersion("3.4.0-SNAPSHOT") - } +sourceSets { + named("main") { + java.srcDir("./src/main/java") + resources.srcDir("./src/main/resources") + } } java { - toolchain.languageVersion.set(JavaLanguageVersion.of(targetJavaVersion)) -} - -tasks.processResources { - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "Base.java")) { - expand( - mapOf( - "version" to project.version, - "description" to project.description - ) - ) - } -} - -tasks.shadowJar { - relocate("org.yaml.snakeyaml", "dev.colbster937.shaded.snakeyaml") - relocate("inet.ipaddr", "dev.colbster937.shaded.ipaddr") - archiveVersion.set("") - archiveClassifier.set("") + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) } tasks.withType().configureEach { options.encoding = "UTF-8" - options.release.set(targetJavaVersion) + options.release.set(17) +} + +tasks.withType() { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + outputs.upToDateWhen { false } + doFirst { + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) { + expand(mapOf( + "plugin_name" to PLUGIN_NAME, + "plugin_iden" to PLUGIN_IDEN, + "plugin_desc" to PLUGIN_DESC, + "plugin_vers" to PLUGIN_VERS, + "plugin_site" to PLUGIN_SITE, + "plugin_depa" to PLUGIN_DEPA_J, + "plugin_depb" to PLUGIN_DEPB_J, + "plugin_depc" to PLUGIN_DEPC_J, + "plugin_sdpa" to PLUGIN_SDPA_J, + "plugin_sdpb" to PLUGIN_SDPB_J, + "plugin_prov" to PLUGIN_PROV_J, + "plugin_athr" to PLUGIN_ATHR_J, + "plugin_ctbr" to PLUGIN_CTBR_J, + )) + } + } + + inputs.files(tasks.named("compileJava").map { it.outputs.files }) +} + +tasks.withType() { + minecraftVersion("1.12.2") + runDirectory.set(layout.projectDirectory.dir("run/paper")) + downloadPlugins { + github("lax1dude", "eaglerxserver", "v1.0.8", "EaglerXServer.jar") + modrinth("placeholderapi", "2.11.7") + } +} + +tasks.withType() { + waterfallVersion("1.21") + runDirectory.set(layout.projectDirectory.dir("run/waterfall")) + downloadPlugins { + github("lax1dude", "eaglerxserver", "v1.0.8", "EaglerXServer.jar") + } +} + +tasks.withType() { + velocityVersion("3.4.0-SNAPSHOT") + runDirectory.set(layout.projectDirectory.dir("run/velocity")) + downloadPlugins { + github("lax1dude", "eaglerxserver", "v1.0.8", "EaglerXServer.jar") + modrinth("miniplaceholders", "3.1.0") + } +} + +tasks.jar { + archiveFileName.set("$PLUGIN_NAME-$PLUGIN_VERS.jar") +} + +tasks.shadowJar { + relocate("org.bstats", "$PLUGIN_DOMN.$PLUGIN_IDEN.shaded.bstats") + relocate("de.marhali.json5", "$PLUGIN_DOMN.$PLUGIN_IDEN.shaded.json5") + relocate("org.semver4j.semver4j", "$PLUGIN_DOMN.$PLUGIN_IDEN.shaded.semver4j") + // relocate("net.kyori.adventure", "$PLUGIN_DOMN.$PLUGIN_IDEN.shaded.adventure") + archiveFileName.set("$PLUGIN_NAME-$PLUGIN_VERS.jar") +} + +tasks.register("printVars") { + group = "help" + doLast { + println("VERS = " + PLUGIN_VERS) + println("AFCT = " + tasks.named("shadowJar").get().outputs.files.singleFile.name) + } +} + +fun getJSONObj(list: List): String { + return if (list.isNotEmpty()) { + list.joinToString( + separator = "\", \"", + prefix = "\"", + postfix = "\"" + ) + } else { + "" + } +} + +fun getJSONObjMerge(a: List, b: List): String { + val c = a.joinToString(", ") { "{\"id\":\"$it\",\"optional\":false}" } + val d = b.joinToString(", ") { "{\"id\":\"$it\",\"optional\":true}" } + return listOf(c, d).filter { it.isNotEmpty() }.joinToString(",") } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5eb6697 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs = -Xmx1G +org.gradle.parallel = true +org.gradle.problems.report = false \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c18b01baac1d36e4342cda310ec06cea44621c28 GIT binary patch literal 15408 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

A|D$}^~xlOlYH_=MswwhW0)vN0-DrL!-{ zGbHPV4BS-)nan8YM0eNEZgGVmPC2KGX%mskG)?5>kKAkkSLv1HY^fND8t5@Sjmsg^(bs8AOwx>M z+!YaXO`oHSi2w6X8vr-*i+JT~!>Nrj$!C%r{?uE)yu7^p z;otpT^xhv`fZI5Z?Z>Y}xc$F>@)L|29SZGUtB5y3>XFxzv3gGLU~%aY_4^L0PxVp4a;;vd?h> zE<{bpNCo-3f;xw0Ds<6_ke(bIaqOikP@tQvY9>l@{BB12vDuG zmpP$(!5N$nAwUK5J7&{)(WKWuDe53sWvcyHx&9$AbHnszjfsJaXf$A--*Q} zJsBiCru!u+9C8J6YaZ_t0-#?+9%olffrcOJM0_gOs%)z3l18 zA_9>r1L4|rkK;J5z2PUd1nP51HiAr-c0~K9@!$lqB$Is7I4U7cR5)_5`p0n#t{~AY zNI3}F)z^*X>&|g)v=&NyRDVx%{v?Js0@uHnvl% zRVuvI7pl=%?)`}z(s3NeH8#F0{z*|RDJCLQrOw}@_HeaN7ee?cYezgf)%C9oOx2t; znHGE;$8kvn&~^KDYgvt&BNK^T%6q!*mu^-(F$P`C@8FNh>vuB-9qd0sf#bMGyq|WU zDg>?OZ4zg!zPmFTi<C4N@ z=gB~{x`~e8ni47NR7N_q;}%Cckm7&+-tSr6M5QgKUaQ0(UF_&4?$Y6vWHYGV_xIz^ ze)-F*KDXaU-|&lH#NTn=K$N8FsDzjsR6-H*LcT1O>P~B%6~XbW-nM@K3Wa|v7ES9u zs$rVz-#aFn%40o?fj|Gj9j%+Sd(E+imz z=IVD!fRYNkkhS!Pp1v}?Y5d;ZV`{k7o?8-2(#3GjDA0*2bVSm#!2S+hCBs!ztvQ`o zOD5S&#;7MFu)hM@BQbdw&y%t0WbD`{JpcmTl|`Sjp~=I47FbBUb{Y~m;*NL*l_qlG z2sPG}XnZDPpwib48B-E4b(ZVbEOhaaDn55|lUChlYVW1J-zsKSn@l!gVz|-Em4RNHWX5uo?7BMLiyM&;@0#+=5NE~Kfh z>)Fe=$^9fTJ;|()7WwnfS;*i6qA)@{TGiCkEzm@S^pO^VTlAFer4rEY|H+&MXQbW0 z4JwNH6^?F=cZhm2&Fb7CbbU5u8j!|(ZpL3SFP$_mJSzeq)o||hWDcF$xK&%}wD}}? z*3WDVlCE0!x~^}APVVXq%xMDYJJ04kRhq6kKYnzBg)yz|r?V0jDsa_t=`8tM?EdG3m9>OODjAcNYpBxO-qo=zx4efFL`!lXLO8BTS7 zoK<&2R|)^1`UHqN)N$~*zk98VKwM*Ct9OXp~(*%0FmA5I0C9E(AB+nweXLyfJkEw&8w5_hmvL^ z3(=8wbb%4sssj=Dbxc}!64CImEKjB13OhzGfWsGSxp!4Jk zPLlAhT42ZKe?j}9dsd|7*P*{oO0O&hcA|sbqEL%RG0vBSt9#h|YQvGah_tz0Vt75NY?0uJoqLN_cWS}nACl!OF43W-IdxV8Vhp3X6Fo_un)K^GrJ)=RX(h_2i z5XDC*@VWXm;c(n@RB~Y|UA&}uNf$>t=F{aXyK%;QhWm-s7bS$;y-!HVt};xOqN;P) zl0EpU(0|>i<2=6G^qt}DvISjN?m{nSa`IDQi)V=~>Be%B=@^v{olY*Y&GoUB#V2CFIo_r8E=MO_*%Qo=3?A?W!hg(Z{Md^H3x z=?9=rg6z2}-ei* z{p#w^A+7EIpTGONrPuA^;XnU}f4DgU=p3jci!0bp1VuVDu>QP-N<$^8Fl%p~F4i=R zxV4ujee5*F=Ma;N<0Clf^HNINx@bXqlg{`reqn8}o01%K2< zkOh5@2bOl!DKGCd`VxfUj49P-NJd%{)!;pbV|!Di>WJ-529A>KU7=BjsL$WKBnyIc z0#eI2flzyh;iw!}y5Pv9pJe#z)`>fhs@K0oj{BPDqX=BW4X91tX>pL+jYV~oO$$3n z1z`2_)=|gTwAzVbBQp>#L3ENq9ZE?yxrRsF{4~vZk2RIH+$Z4}(Wv-t0LbQ7i{w(C z(3L&jYcbKGsZIAX+WpWD+(}F& zbKcUSmkQS2vo)9qo=$Fb#~TpccT(<|#P77tJ5i_Ivr*WR4!tHSA+I4BgOprsH33y@ zt4^%j%%mYA)fl+7rA}IRDFW(Z)vd(Vn&8<|*RS}Ry~6%IiIY0DXX;$2K<|{mJxwl# zRw@zMYL{8KHE}~^(7M;F3OsV{gcMToyffz2g8S=#{nxAB(_arZ-=F^Y$1PW+kjy?xqbEw!)qPm16LdFCEna7Jlr^Fj?Aq`@t)VBLij?|xrPl5Buw%2?_g7@9WfK>4-%-6Ymxth_;eWYyCU8nAAq7 zoc^O#_$3pHw<6427_cfVal8Pj!0oC}-DtNm1W4lGR2+n;16R!T9^%!cp=46?eFea)5g=>DHn@2&S(7Y?|&n=J=W5lD^?APjg)u-$8cO$1N3bPZ0g zl^A110`6UGwOUiO>asF=_gGVD^<^@>ne0o40+u8eRyRH+03A_v&w>pyv~<$)Og6Dv zG!--^MLmz^@l{PcRU+>CB5rjb($eXYb_@=2$%y{=kmLgQJQjYRI(#M?giUA zwH5bp)1fK^eK`+8i%9l9yu+bEHC&_HX!q0utQ_d13UD&{$8|(5wC=v+1Jsn+j*L){ zmd1Ctw&YIB&lxKs%Bc)6;UScWiM!`}^!jOdMU-`JWdtS&wj=&X7Rnj<4pl6UO--Q3 zwQkrgA6iGys8A>oG$CW0-5^Lhx%ZI=cv7V)g_`yvbJp<~G)_;@qN^MD!~qaOjMkxd z#RusGp}lkiIuCNGVkW0I;0z%}4UZE4EAbO$p!d}GPlj|TE`!vZtoNOo-qr2F6RARS z&`dS^bSF!o@vRX6Q9(-P;t@S}Wu1wkUBBjU{_WqcdXLiPo+8!) z^Zb<~QfrfNH`fjyCj@X(&FN}wx^=y%#j1$9&dRMVoNLZ-EyVYRo~f5g7HJ|2M%dmx z!+B!#lhCq@Y26&eTOpr{fJ+b>S$k>iLxnU@Ay9SWr(A%m^qWi*x~CyX9%^S`T_Kg) zM6TOwPeyg1{wQ>-3hS84gE#<#Zw*WTCl9Kvnw66=M;k@o0vCRQGiusmrSDVf|217ZnaFrk@(e^?BJ4VEtg6 z7Cza$mujTBRtP|wm)v>M)SmO;v|!{>HanSKtUinAS0v3f_cVGm1YNHY$End{p$j;f zimPU_lTqULdRWtj8k&xCy$?hZY)bs>5&Bcbo+P!k7pJndo!7nkhG9*02UWq^Ir3gBuz4P}_1?n8kdJiPtJZ&kj(CX{! z>wkp+U8`@pxX4CH`G8wiZ&A-$#a?O=(xMR62vo&^k2uCVA%R^(8rcYYxKAn_=bn9X zZyZuxzyFjB<=8sJW1@0<>xHQ{tGeE)9JT77_<#TMFIT_jw6_0`e)Bgo!wKfvrw+wc z_YwB|)*Ii2loh!*Ns+7jQmyU95p-Ld7TgIaQfDs}fCSd1C76#w;3Wo(&O}HjN|Wlo zJ@@_ql zFp8|3Ti)l+NReI|I$4N1q>AVJR0%kZmE5pH1`DJ%U9Fe4z<2wRlweoU!_v}i)&#`1uPO$fhtdXpi&F!?u)zO>BzLb zhXU1LN^0QK5^6F!j`p713i@|zUj2Q`-b)brCj{W23yJZlraQgJTS8?TOR2vlaaafI z_KX7PWGLdTCs`1ZIk-eC_cgha^|&_T04e$=8Ug3`epRN7@c;lDdPzhe&LZNGw>uLF)If!{=QHr30&=Xh{M z2omVt8Mh?t>74P)%YW7ZB&jKS5>syA-WxyB+$UXvdo^YWjP25Jou{Hsgxt%7Al-wi z)TP$6yN5Ib;=W@^JNi1-I?ZcOB(`typj;|*qC)$K1wSf;n-Yg^qdHwYHE~xVh&>3< zP!*#j40fRr`Q1V`DJchbCBY7gsQCHbDlR1TIhkg=Cok`QKSe{N_{qWDWF0xa=H8p1 z+*_K)q!oqTXM9fsrW)-oy`p;DlUQy~aerzgABEZP^!6sb3#566Y+lM05E0G2@kYe4 zx=f2AU+F#m5CS*|QZ)!ANn3ep3BJ%98IP{&&@V ziQ$-XD&Gr%=mP0HL`4+2286AtpIdMMsW&2I;3F6Sdfu)EpAJU(fv`cw!++ASR$+kHmT6R`JqSjD!}jKN+)05`IVvhQ7bxD)xHvR8@zVzKbf@0TPX9sV)#;E$9aS2tJ@GwImpJzu{Y0!xNd5Ymgn{I1)S zh-=<+K48*7M~1f=!z zhIEMQt;d6r(9n^4bQ~X@P<4LKPGo~pIN>Jiq@$2;-5E%8{_n@n|LLEudOp7%T>GE= z_HSp1Bj{ixKX=Ve6+~4YyQ}(hH7R?lAL;ghbFP;vL3c*BYzOYF4yj&pmYonD6nft> zUb}}ey2drAFwAwLsd;l1QXumt6k3{0knZ5~9tdZ*mWhlUy)T3i{SX3dVK@_>fLJ>+ zlyi>{@KFf9RNkzs%TJ`fC!tgIxzov0_nb7HkdSU3AVnH3Z1^x6u!_xSHJmDG>mU){3pKt)qh^0+GL~D*3lg`<=jh$o<_7eZ6WreF7 zomwS#(j@M_mni6?Zhk^)b^~21FF|J)BqA^AqNX9}o(Ie*72xT8Bci&>5G@KKT?(pK zi%ofvZh_v?QWI60MahNRcgKGXs09_vL_B;QbD0^RvjqedLoJ{-X{+^}KfwURh*i)w*#`-S^#cqy5=0e|go_80&7j}O7c5E78KSX1K9h5$}GEQx(5mU_#i&tHAsB~+mwU2&SKvcOG?CtdJ?3xs7Sksl!?DJR zymwN{n~-;I$PDS^d&J4g>N!;@avCl><6EJ|h3fcG80kF4WU%CMykHHybq)E%L!7^t zbe7RZqw3iwMBiR}|EFOA>4ll3b-NU8W!TYpaHJLlZ9PNw%LBh{sN zr_D#x#r;Un)rDpxZ8&k_i?hmI@knQlx2OR>gPQI&y#M*Vcb%4N@1xSXis-9)%N@5+ zIe;}XQN}10rg;Q3KsW7V8Zyz(bRptGr`oEMPN%!1*78UNzP7Pr_y6-AeG;Q4gFh7~ zL11e4j8)%=Lz#OI-5i&r?isDV?RYwcJ{D)Ol;!O_cH;G29Q5eZcdxaG+|(l4aysRE zC|8km_;nslDNH@8b8tNs4(&_~U}ZRMH()ZsRXy|y8WO_r$PUcYrbHCfg%9ih9Hdmu zJc;DH3l|=FU7VBUw0O$x1ybl$?YEO#I7H^_QfWlBK+`(oe83t7OJTC?yBsBqhHo z^doxn)uv1`5N?6ed-6=rI;m|Sqr|&P%89C#)rbtw@&xc+BA_hUKwECEQ)noZrVtr z2*7UfREtZgKUc3!q|)hRqOZ{Z(dj96&QnzoCjAobIeqU2?8qP*qY*XVErJElxHz^N z&P0Y=c^L;&-Jl62`$j)|dLJGg@ogQ1N)4&|{v|oFr0_t^0!(`az7qVWEwV1n>Wpd{ z_7NgD-N4U|_!JsbD36W_qwUdZA1~`K@1UWwRH~6-A_w)%hvGNt)oSQqjrcAgY) zxy{=Zc&S)6m4|=yXPU<4q|A! z&rp?~B&*XIby_X@td*YA^-NRcYm`k8b@eP-K@c~~?-Kp{R2<~lG68h6sIZ|Lurf*I z($-v+@>_?E&I=Ny+pih|P`R~k2x}U)Ey~+;AG&ozRn;$4w$PlJDv|mV}^i7Rucq$0Im-Bw`%Cx{ly++=kR6O>(XQNRi;A?}miJ z&Y&#mBw%H+)Fc;GZ%)ZGIyCeU5jl=)Z2TmicaxdA^-8r5JM``L!unbkV&xp1S44vg zA(9&SbTGnk9LJ@g|CU-6=P)U`M@I3bS(sB=@83b0iJqJD)?_TXo3(Zv$8kyMPeRZM zBBhe}#DONG#+Pb?O?uFk+!yUVSFqo8rX9y|+!7H`BhiJ%$~jU-|NLiCJXaNMEtS`H z(A&j5$%dyI=^Yo4z@+0ij;%<5DlNM(n5Z^)WT2DiSMOW@eyQF}i48igx+?VPRDy|U zWbz=6<2bGz8cf7a9F!-T4GJ;12BPmFK*z3=xWy^Rt~V?Zo}_6El>s=i0LO6+&)?Ov zTsoYf8#^7bR#oesRPBF~B2;6;)Mo9P8B~#vV)_s7e;n6{`N^PE$B`u&DyOPNWcrk` zdjI#yz0u{{ox-UaflQ3dr?KooT91FX^vxV2-0FTN5)mtoC$A0ouvA_>U%$N03FQk zggUxSNC!b&8fH~un9njs`Z%rw`-%5={8WXCld)*sp6WtvVw9?lt`PdNE)DFYsZ!!E z9eC+(Ru`%y!%Y>@au|rO<@t%iafgc6QB_O-IV9o*H=9q_}n5Ag&qfK zl3rz^8l=b#bXvx<<_g_p${ZrNBp@dBNg0NBZ(`rq$37h>Y{^VbTm>$ z${Zx%LH?y4w?n+^*Sl1s65=JHaJL`abz@CHM~*0%q&7PFnOrP^V4aaVIDmr$T(2|N z#i!N|Y1W~lyXsukXIJ~RN)f)kzWxUSaMnm&ynA2LJDqxBH>D_@UUVE^69G(P?u5f~ z`-rMrO_jOVPVf^4;Jp9ZdMJg`&Z?$E5R!*Ns-O<2e;iXoK{x<1IoUD%4v~`-rMiao z-Z`lpf5j|TF-_9>*IC45#F0zoB@FBN=l%SrKSj`s@&?KLzwUc!GnP6(`i#5c5j7J+ zy@o(s=OvTL!p_^KB=CedRvECVriR+k>u2rOPbmoE@|n_N>FO_O? zze_|IU8PuZ{+EE6D|59TBWJH!~0@PHd%L1s``udfKyj%LJWT55y^cb>#Az(Q6AViKs6J97#0$$ zSL^MABv^98s#;|SeYy)vVSEbJRo}HM8Fui#gQ=4urKCrKwnTT~m*OVW^PEoaefMzW zo-5;&lEU5nA|6(u8oDVJZ_%Npq$n>LnWwS>i`#^iGGn+8#}g)BWolBw(UtU8Q?XQNlcYR9JpVNuCIbr` zGN36rbtBd+CXMq`$m~>9RVO4}2$Za&15s3L>-d04x{-K9lFm$?MIs%yg)>O9Pqg5S zAVZp6A|$gb)Kh!&ORtT9+Jx06adBW5?`=^Ks@T`9UY0V!s_2LG zNVqkS3bl3(TM9W{;(n!RzqRvA8pV=ArKFr#h1%);>F)QbBWKnR%EiVm)~ilHNq3RO zK#dN0J(Uzoc!5ciQDwB~J#kzH6<|pi`-)UeP*M7v6OTP1C*6i2A&)B*a^FWnhlI20 zQv0v3ub&HnT&hAb%L&A%p0wK(Dn5UYYh61t4Re$VveHZFu9q%$)t5oxc;Y$N^#Bv% zkG!7DhD-=P$Dz3Mn85mEen4kXNWFzaa&Cl}D+6EFi;~P1bCAz{Ruc1dq*wx(w!-Xm z9wBMrbf4wOIik9hDAXj+{t`zj%SARv5{J8=lkAV!Aef2rO$A(==ApOfrP+H0L> zcIQIkf7R`nl4>+%s0>@`S*G46h0F?}q@k8WnFyQ0WL+Drn8sLN+ zb50~FZX$e{s@dqO{hY)Y;c&{~pwyC7*2)beP-DkvnlC9Ez62SR@C{R5fs#q?-gPA4 z8a}#mZd$%e!a$X#nxK<}fTn07d2R`Uq((}u^R(YXh3o6Tz5ni{ptkD{Q)FRfy~wz5 zvS3`U)sb>tUO!P4aw~qz3-R4bo z2D|TZg;l5j_{eJB@^$rb@Oi-n$mTuC1bN( zj6V$zu3>11ldA4ZLTGjlsw4YISgn$&3Kh8cOTklDUQ8yqsH(t8%7%KF zEuDs>YmUfGQDi}N1Cu!m9Wo?qgUD|ujv#^RNvJVt3r^2LA+b&-`~wL{S=KIIbS;ck zp1C6#RZD3q7D7CQ;w}<#3fiBZG}R>NK;5IRv5I(GrShY^r$RmiT9X)_q!S&nt30$5 zV@f71NSg07>e>O*^5}RJw4)sn=ejql=Q1q*fqFCr*e= zr7kmp+(|0Up}A>@vgO=e*rUdHQ=(Y)y(DBF2@{;8nETE}Szz^nou#WzO0_O37N4s8 zCMiBzxs^i0D%PdF>6Mjsg-D;ETv0mgJndxs^w!vVTE45E6|K(OVn}IokRnx8Pf0a| zHyII5GD4N3JSk<7w1Y~;)awP^Wf;`hWf%LCn(1T%xbJoknY1sb5IAWGt-OOv`=$3I zO?f3`UZTmU;$Zr{v==|g!qA$1w|A<~BfOHDLiAxU2#vi|3#j7~loeGssS_=kkdefY zIAW5j3?(FC>JdA=1ZQm0l$p~AsP17(jHYF}$-Uh=9m>V~j*WNglIUqDUW}ZV>Z(1t zFL&Oe%b~l}htoW(teul2S2YDmHCbx?yO?nTu_ud_WLy$~iu8VUdU=V8t|Ocs@>JZ> z#5i~8HR+U8NH1N3BzxA$mMPS`RD>Okznn6#Vko;Cq!?~z)zkcKf)r@nH6avfY?{QN zv~-#XPLP^-1=Z8eO5$V`(MwWyDg!_WOUFei;Y>rX2X}%UAxOnI(rH2}ZI-tCk~})8MR!>V zC)wsUQ(CsHkU@puoRM`!MZ_Re^pzMQVkD_TH6@s7CnD*1O=fJozyCya+#T~;@c=}( z-@&-fa5!d~_UjY=KoK`;(N0x_$+Zx8N|cP%gkYBwcSIN&nMTP&S{FhIP!cPXiAK)x zkqic@x#^Ul6Mm#ZXSNbhSu^)#m0YoklW57V^9a<#PS|HR;B%S=b8&7Gf{;vyW4e>k zWvT|jeKzGxh+!q??0ORvVsU&z7ZMPKW?Dg6AwaqYDs}C}l@WHOQPP0;;{Rv;J-Y&@hC=t_k!{(uD~ucdtu^rcAW+9U4nhVy@vQZTYQK zn@L7za_*N*wAXhuygxMqDKf+fNLJ{8 z4lh(tE0G}Uy4{F?=yc&b!O;XZCm8_3k&xVd(%_V)UR_MtYT01d# z4&Iabh|2IdL!^3fPHI!R3M*2>uB;!eY;vr;LMG`rB~lu!swF3$zT3=B8!jQPCh&M! zmI#5Va_v+)d^#SChG9w7pjx}`c~Qiu6q;QatoSK+uNHdqUHdK}$6UVN4Zu{S$~0D- z48ceXgsA$Fi=nHwW?Js*RDY8+8I^4##8gtZF$o*Ub+}Sw<)BpITf&Pd^g?Cx61DB` zSpdiOcVvaLZXM&=RYY~)wazxEo9vW`t3}sM;7Q;(dGM*orz2IX=g_5#(xxZ!_okCV|<6{7fC3bFx*DPKAIKD(|?kwR8KR2t-OO39(Qa2sf83on=lvADuIpOb_a6 z@rgdao9Ih7AK_aPV~{o}5iVvr)yR1(%B(BJXP3rxLq173s>r|!Y0_+jGESWVQN0%L aulfIAM#lj-7MW!L0000 r1) - return true; - } - return true; - } - - public static LoggerAdapter getLogger() { - if (adapter == null) - throw new IllegalStateException("Logger not initialized!"); - return adapter; - } - - public interface LoggerAdapter { - void info(String msg); - - void warn(String msg); - - void error(String msg); - } - - public static void handleConnection(IEaglercraftLoginEvent e) { - IEaglerLoginConnection conn = e.getLoginConnection(); - String origin = conn.getWebSocketHeader(EnumWebSocketHeader.HEADER_ORIGIN); - String brand = conn.getEaglerBrandString(); - String name = conn.getUsername(); - String ip = getAddr(conn); - String notAllowed1 = "not allowed on the server"; - String notAllowed2 = "not allowed"; - - if (origin != null && !origin.equals("null")) { - for (String origin1 : config.blacklist.origins) { - if (matches(origin, origin1)) { - setKick(e, formatKickMessage("origin", "website", notAllowed1, notAllowed2, origin, conn.getWebSocketHost())); - webhook(conn, origin, brand, "origin"); - return; - } - } - } - - if (brand != null && !brand.equals("null")) { - for (String brand1 : config.blacklist.brands) { - if (matches(brand, brand1)) { - setKick(e, formatKickMessage("brand", "client", notAllowed1, notAllowed2, brand, conn.getWebSocketHost())); - webhook(conn, origin, brand, "brand"); - return; - } - } - } - - if (name != null && !name.equals("null")) { - for (String name1 : config.blacklist.players) { - if (matches(name, name1) || (name.length() > 16 || name.length() < 3)) { - setKick(e, formatKickMessage("player", "username", notAllowed1, notAllowed2, name, conn.getWebSocketHost())); - webhook(conn, origin, brand, "player"); - return; - } - } - } - - if (ip != null && !ip.equalsIgnoreCase("null")) { - if (ipblacklist.check(ip)) { - setKick(e, formatKickMessage("ip address", "ip", notAllowed1, notAllowed2, ip, conn.getWebSocketHost())); - webhook(conn, origin, brand, "ip"); - } - } - } - - public static void setKick(IEaglercraftLoginEvent e, Component msg) { - try { - getLogger().info("Kicked " + e.getProfileUsername()); - e.setKickMessage(msg); - } catch (Throwable ignored) { - String msg1 = LegacyComponentSerializer.legacySection().serialize(msg); - e.setKickMessage(msg1); - } - } - - public static void handleMOTD(IEaglercraftMOTDEvent e) { - if (!config.messages.motd.enabled) return; - - IMOTDConnection conn = e.getMOTDConnection(); - String origin = conn.getWebSocketHeader(EnumWebSocketHeader.HEADER_ORIGIN); - String host = conn.getWebSocketHost() != null ? conn.getWebSocketHost() : ""; - String ip = getAddr(conn); - - String blocktype1 = null; - String easyblocktype1 = null; - String blocked1 = null; - - if (origin != null && !"null".equals(origin)) { - for (String origin2 : config.blacklist.origins) { - if (matches(origin, origin2)) { - blocktype1 = "origin"; - easyblocktype1 = "website"; - blocked1 = origin; - break; - } - } - } - - if (blocktype1 == null && ip != null && !"null".equalsIgnoreCase(ip)) { - boolean blocked = ipblacklist != null && ipblacklist.check(ip); - if (!blocked) { - for (String ip2 : config.blacklist.ips) { - if (matches(ip, ip2)) { blocked = true; break; } - } - } - if (blocked) { - blocktype1 = "ip address"; - easyblocktype1 = "ip"; - blocked1 = ip; - } - } - - if (blocktype1 == null) return; - - final String finalBlocktype = blocktype1; - final String finalEasyblocktype = easyblocktype1; - final String finalBlocked = blocked1; - - List m = List.of(config.messages.motd.text.split("\n")).stream() - .map(line -> line - .replace("%blocktype%", finalBlocktype) - .replace("%easyblocktype%", finalEasyblocktype) - .replace("%notallowed1%", "blacklisted") - .replace("%notallowed2%", "blacklisted") - .replace("%blocked%", finalBlocked) - .replace("%host%", host)) - .map(line -> LegacyComponentSerializer.legacySection() - .serialize(MiniMessage.miniMessage().deserialize(line))) - .collect(Collectors.toList()); - - setMOTD(conn, m); - } - - public static void setMOTD(IMOTDConnection conn, List m) { - conn.setServerMOTD(m); - conn.setPlayerTotal(0); - conn.setPlayerMax(0); - conn.setPlayerList(List.of()); - - if (config.messages.motd.icon != null && !config.messages.motd.icon.isEmpty()) { - try { - BufferedImage img = ImageIO.read(new File(config.messages.motd.icon)); - if (img.getWidth() != 64 || img.getHeight() != 64) { - getLogger().warn("Icon must be 64x64"); - return; - } - - byte[] bytes = new byte[64 * 64 * 4]; - for (int y = 0; y < 64; y++) { - for (int x = 0; x < 64; x++) { - int pixel = img.getRGB(x, y); - int i = (y * 64 + x) * 4; - bytes[i] = (byte) ((pixel >> 16) & 0xFF); - bytes[i + 1] = (byte) ((pixel >> 8) & 0xFF); - bytes[i + 2] = (byte) (pixel & 0xFF); - bytes[i + 3] = (byte) ((pixel >> 24) & 0xFF); - } - } - conn.setServerIcon(bytes); - } catch (IOException ex) { - getLogger().error(ex.toString()); - } - } - conn.sendToUser(); - conn.disconnect(); - } - - public static boolean matches(String text1, String text2) { - return text1.toLowerCase().matches(text2.replace(".", "\\.").replaceAll("\\*", ".*").toLowerCase()); - } - - public static Component formatKickMessage(String type, String easytype, String notAllowed1, String notAllowed2, String value, String host) { - String help = ""; - if ("player".equals(type)) { - help = config.messages.help.player; - } else if ("ip address".equals(type)) { - help = config.messages.help.ip; - } else { - help = config.messages.help.generic; - } - return MiniMessage.miniMessage().deserialize( - config.messages.kick - .replaceAll("%help%", help) - .replaceAll("%blocktype%", type) - .replaceAll("%easyblocktype%", easytype) - .replaceAll("%notallowed1%", notAllowed1) - .replaceAll("%notallowed2%", notAllowed2) - .replaceAll("%blocked%", value) - .replaceAll("%host%", host)); - } - - public static void webhook(IEaglerLoginConnection plr, String origin, String brand, String type) { - String webhook = config.discord.webhook; - if (webhook == null || webhook.isBlank()) - return; - - CompletableFuture.runAsync(() -> { - String addr = getAddr(plr); - int protocol = !plr.isEaglerXRewindPlayer() ? plr.getMinecraftProtocol() : plr.getRewindProtocolVersion(); - String host = plr.getWebSocketHost(); - String userAgent = plr.getWebSocketHeader(EnumWebSocketHeader.HEADER_USER_AGENT); - Boolean rewind = plr.isEaglerXRewindPlayer(); - if (userAgent == null || userAgent.isEmpty()) - userAgent = "undefined"; - - String payload = String.format( - """ - { - "content": "Blocked a blacklisted %s from joining", - "embeds": [ - { - "title": "Player Information", - "description": "🎮 **Name:** %s\\n🏠 **IP:** %s\\n🌄 **PVN:** %s\\n🌐 **Origin:** %s\\n🔋 **Brand:** %s\\n🪑 **Host:** %s\\n🧊 **UA:** %s\\n⏪ **Rewind:** %s" - } - ] - } - """, - type, plr.getUsername(), addr, protocol, origin, brand, plr.isWebSocketSecure() ? "wss://" : "ws://" + host, userAgent, rewind ? "Yes" : "No"); - - try { - HttpURLConnection conn = (HttpURLConnection) new URL(webhook).openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - try (OutputStream os = conn.getOutputStream()) { - os.write(payload.getBytes()); - } - - conn.getInputStream().close(); - } catch (Exception e) { - getLogger().warn("Failed to send webhook: " + e); - } - }); - } - - public static String getAddr(IEaglerLoginConnection conn) { - var addr1 = conn.getPlayerAddress() != null ? conn.getPlayerAddress().toString().substring(1) : "0.0.0.0:0"; - var addr2 = addr1.lastIndexOf(':') != -1 ? addr1.substring(0, addr1.lastIndexOf(':')) : addr1; - return addr2; - } - - public static String getAddr(IMOTDConnection conn) { - var addr1 = conn.getSocketAddress() != null ? conn.getSocketAddress().toString().substring(1) : "0.0.0.0:0"; - var addr2 = addr1.lastIndexOf(':') != -1 ? addr1.substring(0, addr1.lastIndexOf(':')) : addr1; - return addr2; - } - - public static void init() { - File motdIcon = new File(config.messages.motd.icon); - if (!motdIcon.exists()) { - try (InputStream in = ConfigManager.class.getResourceAsStream("/server-blocked.png")) { - if (in != null) { - Files.copy(in, motdIcon.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } catch (IOException e) { - getLogger().warn(e.toString()); - } - } - ipblacklist = new IPBlacklist(); - } - - public static void reloadConfig() { - config = ConfigManager.loadConfig(adapter); - } -} diff --git a/src/main/java/dev/colbster937/originblacklist/base/Command.java b/src/main/java/dev/colbster937/originblacklist/base/Command.java deleted file mode 100644 index f275ce0..0000000 --- a/src/main/java/dev/colbster937/originblacklist/base/Command.java +++ /dev/null @@ -1,173 +0,0 @@ -package dev.colbster937.originblacklist.base; - -import org.yaml.snakeyaml.Yaml; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import static dev.colbster937.originblacklist.base.Base.config; - -public class Command { - private static final String permission = "You do not have permission to use this command."; - - public interface CommandContext { - String getName(); - void reply(String message); - boolean hasPermission(String permission); - String[] getArgs(); - } - - public static void usage(CommandContext ctx) { - ctx.reply("Commands:"); - ctx.reply(" - /originblacklist reload"); - ctx.reply(" - /originblacklist add "); - ctx.reply(" - /originblacklist remove "); - ctx.reply(" - /originblacklist list"); - } - - public static void handle(CommandContext ctx) { - String[] args = ctx.getArgs(); - if (!ctx.hasPermission("originblacklist.command")) { - ctx.reply(permission); - return; - } else if (args.length == 0) { - usage(ctx); - return; - } - - String sub = args[0].toLowerCase(); - String sub1 = args.length > 1 ? args[1].toLowerCase() : ""; - String sub2 = args.length > 2 ? args[2].toLowerCase() : ""; - - switch (sub) { - case "reload" -> { - if (ctx.hasPermission("originblacklist.reload")) { - Base.reloadConfig(); - ctx.reply("Reloaded."); - } else { - ctx.reply(permission); - return; - } - } - - case "add" -> { - if (!ctx.hasPermission("originblacklist.add")) { - ctx.reply(permission); - return; - } - - if (sub1.isEmpty() || sub2.isEmpty()) { - usage(ctx); - return; - } - - List list = switch (sub1) { - case "brand" -> Base.config.blacklist.brands; - case "origin" -> Base.config.blacklist.origins; - case "player" -> Base.config.blacklist.players; - case "ip" -> Base.config.blacklist.ips; - default -> null; - }; - - if (list == null) { - usage(ctx); - return; - } - - if (!list.contains(sub2)) { - list.add(sub2); - ctx.reply("Added " + sub2 + " to " + sub1 + " blacklist."); - } else { - ctx.reply("Already blacklisted."); - } - try { - config.saveConfig(Base.config.toMap(), new File("plugins/originblacklist/config.yml")); - } catch (IOException e) { - throw new RuntimeException(e); - } - Base.reloadConfig(); - } - - case "remove" -> { - if (!ctx.hasPermission("originblacklist.remove")) { - ctx.reply(permission); - return; - } - - if (sub1.isEmpty() || sub2.isEmpty()) { - usage(ctx); - return; - } - - List list = switch (sub1) { - case "brand" -> Base.config.blacklist.brands; - case "origin" -> Base.config.blacklist.origins; - case "player" -> Base.config.blacklist.players; - case "ip" -> Base.config.blacklist.ips; - default -> null; - }; - - if (list == null) { - usage(ctx); - return; - } - - if (list.remove(sub2)) { - ctx.reply("Removed " + sub2 + " from " + sub1 + " blacklist."); - } else { - ctx.reply("Entry not found in " + sub1 + "."); - } - try { - config.saveConfig(Base.config.toMap(), new File("plugins/originblacklist/config.yml")); - } catch (IOException e) { - throw new RuntimeException(e); - } - Base.reloadConfig(); - } - - case "list" -> { - if (!ctx.hasPermission("originblacklist.view")) { - ctx.reply(permission); - return; - } - - ctx.reply("Blacklist:"); - - ctx.reply(" Brands:"); - for (String s : Base.config.blacklist.brands) ctx.reply(" - " + s + ""); - - ctx.reply(" Origins:"); - for (String s : Base.config.blacklist.origins) ctx.reply(" - " + s + ""); - - ctx.reply(" Players:"); - for (String s : Base.config.blacklist.players) ctx.reply(" - " + s + ""); - - ctx.reply(" IPs:"); - for (String s : Base.config.blacklist.ips) ctx.reply(" - " + s + ""); - } - - default -> usage(ctx); - } - } - - public static List suggest(CommandContext ctx) { - String[] args = ctx.getArgs(); - - if (args.length == 1) { - return List.of("reload", "add", "remove", "list"); - } - - if (args.length == 2 && args[0].equalsIgnoreCase("add")) { - return List.of("brand", "origin", "player", "ip"); - } - - return List.of(); - } - - public Map toMap() { - Yaml yaml = new Yaml(); - return yaml.load(yaml.dump(this)); - } -} diff --git a/src/main/java/dev/colbster937/originblacklist/base/ConfigManager.java b/src/main/java/dev/colbster937/originblacklist/base/ConfigManager.java deleted file mode 100644 index f9d548d..0000000 --- a/src/main/java/dev/colbster937/originblacklist/base/ConfigManager.java +++ /dev/null @@ -1,140 +0,0 @@ -package dev.colbster937.originblacklist.base; - -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.DumperOptions; -import java.io.*; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; -import inet.ipaddr.IPAddress; - -public class ConfigManager { - public Messages messages = new Messages(); - //public List subscriptions = List.of(); - public Blacklist blacklist = new Blacklist(); - public Discord discord = new Discord(); - - public static ConfigManager loadConfig(Base.LoggerAdapter logger) { - File f = new File("plugins/originblacklist/config.yml"); - - try { - if (!f.exists()) { - f.getParentFile().mkdirs(); - try (InputStream in = ConfigManager.class.getResourceAsStream("/config.yml")) { - if (in != null) Files.copy(in, f.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - - Constructor constructor = new Constructor(ConfigManager.class, new LoaderOptions()); - constructor.setPropertyUtils(new org.yaml.snakeyaml.introspector.PropertyUtils() {{ - setSkipMissingProperties(true); - }}); - Yaml y = new Yaml(constructor); - ConfigManager l = null; - - try (InputStream in = new FileInputStream(f)) { - l = y.load(in); - } catch (Exception ex) { - logger.warn("Error loading config: " + ex.getMessage()); - } - - if (l == null) { - l = new ConfigManager(); - } - - try { - Yaml raw = new Yaml(); - Map u = raw.load(new FileInputStream(f)); - Map d = raw.load(ConfigManager.class.getResourceAsStream("/config.yml")); - if (mergeConfig(u, d)) saveConfig(u, f); - } catch (Exception ex) { - logger.warn("YAML merge error: " + ex.getMessage()); - } - - l.blacklist.resolveIPS(logger); - - return l; - } catch (IOException e) { - return new ConfigManager(); - } - } - - @SuppressWarnings("unchecked") - private static boolean mergeConfig(Map u, Map d) { - boolean c = false; - for (String k : d.keySet()) { - if (!u.containsKey(k)) { - u.put(k, d.get(k)); - c = true; - } else if (u.get(k) instanceof Map && d.get(k) instanceof Map) - c |= mergeConfig((Map) u.get(k), (Map) d.get(k)); - } - return c; - } - - public static void saveConfig(Map m, File f) throws IOException { - DumperOptions o = new DumperOptions(); - o.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - o.setPrettyFlow(true); - new Yaml(o).dump(m, new FileWriter(f)); - } - - public Map toMap() { - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - options.setPrettyFlow(true); - options.setAllowReadOnlyProperties(true); - - Yaml yaml = new Yaml(options); - String yaml1 = yaml.dumpAsMap(this); - Yaml parser = new Yaml(); - Object yaml2 = parser.load(yaml1); - - return (Map) yaml2; - } - - public static class Blacklist { - public List origins; - public List brands; - public List players; - public List ips = List.of(); - public transient Set ips1 = new CopyOnWriteArraySet<>(); - public boolean missing_origin; - //public String blacklist_redirect; - - public void resolveIPS(Base.LoggerAdapter logger) { - for (String line : ips) { - try { - ips1.add(new inet.ipaddr.IPAddressString(line).toAddress()); - } catch (Throwable ignored) {} - } - } - } - - public static class Discord { - public String webhook; - } - - public static class Messages { - public String kick; - public MOTD motd; - public Help help; - } - - public static class MOTD { - public boolean enabled; - public String text; - public String icon; - } - - public static class Help { - public String generic; - public String player; - public String ip; - } -} diff --git a/src/main/java/dev/colbster937/originblacklist/base/IPBlacklist.java b/src/main/java/dev/colbster937/originblacklist/base/IPBlacklist.java deleted file mode 100644 index 72cb01b..0000000 --- a/src/main/java/dev/colbster937/originblacklist/base/IPBlacklist.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.colbster937.originblacklist.base; - -import java.util.logging.Logger; - -import inet.ipaddr.AddressStringException; -import inet.ipaddr.IPAddress; -import inet.ipaddr.IPAddressString; - -import static dev.colbster937.originblacklist.base.Base.config; - -public class IPBlacklist { - private Logger logger = null; - - public IPBlacklist() { - this.logger = logger; - } - - public boolean check(String addr) { - IPAddress ip; - String addr1 = addr; - try { - if (addr.startsWith("/")) { - addr1 = addr.substring(1); - } - if (addr1.startsWith("[") && addr1.endsWith("]")) { - addr1 = addr1.substring(1, addr1.length() - 1); - } - ip = new IPAddressString(addr1).toAddress(); - } catch (AddressStringException e) { - throw new RuntimeException("Invalid IP address: " + addr, e); - } - - return config.blacklist.ips1.stream().anyMatch(s -> { - try { - return s.contains(ip); - } catch (Exception e) { - return false; - } - }); - } -} diff --git a/src/main/java/dev/colbster937/originblacklist/bukkit/CommandBukkit.java b/src/main/java/dev/colbster937/originblacklist/bukkit/CommandBukkit.java deleted file mode 100644 index 6af3221..0000000 --- a/src/main/java/dev/colbster937/originblacklist/bukkit/CommandBukkit.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.colbster937.originblacklist.bukkit; - -import dev.colbster937.originblacklist.base.Command.CommandContext; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; - -public class CommandBukkit implements CommandExecutor { - - @Override - public boolean onCommand(CommandSender sender, org.bukkit.command.Command command, String label, String[] args) { - dev.colbster937.originblacklist.base.Command.handle(new CommandContext() { - @Override - public String getName() { - return sender.getName(); - } - - @Override - public void reply(String msg) { - sender.sendMessage(LegacyComponentSerializer.legacySection() - .serialize(MiniMessage.miniMessage().deserialize(msg))); - } - - @Override - public boolean hasPermission(String permission) { - return sender.hasPermission(permission); - } - - @Override - public String[] getArgs() { - return args; - } - }); - return true; - } -} \ No newline at end of file diff --git a/src/main/java/dev/colbster937/originblacklist/bukkit/OriginBlacklistBukkit.java b/src/main/java/dev/colbster937/originblacklist/bukkit/OriginBlacklistBukkit.java deleted file mode 100644 index f3051a9..0000000 --- a/src/main/java/dev/colbster937/originblacklist/bukkit/OriginBlacklistBukkit.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.colbster937.originblacklist.bukkit; - -import dev.colbster937.originblacklist.base.Base; -import net.lax1dude.eaglercraft.backend.server.api.bukkit.EaglerXServerAPI; -import net.lax1dude.eaglercraft.backend.server.api.bukkit.event.EaglercraftLoginEvent; -import net.lax1dude.eaglercraft.backend.server.api.bukkit.event.EaglercraftMOTDEvent; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.Plugin; - -public class OriginBlacklistBukkit extends JavaPlugin implements Listener { - - @Override - public void onEnable() { - Plugin plugin = getServer().getPluginManager().getPlugin("EaglercraftXServer"); - if (plugin != null) { - String version = plugin.getDescription().getVersion(); - if (!Base.checkVer(version, Base.pluginVer)) { - getLogger().severe("EaglerXServer " + Base.pluginVer + " is required!"); - throw new RuntimeException("Incompatible plugin version"); - } - } else { - throw new RuntimeException("Missing EaglerXServer"); - } - - - Base.setLogger(new Base.LoggerAdapter() { - @Override public void info(String msg) { getLogger().info(msg); } - @Override public void warn(String msg) { getLogger().warning(msg); } - @Override public void error(String msg) { getLogger().severe(msg); } - }); - - Base.setApi(EaglerXServerAPI.instance()); - Base.reloadConfig(); - Base.init(); - - getCommand("originblacklist").setExecutor(new CommandBukkit()); - getServer().getPluginManager().registerEvents(this, this); - - getLogger().info("Loaded Bukkit plugin"); - } - - @EventHandler - public void onLogin(EaglercraftLoginEvent event) { - Base.handleConnection(event); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onMOTD(EaglercraftMOTDEvent event) { - Base.handleMOTD(event); - } -} diff --git a/src/main/java/dev/colbster937/originblacklist/bungee/CommandBungee.java b/src/main/java/dev/colbster937/originblacklist/bungee/CommandBungee.java deleted file mode 100644 index 05da43e..0000000 --- a/src/main/java/dev/colbster937/originblacklist/bungee/CommandBungee.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.colbster937.originblacklist.bungee; - -import dev.colbster937.originblacklist.base.Command.CommandContext; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.chat.TextComponent; - -public class CommandBungee extends net.md_5.bungee.api.plugin.Command { - - public CommandBungee() { - super("originblacklist", "originblacklist.use"); - } - - @Override - public void execute(CommandSender sender, String[] args) { - dev.colbster937.originblacklist.base.Command.handle(new CommandContext() { - public String getName() { - return sender.getName(); - } - - public void reply(String msg) { - sender.sendMessage(TextComponent.fromLegacyText(msg)); - } - - public boolean hasPermission(String permission) { - return sender.hasPermission(permission); - } - - public String[] getArgs() { - return args; - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/dev/colbster937/originblacklist/bungee/OriginBlacklistBungee.java b/src/main/java/dev/colbster937/originblacklist/bungee/OriginBlacklistBungee.java deleted file mode 100644 index 113888a..0000000 --- a/src/main/java/dev/colbster937/originblacklist/bungee/OriginBlacklistBungee.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.colbster937.originblacklist.bungee; - -import dev.colbster937.originblacklist.base.Base; -import net.lax1dude.eaglercraft.backend.server.api.bungee.EaglerXServerAPI; -import net.lax1dude.eaglercraft.backend.server.api.bungee.event.EaglercraftLoginEvent; -import net.lax1dude.eaglercraft.backend.server.api.bungee.event.EaglercraftMOTDEvent; -import net.md_5.bungee.api.event.PreLoginEvent; -import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -public class OriginBlacklistBungee extends Plugin implements Listener { - - @Override - public void onEnable() { - Plugin plugin = getProxy().getPluginManager().getPlugin("EaglercraftXServer"); - if (plugin != null) { - String version = plugin.getDescription().getVersion(); - if (!Base.checkVer(version, Base.pluginVer)) { - getLogger().severe("EaglerXServer " + Base.pluginVer + " is required!"); - throw new RuntimeException("Incompatible plugin version"); - } - } else { - throw new RuntimeException("Missing EaglerXServer"); - } - - - Base.setLogger(new Base.LoggerAdapter() { - @Override public void info(String msg) { getLogger().info(msg); } - @Override public void warn(String msg) { getLogger().warning(msg); } - @Override public void error(String msg) { getLogger().severe(msg); } - }); - - Base.setApi(EaglerXServerAPI.instance()); - Base.reloadConfig(); - Base.init(); - - getProxy().getPluginManager().registerCommand(this, new CommandBungee()); - getProxy().getPluginManager().registerListener(this, this); - - getLogger().info("Loaded Bungee plugin"); - } - - @EventHandler - public void onLogin(EaglercraftLoginEvent event) { - Base.handleConnection(event); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onMOTD(EaglercraftMOTDEvent event) { - Base.handleMOTD(event); - } -} diff --git a/src/main/java/dev/colbster937/originblacklist/velocity/CommandVelocity.java b/src/main/java/dev/colbster937/originblacklist/velocity/CommandVelocity.java deleted file mode 100644 index a6a29b6..0000000 --- a/src/main/java/dev/colbster937/originblacklist/velocity/CommandVelocity.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.colbster937.originblacklist.velocity; - -import com.velocitypowered.api.command.SimpleCommand; -import dev.colbster937.originblacklist.base.Command; -import dev.colbster937.originblacklist.base.Command.CommandContext; -import net.kyori.adventure.text.minimessage.MiniMessage; - -import java.util.List; - -public class CommandVelocity implements SimpleCommand { - - @Override - public void execute(Invocation invocation) { - Command.handle(new VelocityCommandContext(invocation)); - } - - @Override - public List suggest(Invocation invocation) { - return Command.suggest(new VelocityCommandContext(invocation)); - } - - public static class VelocityCommandContext implements CommandContext { - private final Invocation invocation; - - public VelocityCommandContext(Invocation invocation) { - this.invocation = invocation; - } - - @Override - public String getName() { - return invocation.source().toString(); - } - - @Override - public void reply(String message) { - invocation.source().sendMessage(MiniMessage.miniMessage().deserialize(message)); - } - - @Override - public boolean hasPermission(String permission) { - return invocation.source().hasPermission(permission); - } - - @Override - public String[] getArgs() { - return invocation.arguments(); - } - } -} \ No newline at end of file diff --git a/src/main/java/dev/colbster937/originblacklist/velocity/OriginBlacklistVelocity.java b/src/main/java/dev/colbster937/originblacklist/velocity/OriginBlacklistVelocity.java deleted file mode 100644 index 9414625..0000000 --- a/src/main/java/dev/colbster937/originblacklist/velocity/OriginBlacklistVelocity.java +++ /dev/null @@ -1,59 +0,0 @@ -package dev.colbster937.originblacklist.velocity; - -import com.google.inject.Inject; -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.connection.PreLoginEvent; -import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.proxy.ProxyServer; -import dev.colbster937.originblacklist.base.Base; -import net.kyori.adventure.text.Component; -import net.lax1dude.eaglercraft.backend.server.api.velocity.EaglerXServerAPI; -import net.lax1dude.eaglercraft.backend.server.api.velocity.event.EaglercraftLoginEvent; -import net.lax1dude.eaglercraft.backend.server.api.velocity.event.EaglercraftMOTDEvent; -import java.net.InetAddress; -import org.slf4j.Logger; - -public class OriginBlacklistVelocity { - - private final ProxyServer proxy; - private final Base.LoggerAdapter logger; - - @Inject - public OriginBlacklistVelocity(ProxyServer proxy1, Logger logger1) { - this.proxy = proxy1; - this.logger = new Base.LoggerAdapter() { - @Override public void info(String msg) { logger1.info(msg); } - @Override public void warn(String msg) { logger1.warn(msg); } - @Override public void error(String msg) { logger1.error(msg); } - }; - Base.setLogger(this.logger); - } - - @Subscribe - public void onProxyInitialization(ProxyInitializeEvent event) { - proxy.getPluginManager().getPlugin("eaglerxserver").ifPresentOrElse(plugin -> { - if (!Base.checkVer(plugin.getDescription().getVersion().orElse("1.0.0"), Base.pluginVer)) { - logger.error("EaglerXServer " + Base.pluginVer + " is required!"); - throw new RuntimeException("Incompatible plugin version"); - } - }, () -> { - throw new RuntimeException("Missing EaglerXServer"); - }); - Base.setApi(EaglerXServerAPI.instance()); - Base.reloadConfig(); - Base.init(); - proxy.getCommandManager().register("originblacklist", new CommandVelocity()); - logger.info("Loaded Velocity plugin"); - } - - @Subscribe - public void onLogin(EaglercraftLoginEvent event) { - Base.handleConnection(event); - } - - @Subscribe(order = PostOrder.LAST) - public void onMOTD(EaglercraftMOTDEvent event) { - Base.handleMOTD(event); - } -} diff --git a/src/main/java/xyz/webmc/originblacklist/base/OriginBlacklist.java b/src/main/java/xyz/webmc/originblacklist/base/OriginBlacklist.java new file mode 100644 index 0000000..1911ee7 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/OriginBlacklist.java @@ -0,0 +1,213 @@ +package xyz.webmc.originblacklist.base; + +import xyz.webmc.originblacklist.base.config.OriginBlacklistConfig; +import xyz.webmc.originblacklist.base.enums.EnumBlacklistType; +import xyz.webmc.originblacklist.base.enums.EnumLogLevel; +import xyz.webmc.originblacklist.base.events.OriginBlacklistLoginEvent; +import xyz.webmc.originblacklist.base.events.OriginBlacklistMOTDEvent; +import xyz.webmc.originblacklist.base.util.IOriginBlacklistPlugin; +import xyz.webmc.originblacklist.base.util.OPlayer; +import xyz.webmc.originblacklist.base.util.UpdateChecker; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.semver4j.Semver; + +import de.marhali.json5.Json5Array; +import de.marhali.json5.Json5Element; + +import inet.ipaddr.AddressStringException; +import inet.ipaddr.IPAddressString; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.lax1dude.eaglercraft.backend.server.api.query.IMOTDConnection; + +public final class OriginBlacklist { + public static final Semver REQUIRED_API_VER = new Semver("1.0.2"); + public static final String GENERIC_STR = "generic"; + public static final String UNKNOWN_STR = "unknown"; + public static final String PLUGIN_REPO = "WebMCDevelopment/originblacklist"; + public static final int BSTATS_ID = 28776; + + private final IOriginBlacklistPlugin plugin; + private final OriginBlacklistConfig config; + private boolean updateAvailable; + + public OriginBlacklist(final IOriginBlacklistPlugin plugin) { + this.plugin = plugin; + this.config = new OriginBlacklistConfig(plugin); + this.checkForUpdate(); + plugin.scheduleRepeat(() -> { + this.checkForUpdate(); + }, 60, TimeUnit.MINUTES); + } + + public final void handleLogin(final OriginBlacklistLoginEvent event) { + final OPlayer player = event.getPlayer(); + final EnumBlacklistType blacklisted = this.testBlacklist(player); + if (blacklisted != EnumBlacklistType.NONE) { + final String blacklisted_value; + if (blacklisted == EnumBlacklistType.ORIGIN) { + blacklisted_value = player.getOrigin(); + } else if (blacklisted == EnumBlacklistType.BRAND) { + blacklisted_value = player.getBrand(); + } else if (blacklisted == EnumBlacklistType.NAME) { + blacklisted_value = player.getName(); + } else if (blacklisted == EnumBlacklistType.ADDR) { + blacklisted_value = player.getAddr(); + } else { + blacklisted_value = UNKNOWN_STR; + } + this.plugin.kickPlayer(this.getBlacklistedComponent("kick", blacklisted.getArrayString(), + blacklisted.getAltString(), blacklisted.getString(), "not allowed", "not allowed on the server", + blacklisted_value, blacklisted.getActionString()), event); + final String name = player.getName(); + if (isNonNull(name)) { + this.plugin.log(EnumLogLevel.INFO, "Prevented blacklisted player " + name + " from joining."); + } + } + } + + public final void handleMOTD(final OriginBlacklistMOTDEvent event) { + final OPlayer player = event.getPlayer(); + final EnumBlacklistType blacklisted = this.testBlacklist(player); + if (blacklisted != EnumBlacklistType.NONE) { + final String blacklisted_value; + if (blacklisted == EnumBlacklistType.ORIGIN) { + blacklisted_value = player.getOrigin(); + } else if (blacklisted == EnumBlacklistType.ADDR) { + blacklisted_value = player.getAddr(); + } else { + blacklisted_value = UNKNOWN_STR; + } + this.plugin.setMOTD(this.getBlacklistedComponent("motd", blacklisted.getArrayString(), blacklisted.getAltString(), + blacklisted.getString(), "blacklisted", "blacklisted from the server", blacklisted_value, + blacklisted.getActionString()), event); + } + } + + public final boolean isDebugEnabled() { + return this.config.get("debug").getAsBoolean(); + } + + public final boolean isMetricsEnabled() { + return this.config.get("bStats").getAsBoolean(); + } + + public final OriginBlacklistConfig getConfig() { + return this.config; + } + + public final void setEaglerMOTD(final Component comp, final OriginBlacklistMOTDEvent event) { + final IMOTDConnection conn = event.getEaglerEvent().getMOTDConnection(); + final List lst = new ArrayList<>(); + for (String ln : getComponentString(comp).split("\n")) { + lst.add(ln); + } + conn.setServerMOTD(lst); + conn.setPlayerTotal(0); + conn.setPlayerUnlimited(); + conn.setPlayerList(List.of()); + conn.setServerIcon(this.config.getIconBytes()); + conn.sendToUser(); + conn.disconnect(); + } + + private final EnumBlacklistType testBlacklist(final OPlayer player) { + final String name = player.getName(); + final String addr = player.getAddr(); + final String origin = player.getOrigin(); + final String brand = player.getBrand(); + + if (isNonNull(origin)) { + for (final Json5Element element : this.config.get("blacklist.origins").getAsJson5Array()) { + if (origin.matches(element.getAsString())) { + return EnumBlacklistType.ORIGIN; + } + } + } + + if (isNonNull(brand)) { + for (final Json5Element element : this.config.get("blacklist.brands").getAsJson5Array()) { + if (brand.matches(element.getAsString())) { + return EnumBlacklistType.BRAND; + } + } + } + + if (isNonNull(name)) { + for (final Json5Element element : this.config.get("blacklist.player_names").getAsJson5Array()) { + this.plugin.log(EnumLogLevel.DEBUG, element.getAsString()); + if (name.matches(element.getAsString())) { + return EnumBlacklistType.NAME; + } + } + } + + if (isNonNull(addr)) { + for (final Json5Element element : this.config.get("blacklist.ip_addresses").getAsJson5Array()) { + try { + if ((new IPAddressString(element.getAsString()).toAddress()) + .contains((new IPAddressString(addr)).toAddress())) { + return EnumBlacklistType.ADDR; + } + } catch (final AddressStringException exception) { + exception.printStackTrace(); + } + } + } + + return EnumBlacklistType.NONE; + } + + private final Component getBlacklistedComponent(final String type, final String id, final String blockType, + final String blockTypeAlt, final String notAllowed, final String notAllowedAlt, final String blockValue, + final String action) { + final Json5Array arr = this.config.get("messages." + type).getAsJson5Array(); + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.size(); i++) { + if (i > 0) + sb.append("\n"); + sb.append(arr.get(i).getAsString()); + } + final String str = sb.toString() + .replaceAll("%action%", this.config.get("messages.actions." + action).getAsString()) + .replaceAll("%block_type%", blockType) + .replaceAll("%block_type%", blockType) + .replaceAll("%not_allowed%", notAllowed) + .replaceAll("%not_allowed_alt%", notAllowedAlt) + .replaceAll("%blocked_value%", blockValue); + return MiniMessage.miniMessage().deserialize(str); + } + + private final void checkForUpdate() { + (new Thread(() -> { + this.updateAvailable = UpdateChecker.checkForUpdate(PLUGIN_REPO, this.plugin.getPluginVersion(), + this.config.get("update_checker.allow_snapshots").getAsBoolean()); + if (this.updateAvailable) { + this.plugin.log(EnumLogLevel.INFO, "Update Available! Download at https://github.com/" + PLUGIN_REPO + ".git"); + } + })).run(); + } + + public static final String getComponentString(final Component comp) { + return LegacyComponentSerializer.legacySection().serialize(comp); + } + + public static final String getLegacyFromMiniMessage(final String str) { + return getComponentString(MiniMessage.miniMessage().deserialize(str)); + } + + public static final String getPNGBase64FromBytes(final byte[] bytes) { + return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes); + } + + public static final boolean isNonNull(final String str) { + return str != null && !str.isEmpty() && !str.isBlank() && !str.equals("null"); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/command/CommandContext.java b/src/main/java/xyz/webmc/originblacklist/base/command/CommandContext.java new file mode 100644 index 0000000..d9f1a03 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/command/CommandContext.java @@ -0,0 +1,8 @@ +package xyz.webmc.originblacklist.base.command; + +public interface CommandContext { + String getPlayerName(); + void reply(final String message); + boolean hasPermission(final String permission); + String[] getArgs(); +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/command/ICommand.java b/src/main/java/xyz/webmc/originblacklist/base/command/ICommand.java new file mode 100644 index 0000000..5791ab7 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/command/ICommand.java @@ -0,0 +1,10 @@ +package xyz.webmc.originblacklist.base.command; + +import java.util.List; + +public interface ICommand { + static final String NO_PERMISSION = "You don't have permission to use this command."; + boolean execute(final CommandContext ctx); + List suggest(final CommandContext ctx); + void usage(final CommandContext ctx); +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/command/OriginBlacklistCommand.java b/src/main/java/xyz/webmc/originblacklist/base/command/OriginBlacklistCommand.java new file mode 100644 index 0000000..b08318b --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/command/OriginBlacklistCommand.java @@ -0,0 +1,76 @@ +package xyz.webmc.originblacklist.base.command; + +import xyz.webmc.originblacklist.base.OriginBlacklist; + +import java.util.List; + +import de.marhali.json5.Json5Element; + +public class OriginBlacklistCommand implements ICommand { + private final OriginBlacklist plugin; + + public OriginBlacklistCommand(OriginBlacklist plugin) { + this.plugin = plugin; + } + + @Override + public boolean execute(final CommandContext ctx) { + final String[] args = ctx.getArgs(); + if (ctx.hasPermission("originblacklist.command")) { + if (args.length > 0) { + final String command = args[0].toLowerCase(); + if ("reload".equals(command)) { + if (ctx.hasPermission("originblacklist.command.reload")) { + this.plugin.getConfig().reloadConfig(); + ctx.reply("Configuration Reloaded"); + } else { + ctx.reply(NO_PERMISSION); + } + } else if ("list".equals(command)) { + if (ctx.hasPermission("originblacklist.command.reload")) { + ctx.reply("Blacklist:"); + ctx.reply(" - Origins:"); + for (final Json5Element element : this.plugin.getConfig().get("blacklist.origins").getAsJson5Array()) { + ctx.reply(" - " + element.getAsString() + ""); + } + ctx.reply(" - Brands:"); + for (final Json5Element element : this.plugin.getConfig().get("blacklist.brands").getAsJson5Array()) { + ctx.reply(" - " + element.getAsString() + ""); + } + ctx.reply(" - Players:"); + for (final Json5Element element : this.plugin.getConfig().get("blacklist.player_names").getAsJson5Array()) { + ctx.reply(" - " + element.getAsString() + ""); + } + ctx.reply(" - IPs:"); + for (final Json5Element element : this.plugin.getConfig().get("blacklist.ip_addresses").getAsJson5Array()) { + ctx.reply(" - " + element.getAsString() + ""); + } + } else { + ctx.reply(NO_PERMISSION); + } + } else { + this.usage(ctx); + } + } else { + this.usage(ctx); + } + } else { + ctx.reply(""); + } + return true; + } + + @Override + public List suggest(final CommandContext ctx) { + return List.of(); + } + + @Override + public void usage(CommandContext ctx) { + ctx.reply("Commands:"); + ctx.reply(" - /originblacklist reload"); + //ctx.reply(" - /originblacklist add "); + //ctx.reply(" - /originblacklist remove "); + ctx.reply(" - /originblacklist list"); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/config/OriginBlacklistConfig.java b/src/main/java/xyz/webmc/originblacklist/base/config/OriginBlacklistConfig.java new file mode 100644 index 0000000..03407e6 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/config/OriginBlacklistConfig.java @@ -0,0 +1,257 @@ +package xyz.webmc.originblacklist.base.config; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.util.IOriginBlacklistPlugin; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import javax.imageio.ImageIO; + +import de.marhali.json5.Json5; +import de.marhali.json5.Json5Array; +import de.marhali.json5.Json5Element; +import de.marhali.json5.Json5Object; +import de.marhali.json5.Json5Primitive; + +public final class OriginBlacklistConfig { + private final Json5 json5; + private final File file; + private final Path filePath; + private final File iconFile; + private final Path iconPath; + private Json5Object config; + private byte[] icon; + private String icon64; + + public OriginBlacklistConfig(IOriginBlacklistPlugin plugin) { + this.json5 = Json5.builder(builder -> builder + .quoteless() + .quoteSingle() + .parseComments() + .writeComments() + .prettyPrinting() + .build()); + final String dir = "plugins/" + plugin.getPluginId(); + this.file = new File(dir + "/config.json5"); + this.filePath = file.toPath(); + this.iconFile = new File(dir + "/blacklisted.png"); + this.iconPath = iconFile.toPath(); + this.loadConfig(); + } + + private final void loadConfig() { + try { + this.reloadConfigUnsafe(); + } catch (final IOException exception) { + throw new RuntimeException("Failed to load config.", exception); + } + this.reloadIconImage(); + } + + public final void reloadConfig() { + try { + this.reloadConfigUnsafe(); + } catch (final IOException exception) { + exception.printStackTrace(); + } + this.reloadIconImage(); + } + + private final void reloadConfigUnsafe() throws IOException { + if (this.file.exists()) { + String text = Files.readString(this.file.toPath(), StandardCharsets.UTF_8); + Json5Element parsed = this.json5.parse(text); + if (parsed instanceof Json5Object) { + this.config = (Json5Object) parsed; + if (merge(this.config, getDefaultConfig())) { + this.saveConfig(); + } + } else { + throw new IOException("Config must be an object!"); + } + } else { + this.config = getDefaultConfig(); + this.saveConfig(); + } + } + + private final void reloadIconImage() { + try { + if (!this.iconFile.exists()) { + this.iconFile.getParentFile().mkdirs(); + final InputStream in = OriginBlacklist.class.getResourceAsStream("/blacklisted.png"); + Files.copy(in, iconPath, StandardCopyOption.REPLACE_EXISTING); + in.close(); + } + + final BufferedImage img = ImageIO.read(iconFile); + + if (img.getWidth() == 64 && img.getHeight() == 64) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(img, "png", baos); + this.icon64 = OriginBlacklist.getPNGBase64FromBytes(baos.toByteArray()); + byte[] bytes = new byte[64 * 64 * 4]; + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 64; x++) { + int pixel = img.getRGB(x, y); + int i = (y * 64 + x) * 4; + bytes[i] = (byte) ((pixel >> 16) & 0xFF); + bytes[i + 1] = (byte) ((pixel >> 8) & 0xFF); + bytes[i + 2] = (byte) (pixel & 0xFF); + bytes[i + 3] = (byte) ((pixel >> 24) & 0xFF); + } + this.icon = bytes; + } + } else { + throw new IOException("Icon must be 64x64!"); + } + } catch (final IOException exception) { + exception.printStackTrace(); + } + } + + public final void saveConfig() { + try { + this.file.getParentFile().mkdirs(); + Files.write(this.filePath, this.json5.serialize(this.config).getBytes(StandardCharsets.UTF_8)); + } catch (final IOException exception) { + exception.printStackTrace(); + } + } + + public final Json5Element get(final String key) { + Json5Element element = null; + + if (this.config != null && OriginBlacklist.isNonNull(key)) { + element = this.config; + final String[] parts = key.split("\\."); + + for (final String part : parts) { + if (element instanceof Json5Object) { + final Json5Object obj = (Json5Object) element; + if (obj.has(part)) { + element = obj.get(part); + } else { + element = null; + } + } else { + element = null; + } + + if (element == null) { + break; + } + } + } + + return element; + } + + public final byte[] getIconBytes() { + return this.icon; + } + + public final String getIconBase64URI() { + return this.icon64; + } + + private static final Json5Object getDefaultConfig() { + final Json5Object obj = new Json5Object(); + addJSONObj(obj, "debug", Json5Primitive.fromBoolean(false), null); + final Json5Object mobj = new Json5Object(); + final Json5Array kick = new Json5Array(); + kick.add("This %block_type% is %not_allowed_alt%!"); + kick.add("» %blocked_value% «"); + kick.add(""); + kick.add("%action%"); + kick.add(""); + kick.add("Think this is a mistake? Join our discord:"); + kick.add("discord.gg/changethisintheconfig"); + addJSONObj(mobj, "kick", kick, null); + final Json5Array motd = new Json5Array(); + motd.add("This %block_type% is %not_allowed%!"); + motd.add("» %blocked_value%"); + addJSONObj(mobj, "motd", motd, null); + final Json5Object actions = new Json5Object(); + actions.add("generic", Json5Primitive.fromString("Please switch to a different %block_type%.")); + actions.add("player_name", Json5Primitive.fromString("Please change your %block_type%.")); + actions.add("ip_address", Json5Primitive.fromString("Please contact staff for assistance.")); + addJSONObj(mobj, "actions", actions, null); + addJSONObj(obj, "messages", mobj, null); + final Json5Object bobj = new Json5Object(); + final Json5Array origins = new Json5Array(); + origins.add(".*eaglerhackedclients\\.vercel\\.app.*"); + origins.add(".*eaglerhacks\\.github\\.io.*"); + origins.add(".*mcproject\\.vercel\\.app.*"); + origins.add(".*wurst-b2\\.vercel\\.app.*"); + origins.add(".*flqmedev\\.github\\.io.*"); + origins.add(".*wurst2\\.vercel\\.app.*"); + origins.add(".*dhyeybg7\\.vercel\\.app.*"); + origins.add(".*uec\\.vercel\\.app.*"); + origins.add(".*valux-game\\.github\\.io.*"); + origins.add(".*project516\\.dev.*"); + addJSONObj(bobj, "origins", origins, null); + final Json5Array brands = new Json5Array(); + brands.add(".*dragonx.*"); + brands.add(".*piclient.*"); + brands.add(".*justin.*"); + brands.add(".*wurstx.*"); + brands.add(".*moonlight.*"); + addJSONObj(bobj, "brands", brands, null); + final Json5Array players = new Json5Array(); + players.add("Admin"); + addJSONObj(bobj, "player_names", players, null); + final Json5Array ips = new Json5Array(); + ips.add("192.0.2.0/24"); + addJSONObj(bobj, "ip_addresses", ips, null); + addJSONObj(obj, "blacklist", bobj, null); + final Json5Object dobj = new Json5Object(); + addJSONObj(dobj, "enabled", Json5Primitive.fromBoolean(false), null); + addJSONObj(dobj, "webhook_urls", new Json5Array(), null); + addJSONObj(obj, "discord", dobj, null); + final Json5Object uobj = new Json5Object(); + addJSONObj(uobj, "enabled", Json5Primitive.fromBoolean(true), null); + addJSONObj(uobj, "allow_snapshots", Json5Primitive.fromBoolean(false), null); + addJSONObj(uobj, "auto_update", Json5Primitive.fromBoolean(false), null); + addJSONObj(obj, "update_checker", uobj, null); + addJSONObj(obj, "bStats", Json5Primitive.fromBoolean(true), null); + return obj; + } + + private static final boolean merge(final Json5Object a, final Json5Object b) { + boolean changed = false; + + for (String key : b.keySet()) { + Json5Element element = b.get(key); + if (!a.has(key)) { + a.add(key, element.deepCopy()); + changed = true; + } else { + final Json5Element _element = a.get(key); + if (_element instanceof Json5Object objA && element instanceof Json5Object objB) { + if (merge(objA, objB)) { + changed = true; + } + } + } + } + + return changed; + } + + private static final void addJSONObj(final Json5Object obj, final String key, final Json5Element value, + final String comment) { + if (OriginBlacklist.isNonNull(comment)) { + value.setComment(comment); + } + obj.add(key, value); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/enums/EnumBlacklistType.java b/src/main/java/xyz/webmc/originblacklist/base/enums/EnumBlacklistType.java new file mode 100644 index 0000000..1342572 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/enums/EnumBlacklistType.java @@ -0,0 +1,39 @@ +package xyz.webmc.originblacklist.base.enums; + +import xyz.webmc.originblacklist.base.OriginBlacklist; + +public enum EnumBlacklistType { + ORIGIN("origin", "website", "origins", null), + BRAND("brand", "client", "brands", null), + NAME("name", "username", "player_names", "player_name"), + ADDR("addr", "ip address", "ip_addresses", "ip_address"), + NONE(null, null, null, null); + + private final String str; + private final String alt; + private final String arr; + private final String act; + + private EnumBlacklistType(final String str, final String alt, final String arr, final String act) { + this.str = str; + this.alt = alt; + this.arr = arr; + this.act = OriginBlacklist.isNonNull(act) ? act : OriginBlacklist.GENERIC_STR; + } + + public final String getString() { + return this.str; + } + + public final String getAltString() { + return this.alt; + } + + public final String getArrayString() { + return this.arr; + } + + public final String getActionString() { + return this.act; + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/enums/EnumConnectionType.java b/src/main/java/xyz/webmc/originblacklist/base/enums/EnumConnectionType.java new file mode 100644 index 0000000..f647c41 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/enums/EnumConnectionType.java @@ -0,0 +1,6 @@ +package xyz.webmc.originblacklist.base.enums; + +public enum EnumConnectionType { + JAVA, + EAGLER +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/enums/EnumLogLevel.java b/src/main/java/xyz/webmc/originblacklist/base/enums/EnumLogLevel.java new file mode 100644 index 0000000..5a57d13 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/enums/EnumLogLevel.java @@ -0,0 +1,8 @@ +package xyz.webmc.originblacklist.base.enums; + +public enum EnumLogLevel { + INFO, + WARN, + ERROR, + DEBUG +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistEvent.java b/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistEvent.java new file mode 100644 index 0000000..4053fc1 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistEvent.java @@ -0,0 +1,36 @@ +package xyz.webmc.originblacklist.base.events; + +import xyz.webmc.originblacklist.base.enums.EnumConnectionType; +import xyz.webmc.originblacklist.base.util.OPlayer; + +import net.lax1dude.eaglercraft.backend.server.api.event.IBaseServerEvent; + +public abstract class OriginBlacklistEvent { + private final EnumConnectionType connectionType; + private final IBaseServerEvent eaglerEvent; + private final Object javaEvent; + private final OPlayer player; + + protected OriginBlacklistEvent(final IBaseServerEvent eaglerEvent, final Object javaEvent, final EnumConnectionType connectionType, final OPlayer player) { + this.eaglerEvent = eaglerEvent; + this.javaEvent = javaEvent; + this.connectionType = connectionType; + this.player = player; + } + + protected IBaseServerEvent getEaglerEvent() { + return this.eaglerEvent; + } + + public final Object getJavaEvent() { + return this.javaEvent; + } + + public final EnumConnectionType getConnectionType() { + return this.connectionType; + } + + public final OPlayer getPlayer() { + return this.player; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistLoginEvent.java b/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistLoginEvent.java new file mode 100644 index 0000000..548cb7e --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistLoginEvent.java @@ -0,0 +1,18 @@ +package xyz.webmc.originblacklist.base.events; + +import xyz.webmc.originblacklist.base.enums.EnumConnectionType; +import xyz.webmc.originblacklist.base.util.OPlayer; + +import net.lax1dude.eaglercraft.backend.server.api.event.IEaglercraftLoginEvent; + +public final class OriginBlacklistLoginEvent extends OriginBlacklistEvent { + public OriginBlacklistLoginEvent(final IEaglercraftLoginEvent eaglerEvent, final Object javaEvent, + final EnumConnectionType connectionType, final OPlayer player) { + super(eaglerEvent, javaEvent, connectionType, player); + } + + @Override + public final IEaglercraftLoginEvent getEaglerEvent() { + return (IEaglercraftLoginEvent) super.getEaglerEvent(); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistMOTDEvent.java b/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistMOTDEvent.java new file mode 100644 index 0000000..0f6e51a --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/events/OriginBlacklistMOTDEvent.java @@ -0,0 +1,18 @@ +package xyz.webmc.originblacklist.base.events; + +import xyz.webmc.originblacklist.base.enums.EnumConnectionType; +import xyz.webmc.originblacklist.base.util.OPlayer; + +import net.lax1dude.eaglercraft.backend.server.api.event.IEaglercraftMOTDEvent; + +public final class OriginBlacklistMOTDEvent extends OriginBlacklistEvent { + public OriginBlacklistMOTDEvent(final IEaglercraftMOTDEvent eaglerEvent, final Object javaEvent, + final EnumConnectionType connectionType, final OPlayer player) { + super(eaglerEvent, javaEvent, connectionType, player); + } + + @Override + public final IEaglercraftMOTDEvent getEaglerEvent() { + return (IEaglercraftMOTDEvent) super.getEaglerEvent(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/webmc/originblacklist/base/util/ChatFormat.java b/src/main/java/xyz/webmc/originblacklist/base/util/ChatFormat.java new file mode 100644 index 0000000..3b45208 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/util/ChatFormat.java @@ -0,0 +1,29 @@ +package xyz.webmc.originblacklist.base.util; + +public final class ChatFormat { + private static final String SYMBOL = "§"; + + public static final String BLACK = SYMBOL + "0"; + public static final String DARK_BLUE = SYMBOL + "1"; + public static final String DARK_GREEN = SYMBOL + "2"; + public static final String CYAN = SYMBOL + "3"; + public static final String DARK_RED = SYMBOL + "4"; + public static final String DARK_PURPLE = SYMBOL + "5"; + public static final String GOLD = SYMBOL + "6"; + public static final String GRAY = SYMBOL + "7"; + public static final String DARK_GRAY = SYMBOL + "8"; + public static final String BLUE = SYMBOL + "9"; + public static final String GREEN = SYMBOL + "a"; + public static final String AQUA = SYMBOL + "b"; + public static final String RED = SYMBOL + "c"; + public static final String LIGHT_PURPLE = SYMBOL + "d"; + public static final String YELLOW = SYMBOL + "e"; + public static final String WHITE = SYMBOL + "f"; + + public static final String OBFUSCATED = SYMBOL + "k"; + public static final String BOLD = SYMBOL + "l"; + public static final String STRIKETHROUGH = SYMBOL + "m"; + public static final String UNDERLINE = SYMBOL + "n"; + public static final String ITALIC = SYMBOL + "o"; + public static final String RESET = SYMBOL + "r"; +} \ No newline at end of file diff --git a/src/main/java/xyz/webmc/originblacklist/base/util/IOriginBlacklistPlugin.java b/src/main/java/xyz/webmc/originblacklist/base/util/IOriginBlacklistPlugin.java new file mode 100644 index 0000000..f15d729 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/util/IOriginBlacklistPlugin.java @@ -0,0 +1,22 @@ +package xyz.webmc.originblacklist.base.util; + +import xyz.webmc.originblacklist.base.enums.EnumLogLevel; +import xyz.webmc.originblacklist.base.events.OriginBlacklistLoginEvent; +import xyz.webmc.originblacklist.base.events.OriginBlacklistMOTDEvent; + +import java.util.concurrent.TimeUnit; + +import org.semver4j.Semver; + +import net.kyori.adventure.text.Component; + +public interface IOriginBlacklistPlugin { + public String getPluginId(); + public Semver getPluginVersion(); + public void log(final EnumLogLevel level, final String txt); + public void kickPlayer(final Component txt, final OriginBlacklistLoginEvent event); + public void setMOTD(final Component txt, final OriginBlacklistMOTDEvent event); + public String parsePlaceholders(final OPlayer player, final String str); + public void scheduleRepeat(final Runnable task, final int period, final TimeUnit unit); + public void shutdown(); +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/util/IncompatibleDependencyException.java b/src/main/java/xyz/webmc/originblacklist/base/util/IncompatibleDependencyException.java new file mode 100644 index 0000000..f1d07e6 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/util/IncompatibleDependencyException.java @@ -0,0 +1,14 @@ +package xyz.webmc.originblacklist.base.util; + +import org.semver4j.Semver; + +public final class IncompatibleDependencyException extends RuntimeException { + public IncompatibleDependencyException(final String name, final Semver requiredVersion, final Semver currentVersion) { + super("Incompatible version of " + name + " is present! Required " + requiredVersion + ", but found " + + currentVersion + "."); + } + + public IncompatibleDependencyException(final String name) { + super("Missing dependency " + name + "!"); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/util/OPlayer.java b/src/main/java/xyz/webmc/originblacklist/base/util/OPlayer.java new file mode 100644 index 0000000..f82c1c6 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/util/OPlayer.java @@ -0,0 +1,100 @@ +package xyz.webmc.originblacklist.base.util; + +import xyz.webmc.originblacklist.base.OriginBlacklist; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.UUID; + +import net.lax1dude.eaglercraft.backend.server.api.EnumWebSocketHeader; +import net.lax1dude.eaglercraft.backend.server.api.IEaglerConnection; +import net.lax1dude.eaglercraft.backend.server.api.IEaglerLoginConnection; + +public final class OPlayer { + private final String origin; + private final String addr; + private final String name; + private final UUID uuid; + private final String brand; + + public OPlayer(final IEaglerConnection conn, final String name, final UUID uuid, final String addr, + final String brand) { + this.name = name; + this.uuid = uuid; + if (conn != null) { + this.origin = conn.getWebSocketHeader(EnumWebSocketHeader.HEADER_ORIGIN); + this.addr = formatSocketAddress(conn.getSocketAddress()); + if (conn instanceof IEaglerLoginConnection) { + this.brand = ((IEaglerLoginConnection) conn).getEaglerBrandString(); + } else { + this.brand = OriginBlacklist.UNKNOWN_STR; + } + } else { + this.origin = OriginBlacklist.UNKNOWN_STR; + this.addr = formatIPAddress(addr); + this.brand = brand; + } + } + + public OPlayer(final IEaglerConnection conn, final String name, final UUID uuid) { + this(conn, name, uuid, null, null); + } + + public final String getOrigin() { + return this.origin; + } + + public final String getAddr() { + return this.addr; + } + + public final String getName() { + return this.name; + } + + public final UUID getUUID() { + return this.uuid; + } + + public final String getBrand() { + return this.brand; + } + + private static final String formatIPAddress(String addr) { + if (addr.startsWith("/")) { + addr = addr.substring(1); + } + + int i = addr.lastIndexOf('/'); + if (i != -1) { + addr = addr.substring(i + 1); + } + + if (addr.startsWith("[")) { + i = addr.indexOf(']'); + if (i != -1) + return addr.substring(1, i); + return addr.substring(1); + } + + i = addr.lastIndexOf(':'); + if (i != -1) { + addr = addr.substring(0, i); + } + + return addr; + } + + private static final String formatSocketAddress(final SocketAddress saddr) { + if (saddr instanceof InetSocketAddress) { + final InetSocketAddress isa = (InetSocketAddress) saddr; + if (isa.getAddress() != null) { + return isa.getAddress().getHostAddress(); + } else { + return isa.getHostString(); + } + } else { + return formatIPAddress(saddr.toString()); + } + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/base/util/UpdateChecker.java b/src/main/java/xyz/webmc/originblacklist/base/util/UpdateChecker.java new file mode 100644 index 0000000..f11ad5f --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/base/util/UpdateChecker.java @@ -0,0 +1,47 @@ +package xyz.webmc.originblacklist.base.util; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.semver4j.Semver; + +import de.marhali.json5.Json5; +import de.marhali.json5.Json5Array; +import de.marhali.json5.Json5Element; +import de.marhali.json5.Json5Object; + +public class UpdateChecker { + private static final Json5 json5 = Json5.builder(builder -> builder.build()); + + public static final boolean checkForUpdate(final String repo, final Semver currentVersion, final boolean allowPreRelease) { + try { + final URL url = new URL("https://api.github.com/repos/" + repo + "/releases"); + final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + Json5Element element = json5.parse(reader); + if (element instanceof Json5Array) { + final Json5Array arr = element.getAsJson5Array(); + if (arr.size() > 0) { + element = arr.get(0); + if (element instanceof Json5Object) { + final Json5Object obj = element.getAsJson5Object(); + final String tag = obj.get("tag_name").getAsString(); + final Semver ver = new Semver(tag.substring(1)); + if (ver.isGreaterThan(currentVersion)) { + return true; + } + } + } + } + return false; + } catch (final Throwable t) { + t.printStackTrace(); + return false; + } + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/bukkit/OriginBlacklistBukkit.java b/src/main/java/xyz/webmc/originblacklist/bukkit/OriginBlacklistBukkit.java new file mode 100644 index 0000000..855b1f9 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/bukkit/OriginBlacklistBukkit.java @@ -0,0 +1,235 @@ +package xyz.webmc.originblacklist.bukkit; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.enums.EnumConnectionType; +import xyz.webmc.originblacklist.base.enums.EnumLogLevel; +import xyz.webmc.originblacklist.base.events.OriginBlacklistLoginEvent; +import xyz.webmc.originblacklist.base.events.OriginBlacklistMOTDEvent; +import xyz.webmc.originblacklist.base.util.OPlayer; +import xyz.webmc.originblacklist.base.util.IOriginBlacklistPlugin; +import xyz.webmc.originblacklist.base.util.IncompatibleDependencyException; +import xyz.webmc.originblacklist.bukkit.command.OriginBlacklistCommandBukkit; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.semver4j.Semver; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.AdvancedPie; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.CachedServerIcon; + +import net.kyori.adventure.text.Component; +import net.lax1dude.eaglercraft.backend.server.api.IEaglerXServerAPI; +import net.lax1dude.eaglercraft.backend.server.api.bukkit.EaglerXServerAPI; +import net.lax1dude.eaglercraft.backend.server.api.bukkit.event.EaglercraftLoginEvent; +import net.lax1dude.eaglercraft.backend.server.api.bukkit.event.EaglercraftMOTDEvent; + +public final class OriginBlacklistBukkit extends JavaPlugin implements Listener, IOriginBlacklistPlugin { + private boolean papiPlaceholdersEnabled; + private Object papi; + private OriginBlacklist blacklist; + private IEaglerXServerAPI eaglerAPI; + private Metrics metrics; + + private CachedServerIcon iconCache; + + @Override + public final void onEnable() { + final Plugin eagx = this.getServer().getPluginManager().getPlugin("EaglercraftXServer"); + if (eagx == null) { + throw new IncompatibleDependencyException("EaglercraftXServer"); + } else { + final Semver version = new Semver(eagx.getDescription().getVersion()); + if (version.isLowerThan(OriginBlacklist.REQUIRED_API_VER)) { + throw new IncompatibleDependencyException("EaglerXServer", OriginBlacklist.REQUIRED_API_VER, version); + } + } + this.papiPlaceholdersEnabled = this.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null; + if (this.papiPlaceholdersEnabled) { + try { + this.papi = Class.forName("me.clip.placeholderapi.PlaceholderAPI"); + } catch (final Throwable t) { + this.papi = null; + this.papiPlaceholdersEnabled = false; + } + } else { + this.papi = null; + } + this.blacklist = new OriginBlacklist(this); + this.eaglerAPI = EaglerXServerAPI.instance(); + this.getCommand("originblacklist").setExecutor(new OriginBlacklistCommandBukkit(this.blacklist)); + this.getServer().getPluginManager().registerEvents(this, this); + this.log(EnumLogLevel.INFO, "Initialized Plugin"); + if (this.blacklist.isMetricsEnabled()) { + this.metrics = new Metrics(this, OriginBlacklist.BSTATS_ID); + this.metrics.addCustomChart(new AdvancedPie("player_types", () -> { + final Map playerMap = new HashMap<>(); + + for (final Player player : Bukkit.getOnlinePlayers()) { + final boolean eagler = eaglerAPI.isEaglerPlayerByUUID(player.getUniqueId()); + final String key = eagler ? "Eagler" : "Java"; + playerMap.put(key, playerMap.getOrDefault(key, 0) + 1); + } + + return playerMap; + })); + } + } + + @EventHandler(priority = EventPriority.NORMAL) + public final void onEaglerLogin(final EaglercraftLoginEvent event) { + final OPlayer player = new OPlayer(event.getLoginConnection(), event.getProfileUsername(), event.getProfileUUID()); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(event, null, EnumConnectionType.EAGLER, player)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onEaglerMOTD(final EaglercraftMOTDEvent event) { + final OPlayer player = new OPlayer(event.getMOTDConnection(), null, null); + this.blacklist.handleMOTD(new OriginBlacklistMOTDEvent(event, null, EnumConnectionType.EAGLER, player)); + } + + @EventHandler(priority = EventPriority.LOWEST) + public final void onJavaLogin(final AsyncPlayerPreLoginEvent event) { + final OPlayer player = new OPlayer(null, event.getName(), event.getUniqueId(), + event.getAddress() != null ? event.getAddress().toString() : null, null); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(null, event, EnumConnectionType.JAVA, player)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onJavaMOTD(final ServerListPingEvent event) { + final OPlayer player = new OPlayer(null, null, null, + event.getAddress() != null ? event.getAddress().toString() : null, null); + this.blacklist.handleMOTD(new OriginBlacklistMOTDEvent(null, event, EnumConnectionType.JAVA, player)); + } + + @Override + public final String getPluginId() { + return this.getDescription().getName(); + } + + @Override + public final Semver getPluginVersion() { + return new Semver(this.getDescription().getVersion()); + } + + @Override + public final void log(final EnumLogLevel level, final String txt) { + if (level == EnumLogLevel.WARN) { + this.getLogger().warning(txt); + } else if (level == EnumLogLevel.ERROR) { + this.getLogger().severe(txt); + } else if (level == EnumLogLevel.DEBUG) { + if (this.blacklist != null && this.blacklist.isDebugEnabled()) { + this.getLogger().info(txt); + } + } else { + this.getLogger().info(txt); + } + } + + @Override + public final void kickPlayer(final Component comp, final OriginBlacklistLoginEvent event) { + if (event.getConnectionType() == EnumConnectionType.EAGLER) { + event.getEaglerEvent().setKickMessage(OriginBlacklist.getComponentString(comp)); + } else { + final Object javaEvent = event.getJavaEvent(); + final String msg = OriginBlacklist.getComponentString(comp); + if (javaEvent instanceof AsyncPlayerPreLoginEvent pre) { + pre.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, msg); + } else if (javaEvent instanceof PlayerJoinEvent join) { + join.getPlayer().kickPlayer(msg); + } + } + } + + @Override + public final void setMOTD(final Component comp, final OriginBlacklistMOTDEvent event) { + if (event.getConnectionType() == EnumConnectionType.EAGLER) { + this.blacklist.setEaglerMOTD(comp, event); + } else { + final ServerListPingEvent javaEvent = (ServerListPingEvent) event.getJavaEvent(); + javaEvent.setMotd(OriginBlacklist.getComponentString(comp)); + javaEvent.setMaxPlayers(0); + final CachedServerIcon icon = this.loadIcon(); + if (icon != null) { + try { + javaEvent.setServerIcon(icon); + } catch (final Throwable t) { + } + } + } + } + + private final CachedServerIcon loadIcon() { + if (this.iconCache != null) + return this.iconCache; + final String uri = this.blacklist.getConfig().getIconBase64URI(); + if (uri == null || uri.isEmpty()) + return null; + try { + String b64 = uri; + final int i = b64.indexOf("base64,"); + if (i != -1) + b64 = b64.substring(i + "base64,".length()); + final byte[] png = Base64.getDecoder().decode(b64); + final BufferedImage img = javax.imageio.ImageIO.read(new ByteArrayInputStream(png)); + if (img != null) { + try { + this.iconCache = Bukkit.loadServerIcon(img); + } catch (final Throwable t) { + return null; + } + } else { + return null; + } + + return this.iconCache; + } catch (final Throwable t) { + return null; + } + } + + @Override + public final String parsePlaceholders(final OPlayer player, final String txt) { + if (this.papiPlaceholdersEnabled) { + try { + final UUID uuid = player.getUUID(); + final Player bp = uuid != null ? (Player) Bukkit.getPlayer(uuid) : null; + if (bp != null) { + return (String) ((Class) this.papi) + .getMethod("setPlaceholders", org.bukkit.entity.Player.class, String.class).invoke(null, bp, txt); + } + } catch (final Throwable t) { + } + } + return txt; + } + + @Override + public final void scheduleRepeat(final Runnable task, final int period, final TimeUnit unit) { + long ms = unit.toMillis((long) period); + long ticks = Math.max(1L, ms / 50L); + Bukkit.getScheduler().runTaskTimer(this, task, ticks, ticks); + } + + @Override + public final void shutdown() { + this.metrics.shutdown(); + Bukkit.getScheduler().cancelTasks(this); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/bukkit/command/BKTCommandContext.java b/src/main/java/xyz/webmc/originblacklist/bukkit/command/BKTCommandContext.java new file mode 100644 index 0000000..e265898 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/bukkit/command/BKTCommandContext.java @@ -0,0 +1,36 @@ +package xyz.webmc.originblacklist.bukkit.command; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.command.CommandContext; + +import org.bukkit.command.CommandSender; + +public class BKTCommandContext implements CommandContext { + private final CommandSender sender; + private final String[] args; + + public BKTCommandContext(final CommandSender sender, final String[] args) { + this.sender = sender; + this.args = args; + } + + @Override + public String getPlayerName() { + return this.sender.getName(); + } + + @Override + public void reply(final String message) { + this.sender.sendMessage(OriginBlacklist.getLegacyFromMiniMessage(message)); + } + + @Override + public boolean hasPermission(final String permission) { + return this.sender.hasPermission(permission); + } + + @Override + public String[] getArgs() { + return this.args; + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/bukkit/command/OriginBlacklistCommandBukkit.java b/src/main/java/xyz/webmc/originblacklist/bukkit/command/OriginBlacklistCommandBukkit.java new file mode 100644 index 0000000..7077c53 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/bukkit/command/OriginBlacklistCommandBukkit.java @@ -0,0 +1,26 @@ +package xyz.webmc.originblacklist.bukkit.command; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.command.OriginBlacklistCommand; + +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; + +public class OriginBlacklistCommandBukkit extends OriginBlacklistCommand implements TabExecutor { + public OriginBlacklistCommandBukkit(OriginBlacklist plugin) { + super(plugin); + } + + @Override + public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args) { + return super.execute(new BKTCommandContext(sender, args)); + } + + @Override + public List onTabComplete(final CommandSender sender, final Command command, final String label, final String[] args) { + return super.suggest(new BKTCommandContext(sender, args)); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/bungee/OriginBlacklistBungee.java b/src/main/java/xyz/webmc/originblacklist/bungee/OriginBlacklistBungee.java new file mode 100644 index 0000000..852dca4 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/bungee/OriginBlacklistBungee.java @@ -0,0 +1,200 @@ +package xyz.webmc.originblacklist.bungee; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.enums.EnumConnectionType; +import xyz.webmc.originblacklist.base.enums.EnumLogLevel; +import xyz.webmc.originblacklist.base.events.OriginBlacklistLoginEvent; +import xyz.webmc.originblacklist.base.events.OriginBlacklistMOTDEvent; +import xyz.webmc.originblacklist.base.util.IOriginBlacklistPlugin; +import xyz.webmc.originblacklist.base.util.IncompatibleDependencyException; +import xyz.webmc.originblacklist.base.util.OPlayer; +import xyz.webmc.originblacklist.bungee.command.OriginBlacklistCommandBungee; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.bstats.bungeecord.Metrics; +import org.bstats.charts.AdvancedPie; +import org.semver4j.Semver; + +import net.kyori.adventure.text.Component; +import net.lax1dude.eaglercraft.backend.server.api.IEaglerXServerAPI; +import net.lax1dude.eaglercraft.backend.server.api.bungee.EaglerXServerAPI; +import net.lax1dude.eaglercraft.backend.server.api.bungee.event.EaglercraftLoginEvent; +import net.lax1dude.eaglercraft.backend.server.api.bungee.event.EaglercraftMOTDEvent; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.event.PreLoginEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.event.EventPriority; + +@SuppressWarnings({ "deprecation" }) +public final class OriginBlacklistBungee extends Plugin implements Listener, IOriginBlacklistPlugin { + private ProxyServer proxy; + private boolean papiPlaceholdersEnabled; + private Object papi; + private OriginBlacklist blacklist; + private IEaglerXServerAPI eaglerAPI; + private Metrics metrics; + + @Override + public final void onEnable() { + this.proxy = ProxyServer.getInstance(); + final Plugin eagx = this.getProxy().getPluginManager().getPlugin("EaglercraftXServer"); + if (eagx == null) { + throw new IncompatibleDependencyException("EaglercraftXServer"); + } else { + final Semver version = new Semver(eagx.getDescription().getVersion()); + if (version.isLowerThan(OriginBlacklist.REQUIRED_API_VER)) { + throw new IncompatibleDependencyException("EaglerXServer", OriginBlacklist.REQUIRED_API_VER, version); + } + } + this.papiPlaceholdersEnabled = this.getProxy().getPluginManager().getPlugin("PAPIProxyBridge") != null; + if (this.papiPlaceholdersEnabled) { + try { + this.papi = Class.forName("net.william278.papiproxybridge.api.PlaceholderAPI").getMethod("createInstance") + .invoke(null); + } catch (final Throwable t) { + this.papi = null; + this.papiPlaceholdersEnabled = false; + } + } else { + this.papi = null; + } + this.blacklist = new OriginBlacklist(this); + this.eaglerAPI = EaglerXServerAPI.instance(); + this.getProxy().getPluginManager().registerCommand(this, new OriginBlacklistCommandBungee(this, this.blacklist, "originblacklist")); + this.getProxy().getPluginManager().registerListener(this, this); + this.log(EnumLogLevel.INFO, "Initialized Plugin"); + if (this.blacklist.isMetricsEnabled()) { + this.metrics = new Metrics(this, OriginBlacklist.BSTATS_ID); + this.metrics.addCustomChart(new AdvancedPie("player_types", () -> { + final Map playerMap = new HashMap<>(); + + for (final ProxiedPlayer player : this.proxy.getPlayers()) { + final boolean eagler = eaglerAPI.isEaglerPlayerByUUID(player.getUniqueId()); + final String key = eagler ? "Eagler" : "Java"; + playerMap.put(key, playerMap.getOrDefault(key, 0) + 1); + } + + return playerMap; + })); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onEaglerLogin(final EaglercraftLoginEvent event) { + final OPlayer player = new OPlayer(event.getLoginConnection(), event.getProfileUsername(), event.getProfileUUID()); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(event, null, EnumConnectionType.EAGLER, player)); + } + + @EventHandler(priority = EventPriority.LOWEST) + public final void onEaglerMOTD(final EaglercraftMOTDEvent event) { + final OPlayer player = new OPlayer(event.getMOTDConnection(), null, null); + this.blacklist.handleMOTD(new OriginBlacklistMOTDEvent(event, null, EnumConnectionType.EAGLER, player)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onJavaLogin(final PostLoginEvent event) { + final ProxiedPlayer aPlayer = event.getPlayer(); + final OPlayer bPlayer = new OPlayer(null, aPlayer.getName(), aPlayer.getUniqueId(), + aPlayer.getAddress().toString(), aPlayer.getClientBrand()); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(null, event, EnumConnectionType.JAVA, bPlayer)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onJavaHandshake(final PreLoginEvent event) { + final OPlayer player = new OPlayer(null, null, null, event.getConnection().getAddress().toString(), null); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(null, event, EnumConnectionType.JAVA, player)); + } + + @EventHandler(priority = EventPriority.LOWEST) + public final void onJavaMOTD(final ProxyPingEvent event) { + final OPlayer player = new OPlayer(null, null, null, event.getConnection().getAddress().toString(), null); + this.blacklist.handleMOTD(new OriginBlacklistMOTDEvent(null, event, EnumConnectionType.JAVA, player)); + } + + @Override + public final String getPluginId() { + return this.getDescription().getName(); + } + + @Override + public final Semver getPluginVersion() { + return new Semver(this.getDescription().getVersion()); + } + + @Override + public final void log(final EnumLogLevel level, final String txt) { + if (level == EnumLogLevel.WARN) { + this.getLogger().warning(txt); + } else if (level == EnumLogLevel.ERROR) { + this.getLogger().severe(txt); + } else if (level == EnumLogLevel.DEBUG) { + if (this.blacklist != null && this.blacklist.isDebugEnabled()) { + this.getLogger().info(txt); + } + } else { + this.getLogger().info(txt); + } + } + + @Override + public final void kickPlayer(final Component comp, final OriginBlacklistLoginEvent event) { + final String str = OriginBlacklist.getComponentString(comp); + if (event.getConnectionType() == EnumConnectionType.EAGLER) { + event.getEaglerEvent().setKickMessage(str); + } else { + final Object javaEvent = event.getJavaEvent(); + if (javaEvent instanceof PreLoginEvent preLoginEvent) { + preLoginEvent.getConnection().disconnect(str); + } else if (javaEvent instanceof PostLoginEvent postLoginEvent) { + postLoginEvent.getPlayer().disconnect(str); + } + } + } + + @Override + public final void setMOTD(final Component comp, final OriginBlacklistMOTDEvent event) { + if (event.getConnectionType() == EnumConnectionType.EAGLER) { + this.blacklist.setEaglerMOTD(comp, event); + } else { + final ProxyPingEvent javaEvent = (ProxyPingEvent) event.getJavaEvent(); + final ServerPing ping = javaEvent.getResponse(); + ping.setDescription(OriginBlacklist.getComponentString(comp)); + ping.setFavicon(this.blacklist.getConfig().getIconBase64URI()); + ping.getPlayers().setOnline(0); + ping.getPlayers().setMax(0); + javaEvent.setResponse(ping); + } + } + + @Override + public final String parsePlaceholders(final OPlayer player, final String txt) { + if (this.papiPlaceholdersEnabled && this.papi != null) { + try { + return (String) this.papi.getClass().getMethod("formatPlaceholders", String.class, java.util.UUID.class) + .invoke(this.papi, txt, player.getUUID()); + } catch (final Throwable t) { + } + } + return txt; + } + + @Override + public final void scheduleRepeat(final Runnable task, final int period, final TimeUnit unit) { + this.proxy.getScheduler().schedule(this, task, period, period, unit); + } + + @Override + public final void shutdown() { + this.metrics.shutdown(); + this.proxy.getScheduler().cancel(this); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/bungee/command/BNGCommandContext.java b/src/main/java/xyz/webmc/originblacklist/bungee/command/BNGCommandContext.java new file mode 100644 index 0000000..52422a3 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/bungee/command/BNGCommandContext.java @@ -0,0 +1,37 @@ +package xyz.webmc.originblacklist.bungee.command; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.command.CommandContext; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.chat.TextComponent; + +public class BNGCommandContext implements CommandContext { + private final CommandSender sender; + private final String[] args; + + public BNGCommandContext(final CommandSender sender, final String[] args) { + this.sender = sender; + this.args = args; + } + + @Override + public String getPlayerName() { + return this.sender.getName(); + } + + @Override + public void reply(final String message) { + this.sender.sendMessage(TextComponent.fromLegacy(OriginBlacklist.getLegacyFromMiniMessage(message))); + } + + @Override + public boolean hasPermission(final String permission) { + return this.sender.hasPermission(permission); + } + + @Override + public String[] getArgs() { + return this.args; + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/bungee/command/OriginBlacklistCommandBungee.java b/src/main/java/xyz/webmc/originblacklist/bungee/command/OriginBlacklistCommandBungee.java new file mode 100644 index 0000000..40ec09a --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/bungee/command/OriginBlacklistCommandBungee.java @@ -0,0 +1,30 @@ +package xyz.webmc.originblacklist.bungee.command; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.command.OriginBlacklistCommand; +import xyz.webmc.originblacklist.bungee.OriginBlacklistBungee; + +import java.util.List; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.TabExecutor; + +public class OriginBlacklistCommandBungee extends Command implements TabExecutor { + private final OriginBlacklistCommand cmd; + + public OriginBlacklistCommandBungee(final OriginBlacklistBungee plugin, final OriginBlacklist blacklist, final String command) { + super(command); + this.cmd = new OriginBlacklistCommand(blacklist); + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + this.cmd.execute(new BNGCommandContext(sender, args)); + } + + @Override + public List onTabComplete(final CommandSender sender, final String[] args) { + return this.cmd.suggest(new BNGCommandContext(sender, args)); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/velocity/OriginBlacklistVelocity.java b/src/main/java/xyz/webmc/originblacklist/velocity/OriginBlacklistVelocity.java new file mode 100644 index 0000000..3ee40dc --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/velocity/OriginBlacklistVelocity.java @@ -0,0 +1,226 @@ +package xyz.webmc.originblacklist.velocity; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.enums.EnumConnectionType; +import xyz.webmc.originblacklist.base.enums.EnumLogLevel; +import xyz.webmc.originblacklist.base.events.OriginBlacklistLoginEvent; +import xyz.webmc.originblacklist.base.events.OriginBlacklistMOTDEvent; +import xyz.webmc.originblacklist.base.util.IOriginBlacklistPlugin; +import xyz.webmc.originblacklist.base.util.IncompatibleDependencyException; +import xyz.webmc.originblacklist.base.util.OPlayer; +import xyz.webmc.originblacklist.velocity.command.OriginBlacklistCommandVelocity; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.bstats.charts.AdvancedPie; +import org.bstats.velocity.Metrics; +import org.bstats.velocity.Metrics.Factory; +import org.semver4j.Semver; +import org.slf4j.Logger; + +import com.google.inject.Inject; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PreLoginEvent; +import com.velocitypowered.api.event.player.PlayerClientBrandEvent; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.api.scheduler.ScheduledTask; +import com.velocitypowered.api.util.Favicon; + +import net.kyori.adventure.text.Component; +import net.lax1dude.eaglercraft.backend.server.api.IEaglerXServerAPI; +import net.lax1dude.eaglercraft.backend.server.api.velocity.event.EaglercraftMOTDEvent; +import net.lax1dude.eaglercraft.backend.server.api.velocity.EaglerXServerAPI; +import net.lax1dude.eaglercraft.backend.server.api.velocity.event.EaglercraftLoginEvent; + +@SuppressWarnings({ "deprecation", "unchecked" }) +public final class OriginBlacklistVelocity implements IOriginBlacklistPlugin { + private final PluginContainer plugin; + private final Factory metricsFactory; + private final ProxyServer proxy; + private final Logger logger; + + private boolean papiPlaceholdersEnabled; + private Object papi; + private OriginBlacklist blacklist; + private IEaglerXServerAPI eaglerAPI; + private Metrics metrics; + + @Inject + public OriginBlacklistVelocity(final PluginContainer plugin, Factory metricsFactory, final ProxyServer proxy, + final Logger logger) { + this.plugin = plugin; + this.metricsFactory = metricsFactory; + this.proxy = proxy; + this.logger = logger; + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + this.proxy.getPluginManager().getPlugin("eaglerxserver").ifPresentOrElse(plugin -> { + final Semver version = new Semver(plugin.getDescription().getVersion().orElse("1.0.0")); + if (version.isLowerThan(OriginBlacklist.REQUIRED_API_VER)) { + throw new IncompatibleDependencyException("EaglerXServer", OriginBlacklist.REQUIRED_API_VER, version); + } + }, () -> { + throw new IncompatibleDependencyException("EaglerXServer"); + }); + this.papiPlaceholdersEnabled = this.proxy.getPluginManager().getPlugin("papiproxybridge").isPresent(); + if (this.papiPlaceholdersEnabled) { + try { + this.papi = Class.forName("net.william278.papiproxybridge.api.PlaceholderAPI").getMethod("createInstance") + .invoke(null); + } catch (final Throwable t) { + this.papi = null; + this.papiPlaceholdersEnabled = false; + } + } else { + this.papi = null; + } + this.blacklist = new OriginBlacklist(this); + this.eaglerAPI = EaglerXServerAPI.instance(); + this.proxy.getCommandManager().register("originblacklist", new OriginBlacklistCommandVelocity(this.blacklist)); + this.log(EnumLogLevel.INFO, "Initialized Plugin"); + if (this.blacklist.isMetricsEnabled()) { + this.metrics = this.metricsFactory.make(this, OriginBlacklist.BSTATS_ID); + this.metrics.addCustomChart(new AdvancedPie("player_types", () -> { + final Map playerMap = new HashMap<>(); + + for (final Player player : this.proxy.getAllPlayers()) { + final boolean eagler = eaglerAPI.isEaglerPlayerByUUID(player.getUniqueId()); + final String key = eagler ? "Eagler" : "Java"; + playerMap.put(key, playerMap.getOrDefault(key, 0) + 1); + } + + return playerMap; + })); + } + } + + @Subscribe(order = PostOrder.FIRST) + public final void onEaglerLogin(final EaglercraftLoginEvent event) { + final OPlayer player = new OPlayer(event.getLoginConnection(), event.getProfileUsername(), event.getProfileUUID()); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(event, null, EnumConnectionType.EAGLER, player)); + } + + @Subscribe(order = PostOrder.LAST) + public final void onEaglerMOTD(final EaglercraftMOTDEvent event) { + final OPlayer player = new OPlayer(event.getMOTDConnection(), null, null); + this.blacklist.handleMOTD(new OriginBlacklistMOTDEvent(event, null, EnumConnectionType.EAGLER, player)); + } + + @Subscribe(order = PostOrder.FIRST) + public final void onJavaLogin(final PreLoginEvent event) { + final OPlayer player = new OPlayer(null, event.getUsername(), event.getUniqueId(), + event.getConnection().getRemoteAddress().toString(), null); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(null, event, EnumConnectionType.JAVA, player)); + } + + @Subscribe(order = PostOrder.FIRST) + public final void onJavaHandshake(final PlayerClientBrandEvent event) { + final Player aPlayer = (Player) event.getPlayer(); + final OPlayer bPlayer = new OPlayer(null, aPlayer.getUsername(), aPlayer.getUniqueId(), + aPlayer.getRemoteAddress().getAddress().toString(), event.getBrand()); + this.blacklist.handleLogin(new OriginBlacklistLoginEvent(null, event, EnumConnectionType.JAVA, bPlayer)); + } + + @Subscribe(order = PostOrder.LAST) + public final void onJavaMOTD(final ProxyPingEvent event) { + final OPlayer player = new OPlayer(null, null, null, event.getConnection().getRemoteAddress().getHostString(), + null); + this.blacklist.handleMOTD(new OriginBlacklistMOTDEvent(null, event, EnumConnectionType.JAVA, player)); + } + + @Override + public final String getPluginId() { + return this.plugin.getDescription().getId(); + } + + @Override + public final Semver getPluginVersion() { + return new Semver(this.plugin.getDescription().getVersion().get()); + } + + @Override + public final void log(final EnumLogLevel level, final String txt) { + if (level == EnumLogLevel.WARN) { + this.logger.warn(txt); + } else if (level == EnumLogLevel.ERROR) { + this.logger.error(txt); + } else if (level == EnumLogLevel.DEBUG) { + if (this.blacklist.isDebugEnabled()) { + this.logger.debug(txt); + } + } else { + this.logger.info(txt); + } + } + + @Override + public final void kickPlayer(final Component comp, final OriginBlacklistLoginEvent event) { + if (event.getConnectionType() == EnumConnectionType.EAGLER) { + event.getEaglerEvent().setKickMessage(comp); + } else { + final Object javaEvent = event.getJavaEvent(); + if (javaEvent instanceof PreLoginEvent loginEvent) { + loginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(comp)); + } else if (javaEvent instanceof PlayerClientBrandEvent brandEvent) { + brandEvent.getPlayer().disconnect(comp); + } + } + } + + @Override + public final void setMOTD(final Component comp, final OriginBlacklistMOTDEvent event) { + if (event.getConnectionType() == EnumConnectionType.EAGLER) { + blacklist.setEaglerMOTD(comp, event); + } else { + final ProxyPingEvent javaEvent = (ProxyPingEvent) event.getJavaEvent(); + ServerPing ping = ServerPing.builder() + .description(comp) + .version(new ServerPing.Version(0, "")) + .samplePlayers(List.of()) + .onlinePlayers(0) + .maximumPlayers(0) + .favicon(new Favicon(this.blacklist.getConfig().getIconBase64URI())) + .build(); + javaEvent.setPing(ping); + } + } + + @Override + public final String parsePlaceholders(final OPlayer player, final String txt) { + if (this.papiPlaceholdersEnabled && this.papi != null) { + try { + return (String) this.papi.getClass().getMethod("formatPlaceholders", String.class, java.util.UUID.class) + .invoke(this.papi, txt, player.getUUID()); + } catch (final Throwable t) { + } + } + return txt; + } + + @Override + public final void scheduleRepeat(final Runnable task, final int period, final TimeUnit unit) { + this.proxy.getScheduler() + .buildTask(this, task) + .repeat(period, unit) + .schedule(); + } + + @Override + public final void shutdown() { + this.metrics.shutdown(); + for (ScheduledTask task : this.proxy.getScheduler().tasksByPlugin(this.plugin)) { + task.cancel(); + } + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/velocity/command/OriginBlacklistCommandVelocity.java b/src/main/java/xyz/webmc/originblacklist/velocity/command/OriginBlacklistCommandVelocity.java new file mode 100644 index 0000000..409bb2c --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/velocity/command/OriginBlacklistCommandVelocity.java @@ -0,0 +1,24 @@ +package xyz.webmc.originblacklist.velocity.command; + +import xyz.webmc.originblacklist.base.OriginBlacklist; +import xyz.webmc.originblacklist.base.command.OriginBlacklistCommand; + +import java.util.List; + +import com.velocitypowered.api.command.SimpleCommand; + +public class OriginBlacklistCommandVelocity extends OriginBlacklistCommand implements SimpleCommand { + public OriginBlacklistCommandVelocity(OriginBlacklist plugin) { + super(plugin); + } + + @Override + public void execute(final Invocation invocation) { + super.execute(new VCommandContext(invocation)); + } + + @Override + public List suggest(final Invocation invocation) { + return super.suggest(new VCommandContext(invocation)); + } +} diff --git a/src/main/java/xyz/webmc/originblacklist/velocity/command/VCommandContext.java b/src/main/java/xyz/webmc/originblacklist/velocity/command/VCommandContext.java new file mode 100644 index 0000000..1b130a2 --- /dev/null +++ b/src/main/java/xyz/webmc/originblacklist/velocity/command/VCommandContext.java @@ -0,0 +1,35 @@ +package xyz.webmc.originblacklist.velocity.command; + +import xyz.webmc.originblacklist.base.command.CommandContext; + +import com.velocitypowered.api.command.SimpleCommand.Invocation; + +import net.kyori.adventure.text.minimessage.MiniMessage; + +public class VCommandContext implements CommandContext { + private final Invocation invocation; + + public VCommandContext(final Invocation invocation) { + this.invocation = invocation; + } + + @Override + public String getPlayerName() { + return this.invocation.source().toString(); + } + + @Override + public void reply(final String message) { + this.invocation.source().sendMessage(MiniMessage.miniMessage().deserialize(message)); + } + + @Override + public boolean hasPermission(final String permission) { + return this.invocation.source().hasPermission(permission); + } + + @Override + public String[] getArgs() { + return this.invocation.arguments(); + } +} diff --git a/src/main/resources/server-blocked.png b/src/main/resources/blacklisted.png similarity index 100% rename from src/main/resources/server-blocked.png rename to src/main/resources/blacklisted.png diff --git a/src/main/resources/bungee.yml b/src/main/resources/bungee.yml index 7916bf5..bb401b5 100644 --- a/src/main/resources/bungee.yml +++ b/src/main/resources/bungee.yml @@ -1,7 +1,10 @@ -name: OriginBlacklist -version: ${version} -main: dev.colbster937.originblacklist.bungee.OriginBlacklistBungee -description: ${description} -author: Colbster937 -depends: - - EaglercraftXServer \ No newline at end of file +name: ${plugin_name} +version: ${plugin_vers} +main: xyz.webmc.${plugin_iden}.bungee.${plugin_name}Bungee +description: ${plugin_desc} +website: ${plugin_site} +author: [${plugin_athr}] +contributors: [${plugin_ctbr}] +depends: [${plugin_depb}] +provides: [${plugin_prov}] +softdepend: [${plugin_sdpb}] \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml deleted file mode 100644 index 24cded4..0000000 --- a/src/main/resources/config.yml +++ /dev/null @@ -1,69 +0,0 @@ -messages: - # Valid Placeholders: - # - %blocked% - The player's origin/brand that was blocked - # - %blocktype% - Shows what the player was blocked for - # - %easyblocktype% - Shows what the player was blocked for in an eagler-kid readable form - # - %notallowed1% - Longer "not allowed" message - # - %notallowed2% - Shorter "not allowed" message - # - %host% - The IP the player pinged - # - %help% - The configured help message for the block type - - kick: | - This %easyblocktype% is %notallowed1%! - » %blocked% « - - %help% - - Think this is a mistake? Join our discord: - discord.gg/changethisintheconfig - - # Please note that help is only supported in the kick message, not the MOTD - help: - generic: "Please switch to a different %easyblocktype%." - player: "Please change your %easyblocktype%." - ip: "Please contact staff for assistance." - - motd: - enabled: true - text: | - This %easyblocktype% is %notallowed2%! - » %blocked% - icon: "blacklisted.png" - -# Origin + Brand blacklist supports wildcards -# Everything should be lowercase -blacklist: - origins: - - "*eagler-clients.vercel.app*" - - "*eaglerhackedclients.vercel.app*" - - "*eaglerhacks.github.io*" - brands: - - "*dragonx*" - - "*piclient*" - players: - - "Admin" - ips: - - "192.0.2.0/24" - -discord: - webhook: "" - - - - - - - - - - - - - - - - - - - -# :> \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e583b2c..dfdc264 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,12 @@ -name: OriginBlacklist -version: ${version} -main: dev.colbster937.originblacklist.bukkit.OriginBlacklistBukkit -description: ${description} -author: Colbster937 -depend: - - EaglercraftXServer +name: ${plugin_name} +version: ${plugin_vers} +main: xyz.webmc.${plugin_iden}.bukkit.${plugin_name}Bukkit +description: ${plugin_desc} +website: ${plugin_site} +authors: [${plugin_athr}] +contributors: [${plugin_ctbr}] +depend: [${plugin_depa}] +provides: [${plugin_prov}] +softdepend: [${plugin_sdpa}] commands: originblacklist: \ No newline at end of file diff --git a/src/main/resources/velocity-plugin.json b/src/main/resources/velocity-plugin.json index bf1052d..8dda797 100644 --- a/src/main/resources/velocity-plugin.json +++ b/src/main/resources/velocity-plugin.json @@ -1,14 +1,10 @@ { - "id": "originblacklist", - "name": "OriginBlacklist", - "version": "${version}", - "description": "${description}", - "main": "dev.colbster937.originblacklist.velocity.OriginBlacklistVelocity", - "authors": ["Colbster937"], - "dependencies": [ - { - "id": "eaglerxserver", - "optional": false - } - ] -} + "id": "${plugin_iden}", + "name": "${plugin_name}", + "version": "${plugin_vers}", + "description": "${plugin_desc}", + "website": "${plugin_site}", + "main": "xyz.webmc.${plugin_iden}.velocity.${plugin_name}Velocity", + "authors": [${plugin_athr}], + "dependencies": [${plugin_depc}] +} \ No newline at end of file